Compare commits

...

84 Commits

Author SHA1 Message Date
akwizgran
934997ed91 Check for partial setup of clients due to feature flags reverting. 2023-01-31 11:23:19 +00:00
Torsten Grote
0b94814620 Merge branch 'remove-migration-code' into 'master'
Remove various bits of code whose migration periods have passed

See merge request briar/briar!1750
2023-01-30 13:59:02 +00:00
akwizgran
e82e11acfa Merge branch '2384-mailbox-problem-notification' into 'master'
Clear mailbox problem notification after unlinking

Closes #2384

See merge request briar/briar!1764
2023-01-27 15:09:27 +00:00
Torsten Grote
795461d9a8 Clear mailbox problem notification after unlinking 2023-01-27 11:49:01 -03:00
Torsten Grote
7b8d01cfe0 Merge branch '1822-rss-feeds-backend' into 'master'
Resolve "Import RSS feeds shared by other apps"

See merge request briar/briar!1763
2023-01-25 11:39:34 +00:00
akwizgran
abd04ee7f5 Add tests for feed serialisation/deserialisation. 2023-01-24 17:27:52 +00:00
akwizgran
cc5365eaf0 Remove redundant comparison from test. 2023-01-24 17:27:34 +00:00
akwizgran
6b20b03698 Bump version numbers for 1.4.20 release. 2023-01-24 15:51:48 +00:00
akwizgran
9da7fbf4f6 Update translations. 2023-01-24 15:51:26 +00:00
akwizgran
f64f442fcf Merge branch 'add-comment-for-is-connected' into 'master'
Add comment about NetworkInfo#isConnected()

See merge request briar/briar!1762
2023-01-24 15:25:52 +00:00
akwizgran
6eda2f6d13 AnimalSniffer doesn't allow StandardCharsets in tests. 2023-01-24 14:50:40 +00:00
akwizgran
6faa095dfb FeedMatcher interface doesn't need to be public. 2023-01-24 14:48:55 +00:00
akwizgran
4007fca668 Add integration tests for importing an RSS feed from a file. 2023-01-24 14:15:03 +00:00
akwizgran
28a747f7f3 Add method for adding an RSS feed from an input stream. 2023-01-24 13:57:44 +00:00
Sebastian Kürten
fd2d5c9173 Add comment about NetworkInfo#isConnected() 2023-01-24 14:48:03 +01:00
akwizgran
8f7bb9d26b Don't overwrite the list of feeds after fetching. 2023-01-24 13:28:22 +00:00
akwizgran
dc220200b6 Match newly added RSS feeds to existing feeds. 2023-01-24 12:43:14 +00:00
Torsten Grote
0cea137d75 Merge branch 'update-tor-bridges' into 'master'
Update Tor bridges

See merge request briar/briar!1761
2023-01-23 15:06:28 +00:00
akwizgran
2eef34f424 Use new transaction wrappers. 2023-01-23 13:02:16 +00:00
akwizgran
a68fff9dd2 Merge branch 'tor-0.4.7.13' into 'master'
Upgrade Tor to 0.4.7.13

See merge request briar/briar!1760
2023-01-23 12:11:32 +00:00
akwizgran
ddc8f4a7d7 Add three non-default obfs4 bridges. 2023-01-20 16:12:47 +00:00
akwizgran
f961b6a80b Remove three failing bridges. 2023-01-20 16:11:11 +00:00
akwizgran
93439d9c17 Update translations. 2023-01-20 15:50:28 +00:00
akwizgran
f3ee884816 Upgrade Tor to 0.4.7.13. 2023-01-20 15:34:23 +00:00
akwizgran
8ca22043cf Merge branch '1897-sharing-status' into 'master'
Introduce SharingStatus to report more fine-grained status

Closes #1897

See merge request briar/briar!1758
2023-01-20 14:33:32 +00:00
Torsten Grote
9353b78da8 Clarify sharing state docs 2023-01-20 11:13:35 -03:00
Torsten Grote
429bbe1275 Introduce more sharing states 2023-01-20 11:13:35 -03:00
Torsten Grote
c5fb1416bd Update JavaDoc for SharingState change 2023-01-20 11:13:34 -03:00
Torsten Grote
e52cbd896e Introduce SharingStatus to report more fine-grained status 2023-01-20 11:13:34 -03:00
akwizgran
ab1b8784b7 Merge branch '90-clickable-links' into 'master'
Resolve "Handle Hyperlinks (Clickable Links)"

Closes #90

See merge request briar/briar!1757
2023-01-20 13:47:08 +00:00
akwizgran
55a4daa92f Merge branch 'progressbar-remove-contact' into 'master'
Show progress bar while removing contact

See merge request briar/briar!1759
2023-01-20 13:44:29 +00:00
akwizgran
e52250f1e4 Don't sort list of RSS feeds in UI. 2023-01-18 15:04:38 +00:00
akwizgran
33d01aac8c Add matcher for matching an imported feed against existing feeds. 2023-01-18 15:04:38 +00:00
akwizgran
b920382e44 Store additional properties of RSS feed in metadata. 2023-01-18 15:04:38 +00:00
akwizgran
1a2f85f701 Small code cleanups for feed manager, don't fetch new feeds twice. 2023-01-18 15:04:33 +00:00
Airplane Mode
186bcc0b47 Show progress bar while removing contact 2023-01-16 23:56:26 +00:00
Torsten Grote
8b9140f477 Merge branch 'tor-0.4.7.12' into 'master'
Upgrade Tor to 0.4.7.12

See merge request briar/briar!1755
2023-01-13 16:25:42 +00:00
Katelyn Dickey
f959c32935 Remove autoLink attribute which was causing warnings to show twice, and highlight links in comments before a blog is expanded 2023-01-05 17:20:40 -05:00
akwizgran
1c060bc6db Upgrade Tor to 0.4.7.12. 2023-01-04 17:51:46 +00:00
Katelyn Dickey
5e44e4d308 Add clickable links to blog comments 2023-01-03 20:52:36 -05:00
Katelyn Dickey
75d5dec45f Add clickable links to notices/requests 2023-01-03 18:38:13 -05:00
Katelyn Dickey
d825227eb5 Add clickable links to threads (forums/groups) 2023-01-03 18:38:07 -05:00
Katelyn Dickey
967dd1f18d Add clickable links for conversations 2023-01-03 18:37:40 -05:00
akwizgran
4a4147b563 Bump version numbers for 1.4.19 release. 2022-12-30 11:15:32 +00:00
akwizgran
08b72af647 Update translations. 2022-12-30 11:07:38 +00:00
akwizgran
528e090c6f Merge branch '2409-require-obsolete-bluetooth-permission' into 'master'
Require obsolete BLUETOOTH permission on API 31

Closes #2409

See merge request briar/briar!1754
2022-12-30 10:56:29 +00:00
akwizgran
652f9e5705 Require obsolete BLUETOOTH permission on API 31.
This is a workaround for a platform bug on Xiaomi/Redmi/POCO devices that still checks for the obsolete permission.
2022-12-28 14:12:30 +00:00
akwizgran
6a91ec7a6b Merge branch '2407-bluetooth-permission' into 'master'
Always check Bluetooth permission when trying to get own address

Closes #2407

See merge request briar/briar!1753
2022-12-28 11:07:31 +00:00
akwizgran
c3a9eff96b Always check Bluetooth permission when trying to get own address. 2022-12-22 17:46:12 +00:00
akwizgran
bd05d893eb Merge branch '2397-wrong-type-of-qr-code' into 'master'
Tweak text for unknown QR code type

See merge request briar/briar!1752
2022-12-21 12:29:48 +00:00
akwizgran
6965bc0acd Tweak text for unknown QR code type. 2022-12-21 12:19:31 +00:00
akwizgran
c6e9554026 Merge branch '2397-wrong-type-of-qr-code' into 'master'
Show appropriate error message if user scans wrong kind of QR code

Closes #2397

See merge request briar/briar!1748
2022-12-19 15:43:16 +00:00
akwizgran
ab8734e373 Show relevant message when contact QR code has unknown format. 2022-12-19 10:24:58 +00:00
akwizgran
267956b36c Restore javadoc for qrCodeTooOld flag. 2022-12-19 10:04:55 +00:00
akwizgran
ec84ddb38b Merge branch '2403-show-progress-while-connecting-to-mailbox' into 'master'
Show progress while connecting to mailbox

Closes #2403

See merge request briar/briar!1747
2022-12-14 12:20:43 +00:00
akwizgran
ba2db48d8e Center text, add margin at bottom to center layout. 2022-12-14 12:03:06 +00:00
akwizgran
186f61f771 Set width of text views to 0dp so margins are applied. 2022-12-12 16:14:34 +00:00
akwizgran
47971517cd Bump version numbers for 1.4.18 release. 2022-12-12 14:03:52 +00:00
akwizgran
8db182d7e5 Update translations. 2022-12-12 14:03:01 +00:00
akwizgran
d44a609d0c Merge branch '2405-bonded-devices' into 'master'
Don't try to get bonded Bluetooth devices on API 31+

See merge request briar/briar!1751
2022-12-12 13:58:21 +00:00
akwizgran
0a1892d39f Merge branch 'do-not-crash-when-tor-crashes' into 'master'
Don't crash when the Tor process crashes

See merge request briar/briar!1749
2022-12-12 11:04:52 +00:00
akwizgran
9b092da37a Don't try to get bonded Bluetooth devices on API 31+. 2022-12-07 18:38:36 +00:00
akwizgran
7a3ffcbae6 Remove various bits of code whose migration periods have passed. 2022-12-07 17:47:02 +00:00
akwizgran
852e2c29e3 Don't crash when the Tor process crashes. 2022-12-07 17:28:33 +00:00
akwizgran
1b087d59d4 Merge branch '2400-outline-buttons' into 'master'
Use outlined button style

Closes #2400

See merge request briar/briar!1746
2022-12-07 16:54:34 +00:00
akwizgran
30ce8651b5 Fix ripple effect for outlined buttons on API 21+. 2022-12-07 15:50:56 +00:00
akwizgran
80a8ee4de9 Fix button inheritance. 2022-12-07 11:16:34 +00:00
akwizgran
354f3bc1cf Use chain so that margins are enforced. 2022-12-07 11:13:42 +00:00
akwizgran
1e6b018ff4 Add corner radius and increase top inset. 2022-12-07 11:03:46 +00:00
akwizgran
eba489bb98 Merge branch 'project-dependencies' into 'master'
Refactor dependencies to satisfy Android Studio's linter

See merge request briar/briar!1745
2022-12-05 14:54:12 +00:00
akwizgran
2bfdcaaa42 Declare dependencies for custom jar tasks. 2022-12-02 18:05:28 +00:00
akwizgran
c2e71ef52f Remove configuration: default, make transitive dependencies explicit. 2022-12-02 17:43:52 +00:00
akwizgran
9ee8fe74ba Export bramble/briar-api as API of bramble/briar-core. 2022-12-02 15:53:23 +00:00
akwizgran
95d8783852 Show appropriate error message if contact QR code is scanned. 2022-12-02 14:27:42 +00:00
akwizgran
b4f3604584 Show appropriate error message if mailbox QR code is scanned. 2022-12-02 13:35:00 +00:00
akwizgran
badccac90c Factor out recognition of QR code format. 2022-12-02 13:35:00 +00:00
akwizgran
1b8d1a5a8d Update test expectations. 2022-11-30 17:30:33 +00:00
akwizgran
2fe57d2597 Show progress while connecting to mailbox. 2022-11-30 17:17:08 +00:00
akwizgran
904d5b2ce2 Remove unused dimension. 2022-11-30 10:58:44 +00:00
akwizgran
1911b3dd97 Make OfflineFragment suitable for small screens. 2022-11-30 10:44:39 +00:00
akwizgran
bd430a1009 Use outlined button style for secondary actions. 2022-11-30 10:33:11 +00:00
akwizgran
c16d0e8f45 Refactor dependencies to satisfy Android Studio's linter.
If an Android module depends on another module's default configuration, Android Studio's linter won't recognise references to classes in the other module. Instead, the Android module must depend on the other module without specifying a configuration. This entails some changes in the handling of transitive dependencies, and the other module must include its main classes in its testOutput artifact so the Android module's tests can use them.
2022-11-29 13:35:29 +00:00
akwizgran
847273c558 Merge branch 'transactions-forum' into 'master'
Add transactional versions to functions related to forums

See merge request briar/briar!1743
2022-11-23 17:47:59 +00:00
ialokim
b9bac8b6a5 add transactional versions to functions related to forums 2022-11-23 18:37:07 +01:00
169 changed files with 3973 additions and 1344 deletions

View File

@@ -13,8 +13,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 31 targetSdkVersion 31
versionCode 10417 versionCode 10420
versionName "1.4.17" versionName "1.4.20"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -40,8 +40,16 @@ configurations {
} }
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') // In theory this dependency shouldn't be needed, but without it Android Studio's linter will
// complain about unresolved symbols for bramble-api test classes in bramble-android tests,
// even though the bramble-api test classes are provided by the testImplementation dependency
// below and the compiler can find them
implementation project(':bramble-api')
implementation project(':bramble-core')
implementation 'androidx.annotation:annotation:1.5.0' implementation 'androidx.annotation:annotation:1.5.0'
tor "org.briarproject:tor-android:$tor_version" tor "org.briarproject:tor-android:$tor_version"
tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-android:$obfs4proxy_version"
tor "org.briarproject:snowflake-android:$snowflake_version" tor "org.briarproject:snowflake-android:$snowflake_version"
@@ -51,6 +59,7 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.bramble"> package="org.briarproject.bramble">
<uses-feature <uses-feature
@@ -7,15 +8,17 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- The BLUETOOTH permission was supposed to be removed in API 31 but is still needed on some Xiaomi/Redmi/POCO devices running API 31 -->
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH" android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" /> android:maxSdkVersion="31" />
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH_ADMIN" android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" /> android:maxSdkVersion="30" />
<uses-permission <uses-permission
android:name="android.permission.BLUETOOTH_SCAN" android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" /> android:usesPermissionFlags="neverForLocation"
tools:targetApi="31" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />

View File

@@ -118,6 +118,11 @@ class AndroidNetworkManager implements NetworkManager, Service {
try { try {
NetworkInfo net = connectivityManager.getActiveNetworkInfo(); NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected(); boolean connected = net != null && net.isConnected();
// Research into Android's behavior to check network connectivity
// (https://code.briarproject.org/briar/public-mesh-research/-/issues/19)
// has shown that NetworkInfo#isConnected() returns true if the device
// is connected to any Wifi, independent of whether any specific IP
// address can be reached using it or any domain names can be resolved.
boolean wifi = false, ipv6Only = false; boolean wifi = false, ipv6Only = false;
if (connected) { if (connected) {
wifi = net.getType() == TYPE_WIFI; wifi = net.getType() == TYPE_WIFI;

View File

@@ -26,7 +26,6 @@ import static android.os.Process.myPid;
import static android.os.Process.myTid; import static android.os.Process.myTid;
import static android.os.Process.myUid; import static android.os.Process.myUid;
import static android.provider.Settings.Secure.ANDROID_ID; import static android.provider.Settings.Secure.ANDROID_ID;
import static org.briarproject.bramble.util.AndroidUtils.hasBtConnectPermission;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -53,8 +52,8 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
ContentResolver contentResolver = appContext.getContentResolver(); ContentResolver contentResolver = appContext.getContentResolver();
String id = Settings.Secure.getString(contentResolver, ANDROID_ID); String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id); if (id != null) out.writeUTF(id);
// use bluetooth paired devices as well, if allowed // On API 31 and higher we need permission to access bonded devices
if (hasBtConnectPermission(appContext)) { if (SDK_INT < 31) {
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) { if (bt != null) {

View File

@@ -65,6 +65,9 @@ public class AndroidUtils {
public static Pair<String, String> getBluetoothAddressAndMethod(Context ctx, public static Pair<String, String> getBluetoothAddressAndMethod(Context ctx,
BluetoothAdapter adapter) { BluetoothAdapter adapter) {
// If we don't have permission to access the adapter's address, let
// the caller know we can't find it
if (!hasBtConnectPermission(ctx)) return new Pair<>("", "");
// Return the adapter's address if it's valid and not fake // Return the adapter's address if it's valid and not fake
@SuppressLint("HardwareIds") @SuppressLint("HardwareIds")
String address = adapter.getAddress(); String address = adapter.getAddress();

View File

@@ -26,7 +26,7 @@ dependencyVerification {
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-android:0.0.14-tor1:obfs4proxy-android-0.0.14-tor1.jar:8b08068778b133484b17956d8f7a7710739c33f671a26a68156f4d34e6f28c30', 'org.briarproject:obfs4proxy-android:0.0.14-tor1:obfs4proxy-android-0.0.14-tor1.jar:8b08068778b133484b17956d8f7a7710739c33f671a26a68156f4d34e6f28c30',
'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293', 'org.briarproject:snowflake-android:2.3.1:snowflake-android-2.3.1.jar:1f83c9a070f87b7074af13627709a8b5aced5460104be7166af736b1bb73c293',
'org.briarproject:tor-android:0.4.5.14:tor-android-0.4.5.14.jar:7cf1beaa6c1db51fc8fac263aba9624ef289c3db29772509efcbc59f7057330a', 'org.briarproject:tor-android:0.4.7.13:tor-android-0.4.7.13.jar:7852aab7d2298b80878c7719f34ce665725b494d673ecf2e6f9e697564638cc6',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -8,9 +8,10 @@ apply from: 'witness.gradle'
dependencies { dependencies {
api 'org.briarproject:null-safety:0.1' api 'org.briarproject:null-safety:0.1'
api 'com.google.code.findbugs:jsr305:3.0.2'
api 'javax.inject:javax.inject:1'
api "com.google.dagger:dagger:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"
implementation 'com.google.code.findbugs:jsr305:3.0.2'
implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
@@ -25,7 +26,7 @@ configurations {
testOutput.extendsFrom(testCompile) testOutput.extendsFrom(testCompile)
} }
task jarTest(type: Jar, dependsOn: testClasses) { task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output from sourceSets.test.output, sourceSets.main.output
classifier = 'test' classifier = 'test'
} }
artifacts { artifacts {

View File

@@ -1,18 +1,26 @@
package org.briarproject.bramble.api.keyagreement; package org.briarproject.bramble.api.keyagreement;
public interface KeyAgreementConstants { import org.briarproject.bramble.api.mailbox.MailboxConstants;
/** public interface KeyAgreementConstants {
* The version of the BQP protocol used in beta releases. This version
* number is reserved.
*/
byte BETA_PROTOCOL_VERSION = 89;
/** /**
* The current version of the BQP protocol. * The current version of the BQP protocol.
*/ */
byte PROTOCOL_VERSION = 4; byte PROTOCOL_VERSION = 4;
/**
* The QR code format identifier, used to distinguish BQP QR codes from
* QR codes used for other purposes. See
* {@link MailboxConstants#QR_FORMAT_ID}.
*/
byte QR_FORMAT_ID = 0;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = PROTOCOL_VERSION;
/** /**
* The length of the BQP key commitment in bytes. * The length of the BQP key commitment in bytes.
*/ */

View File

@@ -7,5 +7,5 @@ import java.io.IOException;
@NotNullByDefault @NotNullByDefault
public interface PayloadParser { public interface PayloadParser {
Payload parse(byte[] raw) throws IOException; Payload parse(String payload) throws IOException;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List; import java.util.List;
@@ -19,6 +20,18 @@ public interface MailboxConstants {
*/ */
TransportId ID = new TransportId("org.briarproject.bramble.mailbox"); TransportId ID = new TransportId("org.briarproject.bramble.mailbox");
/**
* The QR code format identifier, used to distinguish mailbox QR codes
* from QR codes used for other purposes. See
* {@link KeyAgreementConstants#QR_FORMAT_ID};
*/
byte QR_FORMAT_ID = 1;
/**
* The QR code format version.
*/
byte QR_FORMAT_VERSION = 0;
/** /**
* Mailbox API versions that we support as a client. This is reported to our * Mailbox API versions that we support as a client. This is reported to our
* contacts by {@link MailboxUpdateManager}. * contacts by {@link MailboxUpdateManager}.

View File

@@ -1,17 +1,44 @@
package org.briarproject.bramble.api.mailbox; package org.briarproject.bramble.api.mailbox;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
public abstract class MailboxPairingState { public abstract class MailboxPairingState {
public static class QrCodeReceived extends MailboxPairingState { public abstract static class Pending extends MailboxPairingState {
public final long timeStarted;
private Pending(long timeStarted) {
this.timeStarted = timeStarted;
}
} }
public static class Pairing extends MailboxPairingState { public static class QrCodeReceived extends Pending {
public QrCodeReceived(long timeStarted) {
super(timeStarted);
}
}
public static class Pairing extends Pending {
public Pairing(long timeStarted) {
super(timeStarted);
}
} }
public static class Paired extends MailboxPairingState { public static class Paired extends MailboxPairingState {
} }
public static class InvalidQrCode extends MailboxPairingState { public static class InvalidQrCode extends MailboxPairingState {
public final QrCodeType qrCodeType;
public final int formatVersion;
public InvalidQrCode(QrCodeType qrCodeType, int formatVersion) {
this.qrCodeType = qrCodeType;
this.formatVersion = formatVersion;
}
} }
public static class MailboxAlreadyPaired extends MailboxPairingState { public static class MailboxAlreadyPaired extends MailboxPairingState {

View File

@@ -27,8 +27,6 @@ public interface TorConstants {
int PREF_TOR_NETWORK_AUTOMATIC = 0; int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1; int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;
int PREF_TOR_NETWORK_WITH_BRIDGES = 2; int PREF_TOR_NETWORK_WITH_BRIDGES = 2;
// TODO: Remove when settings migration code is removed
int PREF_TOR_NETWORK_NEVER = 3;
// Default values for local settings // Default values for local settings
boolean DEFAULT_PREF_PLUGIN_ENABLE = true; boolean DEFAULT_PREF_PLUGIN_ENABLE = true;

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface QrCodeClassifier {
enum QrCodeType {
BQP,
MAILBOX,
UNKNOWN
}
Pair<QrCodeType, Integer> classifyQrCode(String payload);
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.bramble.api.qrcode;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* Thrown when a QR code that has been scanned does not have the expected type.
*/
@Immutable
@NotNullByDefault
public class WrongQrCodeTypeException extends FormatException {
private final QrCodeType qrCodeType;
public WrongQrCodeTypeException(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
public QrCodeType getQrCodeType() {
return qrCodeType;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.util;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -17,13 +16,18 @@ import javax.annotation.Nullable;
import static java.nio.charset.CodingErrorAction.IGNORE; import static java.nio.charset.CodingErrorAction.IGNORE;
import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.CASE_INSENSITIVE;
@SuppressWarnings("CharsetObjectCanBeUsed")
@NotNullByDefault @NotNullByDefault
public class StringUtils { public class StringUtils {
private static final Charset UTF_8 = Charset.forName("UTF-8"); public static final Charset UTF_8 = Charset.forName("UTF-8");
private static Pattern MAC = Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" + public static final Charset US_ASCII = Charset.forName("US-ASCII");
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}", public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
CASE_INSENSITIVE);
private static final Pattern MAC =
Pattern.compile("[0-9a-f]{2}:[0-9a-f]{2}:" +
"[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}",
CASE_INSENSITIVE);
private static final char[] HEX = new char[] { private static final char[] HEX = new char[] {
'0', '1', '2', '3', '4', '5', '6', '7', '0', '1', '2', '3', '4', '5', '6', '7',
@@ -45,11 +49,7 @@ public class StringUtils {
} }
public static byte[] toUtf8(String s) { public static byte[] toUtf8(String s) {
try { return s.getBytes(UTF_8);
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
} }
public static String fromUtf8(byte[] bytes) { public static String fromUtf8(byte[] bytes) {

View File

@@ -9,30 +9,31 @@ apply from: 'witness.gradle'
apply from: '../dagger.gradle' apply from: '../dagger.gradle'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') api project(':bramble-api')
implementation 'org.bouncycastle:bcprov-jdk15to18:1.71'
api 'org.briarproject:jtorctl:0.5'
implementation "org.bouncycastle:bcprov-jdk15to18:$bouncy_castle_version"
//noinspection GradleDependency //noinspection GradleDependency
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
implementation 'org.whispersystems:curve25519-java:0.5.0' implementation 'org.whispersystems:curve25519-java:0.5.0'
implementation 'org.briarproject:jtorctl:0.5'
implementation 'org.briarproject:socks-socket:0.1' implementation 'org.briarproject:socks-socket:0.1'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version" implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6 testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'net.jodah:concurrentunit:0.4.2' testImplementation 'net.jodah:concurrentunit:0.4.2'
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-imposters:$jmock_version"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3" testImplementation "com.squareup.okhttp3:mockwebserver:$mockwebserver_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
@@ -52,7 +53,7 @@ configurations {
testOutput.extendsFrom(testCompile) testOutput.extendsFrom(testCompile)
} }
task jarTest(type: Jar, dependsOn: testClasses) { task jarTest(type: Jar, dependsOn: testClasses) {
from sourceSets.test.output from sourceSets.test.output, sourceSets.main.output
classifier = 'test' classifier = 'test'
} }
artifacts { artifacts {

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.mailbox.MailboxModule; import org.briarproject.bramble.mailbox.MailboxModule;
import org.briarproject.bramble.plugin.PluginModule; import org.briarproject.bramble.plugin.PluginModule;
import org.briarproject.bramble.properties.PropertiesModule; import org.briarproject.bramble.properties.PropertiesModule;
import org.briarproject.bramble.qrcode.QrCodeModule;
import org.briarproject.bramble.record.RecordModule; import org.briarproject.bramble.record.RecordModule;
import org.briarproject.bramble.reliability.ReliabilityModule; import org.briarproject.bramble.reliability.ReliabilityModule;
import org.briarproject.bramble.rendezvous.RendezvousModule; import org.briarproject.bramble.rendezvous.RendezvousModule;
@@ -47,6 +48,7 @@ import dagger.Module;
MailboxModule.class, MailboxModule.class,
PluginModule.class, PluginModule.class,
PropertiesModule.class, PropertiesModule.class,
QrCodeModule.class,
RecordModule.class, RecordModule.class,
ReliabilityModule.class, ReliabilityModule.class,
RendezvousModule.class, RendezvousModule.class,

View File

@@ -19,7 +19,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -29,6 +28,7 @@ import javax.inject.Inject;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT; import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.fromHexString; import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -99,7 +99,7 @@ class AccountManagerImpl implements AccountManager {
} }
try { try {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String key = reader.readLine(); String key = reader.readLine();
reader.close(); reader.close();
return key; return key;
@@ -151,7 +151,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock") @GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException { private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes(Charset.forName("UTF-8"))); out.write(key.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }

View File

@@ -29,7 +29,6 @@ import org.briarproject.nullsafety.NotNullByDefault;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.Provider; import java.security.Provider;
@@ -51,6 +50,7 @@ import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHE
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES; import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@NotNullByDefault @NotNullByDefault
class CryptoComponentImpl implements CryptoComponent { class CryptoComponentImpl implements CryptoComponent {
@@ -460,7 +460,7 @@ class CryptoComponentImpl implements CryptoComponent {
@Override @Override
public String encodeOnion(byte[] publicKey) { public String encodeOnion(byte[] publicKey) {
Digest digest = new SHA3Digest(256); Digest digest = new SHA3Digest(256);
byte[] label = ".onion checksum".getBytes(Charset.forName("US-ASCII")); byte[] label = ".onion checksum".getBytes(US_ASCII);
digest.update(label, 0, label.length); digest.update(label, 0, label.length);
digest.update(publicKey, 0, publicKey.length); digest.update(publicKey, 0, publicKey.length);
digest.update(ONION_HS_PROTOCOL_VERSION); digest.update(ONION_HS_PROTOCOL_VERSION);

View File

@@ -39,12 +39,13 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.nio.charset.Charset;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Scanner; import java.util.Scanner;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
public class MessageEncrypter { public class MessageEncrypter {
@@ -228,7 +229,7 @@ public class MessageEncrypter {
PublicKey publicKey = PublicKey publicKey =
encrypter.getKeyParser().parsePublicKey(keyBytes); encrypter.getKeyParser().parsePublicKey(keyBytes);
String message = readFully(System.in); String message = readFully(System.in);
byte[] plaintext = message.getBytes(Charset.forName("UTF-8")); byte[] plaintext = message.getBytes(UTF_8);
byte[] ciphertext = encrypter.encrypt(publicKey, plaintext); byte[] ciphertext = encrypter.encrypt(publicKey, plaintext);
System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH)); System.out.println(AsciiArmour.wrap(ciphertext, LINE_LENGTH));
} }
@@ -242,7 +243,7 @@ public class MessageEncrypter {
encrypter.getKeyParser().parsePrivateKey(keyBytes); encrypter.getKeyParser().parsePrivateKey(keyBytes);
byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in)); byte[] ciphertext = AsciiArmour.unwrap(readFully(System.in));
byte[] plaintext = encrypter.decrypt(privateKey, ciphertext); byte[] plaintext = encrypter.decrypt(privateKey, ciphertext);
System.out.println(new String(plaintext, Charset.forName("UTF-8"))); System.out.println(new String(plaintext, UTF_8));
} }
private static String readFully(InputStream in) throws IOException { private static String readFully(InputStream in) throws IOException {

View File

@@ -1,7 +1,5 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.data.BdfWriterFactory;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
import org.briarproject.bramble.api.keyagreement.PayloadEncoder; import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.api.keyagreement.PayloadParser; import org.briarproject.bramble.api.keyagreement.PayloadParser;
@@ -19,13 +17,13 @@ public class KeyAgreementModule {
} }
@Provides @Provides
PayloadEncoder providePayloadEncoder(BdfWriterFactory bdfWriterFactory) { PayloadEncoder providePayloadEncoder(PayloadEncoderImpl payloadEncoder) {
return new PayloadEncoderImpl(bdfWriterFactory); return payloadEncoder;
} }
@Provides @Provides
PayloadParser providePayloadParser(BdfReaderFactory bdfReaderFactory) { PayloadParser providePayloadParser(PayloadParserImpl payloadParser) {
return new PayloadParserImpl(bdfReaderFactory); return payloadParser;
} }
@Provides @Provides

View File

@@ -13,7 +13,8 @@ import java.io.IOException;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
@@ -29,7 +30,8 @@ class PayloadEncoderImpl implements PayloadEncoder {
@Override @Override
public byte[] encode(Payload p) { public byte[] encode(Payload p) {
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(PROTOCOL_VERSION); int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
out.write(formatIdAndVersion);
BdfWriter w = bdfWriterFactory.createWriter(out); BdfWriter w = bdfWriterFactory.createWriter(out);
try { try {
w.writeListStart(); // Payload start w.writeListStart(); // Payload start

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.keyagreement; package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
@@ -11,6 +12,9 @@ import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@@ -21,34 +25,42 @@ import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_BLUETOOTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.TRANSPORT_ID_LAN;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class PayloadParserImpl implements PayloadParser { class PayloadParserImpl implements PayloadParser {
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
PayloadParserImpl(BdfReaderFactory bdfReaderFactory) { PayloadParserImpl(BdfReaderFactory bdfReaderFactory,
QrCodeClassifier qrCodeClassifier) {
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public Payload parse(byte[] raw) throws IOException { public Payload parse(String payloadString) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(raw); Pair<QrCodeType, Integer> typeAndVersion =
// First byte: the protocol version qrCodeClassifier.classifyQrCode(payloadString);
int protocolVersion = in.read(); QrCodeType qrCodeType = typeAndVersion.getFirst();
if (protocolVersion == -1) throw new FormatException(); if (qrCodeType != BQP) throw new WrongQrCodeTypeException(qrCodeType);
if (protocolVersion != PROTOCOL_VERSION) { int formatVersion = typeAndVersion.getSecond();
boolean tooOld = protocolVersion < PROTOCOL_VERSION || if (formatVersion != QR_FORMAT_VERSION) {
protocolVersion == BETA_PROTOCOL_VERSION; boolean tooOld = formatVersion < QR_FORMAT_VERSION;
throw new UnsupportedVersionException(tooOld); throw new UnsupportedVersionException(tooOld);
} }
byte[] raw = payloadString.getBytes(ISO_8859_1);
ByteArrayInputStream in = new ByteArrayInputStream(raw);
// First byte: the format identifier and version (already parsed)
if (in.read() == -1) throw new AssertionError();
// The rest of the payload is a BDF list with one or more elements // The rest of the payload is a BDF list with one or more elements
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
BdfList payload = r.readList(); BdfList payload = r.readList();

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
@@ -25,6 +26,7 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
@Inject @Inject
MailboxPairingTaskFactoryImpl( MailboxPairingTaskFactoryImpl(
@@ -34,7 +36,8 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
this.crypto = crypto; this.crypto = crypto;
@@ -42,12 +45,13 @@ class MailboxPairingTaskFactoryImpl implements MailboxPairingTaskFactory {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
this.qrCodeClassifier = qrCodeClassifier;
} }
@Override @Override
public MailboxPairingTask createPairingTask(String qrCodePayload) { public MailboxPairingTask createPairingTask(String qrCodePayload) {
return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db, return new MailboxPairingTaskImpl(qrCodePayload, eventExecutor, db,
crypto, clock, api, mailboxSettingsManager, crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager); mailboxUpdateManager, qrCodeClassifier);
} }
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Consumer; import org.briarproject.bramble.api.Consumer;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -9,18 +10,26 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventExecutor; import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException; import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException; import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@@ -32,7 +41,10 @@ import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
@@ -40,9 +52,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final static Logger LOG = private final static Logger LOG =
getLogger(MailboxPairingTaskImpl.class.getName()); getLogger(MailboxPairingTaskImpl.class.getName());
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final int VERSION_REQUIRED = 32;
private final String payload; private final String payload;
private final Executor eventExecutor; private final Executor eventExecutor;
@@ -52,6 +61,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
private final MailboxApi api; private final MailboxApi api;
private final MailboxSettingsManager mailboxSettingsManager; private final MailboxSettingsManager mailboxSettingsManager;
private final MailboxUpdateManager mailboxUpdateManager; private final MailboxUpdateManager mailboxUpdateManager;
private final QrCodeClassifier qrCodeClassifier;
private final long timeStarted;
private final Object lock = new Object(); private final Object lock = new Object();
@GuardedBy("lock") @GuardedBy("lock")
@@ -68,7 +79,8 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
Clock clock, Clock clock,
MailboxApi api, MailboxApi api,
MailboxSettingsManager mailboxSettingsManager, MailboxSettingsManager mailboxSettingsManager,
MailboxUpdateManager mailboxUpdateManager) { MailboxUpdateManager mailboxUpdateManager,
QrCodeClassifier qrCodeClassifier) {
this.payload = payload; this.payload = payload;
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.db = db; this.db = db;
@@ -77,7 +89,9 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
this.api = api; this.api = api;
this.mailboxSettingsManager = mailboxSettingsManager; this.mailboxSettingsManager = mailboxSettingsManager;
this.mailboxUpdateManager = mailboxUpdateManager; this.mailboxUpdateManager = mailboxUpdateManager;
state = new MailboxPairingState.QrCodeReceived(); this.qrCodeClassifier = qrCodeClassifier;
timeStarted = clock.currentTimeMillis();
state = new QrCodeReceived(timeStarted);
} }
@Override @Override
@@ -99,22 +113,30 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
@Override @Override
public void run() { public void run() {
Pair<QrCodeType, Integer> typeAndVersion =
qrCodeClassifier.classifyQrCode(payload);
QrCodeType qrCodeType = typeAndVersion.getFirst();
int formatVersion = typeAndVersion.getSecond();
if (qrCodeType != MAILBOX || formatVersion != QR_FORMAT_VERSION) {
setState(new InvalidQrCode(qrCodeType, formatVersion));
return;
}
try { try {
pairMailbox(); pairMailbox();
} catch (FormatException e) { } catch (FormatException e) {
onMailboxError(e, new MailboxPairingState.InvalidQrCode()); onMailboxError(e, new InvalidQrCode(qrCodeType, formatVersion));
} catch (MailboxAlreadyPairedException e) { } catch (MailboxAlreadyPairedException e) {
onMailboxError(e, new MailboxPairingState.MailboxAlreadyPaired()); onMailboxError(e, new MailboxAlreadyPaired());
} catch (IOException e) { } catch (IOException e) {
onMailboxError(e, new MailboxPairingState.ConnectionError()); onMailboxError(e, new ConnectionError());
} catch (ApiException | DbException e) { } catch (ApiException | DbException e) {
onMailboxError(e, new MailboxPairingState.UnexpectedError()); onMailboxError(e, new UnexpectedError());
} }
} }
private void pairMailbox() throws IOException, ApiException, DbException { private void pairMailbox() throws IOException, ApiException, DbException {
MailboxProperties mailboxProperties = decodeQrCodePayload(payload); MailboxProperties mailboxProperties = decodeQrCodePayload(payload);
setState(new MailboxPairingState.Pairing()); setState(new Pairing(timeStarted));
MailboxProperties ownerProperties = api.setup(mailboxProperties); MailboxProperties ownerProperties = api.setup(mailboxProperties);
long time = clock.currentTimeMillis(); long time = clock.currentTimeMillis();
db.transaction(false, txn -> { db.transaction(false, txn -> {
@@ -133,7 +155,7 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} }
} }
}); });
setState(new MailboxPairingState.Paired()); setState(new Paired());
} }
private void onMailboxError(Exception e, MailboxPairingState state) { private void onMailboxError(Exception e, MailboxPairingState state) {
@@ -167,14 +189,6 @@ class MailboxPairingTaskImpl implements MailboxPairingTask {
} }
throw new FormatException(); throw new FormatException();
} }
int version = bytes[0] & 0xFF;
if (version != VERSION_REQUIRED) {
if (LOG.isLoggable(WARNING)) {
LOG.warning("QR code has not version " + VERSION_REQUIRED +
": " + version);
}
throw new FormatException();
}
LOG.info("QR code is valid"); LOG.info("QR code is valid");
byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33); byte[] onionPubKey = Arrays.copyOfRange(bytes, 1, 33);
String onion = crypto.encodeOnion(onionPubKey); String onion = crypto.encodeOnion(onionPubKey);

View File

@@ -46,7 +46,6 @@ import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -100,6 +99,7 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion; import static org.briarproject.bramble.util.PrivacyUtils.scrubOnion;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -140,7 +140,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final long maxLatency; private final long maxLatency;
private final int maxIdleTime; private final int maxIdleTime;
private final int socketTimeout; private final int socketTimeout;
private final File torDirectory, geoIpFile, configFile; private final File torDirectory;
private final File configFile;
private final int torSocksPort; private final int torSocksPort;
private final int torControlPort; private final int torControlPort;
private final File doneFile, cookieFile; private final File doneFile, cookieFile;
@@ -195,7 +196,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
this.torDirectory = torDirectory; this.torDirectory = torDirectory;
this.torSocksPort = torSocksPort; this.torSocksPort = torSocksPort;
this.torControlPort = torControlPort; this.torControlPort = torControlPort;
geoIpFile = new File(torDirectory, "geoip");
configFile = new File(torDirectory, "torrc"); configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done"); doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie"); cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
@@ -332,12 +332,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
// The done file may already exist from a previous installation // The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
doneFile.delete(); doneFile.delete();
// The GeoIP file may exist from a previous installation - we can
// save some space by deleting it.
// TODO: Remove after a reasonable migration period
// (added 2022-03-29)
//noinspection ResultOfMethodCallIgnored
geoIpFile.delete();
installTorExecutable(); installTorExecutable();
installObfs4Executable(); installObfs4Executable();
installSnowflakeExecutable(); installSnowflakeExecutable();
@@ -419,9 +413,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path); append(strb, "ClientTransportPlugin meek_lite exec", obfs4Path);
String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath(); String snowflakePath = getSnowflakeExecutableFile().getAbsolutePath();
append(strb, "ClientTransportPlugin snowflake exec", snowflakePath); append(strb, "ClientTransportPlugin snowflake exec", snowflakePath);
//noinspection CharsetObjectCanBeUsed return new ByteArrayInputStream(strb.toString().getBytes(UTF_8));
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
} }
private void listFiles(File f) { private void listFiles(File f) {
@@ -811,7 +803,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
@Override @Override
public void controlConnectionClosed() { public void controlConnectionClosed() {
if (state.isTorRunning()) { if (state.isTorRunning()) {
throw new RuntimeException("Control connection closed"); // TODO: Restart the Tor process
LOG.warning("Control connection closed");
} }
} }

View File

@@ -7,7 +7,7 @@ import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import java.nio.charset.Charset; import static org.briarproject.bramble.util.StringUtils.US_ASCII;
class TorRendezvousCryptoImpl implements TorRendezvousCrypto { class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
@@ -31,6 +31,6 @@ class TorRendezvousCryptoImpl implements TorRendezvousCrypto {
EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC); EdDSAPrivateKeySpec spec = new EdDSAPrivateKeySpec(seed, CURVE_SPEC);
byte[] hash = spec.getH(); byte[] hash = spec.getH();
byte[] base64 = Base64.encode(hash); byte[] base64 = Base64.encode(hash);
return "ED25519-V3:" + new String(base64, Charset.forName("US-ASCII")); return "ED25519-V3:" + new String(base64, US_ASCII);
} }
} }

View File

@@ -0,0 +1,42 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
@Immutable
@NotNullByDefault
class QrCodeClassifierImpl implements QrCodeClassifier {
@Inject
QrCodeClassifierImpl() {
}
@Override
public Pair<QrCodeType, Integer> classifyQrCode(String payload) {
byte[] bytes = payload.getBytes(ISO_8859_1);
if (bytes.length == 0) return new Pair<>(UNKNOWN, 0);
// If this is a Bramble QR code then the first byte encodes the
// format ID (3 bits) and version (5 bits)
int formatIdAndVersion = bytes[0] & 0xFF;
int formatId = formatIdAndVersion >> 5;
int formatVersion = formatIdAndVersion & 0x1F;
if (formatId == KeyAgreementConstants.QR_FORMAT_ID) {
return new Pair<>(BQP, formatVersion);
}
if (formatId == MailboxConstants.QR_FORMAT_ID) {
return new Pair<>(MAILBOX, formatVersion);
}
return new Pair<>(UNKNOWN, 0);
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import dagger.Module;
import dagger.Provides;
@Module
public class QrCodeModule {
@Provides
QrCodeClassifier provideQrCodeClassifier(
QrCodeClassifierImpl qrCodeClassifier) {
return qrCodeClassifier;
}
}

View File

@@ -21,11 +21,11 @@ n Bridge obfs4 185.103.252.72:443 75F15E9339FF572F88F5588D429FEA379744BC53 cert=
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0 n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0 n Bridge obfs4 94.142.246.132:8088 135C158527AA9FE9A2F26EC515EB6999D813D347 cert=wTUz0/5FhAZRkitil5MprGbSF3JzjxjxI1kAmxAdSeDy98NgcLr11f/qUXWDC76Y97RiSg iat-mode=0
n Bridge obfs4 20.102.79.78:22022 B5705F7E616DAB0F477E3E1ADC23E40413F683FE cert=1Cc/hwPtPjzFKGHVOP0j/qmBgnvquRx8+im35/u5TIYjDQ3FlMfA5VvTrQ/hbX8BZZooLQ iat-mode=0 n Bridge obfs4 20.102.79.78:22022 B5705F7E616DAB0F477E3E1ADC23E40413F683FE cert=1Cc/hwPtPjzFKGHVOP0j/qmBgnvquRx8+im35/u5TIYjDQ3FlMfA5VvTrQ/hbX8BZZooLQ iat-mode=0
n Bridge obfs4 207.154.242.137:80 8E67A1B2A342652EE27376BD61BECF5806700E7F cert=qUrR9fan3XPNGNOwn9WGlXLJNZZx0grXH4AZXR+yoBbtbbj5Ak1n4a7TtjYgXcWcs/gHXw iat-mode=0
n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0 n Bridge obfs4 152.70.180.20:1993 3327C43587E66AD5F874C4234A1D72C938AD7318 cert=s7xLRUO2psaX7TMUP2YhXdxItR4U6K7D+E3gQaS/+yWUppevtazIibq4dN1g5Reu6dD2QQ iat-mode=0
n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0 n Bridge obfs4 144.202.12.254:10002 4E220F45CD404C8A3082A36326A5ED19BB8D4404 cert=iLz5YYWO4pUw7U7MRNOSvE0qO+IVeE4kVfFVWPO3coH3FmZtrkvlaTklfXxHZaCcXWBgaA iat-mode=0
n Bridge obfs4 15.235.47.204:42058 869133925B3CD07683BDF01805C36448D090CE88 cert=PFwh4mzZlSTUdcEskpe20t998n5jbr81s+XoX7gmazqzUGHNhkendK5K1j2gOxesz9AkBw iat-mode=0 n Bridge obfs4 109.14.168.159:5082 BFE1416DEFFE969581F016A4A319A87FFB26BA91 cert=n3X1CDdKBPXPIzfKh83p3ydfMzb0AD9gKC+/gIpHb7+xjjAnYO9x3LT+T/MvOIfAXxYySg iat-mode=0
n Bridge obfs4 51.75.93.136:45532 8402B84833527BC249B21AC885134197E624FB5A cert=LwXEf/Dgo0tKdMJByXdlvWiJqyyPw4T284Cg5qygDuIJJNFuz3ED9UhGil6H4Of3gM7wSg iat-mode=0 n Bridge obfs4 45.150.172.16:80 82849E69CBB25EA7F479155F7DCD89D85717FF47 cert=+Krdu1jmVQOxWkMj0mqJHgwbQV49eyD6mZDS+mRExssWNHosa60g4P5Gp81sBJKzN8NrSw iat-mode=0
n Bridge obfs4 185.177.207.132:8443 4FB781F7A9DD39DA53A7996907817FC479874D19 cert=UL2gCAXWW5kEWY4TQ0lNeu6OAmzh40bXYVhMnTWVG8USnyy/zEKGSIPgmwTDMumWr9c1Pg iat-mode=0
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31 v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB v Bridge 213.196.191.96:9060 05E222E2A8C234234FE0CEB58B08A93B8FC360DB

View File

@@ -20,7 +20,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -34,6 +33,7 @@ import static org.briarproject.bramble.test.TestUtils.getIdentity;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey; import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.UTF_8;
import static org.briarproject.bramble.util.StringUtils.getRandomString; import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
@@ -342,7 +342,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
private void storeDatabaseKey(File f, String hex) throws IOException { private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs(); f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes(Charset.forName("UTF-8"))); out.write(hex.getBytes(UTF_8));
out.flush(); out.flush();
out.close(); out.close();
} }
@@ -350,7 +350,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Nullable @Nullable
private String loadDatabaseKey(File f) throws IOException { private String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), Charset.forName("UTF-8"))); new FileInputStream(f), UTF_8));
String hex = reader.readLine(); String hex = reader.readLine();
reader.close(); reader.close();
return hex; return hex;

View File

@@ -2,21 +2,27 @@ package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Bytes; import org.briarproject.bramble.api.Bytes;
import org.briarproject.bramble.api.FormatException; import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException; import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.data.BdfList; import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader; import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory; import org.briarproject.bramble.api.data.BdfReaderFactory;
import org.briarproject.bramble.api.keyagreement.Payload; import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
import org.junit.Test; import org.junit.Test;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.BETA_PROTOCOL_VERSION;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.COMMIT_LENGTH;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION; import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@@ -26,32 +32,29 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
private final BdfReaderFactory bdfReaderFactory = private final BdfReaderFactory bdfReaderFactory =
context.mock(BdfReaderFactory.class); context.mock(BdfReaderFactory.class);
private final QrCodeClassifier qrCodeClassifier =
context.mock(QrCodeClassifier.class);
private final BdfReader bdfReader = context.mock(BdfReader.class); private final BdfReader bdfReader = context.mock(BdfReader.class);
private final PayloadParserImpl payloadParser = private final String payload = getRandomString(123);
new PayloadParserImpl(bdfReaderFactory);
@Test(expected = FormatException.class) private final PayloadParserImpl payloadParser =
public void testThrowsFormatExceptionIfPayloadIsEmpty() throws Exception { new PayloadParserImpl(bdfReaderFactory, qrCodeClassifier);
payloadParser.parse(new byte[0]);
@Test(expected = WrongQrCodeTypeException.class)
public void testThrowsExceptionForWrongQrCodeType() throws Exception {
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
payloadParser.parse(payload);
} }
@Test @Test
public void testThrowsUnsupportedVersionExceptionForOldVersion() public void testThrowsUnsupportedVersionExceptionForOldVersion()
throws Exception { throws Exception {
try { expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION - 1);
payloadParser.parse(new byte[] {PROTOCOL_VERSION - 1});
fail();
} catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld());
}
}
@Test
public void testThrowsUnsupportedVersionExceptionForBetaVersion()
throws Exception {
try { try {
payloadParser.parse(new byte[] {BETA_PROTOCOL_VERSION}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertTrue(e.isTooOld()); assertTrue(e.isTooOld());
@@ -61,8 +64,10 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test @Test
public void testThrowsUnsupportedVersionExceptionForNewVersion() public void testThrowsUnsupportedVersionExceptionForNewVersion()
throws Exception { throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION + 1);
try { try {
payloadParser.parse(new byte[] {PROTOCOL_VERSION + 1}); payloadParser.parse(payload);
fail(); fail();
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
assertFalse(e.isTooOld()); assertFalse(e.isTooOld());
@@ -71,6 +76,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
public void testThrowsFormatExceptionForEmptyList() throws Exception { public void testThrowsFormatExceptionForEmptyList() throws Exception {
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -79,7 +86,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(new BdfList())); will(returnValue(new BdfList()));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -87,6 +94,8 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -97,7 +106,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(false)); will(returnValue(false));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -105,6 +114,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH - 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -115,7 +125,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test(expected = FormatException.class) @Test(expected = FormatException.class)
@@ -123,6 +133,7 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
throws Exception { throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1); byte[] commitment = getRandomBytes(COMMIT_LENGTH + 1);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -133,12 +144,14 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
payloadParser.parse(new byte[] {PROTOCOL_VERSION}); payloadParser.parse(payload);
} }
@Test @Test
public void testAcceptsPayloadWithNoDescriptors() throws Exception { public void testAcceptsPayloadWithNoDescriptors() throws Exception {
byte[] commitment = getRandomBytes(COMMIT_LENGTH); byte[] commitment = getRandomBytes(COMMIT_LENGTH);
expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(bdfReaderFactory).createReader( oneOf(bdfReaderFactory).createReader(
with(any(ByteArrayInputStream.class))); with(any(ByteArrayInputStream.class)));
@@ -149,8 +162,16 @@ public class PayloadParserImplTest extends BrambleMockTestCase {
will(returnValue(true)); will(returnValue(true));
}}); }});
Payload p = payloadParser.parse(new byte[] {PROTOCOL_VERSION}); Payload p = payloadParser.parse(payload);
assertArrayEquals(commitment, p.getCommitment()); assertArrayEquals(commitment, p.getCommitment());
assertTrue(p.getTransportDescriptors().isEmpty()); assertTrue(p.getTransportDescriptors().isEmpty());
} }
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
}
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.mailbox; package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -7,13 +8,24 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.mailbox.MailboxAuthToken; import org.briarproject.bramble.api.mailbox.MailboxAuthToken;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pairing;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.QrCodeReceived;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxProperties; import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager; import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate; import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager; import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion; import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxAlreadyPairedException;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations; import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor; import org.briarproject.bramble.test.ImmediateExecutor;
@@ -27,6 +39,9 @@ import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload; import static org.briarproject.bramble.mailbox.MailboxTestUtils.getQrCodePayload;
import static org.briarproject.bramble.test.TestUtils.getContact; import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
@@ -48,9 +63,8 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
context.mock(MailboxSettingsManager.class); context.mock(MailboxSettingsManager.class);
private final MailboxUpdateManager mailboxUpdateManager = private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class); context.mock(MailboxUpdateManager.class);
private final MailboxPairingTaskFactory factory = private final QrCodeClassifier qrCodeClassifier =
new MailboxPairingTaskFactoryImpl(executor, db, crypto, clock, api, context.mock(QrCodeClassifier.class);
mailboxSettingsManager, mailboxUpdateManager);
private final String onion = getRandomString(56); private final String onion = getRandomString(56);
private final byte[] onionBytes = getRandomBytes(32); private final byte[] onionBytes = getRandomBytes(32);
@@ -68,32 +82,50 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testInitialQrCodeReceivedState() { public void testInitialQrCodeReceivedState() {
MailboxPairingTask task = MailboxPairingTask task = createPairingTask(getRandomString(42));
factory.createPairingTask(getRandomString(42));
task.addObserver(state -> task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.QrCodeReceived) assertTrue(state instanceof QrCodeReceived));
);
} }
@Test @Test
public void testInvalidQrCode() { public void testInvalidQrCodeType() {
MailboxPairingTask task1 = String payload = getRandomString(65);
factory.createPairingTask(getRandomString(42)); MailboxPairingTask task = createPairingTask(payload);
task1.run();
task1.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode)
);
String goodLength = "00" + getRandomString(63); expectClassifyQrCode(payload, BQP, QR_FORMAT_VERSION);
MailboxPairingTask task2 = factory.createPairingTask(goodLength);
task2.run(); task.run();
task2.addObserver(state -> task.addObserver(state ->
assertTrue(state instanceof MailboxPairingState.InvalidQrCode) assertTrue(state instanceof InvalidQrCode));
); }
@Test
public void testInvalidQrCodeVersion() {
String payload = getRandomString(65);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION + 1);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
}
@Test
public void testInvalidQrCodeLength() {
String payload = getRandomString(42);
MailboxPairingTask task = createPairingTask(payload);
expectClassifyQrCode(payload, MAILBOX, QR_FORMAT_VERSION);
task.run();
task.addObserver(state ->
assertTrue(state instanceof InvalidQrCode));
} }
@Test @Test
public void testSuccessfulPairing() throws Exception { public void testSuccessfulPairing() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -121,17 +153,14 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
}}); }});
AtomicInteger i = new AtomicInteger(0); AtomicInteger i = new AtomicInteger(0);
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.addObserver(state -> { task.addObserver(state -> {
if (i.get() == 0) { if (i.get() == 0) {
assertEquals(MailboxPairingState.QrCodeReceived.class, assertEquals(QrCodeReceived.class, state.getClass());
state.getClass());
} else if (i.get() == 1) { } else if (i.get() == 1) {
assertEquals(MailboxPairingState.Pairing.class, assertEquals(Pairing.class, state.getClass());
state.getClass());
} else if (i.get() == 2) { } else if (i.get() == 2) {
assertEquals(MailboxPairingState.Paired.class, assertEquals(Paired.class, state.getClass());
state.getClass());
} else fail("Unexpected change of state " + state.getClass()); } else fail("Unexpected change of state " + state.getClass());
i.getAndIncrement(); i.getAndIncrement();
}); });
@@ -140,24 +169,23 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
@Test @Test
public void testAlreadyPaired() throws Exception { public void testAlreadyPaired() throws Exception {
testApiException(new MailboxApi.MailboxAlreadyPairedException(), testApiException(new MailboxAlreadyPairedException(),
MailboxPairingState.MailboxAlreadyPaired.class); MailboxAlreadyPaired.class);
} }
@Test @Test
public void testMailboxApiException() throws Exception { public void testMailboxApiException() throws Exception {
testApiException(new MailboxApi.ApiException(), testApiException(new ApiException(), UnexpectedError.class);
MailboxPairingState.UnexpectedError.class);
} }
@Test @Test
public void testApiIOException() throws Exception { public void testApiIOException() throws Exception {
testApiException(new IOException(), testApiException(new IOException(), ConnectionError.class);
MailboxPairingState.ConnectionError.class);
} }
private void testApiException(Exception e, private void testApiException(Exception e,
Class<? extends MailboxPairingState> s) throws Exception { Class<? extends MailboxPairingState> s) throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -165,13 +193,14 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(e)); will(throwException(e));
}}); }});
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.run(); task.run();
task.addObserver(state -> assertEquals(state.getClass(), s)); task.addObserver(state -> assertEquals(state.getClass(), s));
} }
@Test @Test
public void testDbException() throws Exception { public void testDbException() throws Exception {
expectClassifyQrCode(validPayload, MAILBOX, QR_FORMAT_VERSION);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
oneOf(crypto).encodeOnion(onionBytes); oneOf(crypto).encodeOnion(onionBytes);
will(returnValue(onion)); will(returnValue(onion));
@@ -188,10 +217,10 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
will(throwException(new DbException())); will(throwException(new DbException()));
}}); }});
MailboxPairingTask task = factory.createPairingTask(validPayload); MailboxPairingTask task = createPairingTask(validPayload);
task.run(); task.run();
task.addObserver(state -> assertEquals(state.getClass(), task.addObserver(state ->
MailboxPairingState.UnexpectedError.class)); assertEquals(state.getClass(), UnexpectedError.class));
} }
private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) { private PredicateMatcher<MailboxProperties> matches(MailboxProperties p2) {
@@ -202,4 +231,22 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
p1.getServerSupports().equals(p2.getServerSupports())); p1.getServerSupports().equals(p2.getServerSupports()));
} }
private MailboxPairingTask createPairingTask(String qrCodePayload) {
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(time));
}});
return new MailboxPairingTaskImpl(qrCodePayload, executor, db,
crypto, clock, api, mailboxSettingsManager,
mailboxUpdateManager, qrCodeClassifier);
}
private void expectClassifyQrCode(String payload, QrCodeType qrCodeType,
int formatVersion) {
context.checking(new Expectations() {{
oneOf(qrCodeClassifier).classifyQrCode(payload);
will(returnValue(new Pair<>(qrCodeType, formatVersion)));
}});
}
} }

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.WeakSingletonProvider; import org.briarproject.bramble.api.WeakSingletonProvider;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -11,20 +10,24 @@ import javax.net.SocketFactory;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.test.TestUtils.getRandomId; import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
class MailboxTestUtils { class MailboxTestUtils {
static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) { static String getQrCodePayload(byte[] onionBytes, byte[] setupToken) {
int formatIdAndVersion = (QR_FORMAT_ID << 5) | QR_FORMAT_VERSION;
byte[] payloadBytes = ByteBuffer.allocate(65) byte[] payloadBytes = ByteBuffer.allocate(65)
.put((byte) 32) // 1 .put((byte) formatIdAndVersion) // 1
.put(onionBytes) // 32 .put(onionBytes) // 32
.put(setupToken) // 32 .put(setupToken) // 32
.array(); .array();
//noinspection CharsetObjectCanBeUsed return new String(payloadBytes, ISO_8859_1);
return new String(payloadBytes, Charset.forName("ISO-8859-1"));
} }
// Used by mailbox integration tests
static String getQrCodePayload(byte[] setupToken) { static String getQrCodePayload(byte[] setupToken) {
return getQrCodePayload(getRandomId(), setupToken); return getQrCodePayload(getRandomId(), setupToken);
} }

View File

@@ -0,0 +1,70 @@
package org.briarproject.bramble.qrcode;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConstants;
import org.briarproject.bramble.api.mailbox.MailboxConstants;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.UNKNOWN;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.junit.Assert.assertEquals;
public class QrCodeClassifierImplTest extends BrambleTestCase {
private final QrCodeClassifier classifier = new QrCodeClassifierImpl();
@Test
public void testClassifiesEmptyStringAsUnknown() {
Pair<QrCodeType, Integer> result = classifier.classifyQrCode("");
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
@Test
public void testClassifiesKeyAgreement() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(KeyAgreementConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(BQP, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesMailbox() {
byte[] payloadBytes = getRandomBytes(123);
for (int version = 0; version < 32; version++) {
int typeAndVersion =
(MailboxConstants.QR_FORMAT_ID << 5) | version;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result =
classifier.classifyQrCode(payload);
assertEquals(MAILBOX, result.getFirst());
assertEquals(version, result.getSecond().intValue());
}
}
@Test
public void testClassifiesUnknownFormatIdAsUnknown() {
byte[] payloadBytes = getRandomBytes(123);
int unknownFormatId = MailboxConstants.QR_FORMAT_ID + 1;
int typeAndVersion = unknownFormatId << 5;
payloadBytes[0] = (byte) typeAndVersion;
String payload = new String(payloadBytes, ISO_8859_1);
Pair<QrCodeType, Integer> result = classifier.classifyQrCode(payload);
assertEquals(UNKNOWN, result.getFirst());
assertEquals(0, result.getSecond().intValue());
}
}

View File

@@ -12,11 +12,13 @@ configurations {
} }
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(':bramble-core')
implementation fileTree(dir: 'libs', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar')
def jna_version = '4.5.2' def jna_version = '4.5.2'
implementation "net.java.dev.jna:jna:$jna_version" implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version" implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor-linux:$tor_version" tor "org.briarproject:tor-linux:$tor_version"
tor "org.briarproject:tor-windows:$tor_version" tor "org.briarproject:tor-windows:$tor_version"
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version" tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
@@ -28,9 +30,11 @@ dependencies {
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation "junit:junit:$junit_version" testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "com.squareup.okhttp3:okhttp:$okhttp_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
} }

View File

@@ -12,7 +12,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@@ -33,6 +32,7 @@ import static java.util.logging.Level.WARNING;
import static jssc.SerialPort.PURGE_RXCLEAR; import static jssc.SerialPort.PURGE_RXCLEAR;
import static jssc.SerialPort.PURGE_TXCLEAR; import static jssc.SerialPort.PURGE_TXCLEAR;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.US_ASCII;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -41,7 +41,6 @@ class ModemImpl implements Modem, WriteHandler, SerialPortEventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ModemImpl.class.getName()); Logger.getLogger(ModemImpl.class.getName());
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private static final int MAX_LINE_LENGTH = 256; private static final int MAX_LINE_LENGTH = 256;
private static final int[] BAUD_RATES = { private static final int[] BAUD_RATES = {
256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200 256000, 128000, 115200, 57600, 38400, 19200, 14400, 9600, 4800, 1200

View File

@@ -15,6 +15,8 @@ dependencyVerification {
'com.google.guava:guava:31.0.1-jre:guava-31.0.1-jre.jar:d5be94d65e87bd219fb3193ad1517baa55a3b88fc91d21cf735826ab5af087b9', 'com.google.guava:guava:31.0.1-jre:guava-31.0.1-jre.jar:d5be94d65e87bd219fb3193ad1517baa55a3b88fc91d21cf735826ab5af087b9',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99', 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b', 'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.squareup.okhttp3:okhttp:3.12.13:okhttp-3.12.13.jar:508234e024ef7e270ab1a6d5b356f5b98e786511239ca986d684fd1e2cf7bc82',
'com.squareup.okio:okio:1.15.0:okio-1.15.0.jar:693fa319a7e8843300602b204023b7674f106ebcb577f2dd5807212b66118bd2',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
@@ -28,8 +30,8 @@ dependencyVerification {
'org.briarproject:obfs4proxy-windows:0.0.14-tor1:obfs4proxy-windows-0.0.14-tor1.jar:9dd122b31b3cd1616f168091dcdb01de049d1e052fe5c089b7627618a8a2694b', 'org.briarproject:obfs4proxy-windows:0.0.14-tor1:obfs4proxy-windows-0.0.14-tor1.jar:9dd122b31b3cd1616f168091dcdb01de049d1e052fe5c089b7627618a8a2694b',
'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3', 'org.briarproject:snowflake-linux:2.3.1:snowflake-linux-2.3.1.jar:99ecf4546d8f79eb8408168c09380fec596558ac934554bf7d4247ea7ef2c9f3',
'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a', 'org.briarproject:snowflake-windows:2.3.1:snowflake-windows-2.3.1.jar:d011f1a72c00a221f56380c19aad8ff11db8c2bb1adb0784125572d80b4d275a',
'org.briarproject:tor-linux:0.4.5.14:tor-linux-0.4.5.14.jar:1844e54cf6df0c85cec219381a3364c759ae444a6b63f7558b757becb7d41d08', 'org.briarproject:tor-linux:0.4.7.13:tor-linux-0.4.7.13.jar:9819ee973cbcdc133f7d04aef9d4b957a35087627a790e532142d15412a9636f',
'org.briarproject:tor-windows:0.4.5.14:tor-windows-0.4.5.14.jar:d337afa1043f0cfa7e6e8c2473d682a5663a2c8052bb97a770450893c78c9b4f', 'org.briarproject:tor-windows:0.4.7.13:tor-windows-0.4.7.13.jar:853d2769665614e26703cbe02e43b218b064c04a0bcd120fdc459cda45bd2606',
'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a', 'org.checkerframework:checker-compat-qual:2.5.5:checker-compat-qual-2.5.5.jar:11d134b245e9cacc474514d2d66b5b8618f8039a1465cdc55bbc0b34e0008b7a',
'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb', 'org.checkerframework:checker-qual:3.12.0:checker-qual-3.12.0.jar:ff10785ac2a357ec5de9c293cb982a2cbb605c0309ea4cc1cb9b9bc6dbe7f3cb',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',

View File

@@ -26,8 +26,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 31 targetSdkVersion 31
versionCode 10417 versionCode 10420
versionName "1.4.17" versionName "1.4.20"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "TorVersion", "\"$tor_version\"" buildConfigField "String", "TorVersion", "\"$tor_version\""
@@ -98,10 +98,15 @@ android {
} }
dependencies { dependencies {
implementation project(path: ':briar-core', configuration: 'default') // In theory this dependency shouldn't be needed, but without it Android Studio's linter will
implementation project(path: ':bramble-core', configuration: 'default') // complain about unresolved symbols for bramble-api test classes in briar-android tests,
// even though the bramble-api test classes are provided by the testImplementation dependency
// below and the compiler can find them
implementation project(':bramble-api')
implementation project(':bramble-core')
implementation project(':bramble-android') implementation project(':bramble-android')
implementation 'org.briarproject:dont-kill-me-lib:0.2.5' implementation project(':briar-core')
implementation 'androidx.fragment:fragment:1.3.4' implementation 'androidx.fragment:fragment:1.3.4'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
@@ -111,6 +116,9 @@ dependencies {
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
implementation 'org.briarproject:dont-kill-me-lib:0.2.5'
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "org.jsoup:jsoup:$jsoup_version"
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'
implementation 'de.hdodenhof:circleimageview:3.1.0' implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24 implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
@@ -130,9 +138,10 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
def espressoVersion = '3.3.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
def espressoVersion = '3.3.0'
testImplementation 'androidx.test:runner:1.4.0' testImplementation 'androidx.test:runner:1.4.0'
testImplementation 'androidx.test.ext:junit:1.1.3' testImplementation 'androidx.test.ext:junit:1.1.3'
testImplementation 'androidx.fragment:fragment-testing:1.4.0' testImplementation 'androidx.fragment:fragment-testing:1.4.0'
@@ -144,18 +153,24 @@ dependencies {
testImplementation "org.jmock:jmock:$jmock_version" testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version" testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version" testImplementation "org.jmock:jmock-imposters:$jmock_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version" testAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput') androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestUtil 'androidx.test:orchestrator:1.3.0'
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestImplementation "junit:junit:$junit_version" androidTestImplementation "junit:junit:$junit_version"
androidTestUtil 'androidx.test:orchestrator:1.3.0'
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"
androidTestCompileOnly 'javax.annotation:jsr250-api:1.0'
androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.1.1' androidTestScreenshotImplementation 'tools.fastlane:screengrab:2.1.1'
androidTestScreenshotImplementation 'com.jraska:falcon:2.2.0' androidTestScreenshotImplementation 'com.jraska:falcon:2.2.0'
androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestScreenshotImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'

View File

@@ -52,7 +52,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import static org.briarproject.bramble.util.AndroidUtils.isUiThread; import static org.briarproject.bramble.util.AndroidUtils.isUiThread;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
@@ -141,11 +140,6 @@ public class BriarService extends Service {
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET); ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false); ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel); nm.createNotificationChannel(ongoingChannel);
// Delete the unused channel previously used for startup
// failure notifications
// TODO: Remove this ID after a reasonable upgrade period
// (added 2021-07-12)
nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
} }
Notification foregroundNotification = Notification foregroundNotification =
notificationManager.getForegroundNotification(); notificationManager.getForegroundNotification();

View File

@@ -41,7 +41,6 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@NotNullByDefault @NotNullByDefault
abstract class BaseViewModel extends DbViewModel implements EventListener { abstract class BaseViewModel extends DbViewModel implements EventListener {
@@ -115,7 +114,7 @@ abstract class BaseViewModel extends DbViewModel implements EventListener {
@DatabaseExecutor @DatabaseExecutor
private String getPostText(Transaction txn, MessageId m) private String getPostText(Transaction txn, MessageId m)
throws DbException { throws DbException {
return HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE); return HtmlUtils.cleanArticle(blogManager.getPostText(txn, m));
} }
LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) { LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) {

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.blog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.text.Spanned; import android.text.Spanned;
import android.text.util.Linkify;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -170,7 +171,12 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
// TODO make author clickable #624 // TODO make author clickable #624
text.setText(c.getComment()); text.setText(c.getComment());
if (fullText) text.setTextIsSelectable(true); Linkify.addLinks(text, Linkify.WEB_URLS);
text.setMovementMethod(null);
if (fullText) {
text.setTextIsSelectable(true);
makeLinksClickable(text, listener::onLinkClick);
}
commentContainer.addView(v); commentContainer.addView(v);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.blog; package org.briarproject.briar.android.blog;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
@@ -16,10 +15,6 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class RssFeedActivity extends BriarActivity public class RssFeedActivity extends BriarActivity
@@ -50,13 +45,13 @@ public class RssFeedActivity extends BriarActivity
viewModel.getImportResult().observeEvent(this, this::onImportResult); viewModel.getImportResult().observeEvent(this, this::onImportResult);
} }
private void onImportResult(RssFeedViewModel.ImportResult result) { private void onImportResult(boolean result) {
if (result == IMPORTED) { if (result) {
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) { if (fm.findFragmentByTag(RssFeedImportFragment.TAG) != null) {
onBackPressed(); onBackPressed();
} }
} else if (result == FAILED) { } else {
String url = viewModel.getUrlFailedImport(); String url = viewModel.getUrlFailedImport();
if (url == null) { if (url == null) {
throw new AssertionError(); throw new AssertionError();
@@ -65,9 +60,6 @@ public class RssFeedActivity extends BriarActivity
RssFeedImportFailedDialogFragment.newInstance(url); RssFeedImportFailedDialogFragment.newInstance(url);
dialog.show(getSupportFragmentManager(), dialog.show(getSupportFragmentManager(),
RssFeedImportFailedDialogFragment.TAG); RssFeedImportFailedDialogFragment.TAG);
} else if (result == EXISTS) {
Toast.makeText(this, R.string.blogs_rss_feeds_import_exists,
Toast.LENGTH_LONG).show();
} }
} }
} }

View File

@@ -28,8 +28,7 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
super(new DiffUtil.ItemCallback<Feed>() { super(new DiffUtil.ItemCallback<Feed>() {
@Override @Override
public boolean areItemsTheSame(Feed a, Feed b) { public boolean areItemsTheSame(Feed a, Feed b) {
return a.getUrl().equals(b.getUrl()) && return a.getBlogId().equals(b.getBlogId()) &&
a.getBlogId().equals(b.getBlogId()) &&
a.getAdded() == b.getAdded(); a.getAdded() == b.getAdded();
} }
@@ -86,8 +85,8 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
delete.setOnClickListener(v -> listener.onDeleteClick(item)); delete.setOnClickListener(v -> listener.onDeleteClick(item));
// Author // Author
if (item.getRssAuthor() != null) { if (item.getProperties().getAuthor() != null) {
author.setText(item.getRssAuthor()); author.setText(item.getProperties().getAuthor());
author.setVisibility(VISIBLE); author.setVisibility(VISIBLE);
authorLabel.setVisibility(VISIBLE); authorLabel.setVisibility(VISIBLE);
} else { } else {
@@ -100,8 +99,8 @@ class RssFeedAdapter extends ListAdapter<Feed, RssFeedAdapter.FeedViewHolder> {
updated.setText(formatDate(ctx, item.getUpdated())); updated.setText(formatDate(ctx, item.getUpdated()));
// Description // Description
if (item.getDescription() != null) { if (item.getProperties().getDescription() != null) {
description.setText(item.getDescription()); description.setText(item.getProperties().getDescription());
description.setVisibility(VISIBLE); description.setVisibility(VISIBLE);
} else { } else {
description.setVisibility(GONE); description.setVisibility(GONE);

View File

@@ -22,7 +22,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Collections; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -38,13 +38,9 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.EXISTS;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.FAILED;
import static org.briarproject.briar.android.blog.RssFeedViewModel.ImportResult.IMPORTED;
@NotNullByDefault @NotNullByDefault
class RssFeedViewModel extends DbViewModel { class RssFeedViewModel extends DbViewModel {
enum ImportResult {IMPORTED, FAILED, EXISTS}
private static final Logger LOG = private static final Logger LOG =
getLogger(RssFeedViewModel.class.getName()); getLogger(RssFeedViewModel.class.getName());
@@ -60,7 +56,7 @@ class RssFeedViewModel extends DbViewModel {
private volatile String urlFailedImport = null; private volatile String urlFailedImport = null;
private final MutableLiveData<Boolean> isImporting = private final MutableLiveData<Boolean> isImporting =
new MutableLiveData<>(false); new MutableLiveData<>(false);
private final MutableLiveEvent<ImportResult> importResult = private final MutableLiveEvent<Boolean> importResult =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@Inject @Inject
@@ -101,7 +97,6 @@ class RssFeedViewModel extends DbViewModel {
private List<Feed> loadFeeds(Transaction txn) throws DbException { private List<Feed> loadFeeds(Transaction txn) throws DbException {
long start = now(); long start = now();
List<Feed> feeds = feedManager.getFeeds(txn); List<Feed> feeds = feedManager.getFeeds(txn);
Collections.sort(feeds);
logDuration(LOG, "Loading feeds", start); logDuration(LOG, "Loading feeds", start);
return feeds; return feeds;
} }
@@ -125,7 +120,7 @@ class RssFeedViewModel extends DbViewModel {
}); });
} }
LiveEvent<ImportResult> getImportResult() { LiveEvent<Boolean> getImportResult() {
return importResult; return importResult;
} }
@@ -138,21 +133,23 @@ class RssFeedViewModel extends DbViewModel {
urlFailedImport = null; urlFailedImport = null;
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
if (exists(url)) {
importResult.postEvent(EXISTS);
return;
}
Feed feed = feedManager.addFeed(url); Feed feed = feedManager.addFeed(url);
List<Feed> updated = addListItem(getList(feeds), feed); // Update the feed if it was already present
if (updated != null) { List<Feed> feedList = getList(feeds);
Collections.sort(updated); if (feedList == null) feedList = new ArrayList<>();
feeds.postValue(new LiveResult<>(updated)); List<Feed> updated = updateListItems(feedList,
f -> f.equals(feed), f -> feed);
// Add the feed if it wasn't already present
if (updated == null) {
feedList.add(feed);
updated = feedList;
} }
importResult.postEvent(IMPORTED); feeds.postValue(new LiveResult<>(updated));
importResult.postEvent(true);
} catch (DbException | IOException e) { } catch (DbException | IOException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
urlFailedImport = url; urlFailedImport = url;
importResult.postEvent(FAILED); importResult.postEvent(false);
} finally { } finally {
isImporting.postValue(false); isImporting.postValue(false);
} }
@@ -163,16 +160,4 @@ class RssFeedViewModel extends DbViewModel {
String getUrlFailedImport() { String getUrlFailedImport() {
return urlFailedImport; return urlFailedImport;
} }
private boolean exists(String url) {
List<Feed> list = getList(feeds);
if (list != null) {
for (Feed feed : list) {
if (url.equals(feed.getUrl())) {
return true;
}
}
}
return false;
}
} }

View File

@@ -3,12 +3,14 @@ package org.briarproject.briar.android.contact.add.nearby;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
abstract class AddContactState { abstract class AddContactState {
static class KeyAgreementListening extends AddContactState { static class KeyAgreementListening extends AddContactState {
final Bitmap qrCode; final Bitmap qrCode;
KeyAgreementListening(Bitmap qrCode) { KeyAgreementListening(Bitmap qrCode) {
@@ -29,6 +31,7 @@ abstract class AddContactState {
} }
static class ContactExchangeFinished extends AddContactState { static class ContactExchangeFinished extends AddContactState {
final ContactExchangeResult result; final ContactExchangeResult result;
ContactExchangeFinished(ContactExchangeResult result) { ContactExchangeFinished(ContactExchangeResult result) {
@@ -37,25 +40,34 @@ abstract class AddContactState {
} }
static class Failed extends AddContactState { static class Failed extends AddContactState {
/**
* Non-null if failed due to the scanned QR code version.
* True if the app producing the code is too old.
* False if the scanning app is too old.
*/
@Nullable
final Boolean qrCodeTooOld;
Failed(@Nullable Boolean qrCodeTooOld) { static class WrongQrCodeType extends Failed {
this.qrCodeTooOld = qrCodeTooOld;
final QrCodeType qrCodeType;
WrongQrCodeType(QrCodeType qrCodeType) {
this.qrCodeType = qrCodeType;
}
} }
Failed() { static class WrongQrCodeVersion extends Failed {
this(null);
/**
* True if the app producing the code is too old.
* False if the scanning app is too old.
*/
final boolean qrCodeTooOld;
WrongQrCodeVersion(boolean qrCodeTooOld) {
this.qrCodeTooOld = qrCodeTooOld;
}
} }
} }
abstract static class ContactExchangeResult { abstract static class ContactExchangeResult {
static class Success extends ContactExchangeResult { static class Success extends ContactExchangeResult {
final Author remoteAuthor; final Author remoteAuthor;
Success(Author remoteAuthor) { Success(Author remoteAuthor) {
@@ -64,6 +76,7 @@ abstract class AddContactState {
} }
static class Error extends ContactExchangeResult { static class Error extends ContactExchangeResult {
@Nullable @Nullable
final Author duplicateAuthor; final Author duplicateAuthor;

View File

@@ -6,12 +6,15 @@ import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeFinished;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed; import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision; import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener; import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
@@ -34,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.ACCEPTED;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -141,9 +145,15 @@ public class AddNearbyContactActivity extends BriarActivity
ContactExchangeResult result = ContactExchangeResult result =
((ContactExchangeFinished) state).result; ((ContactExchangeFinished) state).result;
onContactExchangeResult(result); onContactExchangeResult(result);
} else if (state instanceof WrongQrCodeType) {
QrCodeType qrCodeType = ((WrongQrCodeType) state).qrCodeType;
if (qrCodeType == MAILBOX) onMailboxQrCodeScanned();
else onWrongQrCodeType();
} else if (state instanceof WrongQrCodeVersion) {
boolean qrCodeTooOld = ((WrongQrCodeVersion) state).qrCodeTooOld;
onWrongQrCodeVersion(qrCodeTooOld);
} else if (state instanceof Failed) { } else if (state instanceof Failed) {
Boolean qrCodeTooOld = ((Failed) state).qrCodeTooOld; showErrorFragment();
onAddingContactFailed(qrCodeTooOld);
} }
} }
@@ -170,15 +180,27 @@ public class AddNearbyContactActivity extends BriarActivity
} else throw new AssertionError(); } else throw new AssertionError();
} }
private void onAddingContactFailed(@Nullable Boolean qrCodeTooOld) { private void onMailboxQrCodeScanned() {
if (qrCodeTooOld == null) { String title = getString(R.string.qr_code_invalid);
showErrorFragment(); String msg = getString(R.string.mailbox_qr_code_for_contact);
} else { showNextFragment(
String msg; AddNearbyContactErrorFragment.newInstance(title, msg, false));
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1); }
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg)); private void onWrongQrCodeType() {
} String title = getString(R.string.qr_code_invalid);
String msg = getString(R.string.qr_code_format_unknown);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
}
private void onWrongQrCodeVersion(boolean qrCodeTooOld) {
String title = getString(R.string.qr_code_invalid);
String msg;
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1);
else msg = getString(R.string.qr_code_too_new_1);
showNextFragment(
AddNearbyContactErrorFragment.newInstance(title, msg, false));
} }
private void showErrorFragment() { private void showErrorFragment() {

View File

@@ -22,6 +22,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen; import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick; import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
@@ -31,17 +32,22 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
public static final String TAG = public static final String TAG =
AddNearbyContactErrorFragment.class.getName(); AddNearbyContactErrorFragment.class.getName();
private static final String ERROR_MSG = "errorMessage"; private static final String ARG_TITLE = "title";
private static final String ARG_ERROR_MSG = "message";
private static final String ARG_FEEDBACK = "feedback";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private AddNearbyContactViewModel viewModel; private AddNearbyContactViewModel viewModel;
public static AddNearbyContactErrorFragment newInstance(String errorMsg) { public static AddNearbyContactErrorFragment newInstance(String title,
String errorMessage, boolean feedback) {
AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment(); AddNearbyContactErrorFragment f = new AddNearbyContactErrorFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString(ERROR_MSG, errorMsg); args.putString(ARG_TITLE, title);
args.putString(ARG_ERROR_MSG, errorMessage);
args.putBoolean(ARG_FEEDBACK, feedback);
f.setArguments(args); f.setArguments(args);
return f; return f;
} }
@@ -66,19 +72,32 @@ public class AddNearbyContactErrorFragment extends BaseFragment {
View v = inflater.inflate(R.layout.fragment_error_contact_exchange, View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
container, false); container, false);
// set optional error message String title = null, errorMessage = null;
TextView explanation = v.findViewById(R.id.errorMessage); boolean feedback = true;
Bundle args = getArguments(); Bundle args = getArguments();
String errorMessage = args == null ? null : args.getString(ERROR_MSG); if (args != null) {
if (errorMessage == null) { title = args.getString(ARG_TITLE);
explanation.setText(getString(R.string.add_contact_error_two_way)); errorMessage = args.getString(ARG_ERROR_MSG);
} else { feedback = args.getBoolean(ARG_FEEDBACK, true);
explanation.setText(args.getString(ERROR_MSG)); }
if (title != null) {
TextView titleView = v.findViewById(R.id.errorTitle);
titleView.setText(title);
}
if (errorMessage != null) {
TextView messageView = v.findViewById(R.id.errorMessage);
messageView.setText(errorMessage);
} }
// make feedback link clickable
TextView sendFeedback = v.findViewById(R.id.sendFeedback); TextView sendFeedback = v.findViewById(R.id.sendFeedback);
onSingleLinkClick(sendFeedback, this::triggerFeedback); if (feedback) {
// make feedback link clickable
onSingleLinkClick(sendFeedback, this::triggerFeedback);
} else {
sendFeedback.setVisibility(GONE);
}
// buttons // buttons
Button tryAgain = v.findViewById(R.id.tryAgainButton); Button tryAgain = v.findViewById(R.id.tryAgainButton);

View File

@@ -43,6 +43,7 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.qrcode.WrongQrCodeTypeException;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin; import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -50,9 +51,13 @@ import org.briarproject.briar.android.contact.add.nearby.AddContactState.Contact
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Error;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeResult.Success;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted; import org.briarproject.briar.android.contact.add.nearby.AddContactState.ContactExchangeStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeType;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.Failed.WrongQrCodeVersion;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementListening;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementStarted;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting; import org.briarproject.briar.android.contact.add.nearby.AddContactState.KeyAgreementWaiting;
import org.briarproject.briar.android.contact.add.nearby.AddContactState.QrCodeScanned;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.qrcode.QrCodeUtils; import org.briarproject.briar.android.qrcode.QrCodeUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
@@ -60,7 +65,6 @@ import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -85,6 +89,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.ISO_8859_1;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactPermissionManager.areEssentialPermissionsGranted;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.NO_ADAPTER;
import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED; import static org.briarproject.briar.android.contact.add.nearby.AddNearbyContactViewModel.BluetoothDecision.REFUSED;
@@ -126,9 +131,6 @@ class AddNearbyContactViewModel extends AndroidViewModel
REFUSED REFUSED
} }
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private final EventBus eventBus; private final EventBus eventBus;
private final AndroidExecutor androidExecutor; private final AndroidExecutor androidExecutor;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -376,11 +378,11 @@ class AddNearbyContactViewModel extends AndroidViewModel
} else if (e instanceof KeyAgreementAbortedEvent) { } else if (e instanceof KeyAgreementAbortedEvent) {
LOG.info("KeyAgreementAbortedEvent received"); LOG.info("KeyAgreementAbortedEvent received");
resetPayloadFlags(); resetPayloadFlags();
state.setValue(new AddContactState.Failed()); state.setValue(new Failed());
} else if (e instanceof KeyAgreementFailedEvent) { } else if (e instanceof KeyAgreementFailedEvent) {
LOG.info("KeyAgreementFailedEvent received"); LOG.info("KeyAgreementFailedEvent received");
resetPayloadFlags(); resetPayloadFlags();
state.setValue(new AddContactState.Failed()); state.setValue(new Failed());
} }
} }
@@ -446,22 +448,22 @@ class AddNearbyContactViewModel extends AndroidViewModel
// Ignore results until the KeyAgreementTask is ready // Ignore results until the KeyAgreementTask is ready
if (!gotLocalPayload || gotRemotePayload || currentTask == null) return; if (!gotLocalPayload || gotRemotePayload || currentTask == null) return;
try { try {
byte[] payloadBytes = result.getText().getBytes(ISO_8859_1); Payload remotePayload = payloadParser.parse(result.getText());
if (LOG.isLoggable(INFO))
LOG.info("Remote payload is " + payloadBytes.length + " bytes");
Payload remotePayload = payloadParser.parse(payloadBytes);
gotRemotePayload = true; gotRemotePayload = true;
currentTask.connectAndRunProtocol(remotePayload); currentTask.connectAndRunProtocol(remotePayload);
state.postValue(new AddContactState.QrCodeScanned()); state.postValue(new QrCodeScanned());
} catch (WrongQrCodeTypeException e) {
resetPayloadFlags();
state.postValue(new WrongQrCodeType(e.getQrCodeType()));
} catch (UnsupportedVersionException e) { } catch (UnsupportedVersionException e) {
resetPayloadFlags(); resetPayloadFlags();
state.postValue(new AddContactState.Failed(e.isTooOld())); state.postValue(new WrongQrCodeVersion(e.isTooOld()));
} catch (IOException | IllegalArgumentException e) { } catch (IOException | IllegalArgumentException e) {
LOG.log(WARNING, "QR Code Invalid", e); LOG.log(WARNING, "QR Code Invalid", e);
androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(), androidExecutor.runOnUiThread(() -> Toast.makeText(getApplication(),
R.string.qr_code_invalid, LENGTH_LONG).show()); R.string.qr_code_invalid, LENGTH_LONG).show());
resetPayloadFlags(); resetPayloadFlags();
state.postValue(new AddContactState.Failed()); state.postValue(new Failed());
} }
} }

View File

@@ -12,7 +12,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem, H extends ContactItemViewHolder<I>> public abstract class BaseContactSelectorAdapter<I extends BaseSelectableContactItem, H extends ContactItemViewHolder<I>>
extends BaseContactListAdapter<I, H> { extends BaseContactListAdapter<I, H> {
public BaseContactSelectorAdapter(Context context, Class<I> c, public BaseContactSelectorAdapter(Context context, Class<I> c,
@@ -24,7 +24,7 @@ public abstract class BaseContactSelectorAdapter<I extends SelectableContactItem
Collection<ContactId> selected = new ArrayList<>(); Collection<ContactId> selected = new ArrayList<>();
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
SelectableContactItem item = items.get(i); BaseSelectableContactItem item = items.get(i);
if (item.isSelected()) selected.add(item.getContact().getId()); if (item.isSelected()) selected.add(item.getContact().getId());
} }
return selected; return selected;

View File

@@ -33,7 +33,7 @@ import static org.briarproject.briar.android.contactselection.ContactSelectorAct
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class BaseContactSelectorFragment<I extends SelectableContactItem, A extends BaseContactSelectorAdapter<I, ? extends ContactItemViewHolder<I>>> public abstract class BaseContactSelectorFragment<I extends BaseSelectableContactItem, A extends BaseContactSelectorAdapter<I, ? extends ContactItemViewHolder<I>>>
extends BaseFragment extends BaseFragment
implements OnContactClickListener<I> { implements OnContactClickListener<I> {

View File

@@ -17,7 +17,7 @@ import static org.briarproject.briar.android.util.UiUtils.GREY_OUT;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
public class BaseSelectableContactHolder<I extends SelectableContactItem> public abstract class BaseSelectableContactHolder<I extends BaseSelectableContactItem>
extends ContactItemViewHolder<I> { extends ContactItemViewHolder<I> {
private final CheckBox checkBox; private final CheckBox checkBox;

View File

@@ -0,0 +1,32 @@
package org.briarproject.briar.android.contactselection;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
@NotNullByDefault
public abstract class BaseSelectableContactItem extends ContactItem {
private boolean selected;
public BaseSelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected) {
super(contact, authorInfo);
this.selected = selected;
}
boolean isSelected() {
return selected;
}
void toggleSelected() {
selected = !selected;
}
public abstract boolean isDisabled();
}

View File

@@ -10,7 +10,7 @@ import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Collection; import java.util.Collection;
@NotNullByDefault @NotNullByDefault
public interface ContactSelectorController<I extends SelectableContactItem> public interface ContactSelectorController<I extends BaseSelectableContactItem>
extends DbController { extends DbController {
void loadContacts(GroupId g, Collection<ContactId> selection, void loadContacts(GroupId g, Collection<ContactId> selection,

View File

@@ -11,6 +11,7 @@ import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.util.ArrayList; import java.util.ArrayList;
@@ -53,10 +54,8 @@ public abstract class ContactSelectorControllerImpl
AuthorInfo authorInfo = authorManager.getAuthorInfo(c); AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
// was this contact already selected? // was this contact already selected?
boolean selected = selection.contains(c.getId()); boolean selected = selection.contains(c.getId());
// can this contact be selected?
boolean disabled = isDisabled(g, c);
contacts.add(new SelectableContactItem(c, authorInfo, contacts.add(new SelectableContactItem(c, authorInfo,
selected, disabled)); selected, getSharingStatus(g, c)));
} }
handler.onResult(contacts); handler.onResult(contacts);
} catch (DbException e) { } catch (DbException e) {
@@ -67,7 +66,7 @@ public abstract class ContactSelectorControllerImpl
} }
@DatabaseExecutor @DatabaseExecutor
protected abstract boolean isDisabled(GroupId g, Contact c) protected abstract SharingStatus getSharingStatus(GroupId g, Contact c)
throws DbException; throws DbException;
} }

View File

@@ -2,15 +2,22 @@ package org.briarproject.briar.android.contactselection;
import android.view.View; import android.view.View;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.OnContactClickListener; import org.briarproject.briar.android.contact.OnContactClickListener;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.ERROR;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.INVITE_RECEIVED;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.INVITE_SENT;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.NOT_SUPPORTED;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHARING;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -27,6 +34,19 @@ class SelectableContactHolder
super.bind(item, listener); super.bind(item, listener);
if (item.isDisabled()) { if (item.isDisabled()) {
@StringRes int strRes;
if (item.getSharingStatus() == SHARING) {
strRes = R.string.forum_invitation_already_sharing;
} else if (item.getSharingStatus() == INVITE_SENT) {
strRes = R.string.forum_invitation_already_invited;
} else if (item.getSharingStatus() == INVITE_RECEIVED) {
strRes = R.string.forum_invitation_invite_received;
} else if (item.getSharingStatus() == NOT_SUPPORTED) {
strRes = R.string.forum_invitation_not_supported;
} else if (item.getSharingStatus() == ERROR) {
strRes = R.string.forum_invitation_error;
} else throw new AssertionError("Unhandled SharingStatus");
info.setText(strRes);
info.setVisibility(VISIBLE); info.setVisibility(VISIBLE);
} else { } else {
info.setVisibility(GONE); info.setVisibility(GONE);

View File

@@ -1,36 +1,33 @@
package org.briarproject.briar.android.contactselection; package org.briarproject.briar.android.contactselection;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.sharing.SharingManager.SharingStatus.SHAREABLE;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
public class SelectableContactItem extends ContactItem { public class SelectableContactItem extends BaseSelectableContactItem {
private boolean selected; private final SharingStatus sharingStatus;
private final boolean disabled;
public SelectableContactItem(Contact contact, AuthorInfo authorInfo, public SelectableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected, boolean disabled) { boolean selected, SharingStatus sharingStatus) {
super(contact, authorInfo); super(contact, authorInfo, selected);
this.selected = selected; this.sharingStatus = sharingStatus;
this.disabled = disabled;
} }
boolean isSelected() { public SharingStatus getSharingStatus() {
return selected; return sharingStatus;
}
void toggleSelected() {
selected = !selected;
} }
@Override
public boolean isDisabled() { public boolean isDisabled() {
return disabled; return sharingStatus != SHAREABLE;
} }
} }

View File

@@ -66,6 +66,7 @@ import org.briarproject.briar.android.view.TextAttachmentController.AttachmentLi
import org.briarproject.briar.android.view.TextInputView; import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController; import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendState; import org.briarproject.briar.android.view.TextSendController.SendState;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent; import org.briarproject.briar.api.autodelete.event.ConversationMessagesDeletedEvent;
@@ -476,6 +477,12 @@ public class ConversationActivity extends BriarActivity
actionMode = null; actionMode = null;
} }
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getSupportFragmentManager(), f.getUniqueTag());
}
private void addSelectionTracker() { private void addSelectionTracker() {
RecyclerView recyclerView = list.getRecyclerView(); RecyclerView recyclerView = list.getRecyclerView();
if (recyclerView.getAdapter() != adapter) if (recyclerView.getAdapter() != adapter)
@@ -925,6 +932,7 @@ public class ConversationActivity extends BriarActivity
} }
private void removeContact() { private void removeContact() {
list.showProgressBar();
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
contactManager.removeContact(contactId); contactManager.removeContact(contactId);

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.Context; import android.content.Context;
import android.text.util.Linkify;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
@@ -19,6 +20,7 @@ import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.trim; import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.formatDate; import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.android.util.UiUtils.formatDuration; import static org.briarproject.briar.android.util.UiUtils.formatDuration;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER; import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread @UiThread
@@ -58,6 +60,8 @@ abstract class ConversationItemViewHolder extends ViewHolder {
if (item.getText() != null) { if (item.getText() != null) {
text.setText(trim(item.getText())); text.setText(trim(item.getText()));
Linkify.addLinks(text, Linkify.WEB_URLS);
makeLinksClickable(text, listener::onLinkClick);
} }
long timestamp = item.getTime(); long timestamp = item.getTime();

View File

@@ -20,4 +20,6 @@ interface ConversationListener {
void onAutoDeleteTimerNoticeClicked(); void onAutoDeleteTimerNoticeClicked();
void onLinkClick(String url);
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.text.util.Linkify;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
@@ -13,6 +14,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.bramble.util.StringUtils.trim; import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -40,6 +42,8 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
} else { } else {
msgText.setVisibility(VISIBLE); msgText.setVisibility(VISIBLE);
msgText.setText(trim(text)); msgText.setText(trim(text));
Linkify.addLinks(msgText, Linkify.WEB_URLS);
makeLinksClickable(msgText, listener::onLinkClick);
layout.setBackgroundResource(isIncoming() ? layout.setBackgroundResource(isIncoming() ?
R.drawable.notice_in_bottom : R.drawable.notice_out_bottom); R.drawable.notice_in_bottom : R.drawable.notice_out_bottom);
} }

View File

@@ -436,7 +436,7 @@ class HotspotManager {
} }
private static String createWifiLoginString(String ssid, String password) { private static String createWifiLoginString(String ssid, String password) {
// https://en.wikipedia.org/wiki/QR_code#WiFi_network_login // https://en.wikipedia.org/wiki/QR_code#Joining_a_Wi%E2%80%91Fi_network
// do not remove the dangling ';', it can cause problems to omit it // do not remove the dangling ';', it can cause problems to omit it
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
} }

View File

@@ -6,10 +6,24 @@ import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.ConnectionError;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.InvalidQrCode;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.MailboxAlreadyPaired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Pending;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.UnexpectedError;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.FinalFragment; import org.briarproject.briar.android.fragment.FinalFragment;
import org.briarproject.briar.android.mailbox.MailboxState.CameraError;
import org.briarproject.briar.android.mailbox.MailboxState.IsPaired;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.mailbox.MailboxState.OfflineWhenPairing;
import org.briarproject.briar.android.mailbox.MailboxState.Pairing;
import org.briarproject.briar.android.mailbox.MailboxState.ScanningQrCode;
import org.briarproject.briar.android.mailbox.MailboxState.ShowDownload;
import org.briarproject.briar.android.mailbox.MailboxState.WasUnpaired;
import org.briarproject.briar.android.view.BlankFragment; import org.briarproject.briar.android.view.BlankFragment;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -25,6 +39,9 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.QR_FORMAT_VERSION;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.BQP;
import static org.briarproject.bramble.api.qrcode.QrCodeClassifier.QrCodeType.MAILBOX;
import static org.briarproject.briar.android.util.UiUtils.showFragment; import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -56,24 +73,23 @@ public class MailboxActivity extends BriarActivity {
} }
viewModel.getPairingState().observeEvent(this, state -> { viewModel.getPairingState().observeEvent(this, state -> {
if (state instanceof MailboxState.NotSetup) { if (state instanceof NotSetup) {
onNotSetup(); onNotSetup();
} else if (state instanceof MailboxState.ShowDownload) { } else if (state instanceof ShowDownload) {
onShowDownload(); onShowDownload();
} else if (state instanceof MailboxState.ScanningQrCode) { } else if (state instanceof ScanningQrCode) {
onScanningQrCode(); onScanningQrCode();
} else if (state instanceof MailboxState.Pairing) { } else if (state instanceof Pairing) {
MailboxPairingState s = MailboxPairingState s = ((Pairing) state).pairingState;
((MailboxState.Pairing) state).pairingState;
onMailboxPairingStateChanged(s); onMailboxPairingStateChanged(s);
} else if (state instanceof MailboxState.OfflineWhenPairing) { } else if (state instanceof OfflineWhenPairing) {
onOffline(); onOffline();
} else if (state instanceof MailboxState.CameraError) { } else if (state instanceof CameraError) {
onCameraError(); onCameraError();
} else if (state instanceof MailboxState.IsPaired) { } else if (state instanceof IsPaired) {
onIsPaired(((MailboxState.IsPaired) state).isOnline); onIsPaired(((IsPaired) state).isOnline);
} else if (state instanceof MailboxState.WasUnpaired) { } else if (state instanceof WasUnpaired) {
MailboxState.WasUnpaired s = (MailboxState.WasUnpaired) state; WasUnpaired s = (WasUnpaired) state;
onUnPaired(s.tellUserToWipeMailbox); onUnPaired(s.tellUserToWipeMailbox);
} else { } else {
throw new AssertionError("Unknown state: " + state); throw new AssertionError("Unknown state: " + state);
@@ -104,7 +120,7 @@ public class MailboxActivity extends BriarActivity {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
MailboxState s = viewModel.getPairingState().getLastValue(); MailboxState s = viewModel.getPairingState().getLastValue();
if (s instanceof MailboxState.Pairing) { if (s instanceof Pairing) {
// don't go back in the flow if we are already pairing // don't go back in the flow if we are already pairing
// with the mailbox. We provide a try-again button instead. // with the mailbox. We provide a try-again button instead.
supportFinishAfterTransition(); supportFinishAfterTransition();
@@ -158,33 +174,44 @@ public class MailboxActivity extends BriarActivity {
} }
Fragment f; Fragment f;
String tag; String tag;
if (s instanceof MailboxPairingState.QrCodeReceived) { if (s instanceof Pending) {
f = new MailboxConnectingFragment(); long timeStarted = ((Pending) s).timeStarted;
f = MailboxConnectingFragment.newInstance(timeStarted);
tag = MailboxConnectingFragment.TAG; tag = MailboxConnectingFragment.TAG;
} else if (s instanceof MailboxPairingState.Pairing) { } else if (s instanceof InvalidQrCode) {
f = new MailboxConnectingFragment(); InvalidQrCode i = (InvalidQrCode) s;
tag = MailboxConnectingFragment.TAG; int errorRes;
} else if (s instanceof MailboxPairingState.InvalidQrCode) { if (i.qrCodeType == MAILBOX) {
f = ErrorFragment.newInstance( if (i.formatVersion < QR_FORMAT_VERSION) {
R.string.mailbox_setup_qr_code_wrong_title, errorRes = R.string.mailbox_qr_code_too_old;
R.string.mailbox_setup_qr_code_wrong_description); } else if (i.formatVersion > QR_FORMAT_VERSION) {
errorRes = R.string.mailbox_qr_code_too_new;
} else {
errorRes = R.string.mailbox_setup_qr_code_wrong_description;
}
} else if (i.qrCodeType == BQP) {
errorRes = R.string.contact_qr_code_for_mailbox;
} else {
errorRes = R.string.mailbox_setup_qr_code_wrong_description;
}
f = ErrorFragment.newInstance(R.string.qr_code_invalid, errorRes);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.MailboxAlreadyPaired) { } else if (s instanceof MailboxAlreadyPaired) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_already_paired_title, R.string.mailbox_setup_already_paired_title,
R.string.mailbox_setup_already_paired_description); R.string.mailbox_setup_already_paired_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.ConnectionError) { } else if (s instanceof ConnectionError) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_io_error_title, R.string.mailbox_setup_io_error_title,
R.string.mailbox_setup_io_error_description); R.string.mailbox_setup_io_error_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.UnexpectedError) { } else if (s instanceof UnexpectedError) {
f = ErrorFragment.newInstance( f = ErrorFragment.newInstance(
R.string.mailbox_setup_assertion_error_title, R.string.mailbox_setup_assertion_error_title,
R.string.mailbox_setup_assertion_error_description); R.string.mailbox_setup_assertion_error_description);
tag = ErrorFragment.TAG; tag = ErrorFragment.TAG;
} else if (s instanceof MailboxPairingState.Paired) { } else if (s instanceof Paired) {
f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title, f = FinalFragment.newInstance(R.string.mailbox_setup_paired_title,
R.drawable.ic_check_circle_outline, R.drawable.ic_check_circle_outline,
R.color.briar_brand_green, R.color.briar_brand_green,
@@ -218,6 +245,7 @@ public class MailboxActivity extends BriarActivity {
} }
private void onUnPaired(boolean tellUserToWipeMailbox) { private void onUnPaired(boolean tellUserToWipeMailbox) {
viewModel.clearProblemNotification();
if (tellUserToWipeMailbox) { if (tellUserToWipeMailbox) {
showFragment(getSupportFragmentManager(), new BlankFragment(), showFragment(getSupportFragmentManager(), new BlankFragment(),
BlankFragment.TAG); BlankFragment.TAG);

View File

@@ -1,10 +1,15 @@
package org.briarproject.briar.android.mailbox; package org.briarproject.briar.android.mailbox;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -12,25 +17,71 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static org.briarproject.briar.android.util.UiUtils.formatDuration;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class MailboxConnectingFragment extends Fragment { public class MailboxConnectingFragment extends Fragment {
static final String TAG = MailboxConnectingFragment.class.getName(); static final String TAG = MailboxConnectingFragment.class.getName();
private static final String ARG_STARTED = "started";
private static final long TIMEOUT_MS = TorConstants.EXTRA_CONNECT_TIMEOUT;
private static final long REFRESH_INTERVAL_MS = 1_000;
private final Handler handler = new Handler(Looper.getMainLooper());
// Capture a method reference so we use the same reference for posting
// and removing
private final Runnable refresher = this::updateProgressBar;
private ProgressBar progressBar;
private long timeStarted;
public static MailboxConnectingFragment newInstance(long timeStarted) {
MailboxConnectingFragment f = new MailboxConnectingFragment();
Bundle args = new Bundle();
args.putLong(ARG_STARTED, timeStarted);
f.setArguments(args);
return f;
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_mailbox_connecting, View v = inflater.inflate(R.layout.fragment_mailbox_connecting,
container, false); container, false);
progressBar = v.findViewById(R.id.progressBar);
TextView info = v.findViewById(R.id.info);
String duration = formatDuration(requireContext(), TIMEOUT_MS);
info.setText(getString(R.string.mailbox_setup_connecting_info,
duration));
timeStarted = requireArguments().getLong(ARG_STARTED);
return v;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
requireActivity().setTitle(R.string.mailbox_setup_title); requireActivity().setTitle(R.string.mailbox_setup_title);
updateProgressBar();
} }
@Override
public void onStop() {
super.onStop();
handler.removeCallbacks(refresher);
}
private void updateProgressBar() {
long elapsedMs = System.currentTimeMillis() - timeStarted;
int percent = (int) (elapsedMs * 100 / TIMEOUT_MS);
percent = Math.min(Math.max(percent, 0), 100);
progressBar.setProgress(percent);
handler.postDelayed(refresher, REFRESH_INTERVAL_MS);
}
} }

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.mailbox.MailboxManager; import org.briarproject.bramble.api.mailbox.MailboxManager;
import org.briarproject.bramble.api.mailbox.MailboxPairingState; import org.briarproject.bramble.api.mailbox.MailboxPairingState;
import org.briarproject.bramble.api.mailbox.MailboxPairingState.Paired;
import org.briarproject.bramble.api.mailbox.MailboxPairingTask; import org.briarproject.bramble.api.mailbox.MailboxPairingTask;
import org.briarproject.bramble.api.mailbox.MailboxStatus; import org.briarproject.bramble.api.mailbox.MailboxStatus;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent; import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
@@ -24,7 +25,14 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent; import org.briarproject.bramble.api.plugin.event.TransportInactiveEvent;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.mailbox.MailboxState.CameraError;
import org.briarproject.briar.android.mailbox.MailboxState.IsPaired;
import org.briarproject.briar.android.mailbox.MailboxState.NotSetup; import org.briarproject.briar.android.mailbox.MailboxState.NotSetup;
import org.briarproject.briar.android.mailbox.MailboxState.OfflineWhenPairing;
import org.briarproject.briar.android.mailbox.MailboxState.Pairing;
import org.briarproject.briar.android.mailbox.MailboxState.ScanningQrCode;
import org.briarproject.briar.android.mailbox.MailboxState.ShowDownload;
import org.briarproject.briar.android.mailbox.MailboxState.WasUnpaired;
import org.briarproject.briar.android.qrcode.QrCodeDecoder; import org.briarproject.briar.android.qrcode.QrCodeDecoder;
import org.briarproject.briar.android.viewmodel.DbViewModel; import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
@@ -113,7 +121,7 @@ class MailboxViewModel extends DbViewModel
MailboxStatus mailboxStatus = MailboxStatus mailboxStatus =
mailboxManager.getMailboxStatus(txn); mailboxManager.getMailboxStatus(txn);
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
pairingState.postEvent(new MailboxState.IsPaired(isOnline)); pairingState.postEvent(new IsPaired(isOnline));
status.postValue(mailboxStatus); status.postValue(mailboxStatus);
} else { } else {
pairingState.postEvent(new NotSetup()); pairingState.postEvent(new NotSetup());
@@ -142,14 +150,14 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
private void onTorInactive() { private void onTorInactive() {
MailboxState lastState = pairingState.getLastValue(); MailboxState lastState = pairingState.getLastValue();
if (lastState instanceof MailboxState.IsPaired) { if (lastState instanceof IsPaired) {
// we are already paired, so use IsPaired state // we are already paired, so use IsPaired state
pairingState.setEvent(new MailboxState.IsPaired(false)); pairingState.setEvent(new IsPaired(false));
} else if (lastState instanceof MailboxState.Pairing) { } else if (lastState instanceof Pairing) {
MailboxState.Pairing p = (MailboxState.Pairing) lastState; Pairing p = (Pairing) lastState;
// check that we not just finished pairing (showing success screen) // check that we not just finished pairing (showing success screen)
if (!(p.pairingState instanceof MailboxPairingState.Paired)) { if (!(p.pairingState instanceof Paired)) {
pairingState.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new OfflineWhenPairing());
} }
// else ignore offline event as user will be leaving UI flow anyway // else ignore offline event as user will be leaving UI flow anyway
} }
@@ -158,15 +166,15 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void onScanButtonClicked() { void onScanButtonClicked() {
if (isTorActive()) { if (isTorActive()) {
pairingState.setEvent(new MailboxState.ScanningQrCode()); pairingState.setEvent(new ScanningQrCode());
} else { } else {
pairingState.setEvent(new MailboxState.OfflineWhenPairing()); pairingState.setEvent(new OfflineWhenPairing());
} }
} }
@UiThread @UiThread
void onCameraError() { void onCameraError() {
pairingState.setEvent(new MailboxState.CameraError()); pairingState.setEvent(new CameraError());
} }
@Override @Override
@@ -182,7 +190,7 @@ class MailboxViewModel extends DbViewModel
pairingTask = mailboxManager.startPairingTask(qrCodePayload); pairingTask = mailboxManager.startPairingTask(qrCodePayload);
pairingTask.addObserver(this); pairingTask.addObserver(this);
} else { } else {
pairingState.postEvent(new MailboxState.OfflineWhenPairing()); pairingState.postEvent(new OfflineWhenPairing());
} }
} }
@@ -193,7 +201,7 @@ class MailboxViewModel extends DbViewModel
LOG.info("New pairing state: " + LOG.info("New pairing state: " +
mailboxPairingState.getClass().getSimpleName()); mailboxPairingState.getClass().getSimpleName());
} }
pairingState.setEvent(new MailboxState.Pairing(mailboxPairingState)); pairingState.setEvent(new Pairing(mailboxPairingState));
} }
private boolean isTorActive() { private boolean isTorActive() {
@@ -203,7 +211,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void showDownloadFragment() { void showDownloadFragment() {
pairingState.setEvent(new MailboxState.ShowDownload()); pairingState.setEvent(new ShowDownload());
} }
@UiThread @UiThread
@@ -214,7 +222,7 @@ class MailboxViewModel extends DbViewModel
@UiThread @UiThread
void checkIfOnlineWhenPaired() { void checkIfOnlineWhenPaired() {
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
pairingState.setEvent(new MailboxState.IsPaired(isOnline)); pairingState.setEvent(new IsPaired(isOnline));
} }
LiveData<Boolean> checkConnection() { LiveData<Boolean> checkConnection() {
@@ -227,7 +235,7 @@ class MailboxViewModel extends DbViewModel
checkConnection(success -> { checkConnection(success -> {
boolean isOnline = isTorActive(); boolean isOnline = isTorActive();
// make UI move back to status fragment by changing pairingState // make UI move back to status fragment by changing pairingState
pairingState.postEvent(new MailboxState.IsPaired(isOnline)); pairingState.postEvent(new IsPaired(isOnline));
}); });
} }
@@ -246,7 +254,7 @@ class MailboxViewModel extends DbViewModel
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
boolean wasWiped = mailboxManager.unPair(); boolean wasWiped = mailboxManager.unPair();
pairingState.postEvent(new MailboxState.WasUnpaired(!wasWiped)); pairingState.postEvent(new WasUnpaired(!wasWiped));
} catch (DbException e) { } catch (DbException e) {
handleException(e); handleException(e);
} }

View File

@@ -16,13 +16,12 @@ import org.briarproject.nullsafety.ParametersNotNullByDefault;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.view.View.FOCUS_DOWN;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.util.UiUtils.hideViewOnSmallScreen;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -35,8 +34,6 @@ public class OfflineFragment extends Fragment {
protected MailboxViewModel viewModel; protected MailboxViewModel viewModel;
private NestedScrollView scrollView;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
@@ -54,7 +51,6 @@ public class OfflineFragment extends Fragment {
View v = inflater View v = inflater
.inflate(R.layout.fragment_offline, container, false); .inflate(R.layout.fragment_offline, container, false);
scrollView = (NestedScrollView) v;
Button checkButton = v.findViewById(R.id.checkButton); Button checkButton = v.findViewById(R.id.checkButton);
checkButton.setOnClickListener(view -> { checkButton.setOnClickListener(view -> {
Intent i = new Intent(requireContext(), TransportsActivity.class); Intent i = new Intent(requireContext(), TransportsActivity.class);
@@ -69,8 +65,7 @@ public class OfflineFragment extends Fragment {
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
// Scroll down in case the screen is small, so the button is visible hideViewOnSmallScreen(requireView().findViewById(R.id.iconView));
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
} }
protected void onTryAgainClicked() { protected void onTryAgainClicked() {

View File

@@ -13,6 +13,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListAct
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity; import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.threaded.ThreadListActivity; import org.briarproject.briar.android.threaded.ThreadListActivity;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -158,6 +159,12 @@ public class GroupActivity extends
if (isDissolved != null && !isDissolved) super.onReplyClick(item); if (isDissolved != null && !isDissolved) super.onReplyClick(item);
} }
@Override
public void onLinkClick(String url){
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getSupportFragmentManager(), f.getUniqueTag());
}
private void setGroupEnabled(boolean enabled) { private void setGroupEnabled(boolean enabled) {
sendController.setReady(enabled); sendController.setReady(enabled);
list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f); list.getRecyclerView().setAlpha(enabled ? 1f : 0.5f);

View File

@@ -26,6 +26,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager; import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.util.ArrayList; import java.util.ArrayList;
@@ -140,8 +141,8 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
} }
@Override @Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException { protected SharingStatus getSharingStatus(GroupId g, Contact c) throws DbException {
return !groupInvitationManager.isInvitationAllowed(c, g); return groupInvitationManager.getSharingStatus(c, g);
} }
@Override @Override

View File

@@ -92,7 +92,7 @@ class RevealContactsControllerImpl extends DbControllerImpl
boolean selected = boolean selected =
disabled || selection.contains(c.getId()); disabled || selection.contains(c.getId());
items.add(new RevealableContactItem(c, authorInfo, selected, items.add(new RevealableContactItem(c, authorInfo, selected,
disabled, m.getVisibility())); m.getVisibility()));
} }
} }

View File

@@ -1,22 +1,24 @@
package org.briarproject.briar.android.privategroup.reveal; package org.briarproject.briar.android.privategroup.reveal;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.briar.android.contactselection.SelectableContactItem; import org.briarproject.briar.android.contactselection.BaseSelectableContactItem;
import org.briarproject.briar.api.identity.AuthorInfo; import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.briar.api.privategroup.Visibility; import org.briarproject.briar.api.privategroup.Visibility;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
@NotThreadSafe @NotThreadSafe
@NotNullByDefault @NotNullByDefault
class RevealableContactItem extends SelectableContactItem { class RevealableContactItem extends BaseSelectableContactItem {
private final Visibility visibility; private final Visibility visibility;
RevealableContactItem(Contact contact, AuthorInfo authorInfo, RevealableContactItem(Contact contact, AuthorInfo authorInfo,
boolean selected, boolean disabled, Visibility visibility) { boolean selected, Visibility visibility) {
super(contact, authorInfo, selected, disabled); super(contact, authorInfo, selected);
this.visibility = visibility; this.visibility = visibility;
} }
@@ -24,4 +26,8 @@ class RevealableContactItem extends SelectableContactItem {
return visibility; return visibility;
} }
@Override
public boolean isDisabled() {
return visibility != INVISIBLE;
}
} }

View File

@@ -41,7 +41,6 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
private final ResultCallback callback; private final ResultCallback callback;
private Camera camera = null; private Camera camera = null;
private int cameraIndex = 0;
public QrCodeDecoder(AndroidExecutor androidExecutor, public QrCodeDecoder(AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor, ResultCallback callback) { @IoExecutor Executor ioExecutor, ResultCallback callback) {
@@ -53,14 +52,12 @@ public class QrCodeDecoder implements PreviewConsumer, PreviewCallback {
@Override @Override
public void start(Camera camera, int cameraIndex) { public void start(Camera camera, int cameraIndex) {
this.camera = camera; this.camera = camera;
this.cameraIndex = cameraIndex;
askForPreviewFrame(); askForPreviewFrame();
} }
@Override @Override
public void stop() { public void stop() {
camera = null; camera = null;
cameraIndex = 0;
} }
@UiThread @UiThread

View File

@@ -310,14 +310,12 @@ class BriarReportCollector {
btLeAdvertise); btLeAdvertise);
} }
if (hasBtConnectPermission(ctx)) { Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt);
Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt); String address = p.getFirst();
String address = p.getFirst(); String method = p.getSecond();
String method = p.getSecond(); connectivityInfo.add("BluetoothAddress",
connectivityInfo.add("BluetoothAddress", scrubMacAddress(address));
scrubMacAddress(address)); connectivityInfo.add("BluetoothAddressMethod", method);
connectivityInfo.add("BluetoothAddressMethod", method);
}
} }
return new ReportItem("Connectivity", R.string.dev_report_connectivity, return new ReportItem("Connectivity", R.string.dev_report_connectivity,
connectivityInfo); connectivityInfo);

View File

@@ -18,7 +18,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_
import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.briar.android.settings.SettingsViewModel.BT_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsViewModel.BT_NAMESPACE;
import static org.briarproject.briar.android.settings.SettingsViewModel.TOR_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsViewModel.TOR_NAMESPACE;
@@ -61,32 +60,18 @@ class ConnectionsManager {
} }
void updateTorSettings(Settings settings) { void updateTorSettings(Settings settings) {
Settings torSettings = migrateTorSettings(settings); torEnabled.postValue(settings.getBoolean(PREF_PLUGIN_ENABLE,
torEnabled.postValue(torSettings.getBoolean(PREF_PLUGIN_ENABLE,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE)); TorConstants.DEFAULT_PREF_PLUGIN_ENABLE));
int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, int torNetworkSetting = settings.getInt(PREF_TOR_NETWORK,
DEFAULT_PREF_TOR_NETWORK); DEFAULT_PREF_TOR_NETWORK);
torNetwork.postValue(Integer.toString(torNetworkSetting)); torNetwork.postValue(Integer.toString(torNetworkSetting));
torMobile.postValue(torSettings.getBoolean(PREF_TOR_MOBILE, torMobile.postValue(settings.getBoolean(PREF_TOR_MOBILE,
DEFAULT_PREF_TOR_MOBILE)); DEFAULT_PREF_TOR_MOBILE));
torCharging
.postValue(torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING));
}
// TODO: Remove after a reasonable migration period (added 2020-06-25) torCharging.postValue(settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING,
private Settings migrateTorSettings(Settings s) { DEFAULT_PREF_TOR_ONLY_WHEN_CHARGING));
int network = s.getInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
if (network == PREF_TOR_NETWORK_NEVER) {
s.putInt(PREF_TOR_NETWORK, DEFAULT_PREF_TOR_NETWORK);
s.putBoolean(PREF_PLUGIN_ENABLE, false);
// We don't need to save the migrated settings - the Tor plugin is
// responsible for that. This code just handles the case where the
// settings are loaded before the plugin migrates them.
}
return s;
} }
LiveData<Boolean> btEnabled() { LiveData<Boolean> btEnabled() {

View File

@@ -13,6 +13,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Collection; import java.util.Collection;
@@ -47,8 +48,9 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
} }
@Override @Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException { protected SharingStatus getSharingStatus(GroupId g, Contact c)
return !blogSharingManager.canBeShared(g, c); throws DbException {
return blogSharingManager.getSharingStatus(g, c);
} }
@Override @Override

View File

@@ -13,6 +13,7 @@ import org.briarproject.briar.android.contactselection.ContactSelectorController
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.identity.AuthorManager; import org.briarproject.briar.api.identity.AuthorManager;
import org.briarproject.briar.api.sharing.SharingManager.SharingStatus;
import org.briarproject.nullsafety.NotNullByDefault; import org.briarproject.nullsafety.NotNullByDefault;
import java.util.Collection; import java.util.Collection;
@@ -47,8 +48,9 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
} }
@Override @Override
protected boolean isDisabled(GroupId g, Contact c) throws DbException { protected SharingStatus getSharingStatus(GroupId g, Contact c)
return !forumSharingManager.canBeShared(g, c); throws DbException {
return forumSharingManager.getSharingStatus(g, c);
} }
@Override @Override

View File

@@ -4,6 +4,7 @@ import android.animation.Animator;
import android.animation.ArgbEvaluator; import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.text.util.Linkify;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator; import android.view.animation.AccelerateInterpolator;
@@ -20,6 +21,7 @@ import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static org.briarproject.briar.android.util.UiUtils.makeLinksClickable;
@UiThread @UiThread
@NotNullByDefault @NotNullByDefault
@@ -43,6 +45,8 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
@CallSuper @CallSuper
public void bind(I item, ThreadItemListener<I> listener) { public void bind(I item, ThreadItemListener<I> listener) {
textView.setText(StringUtils.trim(item.getText())); textView.setText(StringUtils.trim(item.getText()));
Linkify.addLinks(textView, Linkify.WEB_URLS);
makeLinksClickable(textView, listener::onLinkClick);
author.setAuthor(item.getAuthor(), item.getAuthorInfo()); author.setAuthor(item.getAuthor(), item.getAuthorInfo());
author.setDate(item.getTimestamp()); author.setDate(item.getTimestamp());

View File

@@ -137,6 +137,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
public interface ThreadItemListener<I> { public interface ThreadItemListener<I> {
void onReplyClick(I item); void onReplyClick(I item);
void onLinkClick(String url);
} }
} }

View File

@@ -19,6 +19,7 @@ import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener; import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextSendController.SendState; import org.briarproject.briar.android.view.TextSendController.SendState;
import org.briarproject.briar.android.view.UnreadMessageButton; import org.briarproject.briar.android.view.UnreadMessageButton;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.attachment.AttachmentHeader; import org.briarproject.briar.api.attachment.AttachmentHeader;
import org.briarproject.nullsafety.MethodsNotNullByDefault; import org.briarproject.nullsafety.MethodsNotNullByDefault;
import org.briarproject.nullsafety.ParametersNotNullByDefault; import org.briarproject.nullsafety.ParametersNotNullByDefault;
@@ -202,6 +203,12 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
} }
} }
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getSupportFragmentManager(), f.getUniqueTag());
}
protected void setToolbarSubTitle(SharingInfo sharingInfo) { protected void setToolbarSubTitle(SharingInfo sharingInfo) {
ActionBar actionBar = getSupportActionBar(); ActionBar actionBar = getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {

View File

@@ -24,7 +24,6 @@ public interface AndroidNotificationManager {
// Notification IDs // Notification IDs
int ONGOING_NOTIFICATION_ID = 1; int ONGOING_NOTIFICATION_ID = 1;
int FAILURE_NOTIFICATION_ID = 2;
int REMINDER_NOTIFICATION_ID = 3; int REMINDER_NOTIFICATION_ID = 3;
int PRIVATE_MESSAGE_NOTIFICATION_ID = 4; int PRIVATE_MESSAGE_NOTIFICATION_ID = 4;
int GROUP_MESSAGE_NOTIFICATION_ID = 5; int GROUP_MESSAGE_NOTIFICATION_ID = 5;
@@ -47,10 +46,6 @@ public interface AndroidNotificationManager {
String HOTSPOT_CHANNEL_ID = "zHotspot"; String HOTSPOT_CHANNEL_ID = "zHotspot";
String MAILBOX_PROBLEM_CHANNEL_ID = "zMailboxProblem"; String MAILBOX_PROBLEM_CHANNEL_ID = "zMailboxProblem";
// This channel is no longer used - keep the ID so we can remove the
// channel from existing installations
String FAILURE_CHANNEL_ID = "zStartupFailure";
// Actions for pending intents // Actions for pending intents
String ACTION_DISMISS_REMINDER = "dismissReminder"; String ACTION_DISMISS_REMINDER = "dismissReminder";
String ACTION_STOP_HOTSPOT = "stopHotspot"; String ACTION_STOP_HOTSPOT = "stopHotspot";

View File

@@ -0,0 +1,7 @@
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?attr/colorControlHighlight">
<item
android:id="@android:id/mask"
android:drawable="@drawable/button_outline_mask" />
<item android:drawable="@drawable/button_outline_background" />
</ripple>

View File

@@ -0,0 +1,19 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="4dp"
android:insetTop="6dp"
android:insetRight="4dp"
android:insetBottom="6dp">
<shape
android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="@color/briar_button_outline" />
<padding
android:bottom="4dp"
android:left="8dp"
android:right="8dp"
android:top="4dp" />
</shape>
</inset>

View File

@@ -0,0 +1,19 @@
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="4dp"
android:insetTop="6dp"
android:insetRight="4dp"
android:insetBottom="6dp">
<shape
android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/white" />
<stroke
android:width="1dp"
android:color="@android:color/white" />
<padding
android:bottom="4dp"
android:left="8dp"
android:right="8dp"
android:top="4dp" />
</shape>
</inset>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="6dp"
android:left="4dp"
android:right="4dp"
android:top="6dp">
<shape android:shape="rectangle">
<corners android:radius="2dp" />
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="@color/briar_button_outline" />
</shape>
</item>
</layer-list>

View File

@@ -51,12 +51,12 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_xlarge" android:layout_marginTop="@dimen/margin_xlarge"
android:text="@string/add_contact_error_two_way"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/sendFeedback" app:layout_constraintBottom_toTopOf="@+id/sendFeedback"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorTitle" app:layout_constraintTop_toBottomOf="@+id/errorTitle" />
tools:text="error explanation" />
<TextView <TextView
android:id="@+id/sendFeedback" android:id="@+id/sendFeedback"

View File

@@ -56,9 +56,7 @@
<Button <Button
android:id="@+id/feedbackButton" android:id="@+id/feedbackButton"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonOutline.Neutral"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/send_feedback" android:text="@string/send_feedback"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -30,9 +30,7 @@
<Button <Button
android:id="@+id/fallbackButton" android:id="@+id/fallbackButton"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonOutline.Neutral"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="@string/hotspot_help_fallback_button" android:text="@string/hotspot_help_fallback_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -62,13 +62,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_marginBottom="1dp"
android:gravity="center" android:gravity="center"
android:text="@string/hotspot_no_peers_connected" android:text="@string/hotspot_no_peers_connected"
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout"
app:layout_constraintBottom_toTopOf="@+id/connectedButton" app:layout_constraintBottom_toTopOf="@+id/connectedButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout" />
<Button <Button
android:id="@+id/connectedButton" android:id="@+id/connectedButton"
@@ -76,8 +75,8 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="1dp" android:layout_marginTop="6dp"
android:layout_marginBottom="1dp" android:drawablePadding="8dp"
android:text="@string/hotspot_button_connected" android:text="@string/hotspot_button_connected"
app:drawableLeftCompat="@drawable/ic_check_white" app:drawableLeftCompat="@drawable/ic_check_white"
app:drawableStartCompat="@drawable/ic_check_white" app:drawableStartCompat="@drawable/ic_check_white"
@@ -85,16 +84,16 @@
app:layout_constraintBottom_toTopOf="@+id/stopButton" app:layout_constraintBottom_toTopOf="@+id/stopButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/connectedView"
tools:visibility="visible" /> tools:visibility="visible" />
<Button <Button
android:id="@+id/stopButton" android:id="@+id/stopButton"
style="@style/BriarButtonFlat.Negative" style="@style/BriarButtonOutline.Negative"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_marginBottom="2dp"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:text="@string/hotspot_button_stop_sharing" android:text="@string/hotspot_button_stop_sharing"
app:drawableLeftCompat="@drawable/ic_portable_wifi_off" app:drawableLeftCompat="@drawable/ic_portable_wifi_off"
@@ -103,6 +102,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/connectedButton"
tools:visibility="visible" /> tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,31 +1,51 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ProgressBar <TextView
android:id="@+id/progressBar" android:id="@+id/title"
style="?android:attr/progressBarStyleLarge" style="@style/TextAppearance.AppCompat.Large"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/textView" android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center"
android:text="@string/mailbox_setup_connecting"
app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
<TextView <ProgressBar
android:id="@+id/textView" android:id="@+id/progressBar"
style="@style/TextAppearance.AppCompat.Large" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:text="@string/mailbox_setup_connecting" android:layout_marginTop="@dimen/margin_xlarge"
android:max="100"
app:layout_constraintBottom_toTopOf="@+id/info"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />
<TextView
android:id="@+id/info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="@dimen/margin_xlarge"
android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" /> app:layout_constraintTop_toBottomOf="@+id/progressBar"
tools:text="This may take up to 2 minutes" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -79,10 +79,11 @@
<org.briarproject.briar.android.view.BriarButton <org.briarproject.briar.android.view.BriarButton
android:id="@+id/button3" android:id="@+id/button3"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
app:buttonStyle="@style/BriarButtonFlat.Negative" android:layout_marginHorizontal="16dp"
app:buttonStyle="@style/BriarButtonOutline.Negative"
app:text="@string/mailbox_status_unlink_button" /> app:text="@string/mailbox_status_unlink_button" />
</LinearLayout> </LinearLayout>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
tools:context=".android.mailbox.MailboxActivity"> tools:context=".android.mailbox.MailboxActivity">
@@ -75,10 +75,11 @@
<org.briarproject.briar.android.view.BriarButton <org.briarproject.briar.android.view.BriarButton
android:id="@+id/button1_1_1" android:id="@+id/button1_1_1"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
app:buttonStyle="@style/BriarButtonFlat.Negative" android:layout_marginHorizontal="16dp"
app:buttonStyle="@style/BriarButtonOutline.Negative"
app:text="@string/mailbox_status_unlink_button" /> app:text="@string/mailbox_status_unlink_button" />
</LinearLayout> </LinearLayout>
@@ -100,10 +101,11 @@
<org.briarproject.briar.android.view.BriarButton <org.briarproject.briar.android.view.BriarButton
android:id="@+id/button1_1_2" android:id="@+id/button1_1_2"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
app:buttonStyle="@style/BriarButtonFlat.Negative" android:layout_marginHorizontal="16dp"
app:buttonStyle="@style/BriarButtonOutline.Negative"
app:text="@string/mailbox_status_unlink_button" /> app:text="@string/mailbox_status_unlink_button" />
</LinearLayout> </LinearLayout>
@@ -125,10 +127,11 @@
<org.briarproject.briar.android.view.BriarButton <org.briarproject.briar.android.view.BriarButton
android:id="@+id/button1_1_3" android:id="@+id/button1_1_3"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
app:buttonStyle="@style/BriarButtonFlat.Positive" android:layout_marginHorizontal="16dp"
app:buttonStyle="@style/BriarButtonOutline.Positive"
app:text="@string/mailbox_status_check_button" /> app:text="@string/mailbox_status_check_button" />
</LinearLayout> </LinearLayout>

View File

@@ -32,7 +32,8 @@
android:id="@+id/statusTitleView" android:id="@+id/statusTitleView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:gravity="center" android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
@@ -47,62 +48,65 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginBottom="16dp" android:layout_marginTop="16dp"
android:gravity="center" android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone" android:visibility="gone"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/checkButton" app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusTitleView" app:layout_constraintTop_toBottomOf="@+id/statusTitleView"
tools:text="@string/mailbox_status_mailbox_too_old_message" tools:text="@string/mailbox_status_mailbox_too_old_message"
tools:visibility="visible" /> tools:visibility="visible" />
<org.briarproject.briar.android.view.BriarButton
android:id="@+id/checkButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
app:buttonStyle="@style/BriarButtonFlat.Neutral"
app:layout_constraintBottom_toTopOf="@+id/statusInfoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
app:text="@string/mailbox_status_check_button" />
<TextView <TextView
android:id="@+id/statusInfoView" android:id="@+id/statusInfoView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:gravity="center" android:gravity="center"
app:layout_constraintBottom_toTopOf="@+id/unlinkButton" app:layout_constraintBottom_toTopOf="@+id/checkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/checkButton" app:layout_constraintTop_toBottomOf="@+id/statusMessageView"
tools:text="@string/mailbox_status_connected_info" /> tools:text="@string/mailbox_status_connected_info" />
<org.briarproject.briar.android.view.BriarButton
android:id="@+id/checkButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:buttonStyle="@style/BriarButtonOutline.Neutral"
app:layout_constraintBottom_toTopOf="@+id/wizardButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusInfoView"
app:text="@string/mailbox_status_check_button" />
<Button <Button
android:id="@+id/wizardButton" android:id="@+id/wizardButton"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonOutline.Neutral"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:text="@string/mailbox_error_wizard_button" android:text="@string/mailbox_error_wizard_button"
android:visibility="gone" android:visibility="gone"
app:drawableTint="@color/briar_button_text_positive" app:drawableTint="@color/briar_button_text_positive"
app:layout_constraintBottom_toTopOf="@+id/unlinkButton" app:layout_constraintBottom_toTopOf="@+id/unlinkButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/statusInfoView" app:layout_constraintTop_toBottomOf="@+id/checkButton"
app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_bias="0.0"
tools:visibility="visible" /> tools:visibility="visible" />
<Button <Button
android:id="@+id/unlinkButton" android:id="@+id/unlinkButton"
style="@style/BriarButtonFlat.Negative" style="@style/BriarButtonOutline.Negative"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:text="@string/mailbox_status_unlink_button" android:text="@string/mailbox_status_unlink_button"

View File

@@ -1,81 +1,82 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"> android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="0dp"
android:layout_weight="1">
<ImageView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/iconView" android:layout_width="match_parent"
android:layout_width="@dimen/hero_square" android:layout_height="wrap_content">
android:layout_height="@dimen/hero_square"
android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/titleView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/transport_tor"
app:tint="@color/briar_red_500"
tools:ignore="ContentDescription" />
<TextView <ImageView
android:id="@+id/titleView" android:id="@+id/iconView"
android:layout_width="0dp" android:layout_width="@dimen/hero_square"
android:layout_height="wrap_content" android:layout_height="@dimen/hero_square"
android:layout_marginHorizontal="@dimen/margin_xlarge" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge" android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center" app:layout_constraintBottom_toTopOf="@+id/titleView"
android:text="@string/offline" app:layout_constraintEnd_toEndOf="parent"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/textView" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintVertical_bias="0.25"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@+id/iconView" /> app:srcCompat="@drawable/transport_tor"
app:tint="@color/briar_red_500"
tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/textView" android:id="@+id/titleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_xlarge" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="@dimen/margin_large" android:gravity="center"
android:text="@string/tor_offline_description" android:text="@string/offline"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toTopOf="@+id/checkButton" app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" /> app:layout_constraintTop_toBottomOf="@+id/iconView" />
<Button <TextView
android:id="@+id/checkButton" android:id="@+id/textView"
style="@style/BriarButtonFlat.Neutral" android:layout_width="0dp"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_margin="@dimen/margin_large"
android:layout_margin="@dimen/margin_large" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:text="@string/tor_offline_button_check" android:text="@string/tor_offline_description"
app:layout_constraintBottom_toTopOf="@+id/button" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" />
<Button </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/button"
style="@style/BriarButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_large"
android:text="@string/try_again_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </ScrollView>
</androidx.core.widget.NestedScrollView> <Button
android:id="@+id/checkButton"
style="@style/BriarButtonOutline.Neutral"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:text="@string/tor_offline_button_check" />
<Button
android:id="@+id/button"
style="@style/BriarButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:text="@string/try_again_button" />
</LinearLayout>

View File

@@ -33,6 +33,7 @@
android:paddingBottom="@dimen/listitem_vertical_margin" android:paddingBottom="@dimen/listitem_vertical_margin"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small" android:textSize="@dimen/text_size_small"
android:textColorLink="@color/briar_text_link"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/authorView" app:layout_constraintTop_toBottomOf="@+id/authorView"

View File

@@ -41,6 +41,7 @@
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner" android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner" android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textColorLink="@color/briar_text_link"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout" app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -49,6 +49,7 @@
android:layout_marginRight="@dimen/message_bubble_padding_sides_inner" android:layout_marginRight="@dimen/message_bubble_padding_sides_inner"
android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner" android:layout_marginBottom="@dimen/message_bubble_padding_bottom_inner"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_text_primary_inverse"
android:textColorLink="@color/briar_text_link_inverse"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/statusLayout" app:layout_constraintBottom_toTopOf="@+id/statusLayout"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -21,6 +21,7 @@
android:layout_marginRight="@dimen/message_bubble_margin_non_tail" android:layout_marginRight="@dimen/message_bubble_margin_non_tail"
android:background="@drawable/msg_in_top" android:background="@drawable/msg_in_top"
android:elevation="@dimen/message_bubble_elevation" android:elevation="@dimen/message_bubble_elevation"
android:textColorLink="@color/briar_text_link"
tools:text="Short message" tools:text="Short message"
tools:visibility="visible" /> tools:visibility="visible" />

View File

@@ -23,6 +23,7 @@
android:background="@drawable/msg_out_top" android:background="@drawable/msg_out_top"
android:elevation="@dimen/message_bubble_elevation" android:elevation="@dimen/message_bubble_elevation"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_text_primary_inverse"
android:textColorLink="@color/briar_text_link_inverse"
tools:text="This is a long long long message that spans over several lines.\n\nIt ends here." tools:text="This is a long long long message that spans over several lines.\n\nIt ends here."
tools:visibility="visible" /> tools:visibility="visible" />

View File

@@ -22,6 +22,7 @@
android:background="@drawable/msg_in_top" android:background="@drawable/msg_in_top"
android:elevation="@dimen/message_bubble_elevation" android:elevation="@dimen/message_bubble_elevation"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textColorLink="@color/briar_text_link"
tools:text="Short message" tools:text="Short message"
tools:visibility="visible" /> tools:visibility="visible" />

View File

@@ -114,6 +114,7 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="@dimen/text_size_medium" android:textSize="@dimen/text_size_medium"
android:textColorLink="@color/briar_text_link"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -224,6 +224,7 @@
<string name="menu_contact">Контакт</string> <string name="menu_contact">Контакт</string>
<!--Adding Contacts--> <!--Adding Contacts-->
<string name="add_contact_title">Добавяне на контакт на живо</string> <string name="add_contact_title">Добавяне на контакт на живо</string>
<string name="add_contact_error_two_way">Сканирахте ли взаимно кодовете си за QR?</string>
<string name="face_to_face">Трябва да се срещнете лично с човека, чиито контакт искате да добавите.\n\nТака никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string> <string name="face_to_face">Трябва да се срещнете лично с човека, чиито контакт искате да добавите.\n\nТака никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
<string name="continue_button">Напред</string> <string name="continue_button">Напред</string>
<string name="try_again_button">Нов опит</string> <string name="try_again_button">Нов опит</string>
@@ -239,6 +240,7 @@
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string> <string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
<string name="connection_error_title">Не може да бъде установена връзка с контакта</string> <string name="connection_error_title">Не може да бъде установена връзка с контакта</string>
<string name="connection_error_feedback">Ако проблемът продължава, <a href="feedback">изпратете обратна връзка</a>, за да ни помогнете да подобрим приложението.</string> <string name="connection_error_feedback">Ако проблемът продължава, <a href="feedback">изпратете обратна връзка</a>, за да ни помогнете да подобрим приложението.</string>
<string name="info_both_must_scan">Трябва взаимно да сканирате кодовете си за QR</string>
<!--Adding Contacts Remotely--> <!--Adding Contacts Remotely-->
<string name="add_contact_remotely_title_case">Добавяне на контакт отдалечено</string> <string name="add_contact_remotely_title_case">Добавяне на контакт отдалечено</string>
<string name="add_contact_nearby_title">Добавяне на контакт на живо</string> <string name="add_contact_nearby_title">Добавяне на контакт на живо</string>
@@ -296,6 +298,7 @@
<string name="different_person_button">Не</string> <string name="different_person_button">Не</string>
<string name="duplicate_link_dialog_text_3">%1$s и %2$s са изпратили еднакви препратки.\n\nЕдиният от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string> <string name="duplicate_link_dialog_text_3">%1$s и %2$s са изпратили еднакви препратки.\n\nЕдиният от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string> <string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
<string name="info_both_must_enter_links">Трябва взаимно да добавите препратките си</string>
<!--Peer trust levels--> <!--Peer trust levels-->
<string name="peer_trust_level_unverified">Непроверен контакт</string> <string name="peer_trust_level_unverified">Непроверен контакт</string>
<string name="peer_trust_level_verified">Проверен контакт</string> <string name="peer_trust_level_verified">Проверен контакт</string>
@@ -329,6 +332,7 @@
<string name="connect_via_bluetooth_intro">В случай, че връзка чрез Bluetooth не се установява автоматично можете да използвате този екран, за да се свържете ръчно.\n\nВие и контакта трябва да бъдете близо един до друг.\n\nДвамата трябва да докоснете бутона „Старт“ едновременно.</string> <string name="connect_via_bluetooth_intro">В случай, че връзка чрез Bluetooth не се установява автоматично можете да използвате този екран, за да се свържете ръчно.\n\nВие и контакта трябва да бъдете близо един до друг.\n\nДвамата трябва да докоснете бутона „Старт“ едновременно.</string>
<string name="connect_via_bluetooth_already_discovering">Осъществява св връзка през Bluetooth. По-късно опитайте отново.</string> <string name="connect_via_bluetooth_already_discovering">Осъществява св връзка през Bluetooth. По-късно опитайте отново.</string>
<string name="connect_via_bluetooth_no_location_permission">Не може да продължи без разрешение за местоположение</string> <string name="connect_via_bluetooth_no_location_permission">Не може да продължи без разрешение за местоположение</string>
<string name="connect_via_bluetooth_no_bluetooth_permission">Не може да продължи без разрешение за устройства наблизо</string>
<string name="connect_via_bluetooth_start">Свързване чрез Bluetooth…</string> <string name="connect_via_bluetooth_start">Свързване чрез Bluetooth…</string>
<string name="connect_via_bluetooth_success">Успешно е създaдена връзка чрез Bluetooth</string> <string name="connect_via_bluetooth_success">Успешно е създaдена връзка чрез Bluetooth</string>
<string name="connect_via_bluetooth_error">Не може да бъде установена връзка чрез Bluetooth.</string> <string name="connect_via_bluetooth_error">Не може да бъде установена връзка чрез Bluetooth.</string>
@@ -586,10 +590,8 @@
\nСлед това свържете Mailbox с Briar чрез сканиране на кода за QR от приложението Mailbox.</string> \nСлед това свържете Mailbox с Briar чрез сканиране на кода за QR от приложението Mailbox.</string>
<string name="mailbox_setup_download_link">Споделяне на препратка за изтегляне</string> <string name="mailbox_setup_download_link">Споделяне на препратка за изтегляне</string>
<string name="mailbox_setup_button_scan">Сканиране на кода за QR на пощенска кутия</string> <string name="mailbox_setup_button_scan">Сканиране на кода за QR на пощенска кутия</string>
<string name="permission_camera_qr_denied_body">Отказахте достъп до камерата, но тя е необходима за добавянето на контакти.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_camera_qr_denied_body">Отказахте достъп до камерата, но достъп е необходим за сканиране на кодове за QR.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="mailbox_setup_connecting">Свързване…</string> <!--This string is shown when connecting to a Mailbox for the first time. The placeholder will be replaced with a duration, e.g. "2 minutes".-->
<string name="mailbox_setup_qr_code_wrong_title">Грешен код на QR</string>
<string name="mailbox_setup_qr_code_wrong_description">Сканираният код е недействителен. Отворете приложението Briar Malibox на устройството, на което е инсталирано и сканирайте кода, който то предостави.</string>
<string name="mailbox_setup_already_paired_title">Пощенската кутия е вече свързана</string> <string name="mailbox_setup_already_paired_title">Пощенската кутия е вече свързана</string>
<string name="mailbox_setup_already_paired_description">Прекъснете връзката с пощенската кутия от другото устройство и опитайте отново.</string> <string name="mailbox_setup_already_paired_description">Прекъснете връзката с пощенската кутия от другото устройство и опитайте отново.</string>
<string name="mailbox_setup_io_error_title">Грешка при свързване</string> <string name="mailbox_setup_io_error_title">Грешка при свързване</string>
@@ -718,14 +720,20 @@
<string name="permission_camera_title">Разрешение за камера</string> <string name="permission_camera_title">Разрешение за камера</string>
<string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string> <string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string>
<string name="permission_location_title">Разрешение за местоположение</string> <string name="permission_location_title">Разрешение за местоположение</string>
<string name="permission_location_request_body">За да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_location_request_body">За да открива устройства чрез Bluetooth, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_camera_location_title">Камера и местоположение</string> <string name="permission_camera_location_title">Камера и местоположение</string>
<string name="permission_camera_location_request_body">За да сканира кодове за QR, на Briar му е необходимо разрешение за достъп до камерата.\n\nЗа да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_camera_location_request_body">За да сканира кода за QR, Briar трябва да използва камерата.\n\nЗа да открива устройства чрез Bluetooth, Briar трябва да има достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_camera_denied_body">Отказахте достъп до камерата, но тя е необходима за добавянето на контакти.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_camera_bluetooth_title">Камера и устройства наблизо</string>
<string name="permission_location_denied_body">Отказахте достъп до местоположението, но то е необходимо за откриване на устройства чрез Bluetooth.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_camera_bluetooth_request_body">За да сканира кода за QR, Briar трябва да използва камерата.\n\nЗа да открива устройства чрез Bluetooth, Briar се нуждае от достъп до местоположението. Briar се нуждае от разрешение да намира и да се свързва с устройства наблизо.</string>
<string name="permission_camera_denied_body">Отказахте достъп до камерата, но достъп е необходим за добавяне на контакти.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_location_denied_body">Отказахте достъп до местоположението, но достъп е необходим за откриване на устройства чрез Bluetooth.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_location_setting_title">Настройки на местоположението</string> <string name="permission_location_setting_title">Настройки на местоположението</string>
<string name="permission_location_setting_body">Местоположението на устройството ви трябва да е включено, за да бъдат откривани устройства чрез Bluetooth. За да продължите включете местоположението. След това можете да го изключите.</string> <string name="permission_location_setting_body">За да бъдат откривани устройства чрез Bluetooth местоположението на устройството ви трябва да е включено. За да продължите включете местоположението. След това можете да го изключите.</string>
<string name="permission_location_setting_hotspot_body">За да създаде безжична точка за достъп местоположението на устройството ви трябва да е включено. За да продължите включете местоположението. След това можете да го изключите.</string>
<string name="permission_location_setting_button">Включване на местеположение</string> <string name="permission_location_setting_button">Включване на местеположение</string>
<string name="permission_bluetooth_title">Разрешение за устройства наблизо</string>
<string name="permission_bluetooth_body">За да извършва разговори чрез Bluetooth, Briar се нуждае от разрешение да намира и да се свързва с устройства наблизо.</string>
<string name="permission_bluetooth_denied_body">Отказахте достъп до устройства наблизо, но достъп е необходим за използване на Bluetooth.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="qr_code">Код за QR</string> <string name="qr_code">Код за QR</string>
<string name="show_qr_code_fullscreen">Код за QR на цял екран</string> <string name="show_qr_code_fullscreen">Код за QR на цял екран</string>
<!--App Locking--> <!--App Locking-->
@@ -747,8 +755,10 @@
<string name="hotspot_notification_channel_title">Безжична точка за достъп</string> <string name="hotspot_notification_channel_title">Безжична точка за достъп</string>
<string name="hotspot_notification_title">Споделяне на Briar извън мрежа</string> <string name="hotspot_notification_title">Споделяне на Briar извън мрежа</string>
<string name="hotspot_button_connected">Напред</string> <string name="hotspot_button_connected">Напред</string>
<string name="permission_hotspot_location_request_body">За да създаде безжична точка за достъп, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string> <string name="permission_hotspot_location_request_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_hotspot_location_denied_body">Отказахте достъп до местоположението, но то е необходимо за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string> <string name="permission_hotspot_location_request_precise_body">За да създаде безжична точка за достъп, Briar се нуждае от разрешение за достъп до точното местоположение.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_hotspot_location_denied_body">Отказахте достъп до местоположението, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_hotspot_location_denied_precise_body">Отказахте достъп до точното местоположение, но достъп е необходим за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="wifi_settings_title">Настройки на Wi-Fi</string> <string name="wifi_settings_title">Настройки на Wi-Fi</string>
<string name="wifi_settings_request_enable_body">За да създаде безжична точка за достъп, Briar се нуждае от безжична мрежа. Включете Wi-Fi.</string> <string name="wifi_settings_request_enable_body">За да създаде безжична точка за достъп, Briar се нуждае от безжична мрежа. Включете Wi-Fi.</string>
<string name="hotspot_tab_manual">Ръчно</string> <string name="hotspot_tab_manual">Ръчно</string>

Some files were not shown because too many files have changed in this diff Show More