Compare commits

...

59 Commits

Author SHA1 Message Date
akwizgran
51794424ce Bump version numbers for 1.2.18 release. 2021-03-11 15:25:32 +00:00
Torsten Grote
5db099bae6 Merge branch 'update-bridges' into 'master'
Update list of Tor bridges

See merge request briar/briar!1403
2021-03-11 15:20:11 +00:00
Torsten Grote
a2faa3bd3b Merge branch '1612-do-not-strip-libs' into 'master'
Don't strip libraries even if the NDK is installed

See merge request briar/briar!1401
2021-03-11 15:14:20 +00:00
akwizgran
a3fb7b5680 Update list of Tor bridges. 2021-03-11 14:24:46 +00:00
akwizgran
264d110dbd Bump version numbers for 1.2.17 release. 2021-03-11 12:35:19 +00:00
akwizgran
839b871a45 Merge branch 'aarch64-finalization' into 'master'
Make headless work on aarch64 and armhf (armv7)

Closes #1854

See merge request briar/briar!1376
2021-03-11 12:28:55 +00:00
akwizgran
2fb4825b8f Don't strip libraries even if the NDK is installed.
This allows reproducible builds regardless of whether the NDK is installed.
2021-03-11 12:20:41 +00:00
Torsten Grote
3f9a66b1b6 Merge branch '1964-no-colons' into 'master'
Remove colons from default filename

Closes #1964

See merge request briar/briar!1400
2021-03-11 11:36:49 +00:00
akwizgran
d796916387 Also remove colons on API >= 19. 2021-03-11 10:33:53 +00:00
akwizgran
fe07b760ea Remove colons from default filename. 2021-03-10 15:44:15 +00:00
Nico Alt
e21e6267d7 Update Tor dependency to include armhf binary
Related MR:
https://code.briarproject.org/briar/tor-reproducer/-/merge_requests/13
2021-03-09 10:46:43 +01:00
Nico Alt
d7afbdf690 Use Tor binary for armhf (armv7)
Example devices are Nexus 5 and Raspberry Pi v2.

Based on https://code.briarproject.org/briar/briar/-/merge_requests/1376

Related to https://code.briarproject.org/briar/briar/-/issues/1854
2021-03-09 12:00:00 +00:00
Torsten Grote
c5d2661c1d Merge branch '1919-password-fields-not-focusable' into 'master'
Condition display of progressbar on a isCreatingAccount LiveData

Closes #1819 and #1919

See merge request briar/briar!1355
2021-03-03 13:10:29 +00:00
Nico Alt
b738bdd14e Actually make headless work on arm aarch64
Following the two comments at
https://code.briarproject.org/briar/briar/-/issues/1854#note_44340

.jar files now get built with

    $ ./gradlew --configure-on-demand briar-headless:x86LinuxJar
    $ ./gradlew --configure-on-demand briar-headless:aarch64LinuxJar

Related to #1854
2021-03-03 12:00:00 +00:00
akwizgran
629cff20a3 Merge branch '1952-oom-avatar-preview-glide' into 'master'
Load avatar previews with Glide to prevent OOM errors

Closes #1952

See merge request briar/briar!1388
2021-03-01 18:02:19 +00:00
Torsten Grote
6cfb70db95 Load image from URI with Glide to prevent OOM errors 2021-03-01 14:15:53 -03:00
Torsten Grote
737ecfb620 Some unrelated code changes to avatar settings 2021-03-01 14:15:08 -03:00
akwizgran
5a424b178e Merge branch '1667-toolbar-options' into 'master'
Make group/create forum/write blog post buttons to always show

Closes #1667

See merge request briar/briar!1377
2021-03-01 16:34:14 +00:00
Torsten Grote
59f4e7c34a Super call to onRequestPermissionsResult() is now required 2021-02-23 10:55:20 -03:00
Torsten Grote
2480824d69 Fix toolbar buttons not showing up after sign-in on lower API levels 2021-02-23 10:55:20 -03:00
akwizgran
a6c2000d81 Merge branch '1825-pending-contact-error' into 'master'
Be more specific about errors when adding pending contact

Closes #1825

See merge request briar/briar!1354
2021-02-22 11:12:49 +00:00
akwizgran
a38a3139d9 Merge branch 'fix-message-in-profile-picture-confirmation' into 'master'
Fix message in profile picture confirmation

See merge request briar/briar!1356
2021-02-22 11:06:58 +00:00
akwizgran
4c8adaa02b Merge branch '1399-unlock-activity-crash' into 'master'
Let LockManager only lock current, not future process

Closes #1399

See merge request briar/briar!1374
2021-02-22 10:49:17 +00:00
akwizgran
8a534b4503 Bump version numbers for 1.2.16 release. 2021-02-19 18:01:56 +00:00
akwizgran
e5b2275c82 Merge branch '1947-forum-crash' into 'master'
Don't add new thread items when the existing ones haven't loaded

Closes #1947

See merge request briar/briar!1375
2021-02-19 17:27:38 +00:00
Torsten Grote
5159593825 Don't add new item when the existing ones haven't loaded 2021-02-19 14:17:21 -03:00
Torsten Grote
a546fecc01 Let LockManager only lock current, not future process
This fixes a bug on Android 8
where the AlarmManager would re-start a killed BriarService.
Then the LockManager lingers around locked and causes an ANR on Android 8.x when the user comes back to it.
2021-02-19 10:42:43 -03:00
Nico Alt
3e7e37f5f6 Include pending contact id in error response 2021-02-19 12:00:00 +00:00
Nico Alt
d095ba0b15 Include name/alias of already existing (pending) contact in error 2021-02-19 14:44:56 +01:00
Nico Alt
7fab97d26c Be more specific about errors when adding pending contact
Following the docs at
https://code.briarproject.org/briar/briar/-/blob/beta-1.2.14/bramble-api/src/main/java/org/briarproject/bramble/api/contact/ContactManager.java#L110

Fixes #1825
2021-02-19 14:44:56 +01:00
akwizgran
6fbc82ee27 Merge branch '1075-1146-1317-ongoing-notification' into 'master'
Use IMPORTANCE_LOW for ongoing notification, don't show a badge

Closes #1317, #1146, and #1075

See merge request briar/briar!1369
2021-02-18 17:00:47 +00:00
akwizgran
885b03cfd7 Bump version numbers for 1.2.15 release. 2021-02-18 15:27:57 +00:00
akwizgran
f81bfcafeb Update translations. 2021-02-18 15:26:10 +00:00
akwizgran
f36f1cf3d4 Merge branch '1764-fix-change-app-language-does-not-work' into 'master'
Resolve "Change app language does not work"

Closes #1764

See merge request briar/briar!1367
2021-02-17 16:59:59 +00:00
Torsten Grote
7d6a63d866 Merge branch '1934-upgrade-obfs4proxy' into 'master'
Upgrade obfs4proxy to 0.0.12-dev

Closes #1934

See merge request briar/briar!1372
2021-02-17 16:58:22 +00:00
akwizgran
15ebdf8dd5 Upgrade obfs4proxy to 0.0.12-dev. 2021-02-17 16:41:49 +00:00
akwizgran
db2c235283 Merge branch 'private-group-disabled' into 'master'
Fix disabled groups after screen rotation

See merge request briar/briar!1371
2021-02-17 14:09:57 +00:00
Daniel Lublin
6b61725c6a Condition display of progressbar on a isCreatingAccount LiveData
Avoiding the mess with saving onSaveInstanceState, and the (in this
case) unwanted restoring of it upon back-button tap.

Closes #1919

Test instructions:

- Precondition: fresh install, setting up a new account
  - Testing specific bug fix:
    - Choose a name, tap next
    - Choose a password, tap next
      - Not testable on some devices which display "Create account" instead of "Next"
    - You are now on Background connections screen
    - Tap Back-button ◁
    - Ensure that password can be changed again
  - During setup process, rotate device and ensure that:
    - entered text is kept
    - progressbar is continuously displayed
2021-02-17 13:57:08 +01:00
Sebastian Kürten
e5bd43469e Add Javados to Localizer#setLocale() 2021-02-15 14:54:20 +01:00
Torsten Grote
9366c184d8 Fix disabled groups after screen rotation
isDissolved was reverted to LiveData that only shows a dialog when the activity was first opened
2021-02-15 09:55:59 -03:00
Sebastian Kürten
73d2c964d4 Make language switching for robust 2021-02-15 12:31:51 +01:00
akwizgran
fb2b4209cf Use IMPORTANCE_LOW for ongoing notification, don't show a badge. 2021-02-10 11:46:41 +00:00
Torsten Grote
a04b512497 Merge branch 'tor-0.3.5.13' into 'master'
Upgrade Tor to 0.3.5.13

Closes #1922

See merge request briar/briar!1363
2021-02-09 12:15:45 +00:00
akwizgran
3d9515e308 Also upgrade obfs4proxy and bramble-java's Tor. 2021-02-09 12:05:54 +00:00
akwizgran
1b19b331b1 Merge branch '1904-fragment-started-too-late' into 'master'
Don't launch fragments with back button when not started

Closes #1904

See merge request briar/briar!1365
2021-02-09 11:05:08 +00:00
akwizgran
d151a2d7f7 Merge branch '1910-state-exception-when-adding-contact' into 'master'
Restore remote handshake link when AddContactViewModel gets destroyed

Closes #1910

See merge request briar/briar!1364
2021-02-09 10:49:38 +00:00
Torsten Grote
9712a4b849 Don't launch fragments with back button when not started
Sounds strange, but apparently can happen.
2021-02-08 16:38:15 -03:00
Torsten Grote
cf1ac5e3e5 Restore remote handshake link when AddContactViewModel gets destroyed 2021-02-08 16:03:10 -03:00
Torsten Grote
cb859e998d Upgrade Tor to 0.3.5.13 2021-02-08 15:44:35 -03:00
akwizgran
0b9345f867 Merge branch '1621-link-disappearing' into 'master'
Remove monospace typeface from our briar:// link

Closes #1621

See merge request briar/briar!1362
2021-02-08 18:36:16 +00:00
Torsten Grote
12988120d1 Remove monospace typeface from our briar:// link
as this makes the text to become invisible when selecting all text on API 15-17
2021-02-08 14:45:57 -03:00
akwizgran
8d6c866e62 Merge branch '1926-cap-scrypt-cost' into 'master'
Cap the scrypt cost parameter to avoid OOM

Closes #1926

See merge request briar/briar!1360
2021-02-08 17:30:57 +00:00
akwizgran
8f82cf3c73 Merge branch '1917-logcat-process' into 'master'
Fix crash reporter to capture logs from main process

Closes #1917

See merge request briar/briar!1359
2021-02-08 16:58:12 +00:00
Torsten Grote
21112ce092 Encrypt logs before handing them to crash report process 2021-02-08 13:43:37 -03:00
akwizgran
21ee3ea00d Merge branch 'add-custom-dictionary' into 'master'
Add a custom dictionary

See merge request briar/briar!1361
2021-02-08 14:01:43 +00:00
Sebastian Kürten
bb964101b3 Add a custom dictionary
This reduces the amount of words highlighted by the spell checker and
helps focussing on words that are really misspelled.
2021-02-08 14:35:14 +01:00
akwizgran
d796eff0f6 Cap the scrypt cost parameter to avoid OOM. 2021-02-08 11:32:03 +00:00
Torsten Grote
700ea2b387 Add support for logs to StreamReader and StreamWriter
Shamelessly stolen from d9b4c013
2021-02-05 17:07:48 -03:00
Sebastian Kürten
e4a66615a7 Fix remark in dialog for confirming profile picture 2021-02-04 18:43:32 +01:00
84 changed files with 1105 additions and 292 deletions

14
.idea/dictionaries/briar.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<component name="ProjectDictionaryState">
<dictionary name="briar">
<words>
<w>briar</w>
<w>briarproject</w>
<w>emoji</w>
<w>encrypter</w>
<w>identicon</w>
<w>introducee</w>
<w>introducer</w>
<w>onboarding</w>
</words>
</dictionary>
</component>

View File

@@ -8,11 +8,15 @@ android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.2' buildToolsVersion '30.0.2'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10214 versionCode 10218
versionName "1.2.14" versionName "1.2.18"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,8 +42,8 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.12@zip' tor 'org.briarproject:tor-android:0.3.5.13@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip' tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -31,6 +31,7 @@ public class AndroidUtils {
private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00"; private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00";
private static final String STORED_REPORTS = "dev-reports"; private static final String STORED_REPORTS = "dev-reports";
private static final String STORED_LOGCAT = "dev-logcat";
public static Collection<String> getSupportedArchitectures() { public static Collection<String> getSupportedArchitectures() {
List<String> abis = new ArrayList<>(); List<String> abis = new ArrayList<>();
@@ -107,6 +108,10 @@ public class AndroidUtils {
return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); return ctx.getDir(STORED_REPORTS, MODE_PRIVATE);
} }
public static File getLogcatFile(Context ctx) {
return new File(ctx.getFilesDir(), STORED_LOGCAT);
}
/** /**
* Returns an array of supported content types for image attachments. * Returns an array of supported content types for image attachments.
* GIFs can't be compressed on API < 24 so they're not supported. * GIFs can't be compressed on API < 24 so they're not supported.

View File

@@ -75,8 +75,8 @@ dependencyVerification {
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a', 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c', 'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df', 'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',

View File

@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
*/ */
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in, StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamDecrypter} for decrypting a log stream.
*/
StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey);
} }

View File

@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange * Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream. * stream.
*/ */
StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out, StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamEncrypter} for encrypting a log stream.
*/
StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

View File

@@ -13,4 +13,6 @@ public interface DevConfig {
String getDevOnionAddress(); String getDevOnionAddress();
File getReportDir(); File getReportDir();
File getLogcatFile();
} }

View File

@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
/** /**
* Creates an {@link InputStream InputStream} for reading from a contact * Creates an {@link InputStream InputStream} for reading from a contact
* exchangestream. * exchange stream.
*/ */
InputStream createContactExchangeStreamReader(InputStream in, InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates an {@link InputStream} for reading from a log stream.
*/
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
} }

View File

@@ -7,17 +7,19 @@ import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
public interface StreamWriterFactory { public interface StreamWriterFactory {
/** /**
* Creates an {@link OutputStream OutputStream} for writing to a * Creates a {@link StreamWriter} for writing to a transport stream.
* transport stream
*/ */
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates an {@link OutputStream OutputStream} for writing to a contact * Creates a {@link StreamWriter} for writing to a contact exchange stream.
* exchange stream.
*/ */
StreamWriter createContactExchangeStreamWriter(OutputStream out, StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamWriter} for writing to a log stream.
*/
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
} }

View File

@@ -9,14 +9,16 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
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.now; import static org.briarproject.bramble.util.LogUtils.now;
class ScryptKdf implements PasswordBasedKdf { class ScryptKdf implements PasswordBasedKdf {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ScryptKdf.class.getName()); getLogger(ScryptKdf.class.getName());
private static final int MIN_COST = 256; // Min parameter N private static final int MIN_COST = 256; // Min parameter N
private static final int MAX_COST = 1024 * 1024; // Max parameter N private static final int MAX_COST = 1024 * 1024; // Max parameter N
@@ -33,10 +35,20 @@ class ScryptKdf implements PasswordBasedKdf {
@Override @Override
public int chooseCostParameter() { public int chooseCostParameter() {
// Scrypt uses at least 128 * N * r bytes of memory. Don't use more
// than half of the JVM's max heap size or we may run out of memory.
// https://blog.filippo.io/the-scrypt-parameters/
long maxMemory = Runtime.getRuntime().maxMemory();
long maxCost = min(MAX_COST, maxMemory / BLOCK_SIZE / 256);
if (LOG.isLoggable(INFO) && maxCost < MAX_COST) {
LOG.info("Max cost capped at " + maxCost
+ " due to max heap size " + maxMemory);
}
// Increase the cost from min to max while measuring performance // Increase the cost from min to max while measuring performance
int cost = MIN_COST; int cost = MIN_COST;
while (cost * 2 <= MAX_COST && measureDuration(cost) * 2 <= TARGET_MS) while (cost * 2 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) {
cost *= 2; cost *= 2;
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("KDF cost parameter " + cost); LOG.info("KDF cost parameter " + cost);
return cost; return cost;

View File

@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
SecretKey headerKey) { SecretKey headerKey) {
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey); return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
} }
@Override
public StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey) {
return createContactExchangeStreamDecrypter(in, headerKey);
}
} }

View File

@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
} }
@Override @Override
public StreamEncrypter createContactExchangeStreamDecrypter( public StreamEncrypter createContactExchangeStreamEncrypter(
OutputStream out, SecretKey headerKey) { OutputStream out, SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get(); AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH]; byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce, return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey); headerKey, frameKey);
} }
@Override
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey) {
return createContactExchangeStreamEncrypter(out, headerKey);
}
} }

View File

@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
@Override @Override
public InputStream createStreamReader(InputStream in, StreamContext ctx) { public InputStream createStreamReader(InputStream in, StreamContext ctx) {
return new StreamReaderImpl( return new StreamReaderImpl(streamDecrypterFactory
streamDecrypterFactory.createStreamDecrypter(in, ctx)); .createStreamDecrypter(in, ctx));
} }
@Override @Override
public InputStream createContactExchangeStreamReader(InputStream in, public InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamReaderImpl( return new StreamReaderImpl(streamDecrypterFactory
streamDecrypterFactory.createContactExchangeStreamDecrypter(in, .createContactExchangeStreamDecrypter(in, headerKey));
headerKey)); }
@Override
public InputStream createLogStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory
.createLogStreamDecrypter(in, headerKey));
} }
} }

View File

@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
@Override @Override
public StreamWriter createStreamWriter(OutputStream out, public StreamWriter createStreamWriter(OutputStream out,
StreamContext ctx) { StreamContext ctx) {
return new StreamWriterImpl( return new StreamWriterImpl(streamEncrypterFactory
streamEncrypterFactory.createStreamEncrypter(out, ctx)); .createStreamEncrypter(out, ctx));
} }
@Override @Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out, public StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamWriterImpl( return new StreamWriterImpl(streamEncrypterFactory
streamEncrypterFactory.createContactExchangeStreamDecrypter(out, .createContactExchangeStreamEncrypter(out, headerKey));
headerKey)); }
@Override
public StreamWriter createLogStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory
.createLogStreamEncrypter(out, headerKey));
} }
} }

View File

@@ -2,12 +2,11 @@ Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUV
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1 Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0 Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0 Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0 Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0 Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0 Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0 Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0 Bridge obfs4 209.148.46.65:443 74FAD13168806246602538555B5521A0383A1875 cert=ssH+9rP8dG2NLDN2XuFw63hIO/9MNNinLmxQDpVa+7kTOa9/m+tGWT1SmSYpQ9uTBGa6Hw iat-mode=0
Bridge obfs4 146.57.248.225:22 10A6CD36A537FCE513A322361547444B393989F0 cert=K1gDtDAIcUfeLqbstggjIw2rtgIKqdIhUlHp82XRqNSq/mtAjp1BIC9vHKJ2FAEpGssTPw iat-mode=0
Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0 Bridge obfs4 45.145.95.6:27015 C5B7CD6946FF10C5B3E89691A7D3F2C122D2117C cert=TD7PbUO0/0k6xYHMPW3vJxICfkMZNdkRrb63Zhl5j9dW3iRGiCx0A7mPhe5T2EDzQ35+Zw iat-mode=0
Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0 Bridge obfs4 51.222.13.177:80 5EDAC3B810E12B01F6FD8050D2FD3E277B289A08 cert=2uplIpLQ0q9+0qMFrK5pkaYRDOe460LL9WHBvatgkuRr/SL31wBOEupaMMJ6koRE6Ld0ew iat-mode=0
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0 Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0

View File

@@ -16,8 +16,8 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar') implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2' implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2' implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.5.12@zip' tor 'org.briarproject:tor:0.3.5.13-1@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip' tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -25,6 +25,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.OsUtils.isLinux; import static org.briarproject.bramble.util.OsUtils.isLinux;
@@ -96,8 +97,15 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
String architecture = null; String architecture = null;
if (isLinux()) { if (isLinux()) {
String arch = System.getProperty("os.arch"); String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) { if (arch.equals("amd64")) {
architecture = "linux-x86_64"; architecture = "linux-x86_64";
} else if (arch.equals("aarch64")) {
architecture = "linux-aarch64";
} else if (arch.equals("arm")) {
architecture = "linux-armhf";
} }
} }
if (architecture == null) { if (architecture == null) {
@@ -105,6 +113,10 @@ public class UnixTorPluginFactory implements DuplexPluginFactory {
return null; return null;
} }
if (LOG.isLoggable(INFO)) {
LOG.info("The selected architecture for Tor is " + architecture);
}
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL, Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE); MAX_POLLING_INTERVAL, BACKOFF_BASE);
TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl(); TorRendezvousCrypto torRendezvousCrypto = new TorRendezvousCryptoImpl();

View File

@@ -23,8 +23,8 @@ dependencyVerification {
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d', 'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642', 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006', 'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -19,11 +19,15 @@ android {
compileSdkVersion 30 compileSdkVersion 30
buildToolsVersion '30.0.2' buildToolsVersion '30.0.2'
packagingOptions {
doNotStrip '**/*.so'
}
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10214 versionCode 10218
versionName "1.2.14" versionName "1.2.18"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -95,6 +99,7 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android') implementation project(':bramble-android')
implementation 'androidx.fragment:fragment:1.3.0'
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.3.1' implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
@@ -136,6 +141,7 @@ dependencies {
testImplementation "org.jmock:jmock:$jmockVersion" testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion" testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion" testImplementation "org.jmock:jmock-legacy:$jmockVersion"
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput') androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'

View File

@@ -34,6 +34,7 @@ import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
@@ -179,6 +180,10 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager(); AndroidWakeLockManager wakeLockManager();
CachingLogHandler logHandler();
Thread.UncaughtExceptionHandler exceptionHandler();
void inject(SignInReminderReceiver briarService); void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService); void inject(BriarService briarService);

View File

@@ -33,12 +33,13 @@ import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule; import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule; import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule; import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.sharing.SharingModule; import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl; import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
@@ -74,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
SetupModule.class, SetupModule.class,
DozeHelperModule.class, DozeHelperModule.class,
ContactExchangeModule.class, ContactExchangeModule.class,
LoggingModule.class,
LoginModule.class, LoginModule.class,
NavDrawerModule.class, NavDrawerModule.class,
ViewModelModule.class, ViewModelModule.class,
@@ -192,6 +194,11 @@ public class AppModule {
public File getReportDir() { public File getReportDir() {
return AndroidUtils.getReportDir(app.getApplicationContext()); return AndroidUtils.getReportDir(app.getApplicationContext());
} }
@Override
public File getLogcatFile() {
return AndroidUtils.getLogcatFile(app.getApplicationContext());
}
}; };
return devConfig; return devConfig;
} }

View File

@@ -6,9 +6,6 @@ import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleApplication; import org.briarproject.bramble.BrambleApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.Collection;
import java.util.logging.LogRecord;
/** /**
* This exists so that the Application object will not necessarily be cast * This exists so that the Application object will not necessarily be cast
* directly to the Briar application object. * directly to the Briar application object.
@@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication {
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class; Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;
Collection<LogRecord> getRecentLogRecords();
AndroidComponent getApplicationComponent(); AndroidComponent getApplicationComponent();
SharedPreferences getDefaultSharedPreferences(); SharedPreferences getDefaultSharedPreferences();

View File

@@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection; import java.lang.Thread.UncaughtExceptionHandler;
import java.util.logging.Handler; import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -42,22 +40,18 @@ public class BriarApplicationImpl extends Application
private static final Logger LOG = private static final Logger LOG =
getLogger(BriarApplicationImpl.class.getName()); getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs; private volatile SharedPreferences prefs;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null) if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base); prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here. // Loading the language needs to be done here.
Localizer.initialize(prefs); Localizer.initialize(prefs);
super.attachBaseContext( super.attachBaseContext(
Localizer.getInstance().setLocale(base)); Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
setTheme(base, prefs); setTheme(base, prefs);
} }
@@ -67,6 +61,11 @@ public class BriarApplicationImpl extends Application
if (IS_DEBUG_BUILD) enableStrictMode(); if (IS_DEBUG_BUILD) enableStrictMode();
applicationComponent = createApplicationComponent();
UncaughtExceptionHandler exceptionHandler =
applicationComponent.exceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
Logger rootLogger = getLogger(""); Logger rootLogger = getLogger("");
Handler[] handlers = rootLogger.getHandlers(); Handler[] handlers = rootLogger.getHandlers();
// Disable the Android logger for release builds // Disable the Android logger for release builds
@@ -78,12 +77,12 @@ public class BriarApplicationImpl extends Application
// Restore the default handlers after the level raising handler // Restore the default handlers after the level raising handler
for (Handler handler : handlers) rootLogger.addHandler(handler); for (Handler handler : handlers) rootLogger.addHandler(handler);
} }
CachingLogHandler logHandler = applicationComponent.logHandler();
rootLogger.addHandler(logHandler); rootLogger.addHandler(logHandler);
rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO); rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO);
LOG.info("Created"); LOG.info("Created");
applicationComponent = createApplicationComponent();
EmojiManager.install(new GoogleEmojiProvider()); EmojiManager.install(new GoogleEmojiProvider());
} }
@@ -136,11 +135,6 @@ public class BriarApplicationImpl extends Application
return applicationComponent; return applicationComponent;
} }
@Override
public Collection<LogRecord> getRecentLogRecords() {
return logHandler.getRecentLogRecords();
}
@Override @Override
public AndroidComponent getApplicationComponent() { public AndroidComponent getApplicationComponent() {
return applicationComponent; return applicationComponent;

View File

@@ -37,7 +37,7 @@ import javax.inject.Inject;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
@@ -46,6 +46,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.Process.myPid;
import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET; import static androidx.core.app.NotificationCompat.VISIBILITY_SECRET;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -56,8 +57,10 @@ 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.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_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_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK; import static org.briarproject.briar.api.android.LockManager.ACTION_LOCK;
import static org.briarproject.briar.api.android.LockManager.EXTRA_PID;
public class BriarService extends Service { public class BriarService extends Service {
@@ -120,11 +123,17 @@ public class BriarService extends Service {
if (SDK_INT >= 26) { if (SDK_INT >= 26) {
NotificationManager nm = (NotificationManager) NotificationManager nm = (NotificationManager)
requireNonNull(getSystemService(NOTIFICATION_SERVICE)); requireNonNull(getSystemService(NOTIFICATION_SERVICE));
// Delete the old notification channel, which had
// IMPORTANCE_NONE and showed a badge
nm.deleteNotificationChannel(ONGOING_CHANNEL_OLD_ID);
// Use IMPORTANCE_LOW so the system doesn't show its own
// notification on API 26-27
NotificationChannel ongoingChannel = new NotificationChannel( NotificationChannel ongoingChannel = new NotificationChannel(
ONGOING_CHANNEL_ID, ONGOING_CHANNEL_ID,
getString(R.string.ongoing_notification_title), getString(R.string.ongoing_notification_title),
IMPORTANCE_NONE); IMPORTANCE_LOW);
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET); ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel); nm.createNotificationChannel(ongoingChannel);
NotificationChannel failureChannel = new NotificationChannel( NotificationChannel failureChannel = new NotificationChannel(
FAILURE_CHANNEL_ID, FAILURE_CHANNEL_ID,
@@ -170,6 +179,7 @@ public class BriarService extends Service {
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
super.attachBaseContext(Localizer.getInstance().setLocale(base)); super.attachBaseContext(Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
} }
private void showStartupFailureNotification(StartResult result) { private void showStartupFailureNotification(StartResult result) {
@@ -202,7 +212,12 @@ public class BriarService extends Service {
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_LOCK.equals(intent.getAction())) { if (ACTION_LOCK.equals(intent.getAction())) {
lockManager.setLocked(true); int pid = intent.getIntExtra(EXTRA_PID, -1);
if (pid == myPid()) lockManager.setLocked(true);
else if (LOG.isLoggable(WARNING)) {
LOG.warning("Tried to lock process " + pid + " but this is " +
myPid());
}
} }
return START_NOT_STICKY; // Don't restart automatically if killed return START_NOT_STICKY; // Don't restart automatically if killed
} }

View File

@@ -68,7 +68,21 @@ public class Localizer {
return new Locale(tag); return new Locale(tag);
} }
// Returns the localized version of context /*
* Apply localization to the specified context.
*
* It updates the configuration of the context's resources object but can
* also return a new context derived from the context parameter. Hence
* make sure to work with the return value of this method instead of
* the context you passed as a parameter.
*
* This method also has side-effects as it calls Locale#setDefault().
*
* When using this in attachBaseContext() of Application, Service or
* Activity subclasses, it is important to not only apply this method to the
* base Context parameter received in that method, but also apply it on the
* class itself which also extends Context.
*/
public Context setLocale(Context context) { public Context setLocale(Context context) {
Resources res = context.getResources(); Resources res = context.getResources();
Configuration conf = res.getConfiguration(); Configuration conf = res.getConfiguration();
@@ -82,7 +96,7 @@ public class Localizer {
Locale.setDefault(locale); Locale.setDefault(locale);
if (SDK_INT >= 17) { if (SDK_INT >= 17) {
conf.setLocale(locale); conf.setLocale(locale);
context.createConfigurationContext(conf); context = context.createConfigurationContext(conf);
} else } else
conf.locale = locale; conf.locale = locale;
//noinspection deprecation //noinspection deprecation

View File

@@ -33,7 +33,6 @@ public class DozeFragment extends SetupFragment
private DozeView dozeView; private DozeView dozeView;
private HuaweiView huaweiView; private HuaweiView huaweiView;
private Button next; private Button next;
private ProgressBar progressBar;
private boolean secondAttempt = false; private boolean secondAttempt = false;
public static DozeFragment newInstance() { public static DozeFragment newInstance() {
@@ -58,11 +57,19 @@ public class DozeFragment extends SetupFragment
huaweiView = v.findViewById(R.id.huaweiView); huaweiView = v.findViewById(R.id.huaweiView);
huaweiView.setOnCheckedChangedListener(this); huaweiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next); next = v.findViewById(R.id.next);
progressBar = v.findViewById(R.id.progress); ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting); dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(this); next.setOnClickListener(this);
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
});
return v; return v;
} }
@@ -104,15 +111,6 @@ public class DozeFragment extends SetupFragment
@Override @Override
public void onClick(View view) { public void onClick(View view) {
setNextClicked();
viewModel.dozeExceptionConfirmed(); viewModel.dozeExceptionConfirmed();
} }
@Override
void setNextClicked() {
super.setNextClicked();
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
} }

View File

@@ -32,8 +32,10 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static android.app.AlarmManager.ELAPSED_REALTIME; import static android.app.AlarmManager.ELAPSED_REALTIME;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.app.PendingIntent.getService; import static android.app.PendingIntent.getService;
import static android.content.Context.ALARM_SERVICE; import static android.content.Context.ALARM_SERVICE;
import static android.os.Process.myPid;
import static android.os.SystemClock.elapsedRealtime; import static android.os.SystemClock.elapsedRealtime;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -75,23 +77,25 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
LockManagerImpl(Application app, SettingsManager settingsManager, LockManagerImpl(Application app, SettingsManager settingsManager,
AndroidNotificationManager notificationManager, AndroidNotificationManager notificationManager,
@DatabaseExecutor Executor dbExecutor) { @DatabaseExecutor Executor dbExecutor) {
this.appContext = app.getApplicationContext(); appContext = app.getApplicationContext();
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.notificationManager = notificationManager; this.notificationManager = notificationManager;
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.alarmManager = alarmManager =
(AlarmManager) appContext.getSystemService(ALARM_SERVICE); (AlarmManager) appContext.getSystemService(ALARM_SERVICE);
Intent i = Intent i =
new Intent(ACTION_LOCK, null, appContext, BriarService.class); new Intent(ACTION_LOCK, null, appContext, BriarService.class);
this.lockIntent = getService(appContext, 0, i, 0); i.putExtra(EXTRA_PID, myPid());
this.timeoutNever = Integer.valueOf( // When not using FLAG_UPDATE_CURRENT, the intent might have no extras
lockIntent = getService(appContext, 0, i, FLAG_UPDATE_CURRENT);
timeoutNever = Integer.parseInt(
appContext.getString(R.string.pref_lock_timeout_value_never)); appContext.getString(R.string.pref_lock_timeout_value_never));
this.timeoutDefault = Integer.valueOf( timeoutDefault = Integer.parseInt(
appContext.getString(R.string.pref_lock_timeout_value_default)); appContext.getString(R.string.pref_lock_timeout_value_default));
this.timeoutMinutes = timeoutNever; timeoutMinutes = timeoutNever;
// setting this in the constructor makes #getValue() @NonNull // setting this in the constructor makes #getValue() @NonNull
this.lockable.setValue(false); lockable.setValue(false);
} }
@Override @Override
@@ -148,7 +152,7 @@ public class LockManagerImpl implements LockManager, Service, EventListener {
boolean oldValue = lockable.getValue(); boolean oldValue = lockable.getValue();
boolean newValue = hasScreenLock(appContext) && lockableSetting; boolean newValue = hasScreenLock(appContext) && lockableSetting;
if (oldValue != newValue) { if (oldValue != newValue) {
this.lockable.setValue(newValue); lockable.setValue(newValue);
} }
} }

View File

@@ -38,7 +38,6 @@ public class SetPasswordFragment extends SetupFragment {
private TextInputEditText passwordConfirmation; private TextInputEditText passwordConfirmation;
private StrengthMeter strengthMeter; private StrengthMeter strengthMeter;
private Button nextButton; private Button nextButton;
private ProgressBar progressBar;
public static SetPasswordFragment newInstance() { public static SetPasswordFragment newInstance() {
return new SetPasswordFragment(); return new SetPasswordFragment();
@@ -64,7 +63,7 @@ public class SetPasswordFragment extends SetupFragment {
v.findViewById(R.id.password_confirm_wrapper); v.findViewById(R.id.password_confirm_wrapper);
passwordConfirmation = v.findViewById(R.id.password_confirm); passwordConfirmation = v.findViewById(R.id.password_confirm);
nextButton = v.findViewById(R.id.next); nextButton = v.findViewById(R.id.next);
progressBar = v.findViewById(R.id.progress); ProgressBar progressBar = v.findViewById(R.id.progress);
passwordEntry.addTextChangedListener(this); passwordEntry.addTextChangedListener(this);
passwordConfirmation.addTextChangedListener(this); passwordConfirmation.addTextChangedListener(this);
@@ -75,6 +74,17 @@ public class SetPasswordFragment extends SetupFragment {
passwordConfirmation.setImeOptions(IME_ACTION_DONE); passwordConfirmation.setImeOptions(IME_ACTION_DONE);
} }
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
// this also avoids the keyboard popping up
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
}
});
return v; return v;
} }
@@ -116,20 +126,6 @@ public class SetPasswordFragment extends SetupFragment {
IBinder token = passwordEntry.getWindowToken(); IBinder token = passwordEntry.getWindowToken();
Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); Object o = getContext().getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).hideSoftInputFromWindow(token, 0); ((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
setNextClicked();
viewModel.setPassword(passwordEntry.getText().toString()); viewModel.setPassword(passwordEntry.getText().toString());
} }
@Override
void setNextClicked() {
super.setNextClicked();
passwordEntry.setFocusable(false);
passwordConfirmation.setFocusable(false);
if (!viewModel.needToShowDozeFragment()) {
nextButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
}
} }

View File

@@ -7,7 +7,6 @@ import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
@@ -19,8 +18,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -35,7 +32,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener { OnEditorActionListener, OnClickListener {
private final static String STATE_KEY_CLICKED = "setupFragmentClicked"; private final static String STATE_KEY_CLICKED = "setupFragmentClicked";
private boolean clicked = false;
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -48,27 +44,6 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
.get(SetupViewModel.class); .get(SetupViewModel.class);
} }
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
clicked = savedInstanceState.getBoolean(STATE_KEY_CLICKED);
}
if (clicked) {
setNextClicked();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_KEY_CLICKED, clicked);
}
@CallSuper
void setNextClicked() {
this.clicked = true;
}
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu); inflater.inflate(R.menu.help_action, menu);
@@ -114,5 +89,4 @@ abstract class SetupFragment extends BaseFragment implements TextWatcher,
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
// noop // noop
} }
} }

View File

@@ -17,6 +17,8 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME; import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
@@ -36,6 +38,8 @@ class SetupViewModel extends AndroidViewModel {
@Nullable @Nullable
private String authorName, password; private String authorName, password;
private final MutableLiveEvent<State> state = new MutableLiveEvent<>(); private final MutableLiveEvent<State> state = new MutableLiveEvent<>();
private final MutableLiveData<Boolean> isCreatingAccount =
new MutableLiveData<>(false);
private final AccountManager accountManager; private final AccountManager accountManager;
private final Executor ioExecutor; private final Executor ioExecutor;
@@ -67,6 +71,10 @@ class SetupViewModel extends AndroidViewModel {
return state; return state;
} }
LiveData<Boolean> getIsCreatingAccount() {
return isCreatingAccount;
}
void setAuthorName(String authorName) { void setAuthorName(String authorName) {
this.authorName = authorName; this.authorName = authorName;
state.setEvent(SET_PASSWORD); state.setEvent(SET_PASSWORD);
@@ -97,6 +105,7 @@ class SetupViewModel extends AndroidViewModel {
private void createAccount() { private void createAccount() {
if (authorName == null) throw new IllegalStateException(); if (authorName == null) throw new IllegalStateException();
if (password == null) throw new IllegalStateException(); if (password == null) throw new IllegalStateException();
isCreatingAccount.setValue(true);
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
if (accountManager.createAccount(authorName, password)) { if (accountManager.createAccount(authorName, password)) {
LOG.info("Created account"); LOG.info("Created account");

View File

@@ -77,7 +77,7 @@ public class UnlockActivity extends BaseActivity {
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, protected void onActivityResult(int requestCode, int resultCode,
Intent data) { @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_KEYGUARD_UNLOCK) { if (requestCode == REQUEST_KEYGUARD_UNLOCK) {
if (resultCode == RESULT_OK) unlock(); if (resultCode == RESULT_OK) unlock();

View File

@@ -109,6 +109,7 @@ public abstract class BaseActivity extends AppCompatActivity
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
super.attachBaseContext( super.attachBaseContext(
Localizer.getInstance().setLocale(base)); Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
} }
public ActivityComponent getActivityComponent() { public ActivityComponent getActivityComponent() {

View File

@@ -34,7 +34,6 @@ import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener; import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -102,7 +101,8 @@ public class ContactListFragment extends BaseFragment
.observe(getViewLifecycleOwner(), result -> { .observe(getViewLifecycleOwner(), result -> {
result.onError(this::handleException).onSuccess(items -> { result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items); adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData(); // TODO remove when BriarRecyclerView was adapted
list.showData();
}); });
}); });
viewModel.getHasPendingContacts() viewModel.getHasPendingContacts()

View File

@@ -124,7 +124,7 @@ public class AddContactViewModel extends DbViewModel {
return addContactResult; return addContactResult;
} }
public void updatePendingContact(String name, PendingContact p) { void updatePendingContact(String name, PendingContact p) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
contactManager.removePendingContact(p.getId()); contactManager.removePendingContact(p.getId());

View File

@@ -29,11 +29,11 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AlertDialog.Builder;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
@@ -48,6 +48,7 @@ import static org.briarproject.briar.android.util.UiUtils.getDialogIcon;
public class NicknameFragment extends BaseFragment { public class NicknameFragment extends BaseFragment {
private static final String TAG = NicknameFragment.class.getName(); private static final String TAG = NicknameFragment.class.getName();
private static final String SAVED_LINK = "savedLink";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -67,6 +68,20 @@ public class NicknameFragment extends BaseFragment {
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(AddContactViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
// When the activity (and the ViewModel) get destroyed,
// the link will not be available anymore and needs to be restored.
// TODO migrate to SavedStateViewModelFactory (once we can use it)
String savedLink = savedInstanceState.getString(SAVED_LINK);
if (savedLink != null) viewModel.setRemoteHandshakeLink(savedLink);
}
} }
@Nullable @Nullable
@@ -74,14 +89,9 @@ public class NicknameFragment extends BaseFragment {
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
if (getActivity() == null || getContext() == null) return null;
View v = inflater.inflate(R.layout.fragment_nickname, View v = inflater.inflate(R.layout.fragment_nickname,
container, false); container, false);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(AddContactViewModel.class);
contactNameLayout = v.findViewById(R.id.contactNameLayout); contactNameLayout = v.findViewById(R.id.contactNameLayout);
contactNameInput = v.findViewById(R.id.contactNameInput); contactNameInput = v.findViewById(R.id.contactNameInput);
@@ -93,6 +103,12 @@ public class NicknameFragment extends BaseFragment {
return v; return v;
} }
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(SAVED_LINK, viewModel.getRemoteHandshakeLink());
}
@Nullable @Nullable
private String getNicknameOrNull() { private String getNicknameOrNull() {
Editable text = contactNameInput.getText(); Editable text = contactNameInput.getText();

View File

@@ -24,10 +24,7 @@ import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.PullDownLayout; import org.briarproject.briar.android.view.PullDownLayout;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
@@ -293,13 +290,10 @@ public class ImageActivity extends BriarActivity
@RequiresApi(api = 19) @RequiresApi(api = 19)
private Intent getCreationIntent() { private Intent getCreationIntent() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
Locale.getDefault());
String fileName = sdf.format(new Date());
Intent intent = new Intent(ACTION_CREATE_DOCUMENT); Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addCategory(CATEGORY_OPENABLE); intent.addCategory(CATEGORY_OPENABLE);
intent.setType(getVisibleAttachment().getMimeType()); intent.setType(getVisibleAttachment().getMimeType());
intent.putExtra(EXTRA_TITLE, fileName); intent.putExtra(EXTRA_TITLE, viewModel.getFileName());
return intent; return intent;
} }

View File

@@ -225,8 +225,8 @@ public class ImageViewModel extends DbViewModel implements EventListener {
}); });
} }
private String getFileName() { String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd",
Locale.getDefault()); Locale.getDefault());
return sdf.format(new Date()); return sdf.format(new Date());
} }

View File

@@ -10,9 +10,8 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext; import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -47,13 +46,11 @@ public abstract class BaseFragment extends Fragment
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { if (item.getItemId() == android.R.id.home) {
case android.R.id.home: requireActivity().onBackPressed();
listener.onBackPressed(); return true;
return true;
default:
return super.onOptionsItemSelected(item);
} }
return super.onOptionsItemSelected(item);
} }
@UiThread @UiThread
@@ -79,6 +76,7 @@ public abstract class BaseFragment extends Fragment
void handleException(Exception e); void handleException(Exception e);
} }
@Deprecated
@CallSuper @CallSuper
@Override @Override
public void runOnUiThreadUnlessDestroyed(Runnable r) { public void runOnUiThreadUnlessDestroyed(Runnable r) {

View File

@@ -411,6 +411,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@UiThread @UiThread
public void onRequestPermissionsResult(int requestCode, public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) { String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION) if (requestCode != REQUEST_PERMISSION_CAMERA_LOCATION)
throw new AssertionError(); throw new AssertionError();
if (gotPermission(CAMERA, permissions, grantResults)) { if (gotPermission(CAMERA, permissions, grantResults)) {

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.logging.Formatter; import java.util.logging.Formatter;
@@ -17,6 +18,16 @@ import static java.util.Locale.US;
@NotNullByDefault @NotNullByDefault
public class BriefLogFormatter extends Formatter { public class BriefLogFormatter extends Formatter {
public static String formatLog(Formatter formatter,
Collection<LogRecord> logRecords) {
StringBuilder sb = new StringBuilder();
for (LogRecord record : logRecords) {
String formatted = formatter.format(record);
sb.append(formatted).append('\n');
}
return sb.toString();
}
private final Object lock = new Object(); private final Object lock = new Object();
private final DateFormat dateFormat; // Locking: lock private final DateFormat dateFormat; // Locking: lock
private final Date date; // Locking: lock private final Date date; // Locking: lock

View File

@@ -21,6 +21,10 @@ public class CachingLogHandler extends Handler {
// Locking: lock // Locking: lock
private final Queue<LogRecord> recent = new LinkedList<>(); private final Queue<LogRecord> recent = new LinkedList<>();
// package-private constructor
CachingLogHandler() {
}
@Override @Override
public void publish(LogRecord record) { public void publish(LogRecord record) {
synchronized (lock) { synchronized (lock) {

View File

@@ -0,0 +1,16 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import androidx.annotation.Nullable;
@NotNullByDefault
public interface LogDecrypter {
/**
* Returns decrypted log records from {@link AndroidUtils#getLogcatFile}
* or null if there was an error reading the logs.
*/
@Nullable
String decryptLogs(@Nullable byte[] logKey);
}

View File

@@ -0,0 +1,61 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class LogDecrypterImpl implements LogDecrypter {
private static final Logger LOG =
getLogger(LogDecrypterImpl.class.getName());
private final DevConfig devConfig;
private final StreamReaderFactory streamReaderFactory;
@Inject
LogDecrypterImpl(DevConfig devConfig,
StreamReaderFactory streamReaderFactory) {
this.devConfig = devConfig;
this.streamReaderFactory = streamReaderFactory;
}
@Nullable
@Override
public String decryptLogs(@Nullable byte[] logKey) {
if (logKey == null) return null;
SecretKey key = new SecretKey(logKey);
File logFile = devConfig.getLogcatFile();
try (InputStream in = new FileInputStream(logFile)) {
InputStream reader =
streamReaderFactory.createLogStreamReader(in, key);
Scanner s = new Scanner(reader);
StringBuilder sb = new StringBuilder();
while (s.hasNextLine()) sb.append(s.nextLine()).append("\n");
s.close();
return sb.toString();
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
} finally {
//noinspection ResultOfMethodCallIgnored
logFile.delete();
}
}
}

View File

@@ -0,0 +1,16 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.AndroidUtils;
import androidx.annotation.Nullable;
@NotNullByDefault
public interface LogEncrypter {
/**
* Writes encrypted log records to {@link AndroidUtils#getLogcatFile}
* and returns the encryption key if everything went fine.
*/
@Nullable
byte[] encryptLogs();
}

View File

@@ -0,0 +1,77 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class LogEncrypterImpl implements LogEncrypter {
private static final Logger LOG =
getLogger(LogEncrypterImpl.class.getName());
private final DevConfig devConfig;
private final CachingLogHandler logHandler;
private final CryptoComponent crypto;
private final StreamWriterFactory streamWriterFactory;
@Inject
LogEncrypterImpl(DevConfig devConfig,
CachingLogHandler logHandler,
CryptoComponent crypto,
StreamWriterFactory streamWriterFactory) {
this.devConfig = devConfig;
this.logHandler = logHandler;
this.crypto = crypto;
this.streamWriterFactory = streamWriterFactory;
}
@Nullable
@Override
public byte[] encryptLogs() {
SecretKey logKey = crypto.generateSecretKey();
File logFile = devConfig.getLogcatFile();
try (OutputStream out = new FileOutputStream(logFile)) {
StreamWriter streamWriter =
streamWriterFactory.createLogStreamWriter(out, logKey);
Writer writer =
new OutputStreamWriter(streamWriter.getOutputStream());
writeLogString(writer);
writer.close();
return logKey.getBytes();
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
private void writeLogString(Writer writer) throws IOException {
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : logHandler.getRecentLogRecords()) {
String formatted = formatter.format(record);
writer.append(formatted).append('\n');
}
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.android.logging;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class LoggingModule {
@Provides
@Singleton
CachingLogHandler provideCachingLogHandler() {
return new CachingLogHandler();
}
@Provides
@Singleton
LogEncrypter provideLogEncrypter(LogEncrypterImpl logEncrypter) {
return logEncrypter;
}
@Provides
@Singleton
LogDecrypter provideLogDecrypter(LogDecrypterImpl logDecrypter) {
return logDecrypter;
}
}

View File

@@ -49,14 +49,12 @@ import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
@@ -67,8 +65,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static androidx.core.view.GravityCompat.START; import static androidx.core.view.GravityCompat.START;
import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static androidx.drawerlayout.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE; import static androidx.lifecycle.Lifecycle.State.STARTED;
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.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
@@ -145,7 +142,7 @@ public class NavDrawerActivity extends BriarActivity implements
if (ask) showDozeDialog(getString(R.string.setup_doze_intro)); if (ask) showDozeDialog(getString(R.string.setup_doze_intro));
}); });
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = setUpCustomToolbar(false);
drawerLayout = findViewById(R.id.drawer_layout); drawerLayout = findViewById(R.id.drawer_layout);
navigation = findViewById(R.id.navigation); navigation = findViewById(R.id.navigation);
GridView transportsView = findViewById(R.id.transportsView); GridView transportsView = findViewById(R.id.transportsView);
@@ -155,11 +152,6 @@ public class NavDrawerActivity extends BriarActivity implements
startActivity(new Intent(this, TransportsActivity.class)); startActivity(new Intent(this, TransportsActivity.class));
}); });
setSupportActionBar(toolbar);
ActionBar actionBar = requireNonNull(getSupportActionBar());
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.nav_drawer_open_description, R.string.nav_drawer_open_description,
R.string.nav_drawer_close_description) { R.string.nav_drawer_close_description) {
@@ -183,9 +175,6 @@ public class NavDrawerActivity extends BriarActivity implements
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
showSignOutFragment(); showSignOutFragment();
} else if (state == null) {
startFragment(ContactListFragment.newInstance(),
R.id.nav_btn_contacts);
} }
if (state == null) { if (state == null) {
// do not call this again when there's existing state // do not call this again when there's existing state
@@ -275,7 +264,6 @@ public class NavDrawerActivity extends BriarActivity implements
@Override @Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) { public boolean onNavigationItemSelected(@NonNull MenuItem item) {
drawerLayout.closeDrawer(START); drawerLayout.closeDrawer(START);
clearBackStack();
if (item.getItemId() == R.id.nav_btn_lock) { if (item.getItemId() == R.id.nav_btn_lock) {
lockManager.setLocked(true); lockManager.setLocked(true);
ActivityCompat.finishAfterTransition(this); ActivityCompat.finishAfterTransition(this);
@@ -295,8 +283,14 @@ public class NavDrawerActivity extends BriarActivity implements
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentByTag(SignOutFragment.TAG) != null) { if (fm.findFragmentByTag(SignOutFragment.TAG) != null) {
finish(); finish();
} else if (fm.getBackStackEntryCount() == 0 } else if (fm.getBackStackEntryCount() == 0 &&
&& fm.findFragmentByTag(ContactListFragment.TAG) == null) { fm.findFragmentByTag(ContactListFragment.TAG) == null) {
// don't start fragments in the wrong part of lifecycle (#1904)
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) {
LOG.warning("Tried to start contacts fragment in state " +
getLifecycle().getCurrentState().name());
return;
}
/* /*
* This makes sure that the first fragment (ContactListFragment) the * This makes sure that the first fragment (ContactListFragment) the
* user sees is the same as the last fragment the user sees before * user sees is the same as the last fragment the user sees before
@@ -339,30 +333,12 @@ public class NavDrawerActivity extends BriarActivity implements
startFragment(fragment); startFragment(fragment);
} }
private void startFragment(BaseFragment fragment) { private void startFragment(BaseFragment f) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) getSupportFragmentManager().beginTransaction()
startFragment(fragment, false); .setCustomAnimations(R.anim.fade_in, R.anim.fade_out,
else startFragment(fragment, true); R.anim.fade_in, R.anim.fade_out)
} .replace(R.id.fragmentContainer, f, f.getUniqueTag())
.commit();
private void startFragment(BaseFragment fragment,
boolean isAddedToBackStack) {
FragmentTransaction trans =
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fade_in,
R.anim.fade_out, R.anim.fade_in,
R.anim.fade_out)
.replace(R.id.fragmentContainer, fragment,
fragment.getUniqueTag());
if (isAddedToBackStack) {
trans.addToBackStack(fragment.getUniqueTag());
}
trans.commit();
}
private void clearBackStack() {
getSupportFragmentManager().popBackStackImmediate(null,
POP_BACK_STACK_INCLUSIVE);
} }
@Override @Override

View File

@@ -79,9 +79,10 @@ public class GroupActivity extends
// start with group disabled and enable when not dissolved // start with group disabled and enable when not dissolved
setGroupEnabled(false); setGroupEnabled(false);
viewModel.isDissolved().observeEvent(this, dissolved -> { viewModel.isDissolved().observe(this, dissolved -> {
setGroupEnabled(!dissolved); setGroupEnabled(!dissolved);
if (dissolved) onGroupDissolved(); // only show dialog when no prior state
if (dissolved && state == null) onGroupDissolved();
}); });
} }
@@ -153,7 +154,7 @@ public class GroupActivity extends
@Override @Override
public void onReplyClick(GroupMessageItem item) { public void onReplyClick(GroupMessageItem item) {
Boolean isDissolved = viewModel.isDissolved().getLastValue(); Boolean isDissolved = viewModel.isDissolved().getValue();
if (isDissolved != null && !isDissolved) super.onReplyClick(item); if (isDissolved != null && !isDissolved) super.onReplyClick(item);
} }

View File

@@ -22,8 +22,6 @@ import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.sharing.SharingController; import org.briarproject.briar.android.sharing.SharingController;
import org.briarproject.briar.android.threaded.ThreadListViewModel; import org.briarproject.briar.android.threaded.ThreadListViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker; import org.briarproject.briar.api.client.MessageTracker;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
@@ -71,8 +69,8 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
private final MutableLiveData<PrivateGroup> privateGroup = private final MutableLiveData<PrivateGroup> privateGroup =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>(); private final MutableLiveData<Boolean> isCreator = new MutableLiveData<>();
private final MutableLiveEvent<Boolean> isDissolved = private final MutableLiveData<Boolean> isDissolved =
new MutableLiveEvent<>(); new MutableLiveData<>();
@Inject @Inject
GroupViewModel(Application application, GroupViewModel(Application application,
@@ -129,7 +127,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} else if (e instanceof GroupDissolvedEvent) { } else if (e instanceof GroupDissolvedEvent) {
GroupDissolvedEvent g = (GroupDissolvedEvent) e; GroupDissolvedEvent g = (GroupDissolvedEvent) e;
if (g.getGroupId().equals(groupId)) { if (g.getGroupId().equals(groupId)) {
isDissolved.setEvent(true); isDissolved.setValue(true);
} }
} else { } else {
super.eventOccurred(e); super.eventOccurred(e);
@@ -164,7 +162,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
loadList(txn -> { loadList(txn -> {
// check first if group is dissolved // check first if group is dissolved
isDissolved isDissolved
.postEvent(privateGroupManager.isDissolved(txn, groupId)); .postValue(privateGroupManager.isDissolved(txn, groupId));
// now continue to load the items // now continue to load the items
long start = now(); long start = now();
List<GroupMessageHeader> headers = List<GroupMessageHeader> headers =
@@ -282,7 +280,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
return isCreator; return isCreator;
} }
LiveEvent<Boolean> isDissolved() { LiveData<Boolean> isDissolved() {
return isDissolved; return isDissolved;
} }

View File

@@ -1,29 +1,40 @@
package org.briarproject.briar.android.reporting; package org.briarproject.briar.android.reporting;
import android.content.Context; import android.app.Application;
import android.os.Process; import android.os.Process;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.logging.LogEncrypter;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import javax.inject.Inject;
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity; import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
@NotNullByDefault @NotNullByDefault
public class BriarExceptionHandler implements UncaughtExceptionHandler { class BriarExceptionHandler implements UncaughtExceptionHandler {
private final Context ctx; private final Application app;
private final LogEncrypter logEncrypter;
private final long appStartTime; private final long appStartTime;
public BriarExceptionHandler(Context ctx) { @Inject
this.ctx = ctx; BriarExceptionHandler(Application app, LogEncrypter logEncrypter) {
this.appStartTime = System.currentTimeMillis(); this.app = app;
this.logEncrypter = logEncrypter;
appStartTime = System.currentTimeMillis();
} }
@Override @Override
public void uncaughtException(Thread t, Throwable e) { public void uncaughtException(Thread t, Throwable e) {
// encrypt logs to disk before handing over to new process
// the intent has limited space, so we can't reliably store them there.
byte[] logKey = logEncrypter.encryptLogs();
// activity runs in its own process, so we can kill the old one // activity runs in its own process, so we can kill the old one
startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime); startDevReportActivity(app.getApplicationContext(),
CrashReportActivity.class, e, appStartTime, logKey);
Process.killProcess(Process.myPid()); Process.killProcess(Process.myPid());
System.exit(10); System.exit(10);
} }

View File

@@ -25,8 +25,6 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.BuildConfig; import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo; import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem; import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo; import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
@@ -41,8 +39,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@@ -74,8 +70,8 @@ class BriarReportCollector {
this.ctx = ctx; this.ctx = ctx;
} }
public ReportData collectReportData(@Nullable Throwable t, ReportData collectReportData(@Nullable Throwable t, long appStartTime,
long appStartTime) { String logs) {
ReportData reportData = new ReportData() ReportData reportData = new ReportData()
.add(getBasicInfo(t)) .add(getBasicInfo(t))
.add(getDeviceInfo()); .add(getDeviceInfo());
@@ -86,7 +82,7 @@ class BriarReportCollector {
.add(getStorage()) .add(getStorage())
.add(getConnectivity()) .add(getConnectivity())
.add(getBuildConfig()) .add(getBuildConfig())
.add(getLogcat()) .add(getLogcat(logs))
.add(getDeviceFeatures()); .add(getDeviceFeatures());
} }
@@ -313,15 +309,8 @@ class BriarReportCollector {
buildConfig); buildConfig);
} }
private ReportItem getLogcat() { private ReportItem getLogcat(String logs) {
BriarApplication app = (BriarApplication) ctx.getApplicationContext(); return new ReportItem("Logcat", R.string.dev_report_logcat, logs);
StringBuilder sb = new StringBuilder();
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : app.getRecentLogRecords()) {
sb.append(formatter.format(record)).append('\n');
}
return new ReportItem("Logcat", R.string.dev_report_logcat,
sb.toString());
} }
private ReportItem getDeviceFeatures() { private ReportItem getDeviceFeatures() {

View File

@@ -35,6 +35,7 @@ public class CrashReportActivity extends BaseActivity
public static final String EXTRA_THROWABLE = "throwable"; public static final String EXTRA_THROWABLE = "throwable";
public static final String EXTRA_APP_START_TIME = "appStartTime"; public static final String EXTRA_APP_START_TIME = "appStartTime";
public static final String EXTRA_APP_LOGCAT = "logcat";
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -56,7 +57,8 @@ public class CrashReportActivity extends BaseActivity
Intent intent = getIntent(); Intent intent = getIntent();
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE); Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1); long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
viewModel.init(t, appStartTime); byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT);
viewModel.init(t, appStartTime, logKey);
viewModel.getShowReport().observeEvent(this, show -> { viewModel.getShowReport().observeEvent(this, show -> {
if (show) displayFragment(true); if (show) displayFragment(true);
}); });

View File

@@ -15,4 +15,8 @@ public abstract class DevReportModule {
@ViewModelKey(ReportViewModel.class) @ViewModelKey(ReportViewModel.class)
abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel); abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel);
@Binds
abstract Thread.UncaughtExceptionHandler bindUncaughtExceptionHandler(
BriarExceptionHandler handler);
} }

View File

@@ -11,6 +11,9 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.logging.LogDecrypter;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo; import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
@@ -19,6 +22,7 @@ import org.json.JSONException;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Formatter;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
@@ -36,13 +40,16 @@ import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
@NotNullByDefault @NotNullByDefault
public class ReportViewModel extends AndroidViewModel { class ReportViewModel extends AndroidViewModel {
private static final Logger LOG = private static final Logger LOG =
getLogger(ReportViewModel.class.getName()); getLogger(ReportViewModel.class.getName());
private final CachingLogHandler logHandler;
private final LogDecrypter logDecrypter;
private final BriarReportCollector collector; private final BriarReportCollector collector;
private final DevReporter reporter; private final DevReporter reporter;
private final PluginManager pluginManager; private final PluginManager pluginManager;
@@ -58,18 +65,39 @@ public class ReportViewModel extends AndroidViewModel {
private boolean isFeedback; private boolean isFeedback;
@Inject @Inject
public ReportViewModel(@NonNull Application application, ReportViewModel(@NonNull Application application,
DevReporter reporter, PluginManager pluginManager) { CachingLogHandler logHandler,
LogDecrypter logDecrypter,
DevReporter reporter,
PluginManager pluginManager) {
super(application); super(application);
this.collector = new BriarReportCollector(application); collector = new BriarReportCollector(application);
this.logHandler = logHandler;
this.logDecrypter = logDecrypter;
this.reporter = reporter; this.reporter = reporter;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
} }
void init(@Nullable Throwable t, long appStartTime) { void init(@Nullable Throwable t, long appStartTime,
@Nullable byte[] logKey) {
isFeedback = t == null; isFeedback = t == null;
if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> { if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> {
ReportData data = collector.collectReportData(t, appStartTime); String decryptedLogs;
if (isFeedback) {
Formatter formatter = new BriefLogFormatter();
decryptedLogs =
formatLog(formatter, logHandler.getRecentLogRecords());
} else {
decryptedLogs = logDecrypter.decryptLogs(logKey);
if (decryptedLogs == null) {
// error decrypting logs, get logs from this process
Formatter formatter = new BriefLogFormatter();
decryptedLogs = formatLog(formatter,
logHandler.getRecentLogRecords());
}
}
ReportData data =
collector.collectReportData(t, appStartTime, decryptedLogs);
reportData.postValue(data); reportData.postValue(data);
}).start(); }).start();
} }
@@ -110,8 +138,8 @@ public class ReportViewModel extends AndroidViewModel {
} }
/** /**
* The content of the report * The content of the report that will be loaded after
* that will be loaded after {@link #init(Throwable, long)} was called. * {@link #init(Throwable, long, byte[])} was called.
*/ */
LiveData<ReportData> getReportData() { LiveData<ReportData> getReportData() {
return reportData; return reportData;

View File

@@ -9,10 +9,13 @@ import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import javax.inject.Inject; import javax.inject.Inject;
@@ -32,7 +35,7 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
@Inject @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private SettingsViewModel settingsViewModel; private SettingsViewModel viewModel;
private static final String ARG_URI = "uri"; private static final String ARG_URI = "uri";
private Uri uri; private Uri uri;
@@ -51,6 +54,9 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
public void onAttach(Context ctx) { public void onAttach(Context ctx) {
super.onAttach(ctx); super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this); ((BaseActivity) requireActivity()).getActivityComponent().inject(this);
ViewModelProvider provider =
new ViewModelProvider(requireActivity(), viewModelFactory);
viewModel = provider.get(SettingsViewModel.class);
} }
@Override @Override
@@ -60,32 +66,34 @@ public class ConfirmAvatarDialogFragment extends DialogFragment {
uri = Uri.parse(argUri); uri = Uri.parse(argUri);
FragmentActivity activity = requireActivity(); FragmentActivity activity = requireActivity();
LayoutInflater inflater = LayoutInflater.from(activity);
ViewModelProvider provider =
new ViewModelProvider(activity, viewModelFactory);
settingsViewModel = provider.get(SettingsViewModel.class);
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
LayoutInflater inflater = LayoutInflater.from(getContext());
final View view = final View view =
inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null); inflater.inflate(R.layout.fragment_confirm_avatar_dialog, null);
builder.setView(view);
builder.setTitle(R.string.dialog_confirm_profile_picture_title);
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.change,
(dialog, id) -> settingsViewModel.setAvatar(uri));
ImageView imageView = view.findViewById(R.id.image); ImageView imageView = view.findViewById(R.id.image);
imageView.setImageURI(uri);
TextView textViewUserName = view.findViewById(R.id.username); TextView textViewUserName = view.findViewById(R.id.username);
settingsViewModel.getOwnIdentityInfo().observe(activity,
us -> textViewUserName.setText(us.getLocalAuthor().getName()));
return builder.create(); GlideApp.with(imageView)
.load(uri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.ic_image_broken)
.into(imageView)
.waitForLayout();
// we can't use getViewLifecycleOwner() here
// as this fragment technically doesn't have a view
viewModel.getOwnIdentityInfo().observe(activity, us ->
textViewUserName.setText(us.getLocalAuthor().getName())
);
int theme = R.style.BriarDialogTheme;
return new AlertDialog.Builder(activity, theme)
.setView(view)
.setTitle(R.string.dialog_confirm_profile_picture_title)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.change, (d, id) ->
viewModel.setAvatar(uri)
)
.create();
} }
} }

View File

@@ -79,7 +79,7 @@ class SettingsViewModel extends AndroidViewModel {
return ownIdentityInfo; return ownIdentityInfo;
} }
public LiveEvent<Boolean> getSetAvatarFailed() { LiveEvent<Boolean> getSetAvatarFailed() {
return setAvatarFailed; return setAvatarFailed;
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.splash; package org.briarproject.briar.android.splash;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@@ -7,6 +8,7 @@ import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@@ -27,6 +29,13 @@ public class ExpiredActivity extends AppCompatActivity
findViewById(R.id.download_briar_button).setOnClickListener(this); findViewById(R.id.download_briar_button).setOnClickListener(this);
} }
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
Localizer.getInstance().setLocale(this);
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Uri uri = Uri.parse("https://briarproject.org/download.html"); Uri uri = Uri.parse("https://briarproject.org/download.html");

View File

@@ -197,6 +197,10 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
*/ */
@UiThread @UiThread
protected void addItem(I item, boolean scrollToItem) { protected void addItem(I item, boolean scrollToItem) {
// If items haven't loaded, we need to wait until they have.
// Since this was a R/W DB transaction, the load will pick up this item.
if (items.getValue() == null) return;
messageTree.add(item); messageTree.add(item);
if (scrollToItem) this.scrollToItem.set(item.getId()); if (scrollToItem) this.scrollToItem.set(item.getId());
items.setValue(new LiveResult<>(messageTree.depthFirstOrder())); items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));

View File

@@ -97,6 +97,7 @@ import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes;
import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.BuildConfig.APPLICATION_ID;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME; import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME;
import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE; import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE;
@@ -357,16 +358,17 @@ public class UiUtils {
} }
public static void triggerFeedback(Context ctx) { public static void triggerFeedback(Context ctx) {
startDevReportActivity(ctx, FeedbackActivity.class, null, null); startDevReportActivity(ctx, FeedbackActivity.class, null, null, null);
} }
public static void startDevReportActivity(Context ctx, public static void startDevReportActivity(Context ctx,
Class<? extends FragmentActivity> activity, @Nullable Throwable t, Class<? extends FragmentActivity> activity, @Nullable Throwable t,
@Nullable Long appStartTime) { @Nullable Long appStartTime, @Nullable byte[] logKey) {
final Intent dialogIntent = new Intent(ctx, activity); final Intent dialogIntent = new Intent(ctx, activity);
dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
dialogIntent.putExtra(EXTRA_THROWABLE, t); dialogIntent.putExtra(EXTRA_THROWABLE, t);
dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime); dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime);
dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey);
ctx.startActivity(dialogIntent); ctx.startActivity(dialogIntent);
} }

View File

@@ -39,7 +39,8 @@ public interface AndroidNotificationManager {
String BLOG_CHANNEL_ID = "blogs"; String BLOG_CHANNEL_ID = "blogs";
// Channels are sorted by channel ID in the Settings app, so use IDs // Channels are sorted by channel ID in the Settings app, so use IDs
// that will sort below the main channels such as contacts // that will sort below the main channels such as contacts
String ONGOING_CHANNEL_ID = "zForegroundService"; String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
String ONGOING_CHANNEL_ID = "zForegroundService2";
String FAILURE_CHANNEL_ID = "zStartupFailure"; String FAILURE_CHANNEL_ID = "zStartupFailure";
String REMINDER_CHANNEL_ID = "zSignInReminder"; String REMINDER_CHANNEL_ID = "zSignInReminder";

View File

@@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
public interface LockManager { public interface LockManager {
String ACTION_LOCK = "lock"; String ACTION_LOCK = "lock";
String EXTRA_PID = "PID";
/** /**
* Stops the inactivity timer when the user interacts with the app. * Stops the inactivity timer when the user interacts with the app.

View File

@@ -52,16 +52,17 @@
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/close" android:contentDescription="@string/close"
android:scaleType="center" android:scaleType="center"
app:srcCompat="@drawable/ic_close"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_close"
app:tint="@color/briar_text_tertiary_inverse" /> app:tint="@color/briar_text_tertiary_inverse" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout <androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer" android:id="@+id/fragmentContainer"
android:name="org.briarproject.briar.android.contact.ContactListFragment"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -112,7 +112,6 @@
android:textColor="@color/briar_primary" android:textColor="@color/briar_primary"
android:textIsSelectable="true" android:textIsSelectable="true"
android:textSize="18sp" android:textSize="18sp"
android:typeface="monospace"
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/yourLinkIcon" app:layout_constraintTop_toBottomOf="@+id/yourLinkIcon"

View File

@@ -139,10 +139,10 @@
<Space <Space
android:id="@+id/space" android:id="@+id/space"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="wrap_content"
app:layout_constrainedHeight="true"
app:layout_constraintBottom_toTopOf="@+id/addButton" app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_default="wrap"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" /> app:layout_constraintTop_toBottomOf="@+id/contactNameLayout" />

View File

@@ -5,27 +5,28 @@
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item <item
android:id="@+id/nav_btn_contacts" android:id="@+id/nav_btn_contacts"
android:checked="true"
android:icon="@drawable/ic_contacts" android:icon="@drawable/ic_contacts"
android:title="@string/contact_list_button"/> android:title="@string/contact_list_button" />
<item <item
android:id="@+id/nav_btn_groups" android:id="@+id/nav_btn_groups"
android:icon="@drawable/ic_group" android:icon="@drawable/ic_group"
android:title="@string/groups_button"/> android:title="@string/groups_button" />
<item <item
android:id="@+id/nav_btn_forums" android:id="@+id/nav_btn_forums"
android:icon="@drawable/ic_forums_black_24dp" android:icon="@drawable/ic_forums_black_24dp"
android:title="@string/forums_button"/> android:title="@string/forums_button" />
<item <item
android:id="@+id/nav_btn_blogs" android:id="@+id/nav_btn_blogs"
android:icon="@drawable/blogs" android:icon="@drawable/blogs"
android:title="@string/blogs_button"/> android:title="@string/blogs_button" />
</group> </group>
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item <item
android:id="@+id/nav_btn_settings" android:id="@+id/nav_btn_settings"
android:icon="@drawable/ic_settings_black" android:icon="@drawable/ic_settings_black"
android:title="@string/settings_button"/> android:title="@string/settings_button" />
<item <item
android:id="@+id/nav_btn_lock" android:id="@+id/nav_btn_lock"
android:icon="@drawable/startup_lock" android:icon="@drawable/startup_lock"
@@ -35,7 +36,7 @@
<item <item
android:id="@+id/nav_btn_signout" android:id="@+id/nav_btn_signout"
android:icon="@drawable/ic_signout" android:icon="@drawable/ic_signout"
android:title="@string/sign_out_button"/> android:title="@string/sign_out_button" />
</group> </group>
</menu> </menu>

View File

@@ -67,8 +67,11 @@
<string name="lock_button">قفل کردن برنامه</string> <string name="lock_button">قفل کردن برنامه</string>
<string name="settings_button">تنظیمات</string> <string name="settings_button">تنظیمات</string>
<string name="sign_out_button">خروج</string> <string name="sign_out_button">خروج</string>
<string name="transports_onboarding_text">برای کنترل چگونگی اتصال Briar (برایر) به مخاطبین خود، اینجا را لمس کنید.</string>
<!--Transports: Tor--> <!--Transports: Tor-->
<string name="transport_tor">اینترنت</string> <string name="transport_tor">اینترنت</string>
<string name="tor_device_status_online_wifi">تلفن شما از طریق Wi-Fi به اینترنت دسترسی دارد.</string>
<string name="tor_device_status_online_mobile">تلفن شما از طریق دیتا سیمکارت به اینترنت دسترسی دارد.</string>
<string name="tor_device_status_offline">تلفن شما دارای دسترسی اینترنتی نیست</string> <string name="tor_device_status_offline">تلفن شما دارای دسترسی اینترنتی نیست</string>
<string name="tor_plugin_status_enabling">Briar در حال اتصال به اینترنت می باشد</string> <string name="tor_plugin_status_enabling">Briar در حال اتصال به اینترنت می باشد</string>
<string name="tor_plugin_status_active">Briar به اینترنت متصل شد</string> <string name="tor_plugin_status_active">Briar به اینترنت متصل شد</string>
@@ -462,6 +465,10 @@
برای وارد کردن خوراک روی آیکون + ضربه بزنید</string> برای وارد کردن خوراک روی آیکون + ضربه بزنید</string>
<string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string> <string name="blogs_rss_feeds_manage_error">مشکلی با بارگذاری فیدهای شما وجود داشت. لطفا بعدا امتحان کنید.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">برای تغییر تصویر نمایه خود اینجا را لمس کنید.</string>
<string name="dialog_confirm_profile_picture_title">تغییر تصویر نمایه</string>
<string name="dialog_confirm_profile_picture_remark">تنها مخاطبین شما می‌توانند تصویر نمایه شما را مشاهده کنند.</string>
<string name="change_profile_picture_failed_message">تاسفیم اما هنگام بروزرسانی تصویر نمایه شما مشکلی رخ داد.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">زبان و منطقه</string> <string name="pref_language_title">زبان و منطقه</string>
<string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را دوباره راه اندازی کنید.</string> <string name="pref_language_changed">این تنظیمات زمانی که Briar (برایر) را ری استارت کنید تاثیر خود را می گذارند. لطفا خارج شوید و Briar (برایر) را دوباره راه اندازی کنید.</string>
@@ -571,17 +578,20 @@
<string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string> <string name="include_debug_report_feedback">قرار دادن داده های ناشناس درباره این دستگاه</string>
<string name="dev_report_basic_info">اطلاعات پایه</string> <string name="dev_report_basic_info">اطلاعات پایه</string>
<string name="dev_report_device_info">اطلاعات دستگاه</string> <string name="dev_report_device_info">اطلاعات دستگاه</string>
<string name="dev_report_stacktrace">Stacktrace</string>
<string name="dev_report_time_info">اطلاعات زمانی</string> <string name="dev_report_time_info">اطلاعات زمانی</string>
<string name="dev_report_memory">حافظه</string> <string name="dev_report_memory">حافظه</string>
<string name="dev_report_storage">حافظه</string> <string name="dev_report_storage">حافظه</string>
<string name="dev_report_connectivity">اتصال</string> <string name="dev_report_connectivity">اتصال</string>
<string name="dev_report_build_config">پیکربندی ساخت</string> <string name="dev_report_build_config">پیکربندی ساخت</string>
<string name="dev_report_logcat">لاگ برنامه</string>
<string name="dev_report_device_features">ویژگی‌های دستگاه</string> <string name="dev_report_device_features">ویژگی‌های دستگاه</string>
<string name="send_report">ارسال گزارش</string> <string name="send_report">ارسال گزارش</string>
<string name="close">بستن</string> <string name="close">بستن</string>
<string name="dev_report_sending">در حال فرستادن نظر...</string> <string name="dev_report_sending">در حال فرستادن نظر...</string>
<string name="dev_report_sent">بازخورد ارسال شد</string> <string name="dev_report_sent">بازخورد ارسال شد</string>
<string name="dev_report_saved">گزارش ذخیره شد. دفعه بعدی که وارد Briar (برایر) شدید فرستاده خواهد شد.</string> <string name="dev_report_saved">گزارش ذخیره شد. دفعه بعدی که وارد Briar (برایر) شدید فرستاده خواهد شد.</string>
<string name="dev_report_error">خطا در ارسال گزارش</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">خروج از Briar (برایر)...</string> <string name="progress_title_logout">خروج از Briar (برایر)...</string>
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
@@ -591,7 +601,9 @@
این برنامه ها ممکن است روی Briar (برایر) قرار گرفته باشند: این برنامه ها ممکن است روی Briar (برایر) قرار گرفته باشند:
%1$s</string> %1$s</string>
<string name="screen_filter_body_api_30">برنامه دیگری بر روی برنامه Briar (برایر) قرار دارد. برای محافظت از امنیت شما، Briar (برایر) هنگامی که برنامه دیگری روی آن باز است، به لمس پاسخ نخواهد داد. \n\nبرای یافتن برنامه مذکور، برنامه‌های زیر را بررسی کنید.</string>
<string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی Briar (برایر) قرار بگیرند</string> <string name="screen_filter_allow">به این برنامه ها اجازه بده تا روی Briar (برایر) قرار بگیرند</string>
<string name="screen_filter_review_apps">بررسی برنامه‌ها</string>
<!--Permission Requests--> <!--Permission Requests-->
<string name="permission_camera_title">دسترسی به دوربین</string> <string name="permission_camera_title">دسترسی به دوربین</string>
<string name="permission_camera_request_body">برای اسکن کردن کد کیوآر دسترسی به دوربین لازم است.</string> <string name="permission_camera_request_body">برای اسکن کردن کد کیوآر دسترسی به دوربین لازم است.</string>
@@ -608,6 +620,7 @@ Briar (برایر) موقعیت شما را ذخیره نمی‌کند و آن
<string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد. <string name="permission_camera_denied_body">شما دسترسی به دوربین را رد کرده اید، اما افزودن مخاطب نیاز به دوربین دارد.
لطفا اجازه دسترسی را بدهید.</string> لطفا اجازه دسترسی را بدهید.</string>
<string name="permission_location_denied_body">شما دسترسی به موقعیت خود را نداده‌اید اما Briar (برایر) برای یافتن دستگاه‌های بلوتوث نیاز به این دسترسی دارد.\n\nلطفا این دسترسی را فراهم کنید.</string>
<string name="qr_code">کد کیوآر</string> <string name="qr_code">کد کیوآر</string>
<string name="show_qr_code_fullscreen">نمایش کد کیوآر به صورت فول اسکرین</string> <string name="show_qr_code_fullscreen">نمایش کد کیوآر به صورت فول اسکرین</string>
<!--App Locking--> <!--App Locking-->
@@ -618,6 +631,7 @@ Briar (برایر) موقعیت شما را ذخیره نمی‌کند و آن
<string name="lock_is_locked">Briar (برایر) قفل می باشد</string> <string name="lock_is_locked">Briar (برایر) قفل می باشد</string>
<string name="lock_tap_to_unlock">برای آنلاک کردن کلیک کنید</string> <string name="lock_tap_to_unlock">برای آنلاک کردن کلیک کنید</string>
<!--Connections Screen--> <!--Connections Screen-->
<string name="transports_help_text">Briar (برایر) می‌تواند از طریق اینترنت، Wi-Fi و یا بلوتوث به مخاطبین شما متصل گردد.\n\nارتباط با اینترنت از طریق شبکه‌ی تور صورت می‌پذیرد.\n\nاگر دسترسی به مخاطب شما از روش‌های مختلفی ممکن باشد، Briar (برایر) به صورت موازی از آن‌ها استفاده خواهد کرد.</string>
<!--Screenshots--> <!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.--> <!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">آلیس</string> <string name="screenshot_alice">آلیس</string>

View File

@@ -425,6 +425,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Sen fontes RSS que mostrar\n\nToque na icona + para importar unha fonte</string> <string name="blogs_rss_feeds_manage_empty_state">Sen fontes RSS que mostrar\n\nToque na icona + para importar unha fonte</string>
<string name="blogs_rss_feeds_manage_error">Aconteceu un problema ao cargar as súas fontes. Por favor, inténteo máis tarde.</string> <string name="blogs_rss_feeds_manage_error">Aconteceu un problema ao cargar as súas fontes. Por favor, inténteo máis tarde.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Toca para cambiar a túa imaxe de perfil</string>
<string name="dialog_confirm_profile_picture_title">Mudar imaxe de perfil</string>
<string name="dialog_confirm_profile_picture_remark">Só os teus contactos poden ver a túa imaxe de perfil</string>
<string name="change_profile_picture_failed_message">Lamentámolo, pero algo fallou cando intentamos actualizar a túa imaxe de pefil</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Idioma &amp; rexión</string> <string name="pref_language_title">Idioma &amp; rexión</string>
<string name="pref_language_changed">Este axuste terá efecto cando reinicie Briar. Por favor desconecte e volte a iniciar Briar.</string> <string name="pref_language_changed">Este axuste terá efecto cando reinicie Briar. Por favor desconecte e volte a iniciar Briar.</string>

View File

@@ -449,6 +449,10 @@
<string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string> <string name="blogs_rss_feeds_manage_empty_state">אין הזנות RSS להראות\n\nהקש על הצלמית + כדי לייבא הזנה</string>
<string name="blogs_rss_feeds_manage_error">הייתה בעיה בטעינת ההזנות שלך. אנא נסה שוב מאוחר יותר.</string> <string name="blogs_rss_feeds_manage_error">הייתה בעיה בטעינת ההזנות שלך. אנא נסה שוב מאוחר יותר.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">הקש כדי לשנות את תמונת הפרופיל שלך</string>
<string name="dialog_confirm_profile_picture_title">שנה תמונת פרופיל</string>
<string name="dialog_confirm_profile_picture_remark">רק אנשי הקשר שלך יכולים לראות את תמונת הפרופיל שלך</string>
<string name="change_profile_picture_failed_message">אנו מצטערים משהו השתבש בעת עדכון תמונת הפרופיל שלך</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">שפה ואזור</string> <string name="pref_language_title">שפה ואזור</string>
<string name="pref_language_changed">הגדרה זו תיכנס לתוקף כשתפעיל מחדש את Briar. אנא התנתק והפעל מחדש את Briar.</string> <string name="pref_language_changed">הגדרה זו תיכנס לתוקף כשתפעיל מחדש את Briar. אנא התנתק והפעל מחדש את Briar.</string>
@@ -558,6 +562,7 @@
<string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string> <string name="include_debug_report_feedback">כלול נתונים אלמוניים לגבי מכשיר זה</string>
<string name="dev_report_basic_info">מידע בסיסי</string> <string name="dev_report_basic_info">מידע בסיסי</string>
<string name="dev_report_device_info">מידע מכשיר</string> <string name="dev_report_device_info">מידע מכשיר</string>
<string name="dev_report_stacktrace">מחסנית עקיבה (Stacktrace)</string>
<string name="dev_report_time_info">מידע זמן</string> <string name="dev_report_time_info">מידע זמן</string>
<string name="dev_report_memory">זיכרון</string> <string name="dev_report_memory">זיכרון</string>
<string name="dev_report_storage">אחסון</string> <string name="dev_report_storage">אחסון</string>

View File

@@ -425,6 +425,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Engin RSS-streymi til að birta\n\nÝttu á + táknið til að flytja inn streymi</string> <string name="blogs_rss_feeds_manage_empty_state">Engin RSS-streymi til að birta\n\nÝttu á + táknið til að flytja inn streymi</string>
<string name="blogs_rss_feeds_manage_error">Vandamál hefur komið upp með að hlaða inn streymunum þínum. Reyndu aftur síðar.</string> <string name="blogs_rss_feeds_manage_error">Vandamál hefur komið upp með að hlaða inn streymunum þínum. Reyndu aftur síðar.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Ýttu til að skipta um auðkennismyndina þína</string>
<string name="dialog_confirm_profile_picture_title">Skipta um auðkennismynd</string>
<string name="dialog_confirm_profile_picture_remark">Einungis tengiliðirnir þínir geta séð auðkennismyndina þína</string>
<string name="change_profile_picture_failed_message">Því miður, eitthvað fór úrskeiðis við að uppfæra auðkennismyndina þína.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Tungumál og landsvæði</string> <string name="pref_language_title">Tungumál og landsvæði</string>
<string name="pref_language_changed">Þessi stilling tekur gildi í næst þegar þú skráir þig inn í Briar. Skráðu þig út og endurræstu Briar.</string> <string name="pref_language_changed">Þessi stilling tekur gildi í næst þegar þú skráir þig inn í Briar. Skráðu þig út og endurræstu Briar.</string>

View File

@@ -425,6 +425,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Nessun feed RSS da mostrare\n\nClicca l\'icona + per importare un feed</string> <string name="blogs_rss_feeds_manage_empty_state">Nessun feed RSS da mostrare\n\nClicca l\'icona + per importare un feed</string>
<string name="blogs_rss_feeds_manage_error">C\'è stato un problema nel caricare i tuoi feeds. Per favore riprova fra poco.</string> <string name="blogs_rss_feeds_manage_error">C\'è stato un problema nel caricare i tuoi feeds. Per favore riprova fra poco.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Tocca per cambiare l\'immagine del profilo</string>
<string name="dialog_confirm_profile_picture_title">Cambia immagine profilo</string>
<string name="dialog_confirm_profile_picture_remark">Solo i tuoi contatti possono vedere l\'immagine del profilo</string>
<string name="change_profile_picture_failed_message">Spiacenti, qualcosa è andato storto aggiornando la tua foto del profilo.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Lingua &amp; regione</string> <string name="pref_language_title">Lingua &amp; regione</string>
<string name="pref_language_changed">Questa impostazione avrà effetto quando riavvierai Briar. Per favore, esci e riavvia Briar.</string> <string name="pref_language_changed">Questa impostazione avrà effetto quando riavvierai Briar. Per favore, esci e riavvia Briar.</string>

View File

@@ -425,6 +425,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Geen RSS-feeds om te tonen\n\nTik op het +-icoon om een feed te importeren</string> <string name="blogs_rss_feeds_manage_empty_state">Geen RSS-feeds om te tonen\n\nTik op het +-icoon om een feed te importeren</string>
<string name="blogs_rss_feeds_manage_error">Er was een probleem met het laden van je feeds. Probeer het alsjeblieft later nog een keer.</string> <string name="blogs_rss_feeds_manage_error">Er was een probleem met het laden van je feeds. Probeer het alsjeblieft later nog een keer.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Tik om je profielfoto te wijzigen</string>
<string name="dialog_confirm_profile_picture_title">Wijzig profielfoto</string>
<string name="dialog_confirm_profile_picture_remark">Alleen je contacten kunnen je profielfoto zien</string>
<string name="change_profile_picture_failed_message">Excuses, maar er is iets misgegaan met het updaten van je profielfoto</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Taal &amp; regio</string> <string name="pref_language_title">Taal &amp; regio</string>
<string name="pref_language_changed">Deze instelling zal werken wanneer u Briar opnieuw opstart. Gelieve uit te loggen en Briar opnieuw te starten.</string> <string name="pref_language_changed">Deze instelling zal werken wanneer u Briar opnieuw opstart. Gelieve uit te loggen en Briar opnieuw te starten.</string>
@@ -564,7 +568,7 @@
<string name="permission_camera_location_title">Camera en locatie</string> <string name="permission_camera_location_title">Camera en locatie</string>
<string name="permission_camera_location_request_body">Om de QR-code in te scannen heeft Briar toegang nodig tot de camera.\n\nOm bluetoothapparaten te ontdekken heeft Briar toestemming nodig tot je locatie.\n\nBriar slaat je locatie niet op en deelt het met niemand.</string> <string name="permission_camera_location_request_body">Om de QR-code in te scannen heeft Briar toegang nodig tot de camera.\n\nOm bluetoothapparaten te ontdekken heeft Briar toestemming nodig tot je locatie.\n\nBriar slaat je locatie niet op en deelt het met niemand.</string>
<string name="permission_camera_denied_body">Je hebt toegang tot de camera niet vrijgegeven, terwijl het toevoegen van contacten de camera nodig heeft.\n\nOverweeg alsjeblieft toegang vrij te geven.</string> <string name="permission_camera_denied_body">Je hebt toegang tot de camera niet vrijgegeven, terwijl het toevoegen van contacten de camera nodig heeft.\n\nOverweeg alsjeblieft toegang vrij te geven.</string>
<string name="permission_location_denied_body">Je hebt geen toegang tot je locatie gegeven, maar Briar heeft deze rechten nodig om apparaten via bleutooth te vinden.\n\nOverweeg a.u.b. deze rechten te geven.</string> <string name="permission_location_denied_body">Je hebt geen toegang tot je locatie gegeven, maar Briar heeft deze rechten nodig om apparaten via bluetooth te vinden.\n\nOverweeg a.u.b. deze rechten te geven.</string>
<string name="qr_code">QR-code</string> <string name="qr_code">QR-code</string>
<string name="show_qr_code_fullscreen">Toon QR-code op volledig scherm</string> <string name="show_qr_code_fullscreen">Toon QR-code op volledig scherm</string>
<!--App Locking--> <!--App Locking-->

View File

@@ -435,6 +435,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Nici un flux RSS de arătat\n\nAtingeți iconița + pentru a adăuga un flux</string> <string name="blogs_rss_feeds_manage_empty_state">Nici un flux RSS de arătat\n\nAtingeți iconița + pentru a adăuga un flux</string>
<string name="blogs_rss_feeds_manage_error">A apărut o eroare la încărcarea fluxurilor dumneavoastră. Vă rugăm să încercați din nou mai târziu.</string> <string name="blogs_rss_feeds_manage_error">A apărut o eroare la încărcarea fluxurilor dumneavoastră. Vă rugăm să încercați din nou mai târziu.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Atingeți pentru a vă schimba poza de profil</string>
<string name="dialog_confirm_profile_picture_title">Schimbare poză de profil</string>
<string name="dialog_confirm_profile_picture_remark">Doar contactele vor vedea poza de contact</string>
<string name="change_profile_picture_failed_message">Ne pare rău, dar ceva nu a funcționat cum trebuie la actualizarea pozei de profil</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Limbă &amp; Regiune</string> <string name="pref_language_title">Limbă &amp; Regiune</string>
<string name="pref_language_changed">Această setare va avea efect după repornirea Briar. Vă rugăm să ieșiți din Briar și să reporniți aplicația.</string> <string name="pref_language_changed">Această setare va avea efect după repornirea Briar. Vă rugăm să ieșiți din Briar și să reporniți aplicația.</string>

View File

@@ -447,6 +447,10 @@
<string name="blogs_rss_feeds_manage_empty_state">Нет RSS-лент для отображения\n\nКоснитесь значка + для импорта ленты</string> <string name="blogs_rss_feeds_manage_empty_state">Нет RSS-лент для отображения\n\nКоснитесь значка + для импорта ленты</string>
<string name="blogs_rss_feeds_manage_error">Ошибка при загрузке вашей ленты. Повторите попытку позже.</string> <string name="blogs_rss_feeds_manage_error">Ошибка при загрузке вашей ленты. Повторите попытку позже.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="change_profile_picture">Нажмите, чтобы изменить изображение вашего профиля </string>
<string name="dialog_confirm_profile_picture_title">Изменить изображение профиля</string>
<string name="dialog_confirm_profile_picture_remark">Только ваши контакты могут видеть изображение вашего профиля</string>
<string name="change_profile_picture_failed_message">Нам очень жаль, но что-то пошло не так во время обновления изображения вашего профиля.</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Язык и регион</string> <string name="pref_language_title">Язык и регион</string>
<string name="pref_language_changed">Этот параметр вступит в силу после перезапуска Briar. Пожалуйста, выйдите и перезапустите Briar.</string> <string name="pref_language_changed">Этот параметр вступит в силу после перезапуска Briar. Пожалуйста, выйдите и перезапустите Briar.</string>

View File

@@ -456,7 +456,7 @@
<!-- Settings Profile Picture --> <!-- Settings Profile Picture -->
<string name="change_profile_picture">Tap to change your profile picture</string> <string name="change_profile_picture">Tap to change your profile picture</string>
<string name="dialog_confirm_profile_picture_title">Change profile picture</string> <string name="dialog_confirm_profile_picture_title">Change profile picture</string>
<string name="dialog_confirm_profile_picture_remark">Only your contacts can see your profile image</string> <string name="dialog_confirm_profile_picture_remark">Only your contacts can see this picture</string>
<string name="change_profile_picture_failed_message">We\'re sorry, but something went wrong while updating your profile picture</string> <string name="change_profile_picture_failed_message">We\'re sorry, but something went wrong while updating your profile picture</string>
<!-- Settings Display --> <!-- Settings Display -->

View File

@@ -0,0 +1,66 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog;
import static org.junit.Assert.assertEquals;
public class LogEncryptionDecryptionTest extends BrambleMockTestCase {
@ClassRule
public static TemporaryFolder folder = new TemporaryFolder();
private final SecureRandom random;
private final CachingLogHandler cachingLogHandler;
private final LogEncrypter logEncrypter;
private final LogDecrypter logDecrypter;
private final BriefLogFormatter logFormatter = new BriefLogFormatter();
public LogEncryptionDecryptionTest() throws IOException {
LoggingComponent loggingComponent = DaggerLoggingComponent.builder()
.loggingTestModule(new LoggingTestModule(folder.newFile()))
.build();
random = loggingComponent.random();
logEncrypter = loggingComponent.logEncrypter();
logDecrypter = loggingComponent.logDecrypter();
cachingLogHandler = loggingComponent.cachingLogHandler();
}
@Test
public void testEncryptedMatchesDecrypted() {
ArrayList<LogRecord> logRecords =
new ArrayList<>(random.nextInt(99) + 1);
for (int i = 0; i < logRecords.size(); i++) {
LogRecord logRecord = getRandomLogRecord();
cachingLogHandler.publish(logRecord);
logRecords.add(logRecord);
}
byte[] logKey = logEncrypter.encryptLogs();
assertEquals(formatLog(logFormatter, logRecords),
logDecrypter.decryptLogs(logKey));
}
private LogRecord getRandomLogRecord() {
Level[] levels = {SEVERE, WARNING, INFO, FINE};
Level level = levels[random.nextInt(levels.length)];
LogRecord logRecord =
new LogRecord(level, getRandomString(random.nextInt(128) + 1));
logRecord.setLoggerName(getRandomString(random.nextInt(23) + 1));
return logRecord;
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.test.TestSecureRandomModule;
import java.security.SecureRandom;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {
BrambleCoreModule.class,
TestSecureRandomModule.class,
LoggingModule.class,
LoggingTestModule.class,
})
public interface LoggingComponent {
SecureRandom random();
CachingLogHandler cachingLogHandler();
LogEncrypter logEncrypter();
LogDecrypter logDecrypter();
}

View File

@@ -0,0 +1,52 @@
package org.briarproject.briar.android.logging;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.reporting.DevConfig;
import java.io.File;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
class LoggingTestModule {
private final File logFile;
LoggingTestModule(File logFile) {
this.logFile = logFile;
}
@Provides
@Singleton
DevConfig provideDevConfig() {
@NotNullByDefault
DevConfig devConfig = new DevConfig() {
@Override
public PublicKey getDevPublicKey() {
throw new UnsupportedOperationException();
}
@Override
public String getDevOnionAddress() {
throw new UnsupportedOperationException();
}
@Override
public File getReportDir() {
throw new UnsupportedOperationException();
}
@Override
public File getLogcatFile() {
return logFile;
}
};
return devConfig;
}
}

View File

@@ -1,7 +1,7 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'androidx.activity:activity-ktx:1.1.0:activity-ktx-1.1.0.aar:1996c36d3d2d62db5020b8ec634b5f854b1a698960c3552e1a00c69221baeabe', 'androidx.activity:activity-ktx:1.1.0:activity-ktx-1.1.0.aar:1996c36d3d2d62db5020b8ec634b5f854b1a698960c3552e1a00c69221baeabe',
'androidx.activity:activity:1.1.0:activity-1.1.0.aar:4f2b35916768032f7d0c20e250e28b29037ed4ce9ebf3da4fcd51bcb0c6067ef', 'androidx.activity:activity:1.2.0:activity-1.2.0.aar:ac27a810554e47b2122bce1f338934e77b173a5a9267eb35f134b6d34f931bae',
'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11', 'androidx.annotation:annotation-experimental:1.0.0:annotation-experimental-1.0.0.aar:b219d2b568e7e4ba534e09f8c2fd242343df6ccbdfbbe938846f5d740e6b0b11',
'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692', 'androidx.annotation:annotation:1.1.0:annotation-1.1.0.jar:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5', 'androidx.appcompat:appcompat-resources:1.2.0:appcompat-resources-1.2.0.aar:c470297c03ff3de1c3d15dacf0be0cae63abc10b52f021dd07ae28daa3100fe5',
@@ -24,29 +24,28 @@ dependencyVerification {
'androidx.exifinterface:exifinterface:1.3.1:exifinterface-1.3.1.aar:ef168daa6eb744c8395c22b49afa5235e6099868a0377175b6d5e3cdff8d7ffc', 'androidx.exifinterface:exifinterface:1.3.1:exifinterface-1.3.1.aar:ef168daa6eb744c8395c22b49afa5235e6099868a0377175b6d5e3cdff8d7ffc',
'androidx.fragment:fragment-ktx:1.2.5:fragment-ktx-1.2.5.aar:50f0f3b734f93829eeac7456b7cb13e5430741e555c535911a958ee4a8242bca', 'androidx.fragment:fragment-ktx:1.2.5:fragment-ktx-1.2.5.aar:50f0f3b734f93829eeac7456b7cb13e5430741e555c535911a958ee4a8242bca',
'androidx.fragment:fragment-testing:1.2.5:fragment-testing-1.2.5.aar:ef3cc3387115f9187665b283e313b13a2bb8826673380317057e2972351df09c', 'androidx.fragment:fragment-testing:1.2.5:fragment-testing-1.2.5.aar:ef3cc3387115f9187665b283e313b13a2bb8826673380317057e2972351df09c',
'androidx.fragment:fragment:1.2.4:fragment-1.2.4.aar:1dc194942574302bf35dae7b81b82273505ec2d38f81d9258ad5c0448daddd82', 'androidx.fragment:fragment:1.3.0:fragment-1.3.0.aar:66db3ed2b11bb5e572a079b87cd3fae9bc5c33c373c71b25f1e3eac7607ab526',
'androidx.fragment:fragment:1.2.5:fragment-1.2.5.aar:d19e82d142def6c4e136da70bf92f194c0ecc61d14ab4e84567b2ced0920fa93',
'androidx.interpolator:interpolator:1.0.0:interpolator-1.0.0.aar:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a', 'androidx.interpolator:interpolator:1.0.0:interpolator-1.0.0.aar:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
'androidx.legacy:legacy-support-core-utils:1.0.0:legacy-support-core-utils-1.0.0.aar:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7', 'androidx.legacy:legacy-support-core-utils:1.0.0:legacy-support-core-utils-1.0.0.aar:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
'androidx.lifecycle:lifecycle-common:2.2.0:lifecycle-common-2.2.0.jar:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b', 'androidx.lifecycle:lifecycle-common:2.3.0:lifecycle-common-2.3.0.jar:15848fb56db32f4c7cdc72b324003183d52a4884d6bf09be708ac7f587d139b5',
'androidx.lifecycle:lifecycle-extensions:2.2.0:lifecycle-extensions-2.2.0.aar:648c8de1d10b025d524a2e46ac994fc3f6bf186826c09ec1a62d250bf1b877ae', 'androidx.lifecycle:lifecycle-extensions:2.2.0:lifecycle-extensions-2.2.0.aar:648c8de1d10b025d524a2e46ac994fc3f6bf186826c09ec1a62d250bf1b877ae',
'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0:lifecycle-livedata-core-ktx-2.2.0.aar:5951f882e95b7e05ceb9adfca0fa2ebd511d63ea5a00da4eae6c6d0c1903da18', 'androidx.lifecycle:lifecycle-livedata-core-ktx:2.2.0:lifecycle-livedata-core-ktx-2.2.0.aar:5951f882e95b7e05ceb9adfca0fa2ebd511d63ea5a00da4eae6c6d0c1903da18',
'androidx.lifecycle:lifecycle-livedata-core:2.2.0:lifecycle-livedata-core-2.2.0.aar:556c1f3af90aa9d7d0d330565adbf6da71b2429148bac91e07c485f4f9abf614', 'androidx.lifecycle:lifecycle-livedata-core:2.3.0:lifecycle-livedata-core-2.3.0.aar:89f480888f2bb8eb62d9b7b1eb34be69b59ec84b24a1b0bdbeb49973478c6da3',
'androidx.lifecycle:lifecycle-livedata:2.2.0:lifecycle-livedata-2.2.0.aar:d83af94860aa9f64cbdc51f40796a7cf55b116f0e6efd752e845c0104c8b16f6', 'androidx.lifecycle:lifecycle-livedata:2.2.0:lifecycle-livedata-2.2.0.aar:d83af94860aa9f64cbdc51f40796a7cf55b116f0e6efd752e845c0104c8b16f6',
'androidx.lifecycle:lifecycle-process:2.2.0:lifecycle-process-2.2.0.aar:3a977e7778fc8418742d388409daaba7ea8fea8823d21ffb96e4c4236f715070', 'androidx.lifecycle:lifecycle-process:2.2.0:lifecycle-process-2.2.0.aar:3a977e7778fc8418742d388409daaba7ea8fea8823d21ffb96e4c4236f715070',
'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0:lifecycle-runtime-ktx-2.2.0.aar:c29fc87694e6ce116b61207221e53ed285862a6628055790b0bcf9ce45d8cc68', 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0:lifecycle-runtime-ktx-2.2.0.aar:c29fc87694e6ce116b61207221e53ed285862a6628055790b0bcf9ce45d8cc68',
'androidx.lifecycle:lifecycle-runtime:2.2.0:lifecycle-runtime-2.2.0.aar:2f866c07a1f33a8c9bb69a9545d4f20b4f0628cd0a155432386d7cb081e1e0bc', 'androidx.lifecycle:lifecycle-runtime:2.3.0:lifecycle-runtime-2.3.0.aar:94f528fd5fb123f75b6e65d07a6ef5cd6c0e69ac604d106aaa12705282456234',
'androidx.lifecycle:lifecycle-service:2.2.0:lifecycle-service-2.2.0.aar:ca2801ffc069555afed8eddd2292130f436956452bc8bbad30fb56f8e4e382a0', 'androidx.lifecycle:lifecycle-service:2.2.0:lifecycle-service-2.2.0.aar:ca2801ffc069555afed8eddd2292130f436956452bc8bbad30fb56f8e4e382a0',
'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0:lifecycle-viewmodel-ktx-2.2.0.aar:f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702', 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0:lifecycle-viewmodel-ktx-2.2.0.aar:f791001f2211947e56ad3d96d12c9ae93fc5589b88f08603f69a2265c9a7d702',
'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0:lifecycle-viewmodel-savedstate-2.2.0.aar:3ce866fb822b20fe2f188f974992869a0a6233fe40acbefcff090d6def5e7f33', 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0:lifecycle-viewmodel-savedstate-2.3.0.aar:49f9532b5104cc1ee64900ed4f696d031d807fba726e0d5d6a52459e8fba4a1d',
'androidx.lifecycle:lifecycle-viewmodel:2.2.0:lifecycle-viewmodel-2.2.0.aar:967efab24d6c49dd414a8c0ac4a1cd09b018f0b8bb43b739ad360c4158ebde27', 'androidx.lifecycle:lifecycle-viewmodel:2.3.0:lifecycle-viewmodel-2.3.0.aar:cea8f26fa232037922b69af9cd1bde2df1211acc8b75253e425b7150a5fca59d',
'androidx.loader:loader:1.0.0:loader-1.0.0.aar:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025', 'androidx.loader:loader:1.0.0:loader-1.0.0.aar:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8', 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0:localbroadcastmanager-1.0.0.aar:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
'androidx.preference:preference:1.1.1:preference-1.1.1.aar:317dcbc38242aea2f6262c06d51b8a22827e98959967edd40f82600a15cb4bff', 'androidx.preference:preference:1.1.1:preference-1.1.1.aar:317dcbc38242aea2f6262c06d51b8a22827e98959967edd40f82600a15cb4bff',
'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd', 'androidx.print:print:1.0.0:print-1.0.0.aar:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
'androidx.recyclerview:recyclerview-selection:1.1.0-rc03:recyclerview-selection-1.1.0-rc03.aar:a548a0771c2c8ca8cf98f1f755b0eef4fac73d1697e6eeb1a6383f557e0eba13', 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03:recyclerview-selection-1.1.0-rc03.aar:a548a0771c2c8ca8cf98f1f755b0eef4fac73d1697e6eeb1a6383f557e0eba13',
'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f', 'androidx.recyclerview:recyclerview:1.1.0:recyclerview-1.1.0.aar:f0d2b5a67d0a91ee1b1c73ef2b636a81f3563925ddd15a1d4e1c41ec28de7a4f',
'androidx.savedstate:savedstate:1.0.0:savedstate-1.0.0.aar:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83', 'androidx.savedstate:savedstate:1.1.0:savedstate-1.1.0.aar:d60bbe44c2c08083a17c5dc678a6d6b4d0a2d664858016ab5c049cbea90a63b7',
'androidx.test.espresso:espresso-contrib:3.3.0:espresso-contrib-3.3.0.aar:f400cabdc181356acf6b210e4509dcb9649d9e2b6b6e218c60fcfc15e8a756d1', 'androidx.test.espresso:espresso-contrib:3.3.0:espresso-contrib-3.3.0.aar:f400cabdc181356acf6b210e4509dcb9649d9e2b6b6e218c60fcfc15e8a756d1',
'androidx.test.espresso:espresso-core:3.3.0:espresso-core-3.3.0.aar:23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee', 'androidx.test.espresso:espresso-core:3.3.0:espresso-core-3.3.0.aar:23ebf6014645e0c60aec7d1ed924d4d4c848ae8c3673b7d8d06b2ec6a56cafee',
'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6', 'androidx.test.espresso:espresso-idling-resource:3.3.0:espresso-idling-resource-3.3.0.aar:29519b112731f289cc6e2f9b2eccc5ea72c754b04272bb93370f45d7e170a7c6',
@@ -57,6 +56,7 @@ dependencyVerification {
'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e', 'androidx.test:monitor:1.3.0:monitor-1.3.0.aar:f73a31306a783e63150c60c49e140dc38da39a1b7947690f4b73387b5ebad77e',
'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a', 'androidx.test:rules:1.3.0:rules-1.3.0.aar:c1753946c498b0d5d7cf341cfed661f66915c4c9deb4ed10462a08ae33b2429a',
'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b', 'androidx.test:runner:1.3.0:runner-1.3.0.aar:61d13f5a9fcbbd73ba18fa84e1d6a0111c6e1c665a89b418126966e61fffd93b',
'androidx.tracing:tracing:1.0.0:tracing-1.0.0.aar:07b8b6139665b884a162eccf97891ca50f7f56831233bf25168ae04f7b568612',
'androidx.transition:transition:1.2.0:transition-1.2.0.aar:a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e', 'androidx.transition:transition:1.2.0:transition-1.2.0.aar:a1e059b3bc0b43a58dec0efecdcaa89c82d2bca552ea5bacf6656c46e853157e',
'androidx.vectordrawable:vectordrawable-animated:1.1.0:vectordrawable-animated-1.1.0.aar:76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8', 'androidx.vectordrawable:vectordrawable-animated:1.1.0:vectordrawable-animated-1.1.0.aar:76da2c502371d9c38054df5e2b248d00da87809ed058f3363eae87ce5e2403f8',
'androidx.vectordrawable:vectordrawable:1.1.0:vectordrawable-1.1.0.aar:46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26', 'androidx.vectordrawable:vectordrawable:1.1.0:vectordrawable-1.1.0.aar:46fd633ac01b49b7fcabc263bf098c5a8b9e9a69774d234edcca04fb02df8e26',

View File

@@ -1,8 +1,10 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'junit:junit:4.13.1:junit-4.13.1.jar:c30719db974d6452793fe191b3638a5777005485bae145924044530ffa5f6122',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52', 'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d', 'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
] ]
} }

View File

@@ -11,9 +11,11 @@ The REST API peer comes as a `jar` file
and needs a Java Runtime Environment (JRE) that supports at least Java 8. and needs a Java Runtime Environment (JRE) that supports at least Java 8.
It currently works only on GNU/Linux operating systems. It currently works only on GNU/Linux operating systems.
To build the `jar` file, you can do this: To build the `jar` file, you need to specify the combination of architecture and platform:
$ ./gradlew --configure-on-demand briar-headless:jar $ ./gradlew --configure-on-demand briar-headless:x86LinuxJar
$ ./gradlew --configure-on-demand briar-headless:aarch64LinuxJar
$ ./gradlew --configure-on-demand briar-headless:armhfLinuxJar
You can start the peer (and its API server) like this: You can start the peer (and its API server) like this:
@@ -105,7 +107,7 @@ The link and the alias should be posted as a JSON object:
} }
``` ```
This starts the process of adding the contact. Adding a pending contact starts the process of adding the contact.
Until it is completed, a pending contact is returned as JSON: Until it is completed, a pending contact is returned as JSON:
```json ```json
@@ -116,6 +118,71 @@ Until it is completed, a pending contact is returned as JSON:
} }
``` ```
Possible errors when adding a pending contact are:
#### 400: Pending contact's link is invalid
```json
{
"error": "INVALID_LINK"
}
```
#### 400: Pending contact's handshake public key is invalid
```json
{
"error": "INVALID_PUBLIC_KEY"
}
```
#### 403: A contact with the same handshake public key already exists
This error may be caused by someone attacking the user with the goal
of discovering the contacts of the user.
In the Android client, upon encountering this issue a message dialog
is shown that asks whether the contact and the just added pending contact
are the same person. If that's the case, a message is shown that the
contact already exists and the pending contact isn't added.
If that's not the case and they are two different persons, the Android
client
[shows the following message](https://code.briarproject.org/briar/briar/-/blob/beta-1.2.14/briar-android/src/main/res/values/strings.xml#L271)
when this happens:
> [Alice] and [Bob] sent you the same link.
>
> One of them may be trying to discover who your contacts are.
>
> Don't tell them you received the same link from someone else.
```json
{
"error": "CONTACT_EXISTS",
"remoteAuthorName": "Bob"
}
```
#### 403: A pending contact with the same handshake public key already exists
This error, too, may be caused by someone attacking the user with the goal
of discovering the contacts of the user.
Just like above, upon encountering this issue a message dialog is shown in
the Android client that asks whether the contact and the just added pending
contact are the same person. If that's the case, the pending contact gets
updated. If that's not the case and they are two different persons, the
Android client shows the same message as above, warning the user about the
possible attack.
```json
{
"error": "PENDING_EXISTS",
"pendingContactId": "jsTgWcsEQ2g9rnomeK1g/hmO8M1Ix6ZIGWAjgBtlS9U=",
"pendingContactAlias": "Alice"
}
```
-----------
Before users can send messages to contacts, they become pending contacts. Before users can send messages to contacts, they become pending contacts.
In this state Briar still needs to do some work in the background (e.g. In this state Briar still needs to do some work in the background (e.g.
spinning up a dedicated hidden service and letting the contact connect to it). spinning up a dedicated hidden service and letting the contact connect to it).

View File

@@ -44,18 +44,32 @@ dependencies {
kaptTest "com.google.dagger:dagger-compiler:$daggerVersion" kaptTest "com.google.dagger:dagger-compiler:$daggerVersion"
} }
jar { void jarFactory(Jar jarTask, jarArchitecture) {
manifest { jarTask.doFirst {
println 'Building ' + jarArchitecture + ' version has started'
}
jarTask.manifest {
attributes( attributes(
'Main-Class': 'org.briarproject.briar.headless.MainKt' 'Main-Class': 'org.briarproject.briar.headless.MainKt'
) )
} }
from { jarTask.setArchiveClassifier(jarArchitecture)
jarTask.from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
} }
doLast() { {
String[] architectures = ["linux-aarch64", "linux-armhf", "linux-x86_64"]
for (String arch : architectures) {
if (arch != jarArchitecture) {
exclude "obfs4proxy_" + arch + ".zip"
exclude "tor_" + arch + ".zip"
}
}
}
jarTask.with jar
jarTask.doLast {
// Rename the original jar // Rename the original jar
File jar = project.jar.archivePath File jar = jarTask.archivePath
String srcPath = jar.toString().replaceFirst('\\.jar$', '.unsorted.jar') String srcPath = jar.toString().replaceFirst('\\.jar$', '.unsorted.jar')
File srcFile = new File(srcPath) File srcFile = new File(srcPath)
jar.renameTo(srcFile) jar.renameTo(srcFile)
@@ -80,9 +94,22 @@ jar {
} }
destStream.close() destStream.close()
srcJarFile.close() srcJarFile.close()
println 'Building ' + jarArchitecture + ' version has finished'
} }
} }
task aarch64LinuxJar(type: Jar) {
jarFactory(it, 'linux-aarch64')
}
task armhfLinuxJar(type: Jar) {
jarFactory(it, 'linux-armhf')
}
task x86LinuxJar(type: Jar) {
jarFactory(it, 'linux-x86_64')
}
// At the moment for non-Android projects we need to explicitly mark the code generated by kapt // At the moment for non-Android projects we need to explicitly mark the code generated by kapt
// as 'generated source code' for correct highlighting and resolve in IDE. // as 'generated source code' for correct highlighting and resolve in IDE.
idea { idea {

View File

@@ -3,6 +3,8 @@ package org.briarproject.briar.headless.contact
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.ForbiddenResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.ContactManager
@@ -12,8 +14,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
import org.briarproject.bramble.api.db.ContactExistsException
import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.db.NoSuchPendingContactException import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.db.PendingContactExistsException
import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.Event
import org.briarproject.bramble.api.event.EventListener import org.briarproject.bramble.api.event.EventListener
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
@@ -25,8 +29,11 @@ import org.briarproject.briar.headless.event.WebSocketController
import org.briarproject.briar.headless.getContactIdFromPathParam import org.briarproject.briar.headless.getContactIdFromPathParam
import org.briarproject.briar.headless.getFromJson import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400
import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403
import org.spongycastle.util.encoders.Base64 import org.spongycastle.util.encoders.Base64
import org.spongycastle.util.encoders.DecoderException import org.spongycastle.util.encoders.DecoderException
import java.security.GeneralSecurityException
import javax.annotation.concurrent.Immutable import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -91,9 +98,32 @@ constructor(
override fun addPendingContact(ctx: Context): Context { override fun addPendingContact(ctx: Context): Context {
val link = ctx.getFromJson(objectMapper, "link") val link = ctx.getFromJson(objectMapper, "link")
val alias = ctx.getFromJson(objectMapper, "alias") val alias = ctx.getFromJson(objectMapper, "alias")
if (!LINK_REGEX.matcher(link).find()) throw BadRequestResponse("Invalid Link") if (!LINK_REGEX.matcher(link).find()) {
ctx.status(BAD_REQUEST_400)
val details = mapOf("error" to "INVALID_LINK")
return ctx.json(details)
}
checkAliasLength(alias) checkAliasLength(alias)
val pendingContact = contactManager.addPendingContact(link, alias) val pendingContact = try {
contactManager.addPendingContact(link, alias)
} catch (e: GeneralSecurityException) {
ctx.status(BAD_REQUEST_400)
val details = mapOf("error" to "INVALID_PUBLIC_KEY")
return ctx.json(details)
} catch (e: ContactExistsException) {
ctx.status(FORBIDDEN_403)
val details =
mapOf("error" to "CONTACT_EXISTS", "remoteAuthorName" to e.remoteAuthor.name)
return ctx.json(details)
} catch (e: PendingContactExistsException) {
ctx.status(FORBIDDEN_403)
val details = mapOf(
"error" to "PENDING_EXISTS",
"pendingContactId" to e.pendingContact.id.bytes,
"pendingContactAlias" to e.pendingContact.alias
)
return ctx.json(details)
}
return ctx.json(pendingContact.output()) return ctx.json(pendingContact.output())
} }

View File

@@ -98,6 +98,49 @@ class ContactControllerIntegrationTest: IntegrationTest() {
assertEquals(401, response.statusCode) assertEquals(401, response.statusCode)
} }
@Test
fun `adding a pending contact with invalid link`() {
val alias = "AliasFoo"
val json = """{
"link": "briar://invalid",
"alias": "$alias"
}"""
val response = post("$url/contacts/add/pending", json)
assertEquals(400, response.statusCode)
assertEquals("INVALID_LINK", response.jsonObject.getString("error"))
}
@Test
fun `adding a pending contact with invalid public key`() {
val alias = "AliasFoo"
val json = """{
"link": "briar://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"alias": "$alias"
}"""
val response = post("$url/contacts/add/pending", json)
assertEquals(400, response.statusCode)
assertEquals("INVALID_PUBLIC_KEY", response.jsonObject.getString("error"))
}
@Test
fun `adding a pending contact that already exists as pending contact`() {
val alias = "AliasFoo"
val json = """{
"link": "${getRealHandshakeLink(crypto)}",
"alias": "$alias"
}"""
var response = post("$url/contacts/add/pending", json)
assertEquals(200, response.statusCode)
val pendingContactId = response.jsonObject.getString("pendingContactId")
response = post("$url/contacts/add/pending", json)
assertEquals(403, response.statusCode)
assertEquals("PENDING_EXISTS", response.jsonObject.getString("error"))
assertEquals(pendingContactId, response.jsonObject.getString("pendingContactId"))
assertEquals(alias, response.jsonObject.getString("pendingContactAlias"))
}
@Test @Test
fun `removing a pending contact needs authentication token`() { fun `removing a pending contact needs authentication token`() {
val response = deleteWithWrongToken("$url/contacts/add/pending") val response = deleteWithWrongToken("$url/contacts/add/pending")

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.headless.contact package org.briarproject.briar.headless.contact
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
import io.javalin.http.ForbiddenResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import io.javalin.plugin.json.JavalinJson.toJson import io.javalin.plugin.json.JavalinJson.toJson
import io.mockk.Runs import io.mockk.Runs
@@ -8,6 +10,7 @@ import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.runs import io.mockk.runs
import io.mockk.verify
import org.briarproject.bramble.api.Pair 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.contact.ContactId import org.briarproject.bramble.api.contact.ContactId
@@ -18,8 +21,10 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent import org.briarproject.bramble.api.contact.event.PendingContactAddedEvent
import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent
import org.briarproject.bramble.api.db.ContactExistsException
import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.db.NoSuchPendingContactException import org.briarproject.bramble.api.db.NoSuchPendingContactException
import org.briarproject.bramble.api.db.PendingContactExistsException
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent
@@ -30,9 +35,11 @@ import org.briarproject.bramble.util.StringUtils.getRandomString
import org.briarproject.briar.headless.ControllerTest import org.briarproject.briar.headless.ControllerTest
import org.briarproject.briar.headless.getFromJson import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import java.security.GeneralSecurityException
import kotlin.random.Random import kotlin.random.Random
internal class ContactControllerTest : ControllerTest() { internal class ContactControllerTest : ControllerTest() {
@@ -96,9 +103,10 @@ internal class ContactControllerTest : ControllerTest() {
"alias": "$alias" "alias": "$alias"
}""" }"""
every { ctx.body() } returns body every { ctx.body() } returns body
assertThrows(BadRequestResponse::class.java) { every { ctx.status(400) } returns ctx
controller.addPendingContact(ctx) every { ctx.json(mapOf("error" to "INVALID_LINK")) } returns ctx
} controller.addPendingContact(ctx)
verify { ctx.status(400) }
} }
@Test @Test
@@ -139,6 +147,84 @@ internal class ContactControllerTest : ControllerTest() {
} }
} }
@Test
fun testAddPendingContactPublicKeyInvalid() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(400) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws GeneralSecurityException()
every { ctx.json(mapOf("error" to "INVALID_PUBLIC_KEY")) } returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(400) }
}
@Test
fun testAddPendingContactSameContactKey() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(403) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws ContactExistsException(null, author)
every {
ctx.json(
mapOf(
"error" to "CONTACT_EXISTS",
"remoteAuthorName" to author.name
)
)
} returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(403) }
}
@Test
fun testAddPendingContactSamePendingContactKey() {
val link = "briar://adnsyffpsenoc3yzlhr24aegfq2pwan7kkselocill2choov6sbhs"
val alias = "Alias123"
val body = """{
"link": "$link",
"alias": "$alias"
}"""
every { ctx.body() } returns body
every { ctx.status(403) } returns ctx
every {
contactManager.addPendingContact(
link,
alias
)
} throws PendingContactExistsException(pendingContact)
every {
ctx.json(
mapOf(
"error" to "PENDING_EXISTS",
"pendingContactId" to pendingContact.id.bytes,
"pendingContactAlias" to pendingContact.alias
)
)
} returns ctx
controller.addPendingContact(ctx)
verify { ctx.status(403) }
}
@Test @Test
fun testListPendingContacts() { fun testListPendingContacts() {
every { contactManager.pendingContacts } returns listOf( every { contactManager.pendingContacts } returns listOf(