Compare commits

..

2 Commits

Author SHA1 Message Date
akwizgran
21d6dfe817 Add transactional helper methods to DbViewModel. 2021-01-26 14:52:25 +00:00
akwizgran
c1e83b22c1 Add helper method for running a DB task and logging any exception it throws. 2021-01-26 13:51:03 +00:00
183 changed files with 654 additions and 1527 deletions

View File

@@ -2,7 +2,6 @@ image: briar/ci-image-android:latest
stages: stages:
- test - test
- optional_tests
- check_reproducibility - check_reproducibility
test: test:
@@ -32,33 +31,3 @@ test_reproducible:
- "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline" - "curl -X POST -F token=${RELEASE_CHECK_TOKEN} -F ref=master -F variables[RELEASE_TAG]=${CI_COMMIT_REF_NAME} https://code.briarproject.org/api/v4/projects/61/trigger/pipeline"
only: only:
- tags - tags
.optional_tests:
stage: optional_tests
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
paths:
- .gradle/wrapper
- .gradle/caches
script:
- OPTIONAL_TESTS=org.briarproject.bramble.plugin.tor.BridgeTest ./gradlew --info bramble-java:test --tests BridgeTest
after_script:
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
manual_tests:
extends: .optional_tests
when: manual
except:
- tags
pre_release_tests:
extends: .optional_tests
only:
- tags

View File

@@ -1,14 +0,0 @@
<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

@@ -1,24 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
<module name="briar.bramble-java" />
<useClassPathOnly />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
<envs>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
</envs>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10216 versionCode 10213
versionName "1.2.16" versionName "1.2.13"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,8 +38,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.13@zip' tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a@zip' tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -31,7 +31,6 @@ 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<>();
@@ -108,10 +107,6 @@ 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.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82', 'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.13:tor-android-0.3.5.13.zip:e0978db136731dae07774b722970cdae1e462fb5adc82845dd80a7e2d87f356c', 'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
'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

@@ -0,0 +1,6 @@
package org.briarproject.bramble.api;
public interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

@@ -19,10 +19,4 @@ 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,12 +17,6 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange * Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream. * stream.
*/ */
StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out, StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out,
SecretKey headerKey);
/**
* Creates a {@link StreamEncrypter} for encrypting a log stream.
*/
StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
} }

View File

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

View File

@@ -16,13 +16,8 @@ public interface StreamReaderFactory {
/** /**
* Creates an {@link InputStream InputStream} for reading from a contact * Creates an {@link InputStream InputStream} for reading from a contact
* exchange stream. * exchangestream.
*/ */
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,19 +7,17 @@ import java.io.OutputStream;
@NotNullByDefault @NotNullByDefault
public interface StreamWriterFactory { public interface StreamWriterFactory {
/** /**
* Creates a {@link StreamWriter} for writing to a transport stream. * Creates an {@link OutputStream OutputStream} for writing to a
* transport stream
*/ */
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates a {@link StreamWriter} for writing to a contact exchange stream. * Creates an {@link OutputStream OutputStream} for writing to a contact
* 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,16 +9,14 @@ 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 =
getLogger(ScryptKdf.class.getName()); Logger.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
@@ -35,20 +33,10 @@ 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 <= maxCost && measureDuration(cost) * 2 <= TARGET_MS) { while (cost * 2 <= MAX_COST && 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,10 +36,4 @@ 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 createContactExchangeStreamEncrypter( public StreamEncrypter createContactExchangeStreamDecrypter(
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,10 +60,4 @@ 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

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,5 +1,6 @@
package org.briarproject.bramble.sync; package org.briarproject.bramble.sync;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent; import org.briarproject.bramble.api.db.DatabaseComponent;

View File

@@ -1,6 +0,0 @@
package org.briarproject.bramble.sync;
interface ThrowingRunnable<T extends Throwable> {
void run() throws T;
}

View File

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

View File

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

View File

@@ -1,14 +1,5 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ 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 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 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: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 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 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
Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw iat-mode=0
Bridge obfs4 13.58.29.242:9443 0C58939A77DA6B6B29D4B5236A75865659607AE0 cert=OylWIEHb/ezpq1zWxW0sgKRn+9ARH2eOcQOZ8/Gew+4l+oKOhQ2jUX/Y+FSl61JorXZUWA iat-mode=0
Bridge obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0
Bridge meek_lite 0.0.2.0:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

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.13@zip' tor 'org.briarproject:tor:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a@zip' tor 'org.briarproject:obfs4proxy:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -53,7 +53,7 @@ public class BridgeTest extends BrambleTestCase {
return component.getCircumventionProvider().getBridges(false); return component.getCircumventionProvider().getBridges(false);
} }
private final static long TIMEOUT = SECONDS.toMillis(60); private final static long TIMEOUT = SECONDS.toMillis(30);
private final static Logger LOG = getLogger(BridgeTest.class.getName()); private final static Logger LOG = getLogger(BridgeTest.class.getName());

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.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766', 'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.13:tor-0.3.5.13.zip:1c5f0b821ee2aadb0ea04aa96caab3ca0a08370cce8de81c2dfe04d172f8a2a0', 'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
'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

@@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0 0,1 16,4V6H14V4H5V20H14V18H16V20A2,2 0 0,1 14,22H5A2,2 0 0,1 3,20V4A2,2 0 0,1 5,2H14Z" /></svg>

Before

Width:  |  Height:  |  Size: 423 B

View File

@@ -22,11 +22,9 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 10216 versionCode 10213
versionName "1.2.16" versionName "1.2.13"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
def now = (long) (System.currentTimeMillis() / 1000) def now = (long) (System.currentTimeMillis() / 1000)
@@ -136,7 +134,6 @@ 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,7 +34,6 @@ 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;
@@ -180,10 +179,6 @@ 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

@@ -255,7 +255,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
R.string.ongoing_notification_title; R.string.ongoing_notification_title;
int text = locked ? R.string.lock_tap_to_unlock : int text = locked ? R.string.lock_tap_to_unlock :
R.string.ongoing_notification_text; R.string.ongoing_notification_text;
int icon = locked ? R.drawable.notification_lock : int icon = locked ? R.drawable.startup_lock :
R.drawable.notification_ongoing; R.drawable.notification_ongoing;
// Ongoing foreground notification that shows BriarService is running // Ongoing foreground notification that shows BriarService is running
NotificationCompat.Builder b = NotificationCompat.Builder b =
@@ -624,7 +624,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
NotificationCompat.Builder b = NotificationCompat.Builder b =
new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID); new NotificationCompat.Builder(appContext, REMINDER_CHANNEL_ID);
b.setSmallIcon(R.drawable.notification_signout); b.setSmallIcon(R.drawable.ic_signout);
b.setColor(getColor(appContext, R.color.briar_primary)); b.setColor(getColor(appContext, R.color.briar_primary));
b.setContentTitle( b.setContentTitle(
appContext.getText(R.string.reminder_notification_title)); appContext.getText(R.string.reminder_notification_title));

View File

@@ -33,13 +33,12 @@ 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;
@@ -75,7 +74,6 @@ 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,
@@ -194,11 +192,6 @@ 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,6 +6,9 @@ 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.
@@ -14,6 +17,8 @@ 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,10 +20,12 @@ 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.lang.Thread.UncaughtExceptionHandler; import java.util.Collection;
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;
@@ -40,18 +42,22 @@ 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);
} }
@@ -61,11 +67,6 @@ 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
@@ -77,12 +78,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());
} }
@@ -135,6 +136,11 @@ 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_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE;
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;
@@ -56,7 +56,6 @@ 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;
@@ -121,17 +120,11 @@ 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_LOW); IMPORTANCE_NONE);
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,
@@ -177,7 +170,6 @@ 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) {

View File

@@ -68,21 +68,7 @@ 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();
@@ -96,7 +82,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 = context.createConfigurationContext(conf); context.createConfigurationContext(conf);
} else } else
conf.locale = locale; conf.locale = locale;
//noinspection deprecation //noinspection deprecation

View File

@@ -109,7 +109,6 @@ 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

@@ -45,10 +45,8 @@ import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
@@ -173,14 +171,9 @@ class ContactListViewModel extends DbViewModel implements EventListener {
} }
void checkForPendingContacts() { void checkForPendingContacts() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { boolean hasPending = !contactManager.getPendingContacts().isEmpty();
boolean hasPending = hasPendingContacts.postValue(hasPending);
!contactManager.getPendingContacts().isEmpty();
hasPendingContacts.postValue(hasPending);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -2,12 +2,9 @@ package org.briarproject.briar.android.contact.add.remote;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact; import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -18,7 +15,6 @@ import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.security.GeneralSecurityException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -66,15 +62,10 @@ public class AddContactViewModel extends DbViewModel {
} }
private void loadHandshakeLink() { private void loadHandshakeLink() {
runOnDbThread(() -> { // If an exception is thrown the UI should stay disabled,
try { // leaving the user unable to proceed
handshakeLink.postValue(contactManager.getHandshakeLink()); runOnDbThreadOrLogException(() ->
} catch (DbException e) { handshakeLink.postValue(contactManager.getHandshakeLink()));
logException(LOG, WARNING, e);
// the UI should stay disabled in this case,
// leaving the user unable to proceed
}
});
} }
LiveData<String> getHandshakeLink() { LiveData<String> getHandshakeLink() {
@@ -106,17 +97,11 @@ public class AddContactViewModel extends DbViewModel {
void addContact(String nickname) { void addContact(String nickname) {
if (remoteHandshakeLink == null) throw new IllegalStateException(); if (remoteHandshakeLink == null) throw new IllegalStateException();
runOnDbThread(() -> { runOnDbThread(() -> {
try { contactManager.addPendingContact(remoteHandshakeLink, nickname);
contactManager.addPendingContact(remoteHandshakeLink, nickname); addContactResult.postValue(new LiveResult<>(true));
addContactResult.postValue(new LiveResult<>(true)); }, e -> {
} catch (UnsupportedVersionException e) { logException(LOG, WARNING, e);
logException(LOG, WARNING, e); addContactResult.postValue(new LiveResult<>(e));
addContactResult.postValue(new LiveResult<>(e));
} catch (DbException | FormatException
| GeneralSecurityException e) {
logException(LOG, WARNING, e);
addContactResult.postValue(new LiveResult<>(e));
}
}); });
} }
@@ -124,15 +109,15 @@ public class AddContactViewModel extends DbViewModel {
return addContactResult; return addContactResult;
} }
void updatePendingContact(String name, PendingContact p) { public void updatePendingContact(String name, PendingContact p) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { contactManager.removePendingContact(p.getId());
contactManager.removePendingContact(p.getId()); addContact(name);
addContact(name); }, e -> {
} catch (NoSuchPendingContactException e) { if (e instanceof NoSuchPendingContactException) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// no error in UI as pending contact was converted into contact // no error in UI as pending contact was converted into contact
} catch (DbException e) { } else {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
addContactResult.postValue(new LiveResult<>(e)); addContactResult.postValue(new LiveResult<>(e));
} }

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,7 +48,6 @@ 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;
@@ -68,20 +67,6 @@ 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
@@ -89,9 +74,14 @@ 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);
@@ -103,12 +93,6 @@ 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

@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.contact.PendingContactState;
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.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -33,10 +32,8 @@ import javax.inject.Inject;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE; import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class PendingContactListViewModel extends DbViewModel public class PendingContactListViewModel extends DbViewModel
@@ -90,24 +87,20 @@ public class PendingContactListViewModel extends DbViewModel
} }
private void loadPendingContacts() { private void loadPendingContacts() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Collection<Pair<PendingContact, PendingContactState>> pairs =
Collection<Pair<PendingContact, PendingContactState>> pairs = contactManager.getPendingContacts();
contactManager.getPendingContacts(); List<PendingContactItem> items = new ArrayList<>(pairs.size());
List<PendingContactItem> items = new ArrayList<>(pairs.size()); boolean online = pairs.isEmpty();
boolean online = pairs.isEmpty(); for (Pair<PendingContact, PendingContactState> pair : pairs) {
for (Pair<PendingContact, PendingContactState> pair : pairs) { PendingContact p = pair.getFirst();
PendingContact p = pair.getFirst(); PendingContactState state = pair.getSecond();
PendingContactState state = pair.getSecond(); long lastPoll = rendezvousPoller.getLastPollTime(p.getId());
long lastPoll = rendezvousPoller.getLastPollTime(p.getId()); items.add(new PendingContactItem(p, state, lastPoll));
items.add(new PendingContactItem(p, state, lastPoll)); online = online || state != OFFLINE;
online = online || state != OFFLINE;
}
pendingContacts.postValue(items);
hasInternetConnection.postValue(online);
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
pendingContacts.postValue(items);
hasInternetConnection.postValue(online);
}); });
} }
@@ -116,13 +109,8 @@ public class PendingContactListViewModel extends DbViewModel
} }
void removePendingContact(PendingContactId id) { void removePendingContact(PendingContactId id) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { contactManager.removePendingContact(id));
contactManager.removePendingContact(id);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
LiveData<Boolean> getHasInternetConnection() { LiveData<Boolean> getHasInternetConnection() {

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.conversation;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.transition.Slide; import android.transition.Slide;
@@ -106,7 +105,6 @@ import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
import androidx.recyclerview.selection.StorageStrategy; import androidx.recyclerview.selection.StorageStrategy;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import de.hdodenhof.circleimageview.CircleImageView; import de.hdodenhof.circleimageview.CircleImageView;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
@@ -481,10 +479,12 @@ public class ConversationActivity extends BriarActivity
@UiThread @UiThread
private void displayContactOnlineStatus() { private void displayContactOnlineStatus() {
if (connectionRegistry.isConnected(contactId)) { if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageResource(R.drawable.contact_online); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_online));
toolbarStatus.setContentDescription(getString(R.string.online)); toolbarStatus.setContentDescription(getString(R.string.online));
} else { } else {
toolbarStatus.setImageResource(R.drawable.contact_offline); toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_offline));
toolbarStatus.setContentDescription(getString(R.string.offline)); toolbarStatus.setContentDescription(getString(R.string.offline));
} }
} }
@@ -935,16 +935,13 @@ public class ConversationActivity extends BriarActivity
return; return;
} }
int color =
ContextCompat.getColor(this, R.color.briar_primary);
Drawable drawable = VectorDrawableCompat
.create(getResources(), R.drawable.ic_more_vert_accent, null);
new MaterialTapTargetPrompt.Builder(ConversationActivity.this, new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
R.style.OnboardingDialogTheme).setTarget(target) R.style.OnboardingDialogTheme).setTarget(target)
.setPrimaryText(R.string.introduction_onboarding_title) .setPrimaryText(R.string.introduction_onboarding_title)
.setSecondaryText(R.string.introduction_onboarding_text) .setSecondaryText(R.string.introduction_onboarding_text)
.setIconDrawable(drawable) .setIcon(R.drawable.ic_more_vert_accent)
.setBackgroundColour(color) .setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary))
.show(); .show();
} }

View File

@@ -149,7 +149,7 @@ public class ConversationViewModel extends DbViewModel
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e; AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) { if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received"); LOG.info("Attachment received");
runOnDbThread(() -> attachmentRetriever runOnDbThreadOrLogException(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId())); .loadAttachmentItem(a.getMessageId()));
} }
} else if (e instanceof AvatarUpdatedEvent) { } else if (e instanceof AvatarUpdatedEvent) {
@@ -194,44 +194,36 @@ public class ConversationViewModel extends DbViewModel
private void loadContact(ContactId contactId) { private void loadContact(ContactId contactId) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { long start = now();
long start = now(); Contact c = contactManager.getContact(contactId);
Contact c = contactManager.getContact(contactId); AuthorInfo authorInfo = authorManager.getAuthorInfo(c);
AuthorInfo authorInfo = authorManager.getAuthorInfo(c); contactItem.postValue(new ContactItem(c, authorInfo));
contactItem.postValue(new ContactItem(c, authorInfo)); logDuration(LOG, "Loading contact", start);
logDuration(LOG, "Loading contact", start); start = now();
start = now(); checkFeaturesAndOnboarding(contactId);
checkFeaturesAndOnboarding(contactId); logDuration(LOG, "Checking for image support", start);
logDuration(LOG, "Checking for image support", start); }, e -> {
} catch (NoSuchContactException e) { if (e instanceof NoSuchContactException) {
contactDeleted.postValue(true); contactDeleted.postValue(true);
} catch (DbException e) { } else {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
}); });
} }
void markMessageRead(GroupId g, MessageId m) { void markMessageRead(GroupId g, MessageId m) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); messagingManager.setReadFlag(g, m, true);
messagingManager.setReadFlag(g, m, true); logDuration(LOG, "Marking read", start);
logDuration(LOG, "Marking read", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
void setContactAlias(String alias) { void setContactAlias(String alias) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { contactManager.setContactAlias(requireNonNull(contactId),
contactManager.setContactAlias(requireNonNull(contactId), alias.isEmpty() ? null : alias);
alias.isEmpty() ? null : alias); loadContact(contactId);
loadContact(contactId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -327,21 +319,17 @@ public class ConversationViewModel extends DbViewModel
@UiThread @UiThread
private void storeMessage(PrivateMessage m) { private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId()); attachmentCreator.onAttachmentsSent(m.getMessage().getId());
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); messagingManager.addLocalMessage(m);
messagingManager.addLocalMessage(m); logDuration(LOG, "Storing message", start);
logDuration(LOG, "Storing message", start); Message message = m.getMessage();
Message message = m.getMessage(); PrivateMessageHeader h = new PrivateMessageHeader(
PrivateMessageHeader h = new PrivateMessageHeader( message.getId(), message.getGroupId(),
message.getId(), message.getGroupId(), message.getTimestamp(), true, true, false, false,
message.getTimestamp(), true, true, false, false, m.hasText(), m.getAttachmentHeaders());
m.hasText(), m.getAttachmentHeaders()); // TODO add text to cache when available here
// TODO add text to cache when available here addedHeader.postEvent(h);
addedHeader.postEvent(h);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -383,12 +371,7 @@ public class ConversationViewModel extends DbViewModel
@UiThread @UiThread
void recheckFeaturesAndOnboarding(ContactId contactId) { void recheckFeaturesAndOnboarding(ContactId contactId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { checkFeaturesAndOnboarding(contactId));
checkFeaturesAndOnboarding(contactId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
} }

View File

@@ -6,7 +6,6 @@ import android.net.Uri;
import android.view.View; import android.view.View;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -198,14 +197,12 @@ public class ImageViewModel extends DbViewModel implements EventListener {
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp, private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
@Nullable Runnable afterCopy) { @Nullable Runnable afterCopy) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { Attachment a =
Attachment a = attachmentReader.getAttachment(attachment.getHeader());
attachmentReader.getAttachment(attachment.getHeader()); copyImageFromDb(a, osp, afterCopy);
copyImageFromDb(a, osp, afterCopy); }, e -> {
} catch (DbException e) { logException(LOG, WARNING, e);
logException(LOG, WARNING, e); saveState.postEvent(true);
saveState.postEvent(true);
}
}); });
} }

View File

@@ -79,6 +79,12 @@ public class ForumActivity extends
} }
} }
@Override
public void onStart() {
super.onStart();
viewModel.clearForumPostNotification();
}
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result,
@Nullable Intent data) { @Nullable Intent data) {
@@ -103,16 +109,16 @@ public class ForumActivity extends
// Handle presses on the action bar items // Handle presses on the action bar items
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.action_forum_share) { if (itemId == R.id.action_forum_share) {
Intent i = new Intent(this, ShareForumActivity.class); Intent i2 = new Intent(this, ShareForumActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes()); i2.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_SHARE_FORUM); startActivityForResult(i2, REQUEST_SHARE_FORUM);
return true; return true;
} else if (itemId == R.id.action_forum_sharing_status) { } else if (itemId == R.id.action_forum_sharing_status) {
Intent i = new Intent(this, ForumSharingStatusActivity.class); Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP); i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes()); i3.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i); startActivity(i3);
return true; return true;
} else if (itemId == R.id.action_forum_delete) { } else if (itemId == R.id.action_forum_delete) {
showUnsubscribeDialog(); showUnsubscribeDialog();

View File

@@ -40,10 +40,8 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID; import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
@@ -164,15 +162,11 @@ class ForumListViewModel extends DbViewModel implements EventListener {
} }
void loadForumInvitations() { void loadForumInvitations() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); int available = forumSharingManager.getInvitations().size();
int available = forumSharingManager.getInvitations().size(); logDuration(LOG, "Loading available", start);
logDuration(LOG, "Loading available", start); numInvitations.postValue(available);
numInvitations.postValue(available);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -26,6 +26,7 @@ import org.briarproject.briar.android.threaded.ThreadListViewModel;
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;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumInvitationResponse; import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumManager; import org.briarproject.briar.api.forum.ForumManager;
@@ -50,10 +51,8 @@ import androidx.lifecycle.MutableLiveData;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -93,9 +92,8 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
if (f.getGroupId().equals(groupId)) { if (f.getGroupId().equals(groupId)) {
LOG.info("Forum post received, adding..."); LOG.info("Forum post received, adding...");
ForumPostItem item = ForumPostItem item = buildItem(f.getHeader(), f.getText());
new ForumPostItem(f.getHeader(), f.getText()); addItem(item);
addItem(item, false);
} }
} else if (e instanceof ForumInvitationResponseReceivedEvent) { } else if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent f = ForumInvitationResponseReceivedEvent f =
@@ -116,20 +114,14 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
} }
} }
protected void clearNotifications() { void clearForumPostNotification() {
notificationManager.clearForumPostNotification(groupId); notificationManager.clearForumPostNotification(groupId);
} }
LiveData<Forum> loadForum() { LiveData<Forum> loadForum() {
MutableLiveData<Forum> forum = new MutableLiveData<>(); MutableLiveData<Forum> forum = new MutableLiveData<>();
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forum.postValue(forumManager.getForum(groupId)));
Forum f = forumManager.getForum(groupId);
forum.postValue(f);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
return forum; return forum;
} }
@@ -140,35 +132,19 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
List<ForumPostHeader> headers = List<ForumPostHeader> headers =
forumManager.getPostHeaders(txn, groupId); forumManager.getPostHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start); logDuration(LOG, "Loading headers", start);
start = now(); return createItems(txn, headers, this::buildItem);
List<ForumPostItem> items = new ArrayList<>();
for (ForumPostHeader header : headers) {
items.add(loadItem(txn, header));
}
logDuration(LOG, "Loading bodies and creating items", start);
return items;
}, this::setItems); }, this::setItems);
} }
private ForumPostItem loadItem(Transaction txn, ForumPostHeader header)
throws DbException {
String text = forumManager.getPostText(txn, header.getId());
return new ForumPostItem(header, text);
}
@Override @Override
public void createAndStoreMessage(String text, public void createAndStoreMessage(String text,
@Nullable MessageId parentId) { @Nullable MessageId parentId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { LocalAuthor author = identityManager.getLocalAuthor();
LocalAuthor author = identityManager.getLocalAuthor(); GroupCount count = forumManager.getGroupCount(groupId);
GroupCount count = forumManager.getGroupCount(groupId); long timestamp = max(count.getLatestMsgTime() + 1,
long timestamp = max(count.getLatestMsgTime() + 1, clock.currentTimeMillis());
clock.currentTimeMillis()); createMessage(text, timestamp, parentId, author);
createMessage(text, timestamp, parentId, author);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -183,47 +159,43 @@ class ForumViewModel extends ThreadListViewModel<ForumPostItem> {
} }
private void storePost(ForumPost msg, String text) { private void storePost(ForumPost msg, String text) {
runOnDbThread(false, txn -> { runOnDbThreadOrLogException(() -> {
long start = now(); long start = now();
ForumPostHeader header = forumManager.addLocalPost(txn, msg); ForumPostHeader header = forumManager.addLocalPost(msg);
addItemAsync(buildItem(header, text));
logDuration(LOG, "Storing forum post", start); logDuration(LOG, "Storing forum post", start);
txn.attach(() -> { });
ForumPostItem item = new ForumPostItem(header, text); }
addItem(item, true);
}); private ForumPostItem buildItem(ForumPostHeader header, String text) {
}, e -> logException(LOG, WARNING, e)); return new ForumPostItem(header, text);
}
@Override
protected String loadMessageText(Transaction txn, PostHeader header)
throws DbException {
return forumManager.getPostText(txn, header.getId());
} }
@Override @Override
protected void markItemRead(ForumPostItem item) { protected void markItemRead(ForumPostItem item) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forumManager.setReadFlag(groupId, item.getId(), true));
forumManager.setReadFlag(groupId, item.getId(), true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
public void loadSharingContacts() { public void loadSharingContacts() {
runOnDbThread(true, txn -> { runOnDbThreadOrLogException(true, txn -> {
Collection<Contact> contacts = Collection<Contact> contacts =
forumSharingManager.getSharedWith(txn, groupId); forumSharingManager.getSharedWith(txn, groupId);
Collection<ContactId> contactIds = new ArrayList<>(contacts.size()); Collection<ContactId> contactIds = new ArrayList<>(contacts.size());
for (Contact c : contacts) contactIds.add(c.getId()); for (Contact c : contacts) contactIds.add(c.getId());
txn.attach(() -> sharingController.addAll(contactIds)); txn.attach(() -> sharingController.addAll(contactIds));
}, e -> logException(LOG, WARNING, e)); });
} }
void deleteForum() { void deleteForum() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { forumManager.removeForum(forumManager.getForum(groupId)));
Forum f = forumManager.getForum(groupId);
forumManager.removeForum(f);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
Toast.makeText(getApplication(), R.string.forum_left_toast, Toast.makeText(getApplication(), R.string.forum_left_toast,
LENGTH_SHORT).show(); LENGTH_SHORT).show();
} }

View File

@@ -4,7 +4,6 @@ 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;
@@ -18,16 +17,6 @@ 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,10 +21,6 @@ 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

@@ -1,16 +0,0 @@
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

@@ -1,61 +0,0 @@
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

@@ -1,16 +0,0 @@
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

@@ -1,77 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,13 +1,17 @@
package org.briarproject.briar.android.login; package org.briarproject.briar.android.login;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import static org.briarproject.briar.android.util.UiUtils.getDialogIcon; import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static java.util.Objects.requireNonNull;
@NotNullByDefault @NotNullByDefault
class LoginUtils { class LoginUtils {
@@ -15,7 +19,9 @@ class LoginUtils {
static AlertDialog createKeyStrengthenerErrorDialog(Context ctx) { static AlertDialog createKeyStrengthenerErrorDialog(Context ctx) {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme); new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
builder.setIcon(getDialogIcon(ctx, R.drawable.alerts_and_states_error)); Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
builder.setIcon(icon);
builder.setTitle(R.string.dialog_title_cannot_check_password); builder.setTitle(R.string.dialog_title_cannot_check_password);
builder.setMessage(R.string.dialog_message_cannot_check_password); builder.setMessage(R.string.dialog_message_cannot_check_password);
builder.setPositiveButton(R.string.ok, null); builder.setPositiveButton(R.string.ok, null);

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.navdrawer;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -60,7 +59,6 @@ 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;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.view.View.GONE; import static android.view.View.GONE;
@@ -68,7 +66,6 @@ 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.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
@@ -298,12 +295,6 @@ public class NavDrawerActivity extends BriarActivity implements
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
@@ -442,7 +433,8 @@ public class NavDrawerActivity extends BriarActivity implements
Transport t = getItem(position); Transport t = getItem(position);
ImageView icon = view.findViewById(R.id.imageView); ImageView icon = view.findViewById(R.id.imageView);
icon.setImageResource(t.iconDrawable); icon.setImageDrawable(ContextCompat.getDrawable(
NavDrawerActivity.this, t.iconDrawable));
icon.setColorFilter(ContextCompat.getColor( icon.setColorFilter(ContextCompat.getColor(
NavDrawerActivity.this, t.iconColor)); NavDrawerActivity.this, t.iconColor));
@@ -484,13 +476,11 @@ public class NavDrawerActivity extends BriarActivity implements
private void showTransportsOnboarding(boolean show, ImageView imageView) { private void showTransportsOnboarding(boolean show, ImageView imageView) {
if (show) { if (show) {
int color = resolveColorAttribute(this, R.attr.colorControlNormal); int color = resolveColorAttribute(this, R.attr.colorControlNormal);
Drawable drawable = VectorDrawableCompat
.create(getResources(), R.drawable.transport_tor, null);
new MaterialTapTargetPrompt.Builder(NavDrawerActivity.this, new MaterialTapTargetPrompt.Builder(NavDrawerActivity.this,
R.style.OnboardingDialogTheme).setTarget(imageView) R.style.OnboardingDialogTheme).setTarget(imageView)
.setPrimaryText(R.string.network_settings_title) .setPrimaryText(R.string.network_settings_title)
.setSecondaryText(R.string.transports_onboarding_text) .setSecondaryText(R.string.transports_onboarding_text)
.setIconDrawable(drawable) .setIcon(R.drawable.transport_tor)
.setIconDrawableColourFilter(color) .setIconDrawableColourFilter(color)
.setBackgroundColour( .setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary)) ContextCompat.getColor(this, R.color.briar_primary))

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.navdrawer;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -66,34 +65,27 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void checkExpiryWarning() { void checkExpiryWarning() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
settingsManager.getSettings(SETTINGS_NAMESPACE);
int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0);
if (warningInt == 0) { if (warningInt == 0) {
// we have not warned before // we have not warned before
showExpiryWarning.postValue(true);
} else {
long warningLong = warningInt * 1000L;
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry = (EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
showExpiryWarning.postValue(true);
} else if (daysBeforeExpiry <= 3 && daysSinceLastWarning > 0) {
showExpiryWarning.postValue(true); showExpiryWarning.postValue(true);
} else { } else {
long warningLong = warningInt * 1000L; showExpiryWarning.postValue(false);
long now = System.currentTimeMillis();
long daysSinceLastWarning =
(now - warningLong) / DAYS.toMillis(1);
long daysBeforeExpiry =
(EXPIRY_DATE - now) / DAYS.toMillis(1);
if (daysSinceLastWarning >= 30) {
showExpiryWarning.postValue(true);
} else if (daysBeforeExpiry <= 3 &&
daysSinceLastWarning > 0) {
showExpiryWarning.postValue(true);
} else {
showExpiryWarning.postValue(false);
}
} }
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
}); });
} }
@@ -101,15 +93,11 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void expiryWarningDismissed() { void expiryWarningDismissed() {
showExpiryWarning.setValue(false); showExpiryWarning.setValue(false);
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = new Settings();
Settings settings = new Settings(); int date = (int) (System.currentTimeMillis() / 1000L);
int date = (int) (System.currentTimeMillis() / 1000L); settings.putInt(EXPIRY_DATE_WARNING, date);
settings.putInt(EXPIRY_DATE_WARNING, date); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -125,15 +113,12 @@ public class NavDrawerViewModel extends DbViewModel {
return; return;
} }
runOnDbThread(() -> { runOnDbThread(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true);
settingsManager.getSettings(SETTINGS_NAMESPACE); shouldAskForDozeWhitelisting.postValue(ask);
boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); }, e -> {
shouldAskForDozeWhitelisting.postValue(ask); logException(LOG, WARNING, e);
} catch (DbException e) { shouldAskForDozeWhitelisting.postValue(true);
logException(LOG, WARNING, e);
shouldAskForDozeWhitelisting.postValue(true);
}
}); });
} }
@@ -145,30 +130,21 @@ public class NavDrawerViewModel extends DbViewModel {
@UiThread @UiThread
void checkTransportsOnboarding() { void checkTransportsOnboarding() {
if (showTransportsOnboarding.getValue() != null) return; if (showTransportsOnboarding.getValue() != null) return;
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
Settings settings = boolean show =
settingsManager.getSettings(SETTINGS_NAMESPACE); settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
boolean show = showTransportsOnboarding.postValue(show);
settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
showTransportsOnboarding.postValue(show);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@UiThread @UiThread
void transportsOnboardingShown() { void transportsOnboardingShown() {
showTransportsOnboarding.setValue(false); showTransportsOnboarding.setValue(false);
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { Settings settings = new Settings();
Settings settings = new Settings(); settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false); settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
} }

View File

@@ -45,12 +45,10 @@ import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.STATE_ON; import static android.bluetooth.BluetoothAdapter.STATE_ON;
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.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
@@ -185,20 +183,16 @@ public class PluginViewModel extends DbViewModel implements EventListener {
} }
private void loadSettings() { private void loadSettings() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { boolean tor = isPluginEnabled(TorConstants.ID,
boolean tor = isPluginEnabled(TorConstants.ID, TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE); torEnabledSetting.postValue(tor);
torEnabledSetting.postValue(tor); boolean wifi = isPluginEnabled(LanTcpConstants.ID,
boolean wifi = isPluginEnabled(LanTcpConstants.ID, LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE); wifiEnabledSetting.postValue(wifi);
wifiEnabledSetting.postValue(wifi); boolean bt = isPluginEnabled(BluetoothConstants.ID,
boolean bt = isPluginEnabled(BluetoothConstants.ID, BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE); btEnabledSetting.postValue(bt);
btEnabledSetting.postValue(bt);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -222,14 +216,10 @@ public class PluginViewModel extends DbViewModel implements EventListener {
} }
private void mergeSettings(Settings s, String namespace) { private void mergeSettings(Settings s, String namespace) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); settingsManager.mergeSettings(s, namespace);
settingsManager.mergeSettings(s, namespace); logDuration(LOG, "Merging settings", start);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -141,7 +141,8 @@ public class TransportsActivity extends BriarActivity {
Transport t = getItem(position); Transport t = getItem(position);
ImageView icon = view.findViewById(R.id.icon); ImageView icon = view.findViewById(R.id.icon);
icon.setImageResource(t.iconDrawable); icon.setImageDrawable(ContextCompat.getDrawable(
TransportsActivity.this, t.iconDrawable));
icon.setColorFilter(ContextCompat.getColor( icon.setColorFilter(ContextCompat.getColor(
TransportsActivity.this, t.iconColor)); TransportsActivity.this, t.iconColor));

View File

@@ -25,7 +25,6 @@ import androidx.lifecycle.ViewModelProvider;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_TEXT_LENGTH;
@@ -81,11 +80,16 @@ public class GroupActivity extends
setGroupEnabled(false); setGroupEnabled(false);
viewModel.isDissolved().observe(this, dissolved -> { viewModel.isDissolved().observe(this, dissolved -> {
setGroupEnabled(!dissolved); setGroupEnabled(!dissolved);
// only show dialog when no prior state if (dissolved) onGroupDissolved();
if (dissolved && state == null) onGroupDissolved();
}); });
} }
@Override
public void onStart() {
super.onStart();
viewModel.clearGroupMessageNotifications();
}
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar // Inflate the menu items for use in the action bar
@@ -112,26 +116,26 @@ public class GroupActivity extends
startActivity(i); startActivity(i);
return true; return true;
} else if (itemId == R.id.action_group_reveal) { } else if (itemId == R.id.action_group_reveal) {
if (requireNonNull(viewModel.isCreator().getValue())) if (viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
Intent i = new Intent(this, RevealContactsActivity.class); Intent i = new Intent(this, RevealContactsActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i); startActivity(i);
return true; return true;
} else if (itemId == R.id.action_group_invite) { } else if (itemId == R.id.action_group_invite) {
if (!requireNonNull(viewModel.isCreator().getValue())) if (!viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
Intent i = new Intent(this, GroupInviteActivity.class); Intent i = new Intent(this, GroupInviteActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes()); i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_GROUP_INVITE); startActivityForResult(i, REQUEST_GROUP_INVITE);
return true; return true;
} else if (itemId == R.id.action_group_leave) { } else if (itemId == R.id.action_group_leave) {
if (requireNonNull(viewModel.isCreator().getValue())) if (viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
showLeaveGroupDialog(); showLeaveGroupDialog();
return true; return true;
} else if (itemId == R.id.action_group_dissolve) { } else if (itemId == R.id.action_group_dissolve) {
if (!requireNonNull(viewModel.isCreator().getValue())) if (!viewModel.isCreator().getValue())
throw new IllegalStateException(); throw new IllegalStateException();
showDissolveGroupDialog(); showDissolveGroupDialog();
return true; return true;
@@ -154,8 +158,7 @@ public class GroupActivity extends
@Override @Override
public void onReplyClick(GroupMessageItem item) { public void onReplyClick(GroupMessageItem item) {
Boolean isDissolved = viewModel.isDissolved().getValue(); if (!viewModel.isDissolved().getValue()) super.onReplyClick(item);
if (isDissolved != null && !isDissolved) super.onReplyClick(item);
} }
private void setGroupEnabled(boolean enabled) { private void setGroupEnabled(boolean enabled) {
@@ -198,7 +201,7 @@ public class GroupActivity extends
viewModel.deletePrivateGroup(); viewModel.deletePrivateGroup();
} }
private void onGroupDissolved() { public void onGroupDissolved() {
AlertDialog.Builder builder = AlertDialog.Builder builder =
new AlertDialog.Builder(this, R.style.BriarDialogTheme); new AlertDialog.Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.groups_dissolved_dialog_title)); builder.setTitle(getString(R.string.groups_dissolved_dialog_title));

View File

@@ -25,6 +25,7 @@ import org.briarproject.briar.android.threaded.ThreadListViewModel;
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;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.api.privategroup.GroupMember; import org.briarproject.briar.api.privategroup.GroupMember;
import org.briarproject.briar.api.privategroup.GroupMessage; import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory; import org.briarproject.briar.api.privategroup.GroupMessageFactory;
@@ -51,10 +52,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -102,10 +101,7 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
if (!g.isLocal() && g.getGroupId().equals(groupId)) { if (!g.isLocal() && g.getGroupId().equals(groupId)) {
LOG.info("Group message received, adding..."); LOG.info("Group message received, adding...");
GroupMessageItem item = buildItem(g.getHeader(), g.getText()); GroupMessageItem item = buildItem(g.getHeader(), g.getText());
addItem(item, false); addItem(item);
// In case the join message comes from the creator,
// we need to reload the sharing contacts
// in case it was delayed and the sharing count is wrong (#850).
if (item instanceof JoinMessageItem && if (item instanceof JoinMessageItem &&
(((JoinMessageItem) item).isInitial())) { (((JoinMessageItem) item).isInitial())) {
loadSharingContacts(); loadSharingContacts();
@@ -135,25 +131,21 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} }
@Override @Override
protected void performInitialLoad() { public void setGroupId(GroupId groupId) {
super.performInitialLoad(); super.setGroupId(groupId);
loadPrivateGroup(groupId); loadPrivateGroup(groupId);
} }
protected void clearNotifications() { public void clearGroupMessageNotifications() {
notificationManager.clearGroupMessageNotification(groupId); notificationManager.clearGroupMessageNotification(groupId);
} }
private void loadPrivateGroup(GroupId groupId) { private void loadPrivateGroup(GroupId groupId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { PrivateGroup g = privateGroupManager.getPrivateGroup(groupId);
PrivateGroup g = privateGroupManager.getPrivateGroup(groupId); privateGroup.postValue(g);
privateGroup.postValue(g); Author author = identityManager.getLocalAuthor();
Author author = identityManager.getLocalAuthor(); isCreator.postValue(g.getCreator().equals(author));
isCreator.postValue(g.getCreator().equals(author));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -168,28 +160,10 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
List<GroupMessageHeader> headers = List<GroupMessageHeader> headers =
privateGroupManager.getHeaders(txn, groupId); privateGroupManager.getHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start); logDuration(LOG, "Loading headers", start);
start = now(); return createItems(txn, headers, this::buildItem);
List<GroupMessageItem> items = new ArrayList<>();
for (GroupMessageHeader header : headers) {
items.add(loadItem(txn, header));
}
logDuration(LOG, "Loading bodies and creating items", start);
return items;
}, this::setItems); }, this::setItems);
} }
private GroupMessageItem loadItem(Transaction txn,
GroupMessageHeader header) throws DbException {
String text;
if (header instanceof JoinMessageHeader) {
// will be looked up later
text = "";
} else {
text = privateGroupManager.getMessageText(txn, header.getId());
}
return buildItem(header, text);
}
private GroupMessageItem buildItem(GroupMessageHeader header, String text) { private GroupMessageItem buildItem(GroupMessageHeader header, String text) {
if (header instanceof JoinMessageHeader) { if (header instanceof JoinMessageHeader) {
return new JoinMessageItem((JoinMessageHeader) header, text); return new JoinMessageItem((JoinMessageHeader) header, text);
@@ -197,21 +171,27 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
return new GroupMessageItem(header, text); return new GroupMessageItem(header, text);
} }
@Override
protected String loadMessageText(
Transaction txn, PostHeader header) throws DbException {
if (header instanceof JoinMessageHeader) {
// will be looked up later
return "";
}
return privateGroupManager.getMessageText(txn, header.getId());
}
@Override @Override
public void createAndStoreMessage(String text, public void createAndStoreMessage(String text,
@Nullable MessageId parentId) { @Nullable MessageId parentId) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { LocalAuthor author = identityManager.getLocalAuthor();
LocalAuthor author = identityManager.getLocalAuthor(); MessageId previousMsgId =
MessageId previousMsgId = privateGroupManager.getPreviousMsgId(groupId);
privateGroupManager.getPreviousMsgId(groupId); GroupCount count = privateGroupManager.getGroupCount(groupId);
GroupCount count = privateGroupManager.getGroupCount(groupId); long timestamp = count.getLatestMsgTime();
long timestamp = count.getLatestMsgTime(); timestamp = max(clock.currentTimeMillis(), timestamp + 1);
timestamp = max(clock.currentTimeMillis(), timestamp + 1); createMessage(text, timestamp, parentId, author, previousMsgId);
createMessage(text, timestamp, parentId, author, previousMsgId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
@@ -227,49 +207,36 @@ class GroupViewModel extends ThreadListViewModel<GroupMessageItem> {
} }
private void storePost(GroupMessage msg, String text) { private void storePost(GroupMessage msg, String text) {
runOnDbThread(false, txn -> { runOnDbThreadOrLogException(() -> {
long start = now(); long start = now();
GroupMessageHeader header = GroupMessageHeader header =
privateGroupManager.addLocalMessage(txn, msg); privateGroupManager.addLocalMessage(msg);
addItemAsync(buildItem(header, text));
logDuration(LOG, "Storing group message", start); logDuration(LOG, "Storing group message", start);
txn.attach(() -> });
addItem(buildItem(header, text), true)
);
}, e -> logException(LOG, WARNING, e));
} }
@Override @Override
protected void markItemRead(GroupMessageItem item) { protected void markItemRead(GroupMessageItem item) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { privateGroupManager.setReadFlag(groupId, item.getId(), true));
privateGroupManager.setReadFlag(groupId, item.getId(), true);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
public void loadSharingContacts() { public void loadSharingContacts() {
runOnDbThread(true, txn -> { runOnDbThreadOrLogException(true, txn -> {
Collection<GroupMember> members = Collection<GroupMember> members =
privateGroupManager.getMembers(txn, groupId); privateGroupManager.getMembers(txn, groupId);
Collection<ContactId> contactIds = new ArrayList<>(); Collection<ContactId> contactIds = new ArrayList<>();
for (GroupMember m : members) { for (GroupMember m : members) {
if (m.getContactId() != null) if (m.getContactId() != null) contactIds.add(m.getContactId());
contactIds.add(m.getContactId());
} }
txn.attach(() -> sharingController.addAll(contactIds)); txn.attach(() -> sharingController.addAll(contactIds));
}, e -> logException(LOG, WARNING, e)); });
} }
void deletePrivateGroup() { void deletePrivateGroup() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { privateGroupManager.removePrivateGroup(groupId));
privateGroupManager.removePrivateGroup(groupId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
LiveData<PrivateGroup> getPrivateGroup() { LiveData<PrivateGroup> getPrivateGroup() {

View File

@@ -16,7 +16,7 @@ class JoinMessageItem extends GroupMessageItem {
JoinMessageItem(JoinMessageHeader h, String text) { JoinMessageItem(JoinMessageHeader h, String text) {
super(h, text); super(h, text);
isInitial = h.isInitial(); this.isInitial = h.isInitial();
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.list;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.db.Transaction;
@@ -49,10 +48,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID; import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
@@ -200,25 +197,17 @@ class GroupListViewModel extends DbViewModel implements EventListener {
} }
void removeGroup(GroupId g) { void removeGroup(GroupId g) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { long start = now();
long start = now(); groupManager.removePrivateGroup(g);
groupManager.removePrivateGroup(g); logDuration(LOG, "Removing group", start);
logDuration(LOG, "Removing group", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }
void loadNumInvitations() { void loadNumInvitations() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { int i = groupInvitationManager.getInvitations().size();
int i = groupInvitationManager.getInvitations().size(); numInvitations.postValue(i);
numInvitations.postValue(i);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}); });
} }

View File

@@ -1,40 +1,29 @@
package org.briarproject.briar.android.reporting; package org.briarproject.briar.android.reporting;
import android.app.Application; import android.content.Context;
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
class BriarExceptionHandler implements UncaughtExceptionHandler { public class BriarExceptionHandler implements UncaughtExceptionHandler {
private final Application app; private final Context ctx;
private final LogEncrypter logEncrypter;
private final long appStartTime; private final long appStartTime;
@Inject public BriarExceptionHandler(Context ctx) {
BriarExceptionHandler(Application app, LogEncrypter logEncrypter) { this.ctx = ctx;
this.app = app; this.appStartTime = System.currentTimeMillis();
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(app.getApplicationContext(), startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime);
CrashReportActivity.class, e, appStartTime, logKey);
Process.killProcess(Process.myPid()); Process.killProcess(Process.myPid());
System.exit(10); System.exit(10);
} }

View File

@@ -25,6 +25,8 @@ 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;
@@ -39,6 +41,8 @@ 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;
@@ -70,8 +74,8 @@ class BriarReportCollector {
this.ctx = ctx; this.ctx = ctx;
} }
ReportData collectReportData(@Nullable Throwable t, long appStartTime, public ReportData collectReportData(@Nullable Throwable t,
String logs) { long appStartTime) {
ReportData reportData = new ReportData() ReportData reportData = new ReportData()
.add(getBasicInfo(t)) .add(getBasicInfo(t))
.add(getDeviceInfo()); .add(getDeviceInfo());
@@ -82,7 +86,7 @@ class BriarReportCollector {
.add(getStorage()) .add(getStorage())
.add(getConnectivity()) .add(getConnectivity())
.add(getBuildConfig()) .add(getBuildConfig())
.add(getLogcat(logs)) .add(getLogcat())
.add(getDeviceFeatures()); .add(getDeviceFeatures());
} }
@@ -309,8 +313,15 @@ class BriarReportCollector {
buildConfig); buildConfig);
} }
private ReportItem getLogcat(String logs) { private ReportItem getLogcat() {
return new ReportItem("Logcat", R.string.dev_report_logcat, logs); BriarApplication app = (BriarApplication) ctx.getApplicationContext();
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,7 +35,6 @@ 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;
@@ -57,8 +56,7 @@ 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);
byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT); viewModel.init(t, appStartTime);
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,8 +15,4 @@ 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,9 +11,6 @@ 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;
@@ -22,7 +19,6 @@ 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;
@@ -40,16 +36,13 @@ 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
class ReportViewModel extends AndroidViewModel { public 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;
@@ -65,39 +58,18 @@ class ReportViewModel extends AndroidViewModel {
private boolean isFeedback; private boolean isFeedback;
@Inject @Inject
ReportViewModel(@NonNull Application application, public ReportViewModel(@NonNull Application application,
CachingLogHandler logHandler, DevReporter reporter, PluginManager pluginManager) {
LogDecrypter logDecrypter,
DevReporter reporter,
PluginManager pluginManager) {
super(application); super(application);
collector = new BriarReportCollector(application); this.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(() -> {
String decryptedLogs; ReportData data = collector.collectReportData(t, appStartTime);
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();
} }
@@ -138,8 +110,8 @@ class ReportViewModel extends AndroidViewModel {
} }
/** /**
* The content of the report that will be loaded after * The content of the report
* {@link #init(Throwable, long, byte[])} was called. * that will be loaded after {@link #init(Throwable, long)} was called.
*/ */
LiveData<ReportData> getReportData() { LiveData<ReportData> getReportData() {
return reportData; return reportData;

View File

@@ -1,6 +1,5 @@
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;
@@ -8,7 +7,6 @@ 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;
@@ -29,13 +27,6 @@ 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

@@ -4,7 +4,8 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ItemReturningAdapter; import org.briarproject.briar.android.util.ItemReturningAdapter;
@@ -20,7 +21,8 @@ import androidx.recyclerview.widget.ListAdapter;
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION; import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
@UiThread @UiThread
@NotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ThreadItemAdapter<I extends ThreadItem> public class ThreadItemAdapter<I extends ThreadItem>
extends ListAdapter<I, BaseThreadItemViewHolder<I>> extends ListAdapter<I, BaseThreadItemViewHolder<I>>
implements ItemReturningAdapter<I> { implements ItemReturningAdapter<I> {
@@ -39,7 +41,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Override @Override
public boolean areContentsTheSame(I a, I b) { public boolean areContentsTheSame(I a, I b) {
return a.isHighlighted() == b.isHighlighted() && return a.isHighlighted() == b.isHighlighted() &&
a.isRead() == b.isRead(); a.isRead() && b.isRead();
} }
}); });
this.listener = listener; this.listener = listener;
@@ -61,7 +63,7 @@ public class ThreadItemAdapter<I extends ThreadItem>
ui.bind(item, listener); ui.bind(item, listener);
} }
int findItemPosition(MessageId id) { public int findItemPosition(MessageId id) {
for (int i = 0; i < getItemCount(); i++) { for (int i = 0; i < getItemCount(); i++) {
if (id.equals(getItem(i).getId())) return i; if (id.equals(getItem(i).getId())) return i;
} }
@@ -89,7 +91,8 @@ public class ThreadItemAdapter<I extends ThreadItem>
@Nullable @Nullable
I getHighlightedItem() { I getHighlightedItem() {
for (I item : getCurrentList()) { for (int i = 0; i < getItemCount(); i++) {
I item = getItem(i);
if (item.isHighlighted()) return item; if (item.isHighlighted()) return item;
} }
return null; return null;

View File

@@ -110,7 +110,7 @@ public abstract class ThreadListActivity<I extends ThreadItem, A extends ThreadI
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
getViewModel().blockAndClearNotifications(); getViewModel().blockNotifications();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }

View File

@@ -5,6 +5,7 @@ import android.app.Application;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager; import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -25,8 +26,11 @@ import org.briarproject.briar.android.viewmodel.LiveResult;
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.MessageTree; import org.briarproject.briar.api.client.MessageTree;
import org.briarproject.briar.api.client.PostHeader;
import org.briarproject.briar.client.MessageTreeImpl; import org.briarproject.briar.client.MessageTreeImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -39,16 +43,16 @@ import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import static java.util.Objects.requireNonNull;
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.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public abstract class ThreadListViewModel<I extends ThreadItem> public abstract class ThreadListViewModel<I extends ThreadItem>
extends DbViewModel implements EventListener { extends DbViewModel
implements EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(ThreadListViewModel.class.getName()); getLogger(ThreadListViewModel.class.getName());
@@ -61,7 +65,7 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
private final MessageTracker messageTracker; private final MessageTracker messageTracker;
private final EventBus eventBus; private final EventBus eventBus;
// UIThread @DatabaseExecutor
private final MessageTree<I> messageTree = new MessageTreeImpl<>(); private final MessageTree<I> messageTree = new MessageTreeImpl<>();
private final MutableLiveData<LiveResult<List<I>>> items = private final MutableLiveData<LiveResult<List<I>>> items =
new MutableLiveData<>(); new MutableLiveData<>();
@@ -73,9 +77,6 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
protected volatile GroupId groupId; protected volatile GroupId groupId;
@Nullable @Nullable
private MessageId replyId; private MessageId replyId;
/**
* Stored list position. Needs to be loaded and set before the list itself.
*/
private final AtomicReference<MessageId> storedMessageId = private final AtomicReference<MessageId> storedMessageId =
new AtomicReference<>(); new AtomicReference<>();
@@ -113,28 +114,19 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
* Needs to be called right after initialization, * Needs to be called right after initialization,
* before calling any other methods. * before calling any other methods.
*/ */
public final void setGroupId(GroupId groupId) {
boolean needsInitialLoad = this.groupId == null;
this.groupId = groupId;
if (needsInitialLoad) performInitialLoad();
}
@CallSuper @CallSuper
protected void performInitialLoad() { public void setGroupId(GroupId groupId) {
// load stored MessageId (last list position) before the list itself this.groupId = groupId;
loadStoredMessageId(); loadStoredMessageId();
loadItems(); loadItems();
loadSharingContacts(); loadSharingContacts();
} }
protected abstract void clearNotifications(); public void blockNotifications() {
void blockAndClearNotifications() {
notificationManager.blockNotification(groupId); notificationManager.blockNotification(groupId);
clearNotifications();
} }
void unblockNotifications() { public void unblockNotifications() {
notificationManager.unblockNotification(groupId); notificationManager.unblockNotification(groupId);
} }
@@ -151,16 +143,11 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
} }
private void loadStoredMessageId() { private void loadStoredMessageId() {
runOnDbThread(() -> { runOnDbThreadOrLogException(() -> {
try { storedMessageId.set(messageTracker.loadStoredMessageId(groupId));
storedMessageId if (LOG.isLoggable(INFO)) {
.set(messageTracker.loadStoredMessageId(groupId)); LOG.info("Loaded last top visible message id " +
if (LOG.isLoggable(INFO)) { storedMessageId);
LOG.info("Loaded last top visible message id " +
storedMessageId);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
} }
}); });
} }
@@ -174,67 +161,75 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
* Loads the ContactIds of all contacts the group is shared with * Loads the ContactIds of all contacts the group is shared with
* and adds them to {@link SharingController}. * and adds them to {@link SharingController}.
*/ */
protected abstract void loadSharingContacts(); public abstract void loadSharingContacts();
@UiThread @UiThread
protected void setItems(LiveResult<List<I>> items) { protected void setItems(LiveResult<List<I>> items) {
if (items.hasError()) { this.items.setValue(items);
this.items.setValue(items); }
} else {
messageTree.clear(); @DatabaseExecutor
// not null, because hasError() is false protected <H extends PostHeader> List<I> createItems(
messageTree.add(requireNonNull(items.getResultOrNull())); Transaction txn, Collection<H> headers, ItemGetter<H, I> itemGetter)
LiveResult<List<I>> result = throws DbException {
new LiveResult<>(messageTree.depthFirstOrder()); long start = now();
this.items.setValue(result); List<I> items = new ArrayList<>();
for (H header : headers) {
String text = loadMessageText(txn, header);
items.add(itemGetter.getItem(header, text));
} }
logDuration(LOG, "Loading bodies and creating items", start);
messageTree.clear();
messageTree.add(items);
return messageTree.depthFirstOrder();
} }
/** /**
* Add a remote item on the UI thread. * Add a remote item on the UI thread.
* * The list will not scroll, but show an unread indicator.
* @param scrollToItem whether the list will scroll to the newly added item
*/ */
@UiThread @UiThread
protected void addItem(I item, boolean scrollToItem) { protected void addItem(I item) {
// 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());
items.setValue(new LiveResult<>(messageTree.depthFirstOrder())); items.setValue(new LiveResult<>(messageTree.depthFirstOrder()));
} }
/**
* Add a local item from the DB thread.
* The list will scroll to the new item.
*/
@DatabaseExecutor
protected void addItemAsync(I item) {
messageTree.add(item);
scrollToItem.set(item.getId());
items.postValue(new LiveResult<>(messageTree.depthFirstOrder()));
}
@DatabaseExecutor
protected abstract String loadMessageText(Transaction txn,
PostHeader header) throws DbException;
@UiThread @UiThread
void setReplyId(@Nullable MessageId id) { public void setReplyId(@Nullable MessageId id) {
replyId = id; replyId = id;
} }
@UiThread @UiThread
@Nullable @Nullable
MessageId getReplyId() { public MessageId getReplyId() {
return replyId; return replyId;
} }
void storeMessageId(@Nullable MessageId messageId) { void storeMessageId(@Nullable MessageId messageId) {
if (messageId != null) { if (messageId != null) {
runOnDbThread(() -> { runOnDbThreadOrLogException(() ->
try { messageTracker.storeMessageId(groupId, messageId));
messageTracker.storeMessageId(groupId, messageId);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
} }
protected abstract void markItemRead(I item); protected abstract void markItemRead(I item);
/**
* Returns the {@link MessageId} of the item that was at the top of the
* list last time or null if there has been nothing stored, yet.
*/
@Nullable @Nullable
MessageId getAndResetRestoredMessageId() { MessageId getAndResetRestoredMessageId() {
return storedMessageId.getAndSet(null); return storedMessageId.getAndSet(null);
@@ -257,4 +252,8 @@ public abstract class ThreadListViewModel<I extends ThreadItem>
return scrollToItem.getAndSet(null); return scrollToItem.getAndSet(null);
} }
public interface ItemGetter<H extends PostHeader, I> {
I getItem(H header, String text);
}
} }

View File

@@ -54,7 +54,6 @@ import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import static android.content.Context.KEYGUARD_SERVICE; import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.Context.POWER_SERVICE; import static android.content.Context.POWER_SERVICE;
@@ -89,6 +88,7 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode; import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
import static androidx.core.content.ContextCompat.getColor; import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.content.ContextCompat.getSystemService; import static androidx.core.content.ContextCompat.getSystemService;
import static androidx.core.graphics.drawable.DrawableCompat.setTint; import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL; import static androidx.core.view.ViewCompat.LAYOUT_DIRECTION_RTL;
@@ -97,7 +97,6 @@ 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;
@@ -358,17 +357,16 @@ public class UiUtils {
} }
public static void triggerFeedback(Context ctx) { public static void triggerFeedback(Context ctx) {
startDevReportActivity(ctx, FeedbackActivity.class, null, null, null); startDevReportActivity(ctx, FeedbackActivity.class, 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 byte[] logKey) { @Nullable Long appStartTime) {
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);
} }
@@ -438,8 +436,7 @@ public class UiUtils {
} }
public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) { public static Drawable getDialogIcon(Context ctx, @DrawableRes int resId) {
Drawable icon = Drawable icon = getDrawable(ctx, resId);
VectorDrawableCompat.create(ctx.getResources(), resId, null);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary)); setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
return icon; return icon;
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.view;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.AttributeSet; import android.util.AttributeSet;
@@ -52,8 +53,9 @@ public class BriarRecyclerView extends FrameLayout {
R.styleable.BriarRecyclerView); R.styleable.BriarRecyclerView);
isScrollingToEnd = attributes isScrollingToEnd = attributes
.getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true); .getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true);
int drawableRes = attributes.getResourceId(R.styleable.BriarRecyclerView_emptyImage, -1); Drawable drawable = attributes
if (drawableRes != -1) setEmptyImage(drawableRes); .getDrawable(R.styleable.BriarRecyclerView_emptyImage);
if (drawable != null) setEmptyImage(drawable);
String emtpyText = String emtpyText =
attributes.getString(R.styleable.BriarRecyclerView_emptyText); attributes.getString(R.styleable.BriarRecyclerView_emptyText);
if (emtpyText != null) setEmptyText(emtpyText); if (emtpyText != null) setEmptyText(emtpyText);
@@ -137,6 +139,11 @@ public class BriarRecyclerView extends FrameLayout {
} }
} }
public void setEmptyImage(Drawable drawable) {
if (recyclerView == null) initViews();
emptyImage.setImageDrawable(drawable);
}
public void setEmptyImage(@DrawableRes int res) { public void setEmptyImage(@DrawableRes int res) {
if (recyclerView == null) initViews(); if (recyclerView == null) initViews();
emptyImage.setImageResource(res); emptyImage.setImageResource(res);

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@@ -29,7 +28,6 @@ import androidx.customview.view.AbsSavedState;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
@@ -255,14 +253,12 @@ public class TextAttachmentController extends TextSendController
public void showImageOnboarding(Activity activity) { public void showImageOnboarding(Activity activity) {
int color = resolveColorAttribute(activity, R.attr.colorControlNormal); int color = resolveColorAttribute(activity, R.attr.colorControlNormal);
Drawable drawable = VectorDrawableCompat
.create(activity.getResources(), R.drawable.ic_image, null);
new MaterialTapTargetPrompt.Builder(activity, new MaterialTapTargetPrompt.Builder(activity,
R.style.OnboardingDialogTheme).setTarget(sendButton) R.style.OnboardingDialogTheme).setTarget(sendButton)
.setPrimaryText(R.string.dialog_title_image_support) .setPrimaryText(R.string.dialog_title_image_support)
.setSecondaryText(R.string.dialog_message_image_support) .setSecondaryText(R.string.dialog_message_image_support)
.setBackgroundColour(getColor(activity, R.color.briar_primary)) .setBackgroundColour(getColor(activity, R.color.briar_primary))
.setIconDrawable(drawable) .setIcon(R.drawable.ic_image)
.setIconDrawableColourFilter(color) .setIconDrawableColourFilter(color)
.show(); .show();
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.briar.R;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.content.ContextCompat;
@UiThread @UiThread
public class TrustIndicatorView extends AppCompatImageView { public class TrustIndicatorView extends AppCompatImageView {
@@ -43,7 +44,7 @@ public class TrustIndicatorView extends AppCompatImageView {
default: default:
res = R.drawable.trust_indicator_unknown; res = R.drawable.trust_indicator_unknown;
} }
setImageResource(res); setImageDrawable(ContextCompat.getDrawable(getContext(), res));
setVisibility(VISIBLE); setVisibility(VISIBLE);
invalidate(); invalidate();

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.viewmodel;
import android.app.Application; import android.app.Application;
import org.briarproject.bramble.api.ThrowingRunnable;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbCallable; import org.briarproject.bramble.api.db.DbCallable;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
@@ -66,7 +67,8 @@ public abstract class DbViewModel extends AndroidViewModel {
* {@link RecyclerView.Adapter}, * {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead. * use {@link #loadList(DbCallable, UiConsumer)} instead.
*/ */
protected void runOnDbThread(Runnable task) { protected void runOnDbThread(ThrowingRunnable<Exception> task,
Consumer<Exception> err) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
lifecycleManager.waitForDatabase(); lifecycleManager.waitForDatabase();
@@ -74,6 +76,8 @@ public abstract class DbViewModel extends AndroidViewModel {
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for database"); LOG.warning("Interrupted while waiting for database");
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} catch (Exception e) {
err.accept(e);
} }
}); });
} }
@@ -101,6 +105,34 @@ public abstract class DbViewModel extends AndroidViewModel {
}); });
} }
/**
* Waits for the DB to open and runs the given task on the
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
* it's caught and logged.
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThreadOrLogException(boolean readOnly,
DbRunnable<Exception> task) {
runOnDbThread(readOnly, task, e -> logException(LOG, WARNING, e));
}
/**
* Waits for the DB to open and runs the given task on the
* {@link DatabaseExecutor}. If the task throws a {@link DbException}
* it's caught and logged.
* <p>
* If you need a list of items to be displayed in a
* {@link RecyclerView.Adapter},
* use {@link #loadList(DbCallable, UiConsumer)} instead.
*/
protected void runOnDbThreadOrLogException(
ThrowingRunnable<Exception> task) {
runOnDbThread(task, e -> logException(LOG, WARNING, e));
}
/** /**
* Loads a list of items on the {@link DatabaseExecutor} within a single * Loads a list of items on the {@link DatabaseExecutor} within a single
* {@link Transaction} and publishes it as a {@link LiveResult} * {@link Transaction} and publishes it as a {@link LiveResult}

View File

@@ -39,17 +39,6 @@ public class LiveEvent<T> extends LiveData<LiveEvent.ConsumableEvent<T>> {
super.observeForever(observer); super.observeForever(observer);
} }
/**
* Returns the last value of the event (even if already consumed)
* or null if there hasn't been any value so far.
*/
@Nullable
public T getLastValue() {
ConsumableEvent<T> event = getValue();
if (event == null) return null;
return event.content;
}
static class ConsumableEvent<T> { static class ConsumableEvent<T> {
private final T content; private final T content;

View File

@@ -39,8 +39,7 @@ 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_OLD_ID = "zForegroundService"; String ONGOING_CHANNEL_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

@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="@android:color/white"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</group>
</vector>

View File

@@ -1,15 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.92"
android:scaleY="0.92"
android:translateX="0.96"
android:translateY="0.96">
<path
android:fillColor="#FF000000"
android:pathData="M16,17V14H9V10H16V7L21,12L16,17M14,2A2,2 0,0 1,16 4V6H14V4H5V20H14V18H16V20A2,2 0,0 1,14 22H5A2,2 0,0 1,3 20V4A2,2 0,0 1,5 2H14Z"/>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 489 B

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