Compare commits

..

1 Commits

Author SHA1 Message Date
Torsten Grote
362b3f396f Remove response messages right after storing them 2018-11-27 11:43:54 -02:00
243 changed files with 2442 additions and 8381 deletions

View File

@@ -39,6 +39,31 @@
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>

View File

@@ -11,8 +11,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 26
versionCode 10106
versionName "1.1.6"
versionCode 10105
versionName "1.1.5"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@@ -30,8 +30,8 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip'
tor 'org.briarproject:tor-android:0.3.4.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'

View File

@@ -12,15 +12,11 @@ import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.util.IoUtils;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
class AndroidAccountManager extends AccountManagerImpl
implements AccountManager {
@@ -93,42 +89,20 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not clear shared preferences");
}
// Delete files, except lib and shared_prefs directories
Set<File> files = new HashSet<>();
File dataDir = new File(appContext.getApplicationInfo().dataDir);
@Nullable
File[] fileArray = dataDir.listFiles();
if (fileArray == null) {
File[] children = dataDir.listFiles();
if (children == null) {
LOG.warning("Could not list files in app data dir");
} else {
for (File file : fileArray) {
String name = file.getName();
for (File child : children) {
String name = child.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) {
files.add(file);
IoUtils.deleteFileOrDir(child);
}
}
}
files.add(appContext.getFilesDir());
files.add(appContext.getCacheDir());
addIfNotNull(files, appContext.getExternalCacheDir());
if (SDK_INT >= 19) {
for (File file : appContext.getExternalCacheDirs()) {
addIfNotNull(files, file);
}
}
if (SDK_INT >= 21) {
for (File file : appContext.getExternalMediaDirs()) {
addIfNotNull(files, file);
}
}
for (File file : files) {
IoUtils.deleteFileOrDir(file);
}
// Recreate the cache dir as some OpenGL drivers expect it to exist
if (!new File(dataDir, "cache").mkdirs())
if (!new File(dataDir, "cache").mkdir())
LOG.warning("Could not recreate cache dir");
}
private void addIfNotNull(Set<File> files, @Nullable File file) {
if (file != null) files.add(file);
}
}

View File

@@ -19,7 +19,9 @@ import javax.inject.Inject;
import static android.content.Intent.ACTION_BATTERY_CHANGED;
import static android.content.Intent.ACTION_POWER_CONNECTED;
import static android.content.Intent.ACTION_POWER_DISCONNECTED;
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
import static android.os.BatteryManager.EXTRA_STATUS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@@ -46,8 +48,9 @@ class AndroidBatteryManager implements BatteryManager, Service {
IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
Intent i = appContext.registerReceiver(null, filter);
if (i == null) return false;
int status = i.getIntExtra(EXTRA_PLUGGED, 0);
return status != 0;
int status = i.getIntExtra(EXTRA_STATUS, -1);
return status == BATTERY_STATUS_CHARGING ||
status == BATTERY_STATUS_FULL;
}
@Override

View File

@@ -112,8 +112,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
// Other directories should be deleted
File potatoDir = new File(testDir, ".potato");
File potatoFile = new File(potatoDir, "file");
File filesDir = new File(testDir, "filesDir");
File externalCacheDir = new File(testDir, "externalCacheDir");
context.checking(new Expectations() {{
oneOf(prefs).edit();
@@ -130,12 +128,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
will(returnValue(true));
oneOf(app).getApplicationInfo();
will(returnValue(applicationInfo));
oneOf(app).getFilesDir();
will(returnValue(filesDir));
oneOf(app).getCacheDir();
will(returnValue(cacheDir));
oneOf(app).getExternalCacheDir();
will(returnValue(externalCacheDir));
}});
assertTrue(dbDir.mkdirs());
@@ -148,8 +140,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertTrue(cacheFile.createNewFile());
assertTrue(potatoDir.mkdirs());
assertTrue(potatoFile.createNewFile());
assertTrue(filesDir.mkdirs());
assertTrue(externalCacheDir.mkdirs());
accountManager.deleteAccount();
@@ -163,8 +153,6 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(cacheFile.exists());
assertFalse(potatoDir.exists());
assertFalse(potatoFile.exists());
assertFalse(filesDir.exists());
assertFalse(externalCacheDir.exists());
}
@After

View File

@@ -1,45 +1,46 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:26.3.2:protos-26.3.2.jar:50238fb4298b297217b184b9cd93c14f83536fcee829eb0ca850bdb5b53251f0',
'com.android.tools.analytics-library:shared:26.3.2:shared-26.3.2.jar:ddd80dcf21905018b7c0583ba72b7282f446084d4952422609a09fbf8237ef71',
'com.android.tools.analytics-library:tracker:26.3.2:tracker-26.3.2.jar:28c575d2d1af003e96d7b375a668ee10b2673a2dd0f6438750aa8c3b42e7d0ad',
'com.android.tools.build:aapt2:3.3.2-5309881:aapt2-3.3.2-5309881-linux.jar:9beead7e8b1057359049f450538aadcd3eb2a5bc3fbdd5e3296cb8fdc2a22412',
'com.android.tools.build:apksig:3.3.2:apksig-3.3.2.jar:84c4aaa20127c6c1fe6bdd334b3f5df71f54ad080be9029c8a10f43b6a908acd',
'com.android.tools.build:apkzlib:3.3.2:apkzlib-3.3.2.jar:d34e523278e5dff565eba3ef3c089d515b2b5cc7b47dc77e2f3465e5e47176ac',
'com.android.tools.build:builder-model:3.3.2:builder-model-3.3.2.jar:055e3db0ecee9e06b9f024034999a29cd92cb1885207b37542126bd8bcc57f46',
'com.android.tools.build:builder-test-api:3.3.2:builder-test-api-3.3.2.jar:0b2e4cd7615bbcad14a3c91fe45ae26693508d06e40ba06c5968b8bc24416618',
'com.android.tools.build:builder:3.3.2:builder-3.3.2.jar:65649704da7aef0487235fd326f0f2e99ed5cf958e80f204496e6e08a42bd9f5',
'com.android.tools.build:gradle-api:3.3.2:gradle-api-3.3.2.jar:3cbd47e41bb70330dd72ec2c9fe51e6173554b484a03829b5a2de9e00841e040',
'com.android.tools.build:manifest-merger:26.3.2:manifest-merger-26.3.2.jar:05c4a6d8b02fb9f08744876477d0a68547c03a8a9069b1f086684fa04af97c33',
'com.android.tools.ddms:ddmlib:26.3.2:ddmlib-26.3.2.jar:d248da8a563d6e46d2c7ebbf371a4877e00510f4ca763c0bb272d5a281bf8b85',
'com.android.tools.external.com-intellij:intellij-core:26.3.2:intellij-core-26.3.2.jar:6c5ecc968230e9f4dcd0fef28885379feace1f0cd8130de6f61d649c86139bf3',
'com.android.tools.external.com-intellij:kotlin-compiler:26.3.2:kotlin-compiler-26.3.2.jar:1007d9b07ccb49cd8eaf30fda10ed4681d4714f2f9ab2ecda39b4e539cc51bbe',
'com.android.tools.external.org-jetbrains:uast:26.3.2:uast-26.3.2.jar:5d1833e562ea4f38a89708dfde695f0a162cbd39d003d3dde818c3fdc2b05317',
'com.android.tools.layoutlib:layoutlib-api:26.3.2:layoutlib-api-26.3.2.jar:d7e61e874ab95f5c350dd38b6a95b5c9dbe0083a02001884264cdb390cb255b8',
'com.android.tools.lint:lint-api:26.3.2:lint-api-26.3.2.jar:5867dfd7fb4a4e161a816a5d29d045f9b542d34594c00a1efec46fb4cd0e1033',
'com.android.tools.lint:lint-checks:26.3.2:lint-checks-26.3.2.jar:4b163b9c93790d2771e92ba8de58a0d9e0671ffcf2ccef3cf496efd442e27517',
'com.android.tools.lint:lint-gradle-api:26.3.2:lint-gradle-api-26.3.2.jar:54cb282e0c054f9bed3f51302ce08b003c8ab7961dfd5a4f6de26c23cc23062f',
'com.android.tools.lint:lint-gradle:26.3.2:lint-gradle-26.3.2.jar:bb139615f4ce97d42cc394b9389b49b76a6eb85be6785a5d272991543b519013',
'com.android.tools.lint:lint:26.3.2:lint-26.3.2.jar:ef7b369f8a56a92ccb0f4c1c357666b9339e4a711a9d84747d446441746cfe4e',
'com.android.tools:annotations:26.3.2:annotations-26.3.2.jar:5bcce8e98b6a2f5ccf13ebcefd8f734e0b35f8b19e456575665631442ce1f7a1',
'com.android.tools:common:26.3.2:common-26.3.2.jar:d9f8e7f0669e9a701568e3db6a87c89cf12d8fa6811c9991e969f950215ecfac',
'com.android.tools:dvlib:26.3.2:dvlib-26.3.2.jar:d84aad56161c7773579303d69714ded6897c64c6ddfd7d456e453231e4dfe811',
'com.android.tools:repository:26.3.2:repository-26.3.2.jar:da611eeb06e9ab8750d25b9e2901e10db8e5ec6304eb4c8b7103d39e0921ea40',
'com.android.tools:sdk-common:26.3.2:sdk-common-26.3.2.jar:82823a3bf25e64fac33a286490f0cf5ac50c2cdb3c540149b030896bb44bf96c',
'com.android.tools:sdklib:26.3.2:sdklib-26.3.2.jar:424d15492af67321900963238646d27495ab60de2a5b19e6a416963bc5d6932b',
'com.android.tools.analytics-library:protos:26.2.1:protos-26.2.1.jar:2f371f5b1f551e85ab08be4d6a2873471b3d44afd1ebf6aa3298f3b796bf691f',
'com.android.tools.analytics-library:shared:26.2.1:shared-26.2.1.jar:4c1e4e705fa4d45f23aaea230557f6508155012d9c296337787c1d7b26a97f5a',
'com.android.tools.analytics-library:tracker:26.2.1:tracker-26.2.1.jar:4a624ecc976539f755ddb0bb8dfc2dd3d08326cfec59a098dbd70f701ca7fb75',
'com.android.tools.build:aapt2:3.2.1-4818971:aapt2-3.2.1-4818971-linux.jar:f431b6f96c91a2c155144b091a9c97d9805c589fe8efc9c930b6cd346cb60a1e',
'com.android.tools.build:apksig:3.2.1:apksig-3.2.1.jar:2b46f2feffea66037aab29e4261b2433c190194a6ef97b958511eb157f2ccba5',
'com.android.tools.build:apkzlib:3.2.1:apkzlib-3.2.1.jar:c39ad0313905932431fe81c8899c2cf39a4d92ad6c4edcaa4b25432f461452aa',
'com.android.tools.build:builder-model:3.2.1:builder-model-3.2.1.jar:a9f68e6abcec122f9cb5ad352d3f05a3eb03acbcdca95e4d25c16310c2c965ff',
'com.android.tools.build:builder-test-api:3.2.1:builder-test-api-3.2.1.jar:533ac6c2b5884bb54967a33791f2628dfdfac7981af39417a333b43d4379b6be',
'com.android.tools.build:builder:3.2.1:builder-3.2.1.jar:aedcbfd115dbe91d09b4113e66ef50589b558d0aa3b2f133b1d867c9b87fae83',
'com.android.tools.build:gradle-api:3.2.1:gradle-api-3.2.1.jar:57cf0ac5ac1dca8afdb3f62b94265e776e7dcfa641cc3844fb53a05193de208d',
'com.android.tools.build:manifest-merger:26.2.1:manifest-merger-26.2.1.jar:8830573263361035d38cfdcb51e2db94029c93865b21334f5fbf8a27984281a6',
'com.android.tools.ddms:ddmlib:26.2.1:ddmlib-26.2.1.jar:a4bf0a29a19980bf27269465cc782064656750b77c26728f82f9e148b705218b',
'com.android.tools.external.com-intellij:intellij-core:26.2.1:intellij-core-26.2.1.jar:4925ad1892c2687cb1a63427d440ef519c8c59215fefe0dc5d541d5d411fcafe',
'com.android.tools.external.com-intellij:kotlin-compiler:26.2.1:kotlin-compiler-26.2.1.jar:daa064fd708f340ee25fb9823c4c74104ac77f1370b76d907eb9ae6daec0a2ae',
'com.android.tools.external.org-jetbrains:uast:26.2.1:uast-26.2.1.jar:f10f7258d2ab9189562cc0f9ad838c0378fdba439229173390a99de02ebac75b',
'com.android.tools.layoutlib:layoutlib-api:26.2.1:layoutlib-api-26.2.1.jar:ddbf4fca123733fa011595b1cc1f4ac2937ed327b60990711fafc33c775c2ade',
'com.android.tools.lint:lint-api:26.2.1:lint-api-26.2.1.jar:3b57e739de567b98bc9ab56c2c0ee66fc026b4adf5843e8f9804ca0666a6f66e',
'com.android.tools.lint:lint-checks:26.2.1:lint-checks-26.2.1.jar:c86f4cc9aaee722ee4ad70062f7b5af91e9b041914af27adc09f545ab0fb3bc6',
'com.android.tools.lint:lint-gradle-api:26.2.1:lint-gradle-api-26.2.1.jar:2283e7af32e301565f2a797e531f0fc8c648077d457afb3ffdddbee638976c2f',
'com.android.tools.lint:lint-gradle:26.2.1:lint-gradle-26.2.1.jar:8fd90b2f3ec788cbb9801c07ab3e1ea2255aa31a6093157d7ea0ff13d0315ecb',
'com.android.tools.lint:lint-kotlin:26.2.1:lint-kotlin-26.2.1.jar:7a6a5d2b18f69cf1b900d857c2632b4c683713c533295933b8b759f8cab4a877',
'com.android.tools.lint:lint:26.2.1:lint-26.2.1.jar:7848b82ae988b90dee259ae7c7e86e05cbf52db6cd21c8bbd38ce7df08f3f8c5',
'com.android.tools:annotations:26.2.1:annotations-26.2.1.jar:7391c6a1e080174b96e64ceb078dadd31ce4d8a2d2fee0ec65be202126f90f24',
'com.android.tools:common:26.2.1:common-26.2.1.jar:a50aab2d6411ff68f4004a87c7e93d87d8e980a0ec3b352246549897ea2d78e5',
'com.android.tools:dvlib:26.2.1:dvlib-26.2.1.jar:72a83bf2839b1df9b1fbf67ba45d1bfb9f966cd774da4320c762b2be8f1688aa',
'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdk-common:26.2.1:sdk-common-26.2.1.jar:759d4b292ca69a35cf961fca377b54158fc6c88108978006999442e80a011cf4',
'com.android.tools:sdklib:26.2.1:sdklib-26.2.1.jar:248df7ad5eac4aeb6f96c394c76760de4b7b89ac056e54d0c21a739368b91b45',
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.0:gson-2.8.0.jar:c6221763bd79c4f1c3dc7f750b5f29a0bb38b367b81314c4f71896e340c40825',
'com.google.dagger:dagger-compiler:2.19:dagger-compiler-2.19.jar:27a4b202a2de908182edb261f8c0a264e08e5e4733d7514bc7fbf0d31da5c0fc',
'com.google.dagger:dagger-producers:2.19:dagger-producers-2.19.jar:a17663abe0fc38b676026950907d4c5f5e2bf338375415861eaff6e3bdb0b768',
'com.google.dagger:dagger-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939',
'com.google.errorprone:error_prone_annotations:2.0.18:error_prone_annotations-2.0.18.jar:cb4cfad870bf563a07199f3ebea5763f0dec440fcda0b318640b1feaa788656b',
'com.google.errorprone:error_prone_annotations:2.1.3:error_prone_annotations-2.1.3.jar:03d0329547c13da9e17c634d1049ea2ead093925e290567e1a364fd6b1fc7ff8',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:guava:23.0:guava-23.0.jar:7baa80df284117e5b945b19b98d367a85ea7b7801bd358ff657946c3bd1b6596',
'com.google.guava:guava:25.0-jre:guava-25.0-jre.jar:3fd4341776428c7e0e5c18a7c10de129475b69ab9d30aeafbb5c277bb6074fa9',
'com.google.guava:guava:26.0-jre:guava-26.0-jre.jar:a0e9cabad665bc20bcd2b01f108e5fc03f756e13aea80abaadb9f407033bea2c',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
@@ -67,22 +68,21 @@ dependencyVerification {
'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:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0',
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a',
'org.briarproject:obfs4proxy-android:0.0.7:obfs4proxy-android-0.0.7.zip:abdfb5d889d848de9bf214f9276abbf454808a505b870819eccc9a9e985bf617',
'org.briarproject:tor-android:0.3.4.8:tor-android-0.3.4.8.zip:989a0352d9d8d8172cd6c2137654e165e5d2beb10ed1211bab3814e224ad1926',
'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.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.codehaus.groovy:groovy-all:2.4.12:groovy-all-2.4.12.jar:6a56af4bd48903d56bec62821876cadefafd007360cc6bd0d8f7aa8d72b38be4',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.3.21:kotlin-reflect-1.3.21.jar:a3065c822633191e0a3e3ee12a29bec234fc4b2864a6bb87ef48cce3e9e0c26a',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.21:kotlin-stdlib-common-1.3.21.jar:cea61f7b611895e64f58569a9757fc0ab0d582f107211e1930e0ce2a0add52a7',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21:kotlin-stdlib-jdk7-1.3.21.jar:a87875604fd42140da6938ae4d35ee61081f4482536efc6d2615b8b626a198af',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.21:kotlin-stdlib-jdk8-1.3.21.jar:5823ed66ac122a1c55442ebca5a209a843ccd87f562edc31a787f3d2e47f74d4',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.21:kotlin-stdlib-1.3.21.jar:38ba2370d9f06f50433e06b2ca775b94473c2e2785f410926079ab793c72b034',
'org.jetbrains.kotlin:kotlin-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71:kotlin-stdlib-common-1.2.71.jar:63999687ff2fce8a592dd180ffbbf8f1d21c26b4044c55cdc74ff3cf3b3cf328',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71:kotlin-stdlib-jdk7-1.2.71.jar:b136bd61b240e07d4d92ce00d3bd1dbf584400a7bf5f220c2f3cd22446858082',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71:kotlin-stdlib-jdk8-1.2.71.jar:ac3c8abf47790b64b4f7e2509a53f0c145e061ac1612a597520535d199946ea9',
'org.jetbrains.kotlin:kotlin-stdlib:1.2.71:kotlin-stdlib-1.2.71.jar:4c895c270b87f5fec2a2796e1d89c15407ee821de961527c28588bb46afbc68b',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',

View File

@@ -0,0 +1,20 @@
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface ContactExchangeListener {
void contactExchangeSucceeded(Author remoteAuthor);
/**
* The exchange failed because the contact already exists.
*/
void duplicateContact(Author remoteAuthor);
/**
* A general failure.
*/
void contactExchangeFailed();
}

View File

@@ -41,7 +41,8 @@ public interface ContactExchangeTask {
/**
* Exchanges contact information with a remote peer.
*/
void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
void startExchange(ContactExchangeListener listener,
LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId,
boolean alice);
}

View File

@@ -13,8 +13,6 @@ import java.util.Collection;
import javax.annotation.Nullable;
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.FAILED;
@NotNullByDefault
public interface ContactManager {
@@ -54,35 +52,6 @@ public interface ContactManager {
long timestamp, boolean alice, boolean verified, boolean active)
throws DbException;
/**
* Returns the static link that needs to be sent to the contact to be added.
*/
String getRemoteContactLink();
/**
* Returns true if the given link is syntactically valid.
*/
boolean isValidRemoteContactLink(String link);
/**
* Requests a new contact to be added via the given {@code link}.
*
* @param link The link received from the contact we want to add.
* @param alias The alias the user has given this contact.
* @return A PendingContact representing the contact to be added.
*/
PendingContact addRemoteContactRequest(String link, String alias);
/**
* Returns a list of {@link PendingContact}s.
*/
Collection<PendingContact> getPendingContacts();
/**
* Removes a {@link PendingContact} that is in state {@link FAILED}.
*/
void removePendingContact(PendingContact pendingContact);
/**
* Returns the contact with the given ID.
*/

View File

@@ -1,54 +0,0 @@
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class PendingContact {
public enum PendingContactState {
WAITING_FOR_CONNECTION,
CONNECTED,
ADDING_CONTACT,
FAILED
}
private final PendingContactId id;
private final String alias;
private final PendingContactState state;
private final long timestamp;
public PendingContact(PendingContactId id, String alias,
PendingContactState state, long timestamp) {
this.id = id;
this.alias = alias;
this.state = state;
this.timestamp = timestamp;
}
public String getAlias() {
return alias;
}
public PendingContactState getState() {
return state;
}
public long getTimestamp() {
return timestamp;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object o) {
return o instanceof PendingContact &&
id.equals(((PendingContact) o).id);
}
}

View File

@@ -1,11 +0,0 @@
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.UniqueId;
public class PendingContactId extends UniqueId {
public PendingContactId(byte[] id) {
super(id);
}
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
@NotNullByDefault
public class ContactExchangeFailedEvent extends Event {
@Nullable
private final Author duplicateRemoteAuthor;
public ContactExchangeFailedEvent(@Nullable Author duplicateRemoteAuthor) {
this.duplicateRemoteAuthor = duplicateRemoteAuthor;
}
public ContactExchangeFailedEvent() {
this(null);
}
@Nullable
public Author getDuplicateRemoteAuthor() {
return duplicateRemoteAuthor;
}
public boolean wasDuplicateContact() {
return duplicateRemoteAuthor != null;
}
}

View File

@@ -1,20 +0,0 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class ContactExchangeSucceededEvent extends Event {
private final Author remoteAuthor;
public ContactExchangeSucceededEvent(Author remoteAuthor) {
this.remoteAuthor = remoteAuthor;
}
public Author getRemoteAuthor() {
return remoteAuthor;
}
}

View File

@@ -1,34 +0,0 @@
package org.briarproject.bramble.api.contact.event;
import org.briarproject.bramble.api.contact.PendingContact.PendingContactState;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a pending contact's state is changed.
*/
@Immutable
@NotNullByDefault
public class PendingContactStateChangedEvent extends Event {
private final PendingContactId id;
private final PendingContactState state;
public PendingContactStateChangedEvent(PendingContactId id,
PendingContactState state) {
this.id = id;
this.state = state;
}
public PendingContactId getId() {
return id;
}
public PendingContactState getPendingContactState() {
return state;
}
}

View File

@@ -16,7 +16,6 @@ public interface TorConstants {
String PREF_TOR_NETWORK = "network2";
String PREF_TOR_PORT = "port";
String PREF_TOR_MOBILE = "useMobileData";
String PREF_TOR_ONLY_WHEN_CHARGING = "onlyWhenCharging";
int PREF_TOR_NETWORK_AUTOMATIC = 0;
int PREF_TOR_NETWORK_WITHOUT_BRIDGES = 1;

View File

@@ -38,13 +38,6 @@ public interface ClientVersioningManager {
Visibility getClientVisibility(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException;
/**
* Returns the minor version of the given client that is supported by the
* given contact, or -1 if the contact does not support the client.
*/
int getClientMinorVersion(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException;
interface ClientVersioningHook {
void onClientVisibilityChanging(Transaction txn, Contact c,

View File

@@ -2,11 +2,10 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.event.ContactExchangeFailedEvent;
import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.data.BdfDictionary;
@@ -14,7 +13,6 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -65,7 +63,6 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private final ClientHelper clientHelper;
private final RecordReaderFactory recordReaderFactory;
private final RecordWriterFactory recordWriterFactory;
private final EventBus eventBus;
private final Clock clock;
private final ConnectionManager connectionManager;
private final ContactManager contactManager;
@@ -74,6 +71,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
private volatile ContactExchangeListener listener;
private volatile LocalAuthor localAuthor;
private volatile DuplexTransportConnection conn;
private volatile TransportId transportId;
@@ -83,9 +81,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
@Inject
ContactExchangeTaskImpl(DatabaseComponent db, ClientHelper clientHelper,
RecordReaderFactory recordReaderFactory,
RecordWriterFactory recordWriterFactory, EventBus eventBus,
Clock clock, ConnectionManager connectionManager,
ContactManager contactManager,
RecordWriterFactory recordWriterFactory, Clock clock,
ConnectionManager connectionManager, ContactManager contactManager,
TransportPropertyManager transportPropertyManager,
CryptoComponent crypto, StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory) {
@@ -93,7 +90,6 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
this.clientHelper = clientHelper;
this.recordReaderFactory = recordReaderFactory;
this.recordWriterFactory = recordWriterFactory;
this.eventBus = eventBus;
this.clock = clock;
this.connectionManager = connectionManager;
this.contactManager = contactManager;
@@ -104,9 +100,11 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
@Override
public void startExchange(LocalAuthor localAuthor, SecretKey masterSecret,
public void startExchange(ContactExchangeListener listener,
LocalAuthor localAuthor, SecretKey masterSecret,
DuplexTransportConnection conn, TransportId transportId,
boolean alice) {
this.listener = listener;
this.localAuthor = localAuthor;
this.conn = conn;
this.transportId = transportId;
@@ -125,8 +123,8 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
out = conn.getWriter().getOutputStream();
} catch (IOException e) {
logException(LOG, WARNING, e);
listener.contactExchangeFailed();
tryToClose(conn);
eventBus.broadcast(new ContactExchangeFailedEvent());
return;
}
@@ -136,7 +134,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
localProperties = transportPropertyManager.getLocalProperties();
} catch (DbException e) {
logException(LOG, WARNING, e);
eventBus.broadcast(new ContactExchangeFailedEvent());
listener.contactExchangeFailed();
tryToClose(conn);
return;
}
@@ -198,7 +196,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
}
} catch (IOException e) {
logException(LOG, WARNING, e);
eventBus.broadcast(new ContactExchangeFailedEvent());
listener.contactExchangeFailed();
tryToClose(conn);
return;
}
@@ -206,7 +204,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
// Verify the contact's signature
if (!verify(remoteInfo.author, remoteNonce, remoteInfo.signature)) {
LOG.warning("Invalid signature");
eventBus.broadcast(new ContactExchangeFailedEvent());
listener.contactExchangeFailed();
tryToClose(conn);
return;
}
@@ -223,17 +221,15 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
conn);
// Pseudonym exchange succeeded
LOG.info("Pseudonym exchange succeeded");
eventBus.broadcast(
new ContactExchangeSucceededEvent(remoteInfo.author));
listener.contactExchangeSucceeded(remoteInfo.author);
} catch (ContactExistsException e) {
logException(LOG, WARNING, e);
tryToClose(conn);
eventBus.broadcast(
new ContactExchangeFailedEvent(remoteInfo.author));
listener.duplicateContact(remoteInfo.author);
} catch (DbException e) {
logException(LOG, WARNING, e);
tryToClose(conn);
eventBus.broadcast(new ContactExchangeFailedEvent());
listener.contactExchangeFailed();
}
}

View File

@@ -3,8 +3,6 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
@@ -21,16 +19,12 @@ import org.briarproject.bramble.api.transport.KeyManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.contact.PendingContact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.UNKNOWN;
@@ -42,12 +36,6 @@ import static org.briarproject.bramble.util.StringUtils.toUtf8;
@NotNullByDefault
class ContactManagerImpl implements ContactManager {
private static final int LINK_LENGTH = 64;
private static final String REMOTE_CONTACT_LINK =
"briar://" + getRandomBase32String(LINK_LENGTH);
private static final Pattern LINK_REGEX =
Pattern.compile("(briar://)?([a-z2-7]{" + LINK_LENGTH + "})");
private final DatabaseComponent db;
private final KeyManager keyManager;
private final IdentityManager identityManager;
@@ -96,48 +84,6 @@ class ContactManagerImpl implements ContactManager {
verified, active));
}
@Override
public String getRemoteContactLink() {
// TODO replace with real implementation
return REMOTE_CONTACT_LINK;
}
@SuppressWarnings("SameParameterValue")
private static String getRandomBase32String(int length) {
Random random = new Random();
char[] c = new char[length];
for (int i = 0; i < length; i++) {
int character = random.nextInt(32);
if (character < 26) c[i] = (char) ('a' + character);
else c[i] = (char) ('2' + (character - 26));
}
return new String(c);
}
@Override
public boolean isValidRemoteContactLink(String link) {
return LINK_REGEX.matcher(link).matches();
}
@Override
public PendingContact addRemoteContactRequest(String link, String alias) {
// TODO replace with real implementation
PendingContactId id = new PendingContactId(link.getBytes());
return new PendingContact(id, alias, WAITING_FOR_CONNECTION,
System.currentTimeMillis());
}
@Override
public Collection<PendingContact> getPendingContacts() {
// TODO replace with real implementation
return emptyList();
}
@Override
public void removePendingContact(PendingContact pendingContact) {
// TODO replace with real implementation
}
@Override
public Contact getContact(ContactId c) throws DbException {
return db.transactionWithResult(true, txn -> db.getContact(txn, c));

View File

@@ -17,24 +17,16 @@ public interface CircumventionProvider {
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"};
/**
* Countries where obfs4 bridge connection are likely to work.
* Countries where vanilla bridge connection are likely to work.
* Should be a subset of {@link #BLOCKED}.
*/
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" };
/**
* Countries where obfs4 bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] NEEDS_MEEK = {"CN", "IR"};
String[] BRIDGES = { "EG", "BY", "TR", "SY", "VE" };
boolean isTorProbablyBlocked(String countryCode);
boolean doBridgesWork(String countryCode);
boolean needsMeek(String countryCode);
@IoExecutor
List<String> getBridges(boolean meek);
List<String> getBridges();
}

View File

@@ -22,8 +22,6 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
new HashSet<>(asList(BRIDGES));
private static final Set<String> BRIDGES_NEED_MEEK =
new HashSet<>(asList(NEEDS_MEEK));
@Nullable
private volatile List<String> bridges = null;
@@ -42,14 +40,9 @@ class CircumventionProviderImpl implements CircumventionProvider {
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
}
@Override
public boolean needsMeek(String countryCode) {
return BRIDGES_NEED_MEEK.contains(countryCode);
}
@Override
@IoExecutor
public List<String> getBridges(boolean useMeek) {
public List<String> getBridges() {
List<String> bridges = this.bridges;
if (bridges != null) return new ArrayList<>(bridges);
@@ -60,8 +53,6 @@ class CircumventionProviderImpl implements CircumventionProvider {
bridges = new ArrayList<>();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
boolean isMeekBridge = line.startsWith("Bridge meek");
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
if (!line.startsWith("#")) bridges.add(line);
}
scanner.close();

View File

@@ -69,7 +69,6 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3;
@@ -470,19 +469,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!enable) callback.transportDisabled();
}
private void enableBridges(boolean enable, boolean needsMeek)
throws IOException {
private void enableBridges(boolean enable) throws IOException {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
if (needsMeek) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
} else {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
conf.addAll(circumventionProvider.getBridges(needsMeek));
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
conf.addAll(circumventionProvider.getBridges());
controlConnection.setConf(conf);
} else {
controlConnection.setConf("UseBridges", "0");
@@ -655,10 +648,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated");
settings = s.getSettings();
// Works around a bug introduced in Tor 0.3.4.8.
// https://trac.torproject.org/projects/tor/ticket/28027
// Could be replaced with callback.transportDisabled()
// when fixed.
// Works around a bug introduced in Tor 0.3.4.8. Could be
// replaced with callback.transportDisabled() when fixed.
disableNetwork();
updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging());
@@ -694,8 +685,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
boolean onlyWhenCharging =
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
boolean bridgesWork = circumventionProvider.doBridgesWork(country);
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
@@ -710,29 +699,21 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!online) {
LOG.info("Disabling network, device is offline");
enableNetwork(false);
} else if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, device is on battery");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER ||
(!useMobile && !wifi)) {
LOG.info("Disabling network, device is using mobile data");
LOG.info("Disabling network due to setting");
enableNetwork(false);
} else if (automatic && blocked && !bridgesWork) {
LOG.info("Disabling network, country is blocked");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) {
LOG.info("Enabling network, using meek bridges");
enableBridges(true, true);
} else {
LOG.info("Enabling network, using obfs4 bridges");
enableBridges(true, false);
}
LOG.info("Enabling network, using bridges");
enableBridges(true);
enableNetwork(true);
} else {
LOG.info("Enabling network, not using bridges");
enableBridges(false, false);
enableBridges(false);
enableNetwork(true);
}
if (online && wifi && charging) {

View File

@@ -89,9 +89,14 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
public Visibility getClientVisibility(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException {
try {
LatestUpdates latest = findLatestUpdates(txn, contactId);
if (latest == null || latest.remote == null) return INVISIBLE;
Contact contact = db.getContact(txn, contactId);
Group g = getContactGroup(contact);
// Contact may be in the process of being added or removed, so
// contact group may not exist
if (!db.containsGroup(txn, g.getId())) return INVISIBLE;
LatestUpdates latest = findLatestUpdates(txn, g.getId());
if (latest.local == null) throw new DbException();
if (latest.remote == null) return INVISIBLE;
Update localUpdate = loadUpdate(txn, latest.local.messageId);
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
Map<ClientMajorVersion, Visibility> visibilities =
@@ -105,24 +110,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
}
}
@Override
public int getClientMinorVersion(Transaction txn, ContactId contactId,
ClientId clientId, int majorVersion) throws DbException {
try {
LatestUpdates latest = findLatestUpdates(txn, contactId);
if (latest == null || latest.remote == null) return -1;
Update remoteUpdate = loadUpdate(txn, latest.remote.messageId);
ClientMajorVersion cv =
new ClientMajorVersion(clientId, majorVersion);
for (ClientState remote : remoteUpdate.states) {
if (remote.majorVersion.equals(cv)) return remote.minorVersion;
}
return -1;
} catch (FormatException e) {
throw new DbException(e);
}
}
@Override
public void createLocalState(Transaction txn) throws DbException {
if (db.containsGroup(txn, localGroup.getId())) return;
@@ -349,17 +336,6 @@ class ClientVersioningManagerImpl implements ClientVersioningManager, Client,
MAJOR_VERSION, c);
}
@Nullable
private LatestUpdates findLatestUpdates(Transaction txn, ContactId c)
throws DbException, FormatException {
Contact contact = db.getContact(txn, c);
Group g = getContactGroup(contact);
// Contact may be in the process of being added or removed, so
// contact group may not exist
if (!db.containsGroup(txn, g.getId())) return null;
return findLatestUpdates(txn, g.getId());
}
private LatestUpdates findLatestUpdates(Transaction txn, GroupId g)
throws DbException, FormatException {
Map<MessageId, BdfDictionary> metadata =

View File

@@ -1,5 +1,4 @@
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
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
Bridge obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0

View File

@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Metadata;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.sync.ClientId;
@@ -44,7 +43,6 @@ import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
@@ -659,327 +657,4 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
c.registerClient(clientId, 123, 234, hook);
assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata()));
}
@Test
public void testReturnsInvisibleIfContactGroupDoesNotExist()
throws Exception {
expectGetContactGroup(false);
ClientVersioningManagerImpl c = createInstance();
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsInvisibleIfNoRemoteUpdateExists() throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test(expected = DbException.class)
public void testThrowsExceptionIfNoLocalUpdateExists() throws Exception {
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(remoteUpdateId, remoteUpdateMeta)));
}});
ClientVersioningManagerImpl c = createInstance();
c.getClientVisibility(txn, contact.getId(), clientId, 123);
}
@Test
public void testReturnsInvisibleIfClientNotSupportedLocally()
throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported remotely but not locally
BdfList localUpdateBody = BdfList.of(new BdfList(), 1L);
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
will(returnValue(localUpdateBody));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsInvisibleIfClientNotSupportedRemotely()
throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported locally but not remotely
BdfList localUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
will(returnValue(localUpdateBody));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(INVISIBLE, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsVisibleIfClientNotActiveRemotely() throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported locally and remotely but not active
BdfList localUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
will(returnValue(localUpdateBody));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(VISIBLE, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsSharedIfClientActiveRemotely() throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported locally and remotely and active
BdfList localUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, localUpdateId);
will(returnValue(localUpdateBody));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(SHARED, c.getClientVisibility(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsNegativeIfContactGroupDoesNotExist()
throws Exception {
expectGetContactGroup(false);
ClientVersioningManagerImpl c = createInstance();
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsNegativeIfNoRemoteUpdateExists() throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(singletonMap(localUpdateId, localUpdateMeta)));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsNegativeIfClientNotSupportedRemotely()
throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is not supported remotely
BdfList remoteUpdateBody = BdfList.of(new BdfList(), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(-1, c.getClientMinorVersion(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsMinorVersionIfClientNotActiveRemotely()
throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported remotely but not active
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, false)), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
clientId, 123));
}
@Test
public void testReturnsMinorVersionIfClientActiveRemotely()
throws Exception {
MessageId localUpdateId = new MessageId(getRandomId());
BdfDictionary localUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, true));
MessageId remoteUpdateId = new MessageId(getRandomId());
BdfDictionary remoteUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L),
new BdfEntry(MSG_KEY_LOCAL, false));
Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>();
messageMetadata.put(localUpdateId, localUpdateMeta);
messageMetadata.put(remoteUpdateId, remoteUpdateMeta);
// The client is supported remotely and active
BdfList remoteUpdateBody = BdfList.of(BdfList.of(
BdfList.of(clientId.getString(), 123, 234, true)), 1L);
expectGetContactGroup(true);
context.checking(new Expectations() {{
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(messageMetadata));
oneOf(clientHelper).getMessageAsList(txn, remoteUpdateId);
will(returnValue(remoteUpdateBody));
}});
ClientVersioningManagerImpl c = createInstance();
assertEquals(234, c.getClientMinorVersion(txn, contact.getId(),
clientId, 123));
}
private void expectGetContactGroup(boolean exists) throws Exception {
context.checking(new Expectations() {{
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
MAJOR_VERSION, contact);
will(returnValue(contactGroup));
oneOf(db).containsGroup(txn, contactGroup.getId());
will(returnValue(exists));
}});
}
}

View File

@@ -16,7 +16,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'net.java.dev.jna:jna:4.5.2'
implementation 'net.java.dev.jna:jna-platform:4.5.2'
tor 'org.briarproject:tor:0.3.5.8@zip'
tor 'org.briarproject:tor:0.3.4.8@zip'
tor 'org.briarproject:obfs4proxy:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'

View File

@@ -44,7 +44,7 @@ public class BridgeTest extends BrambleTestCase {
public static Iterable<String> data() {
BrambleJavaIntegrationTestComponent component =
DaggerBrambleJavaIntegrationTestComponent.builder().build();
return component.getCircumventionProvider().getBridges(false);
return component.getCircumventionProvider().getBridges();
}
private final static long TIMEOUT = SECONDS.toMillis(30);
@@ -104,12 +104,7 @@ public class BridgeTest extends BrambleTestCase {
}
@Override
public boolean needsMeek(String countryCode) {
return false;
}
@Override
public List<String> getBridges(boolean useMeek) {
public List<String> getBridges() {
return singletonList(bridge);
}
};

View File

@@ -21,7 +21,7 @@ dependencyVerification {
'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.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.8:tor-0.3.5.8.zip:96e83391f01984f28669235fc02fbb0243140a2b3b2c73aeffd0042c8d3ced18',
'org.briarproject:tor:0.3.4.8:tor-0.3.4.8.zip:bc0158c34002f471a4fe14a6a481816c918eb520a220bb027f64be902beb757f',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',

View File

@@ -22,8 +22,8 @@ android {
defaultConfig {
minSdkVersion 15
targetSdkVersion 26
versionCode 10106
versionName "1.1.6"
versionCode 10105
versionName "1.1.5"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -116,14 +116,13 @@ dependencies {
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.google.zxing:core:3.3.3'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.14.0'
implementation 'uk.co.samuelwall:material-tap-target-prompt:2.12.4'
implementation 'com.vanniktech:emoji-google:0.5.1'
def glideVersion = '4.8.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") {
exclude group: 'com.android.support'
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
}
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
@@ -134,7 +133,7 @@ dependencies {
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'org.robolectric:robolectric:4.0.1'
testImplementation 'org.robolectric:shadows-support-v4:3.3.2'
testImplementation 'org.mockito:mockito-core:2.19.0'
testImplementation 'org.mockito:mockito-core:2.13.0'
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,279 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.res.AssetManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.briarproject.bramble.api.UniqueId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
public class AttachmentControllerIntegrationTest {
private static final String smallKitten =
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg/160px-Kitten_in_Rizal_Park%2C_Manila.jpg";
private static final String originalKitten =
"https://upload.wikimedia.org/wikipedia/commons/0/06/Kitten_in_Rizal_Park%2C_Manila.jpg";
private static final String pngKitten =
"https://upload.wikimedia.org/wikipedia/commons/c/c8/Young_cat.png";
private static final String uberGif =
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/uber.gif";
private static final String lottaPixel =
"https://raw.githubusercontent.com/fuzzdb-project/fuzzdb/master/attack/file-upload/malicious-images/lottapixel.jpg";
private static final String imageIoCrash =
"https://www.landaire.net/img/crasher.png";
private static final String gimpCrash =
"https://gitlab.gnome.org/GNOME/gimp/uploads/75f5b7ed3b09b3f1c13f1f65bffe784f/31153c919d3aa634e8e6cff82219fe7352dd8a37.png";
private static final String optiPngAfl =
"https://sourceforge.net/p/optipng/bugs/64/attachment/test.gif";
private static final String librawError =
"https://www.libraw.org/sites/libraw.org/files/P1010671.JPG";
private final AttachmentDimensions dimensions = new AttachmentDimensions(
100, 50, 200, 75, 300
);
private final MessageId msgId = new MessageId(getRandomId());
private final AttachmentController controller =
new AttachmentController(null, dimensions);
@Test
public void testSmallJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(smallKitten);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(160, item.getWidth());
assertEquals(240, item.getHeight());
assertEquals(160, item.getThumbnailWidth());
assertEquals(240, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertEquals("jpg", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testBigJpegImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(originalKitten);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(1728, item.getWidth());
assertEquals(2592, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertEquals("jpg", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testSmallPngImage() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getUrlInputStream(pngKitten);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(737, item.getWidth());
assertEquals(510, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(138, item.getThumbnailHeight());
assertEquals("image/png", item.getMimeType());
assertEquals("png", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testUberGif() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(uberGif);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(1, item.getWidth());
assertEquals(1, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testLottaPixels() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(lottaPixel);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(64250, item.getWidth());
assertEquals(64250, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertEquals("jpg", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testImageIoCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(imageIoCrash);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(1184, item.getWidth());
assertEquals(448, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
assertEquals("image/png", item.getMimeType());
assertEquals("png", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testGimpCrash() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(gimpCrash);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(1, item.getWidth());
assertEquals(1, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testOptiPngAfl() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(optiPngAfl);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(32, item.getWidth());
assertEquals(32, item.getHeight());
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testLibrawError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getUrlInputStream(librawError);
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertTrue(item.hasError());
}
@Test
public void testSmallAnimatedGifMaxDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(65535, item.getWidth());
assertEquals(65535, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testSmallAnimatedGifHugeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(10000, item.getWidth());
assertEquals(10000, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testSmallGifLargeDimensions() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(16384, item.getWidth());
assertEquals(16384, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
assertEquals("image/gif", item.getMimeType());
assertEquals("gif", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testHighError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(1, item.getWidth());
assertEquals(10000, item.getHeight());
assertEquals(dimensions.minWidth, item.getThumbnailWidth());
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertEquals("jpg", item.getExtension());
assertFalse(item.hasError());
}
@Test
public void testWideError() throws Exception {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(is);
AttachmentItem item = controller.getAttachmentItem(h, a, true);
assertEquals(1920, item.getWidth());
assertEquals(1, item.getHeight());
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertEquals("jpg", item.getExtension());
assertFalse(item.hasError());
}
private InputStream getUrlInputStream(String url) throws IOException {
return new URL(url).openStream();
}
private InputStream getAssetInputStream(String name) throws IOException {
AssetManager assets = InstrumentationRegistry.getContext().getAssets();
return assets.open(name);
}
public static byte[] getRandomBytes(int length) {
byte[] b = new byte[length];
new Random().nextBytes(b);
return b;
}
public static byte[] getRandomId() {
return getRandomBytes(UniqueId.LENGTH);
}
}

View File

@@ -1,38 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.briarproject.briar.R;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.InstrumentationRegistry.getInstrumentation;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@RunWith(AndroidJUnit4.class)
public class ConversationActivityNotSignedInTest {
@Rule
public ActivityTestRule<ConversationActivity> testRule =
new ActivityTestRule<>(ConversationActivity.class, false, false);
@Test
public void openWithoutSignedIn() {
Context targetContext = getInstrumentation().getTargetContext();
Intent intent = new Intent(targetContext, ConversationActivity.class);
intent.putExtra(CONTACT_ID, 1);
testRule.launchActivity(intent);
onView(withText(R.string.sign_in_button))
.perform(waitUntilMatches(isDisplayed()));
}
}

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
@@ -18,11 +17,9 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="org.briarproject.briar.android.BriarApplicationImpl"
@@ -31,8 +28,7 @@
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/BriarTheme"
tools:ignore="GoogleAppIndexingWarning">
android:theme="@style/BriarTheme">
<receiver
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
@@ -117,15 +113,6 @@
/>
</activity>
<activity
android:name=".android.conversation.ImageActivity"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
android:theme="@style/BriarTheme.ActionBarOverlay">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
</activity>
<activity
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
android:label="@string/groups_create_group_title"
@@ -158,7 +145,7 @@
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
<activity
android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity"
android:label="@string/groups_member_list"
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity"
@@ -401,7 +388,7 @@
<activity
android:name="org.briarproject.briar.android.panic.PanicResponderActivity"
android:noHistory="true"
android:theme="@style/TranslucentTheme">
android:theme="@style/Theme.AppCompat.NoActionBar">
<!-- this can never have launchMode singleTask or singleInstance! -->
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
@@ -411,12 +398,12 @@
<activity
android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/Theme.AppCompat.NoActionBar">
</activity>
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay">
android:theme="@style/Theme.AppCompat.NoActionBar">
</activity>
<activity

View File

@@ -32,7 +32,7 @@ import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
@@ -169,7 +169,7 @@ public interface AndroidComponent
void inject(NotificationCleanupService notificationCleanupService);
void inject(EmojiTextInputView textInputView);
void inject(TextInputView textInputView);
void inject(BriarModelLoader briarModelLoader);

View File

@@ -231,7 +231,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
showForumPostNotification(f.getGroupId());
} else if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
if (!b.isLocal()) showBlogPostNotification(b.getGroupId());
showBlogPostNotification(b.getGroupId());
} else if (e instanceof IntroductionSucceededEvent) {
showIntroductionNotification();
}

View File

@@ -1,54 +0,0 @@
package org.briarproject.briar.android;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
class BackgroundMonitor implements ActivityLifecycleCallbacks {
private final AtomicInteger foregroundActivities = new AtomicInteger(0);
boolean isRunningInBackground() {
return foregroundActivities.get() == 0;
}
@Override
public void onActivityCreated(Activity a, @Nullable Bundle state) {
}
@Override
public void onActivityStarted(Activity a) {
foregroundActivities.incrementAndGet();
}
@Override
public void onActivityResumed(Activity a) {
}
@Override
public void onActivityPaused(Activity a) {
}
@Override
public void onActivityStopped(Activity a) {
foregroundActivities.decrementAndGet();
}
@Override
public void onActivitySaveInstanceState(Activity a,
@Nullable Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity a) {
}
}

View File

@@ -16,6 +16,4 @@ public interface BriarApplication {
AndroidComponent getApplicationComponent();
SharedPreferences getDefaultSharedPreferences();
boolean isRunningInBackground();
}

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
@@ -32,8 +30,6 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
import static org.acra.ReportField.ANDROID_VERSION;
@@ -83,7 +79,6 @@ public class BriarApplicationImpl extends Application
Logger.getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@@ -120,9 +115,6 @@ public class BriarApplicationImpl extends Application
applicationComponent = createApplicationComponent();
EmojiManager.install(new GoogleEmojiProvider());
if (SDK_INT < 16)
registerActivityLifecycleCallbacks(backgroundMonitor);
}
protected AndroidComponent createApplicationComponent() {
@@ -181,15 +173,4 @@ public class BriarApplicationImpl extends Application
public SharedPreferences getDefaultSharedPreferences() {
return prefs;
}
@Override
public boolean isRunningInBackground() {
if (SDK_INT >= 16) {
RunningAppProcessInfo info = new RunningAppProcessInfo();
ActivityManager.getMyMemoryState(info);
return (info.importance != IMPORTANCE_FOREGROUND);
} else {
return backgroundMonitor.isRunningInBackground();
}
}
}

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.android;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
@@ -33,6 +35,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -71,7 +74,6 @@ public class BriarService extends Service {
@Nullable
private BroadcastReceiver receiver = null;
private BriarApplication app;
@Inject
AndroidNotificationManager notificationManager;
@@ -91,8 +93,8 @@ public class BriarService extends Service {
public void onCreate() {
super.onCreate();
app = (BriarApplication) getApplication();
app.getApplicationComponent().inject(this);
BriarApplication application = (BriarApplication) getApplication();
application.getApplicationComponent().inject(this);
LOG.info("Created");
if (created.getAndSet(true)) {
@@ -218,8 +220,8 @@ public class BriarService extends Service {
public void onLowMemory() {
super.onLowMemory();
LOG.warning("Memory is low");
// If we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi();
// Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16
if (SDK_INT < 16) hideUi();
}
@Override
@@ -233,16 +235,20 @@ public class BriarService extends Service {
LOG.info("Trim memory: near middle of LRU list");
} else if (level == TRIM_MEMORY_COMPLETE) {
LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
// This level may be received if SDK_INT < 16, although the
// constant isn't declared until API level 16
LOG.warning("Trim memory: running critically low");
// If we're not in the foreground, clear the UI to save memory
if (app.isRunningInBackground()) hideUi();
} else if (SDK_INT >= 16) {
if (level == TRIM_MEMORY_RUNNING_MODERATE) {
LOG.info("Trim memory: running moderately low");
} else if (level == TRIM_MEMORY_RUNNING_LOW) {
LOG.info("Trim memory: running low");
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
LOG.info("Trim memory: running critically low");
// If we're not in the foreground, clear the UI to save memory
RunningAppProcessInfo info = new RunningAppProcessInfo();
ActivityManager.getMyMemoryState(info);
if (info.importance != IMPORTANCE_FOREGROUND) hideUi();
} else if (LOG.isLoggable(INFO)) {
LOG.info("Trim memory: unknown level " + level);
}
} else if (LOG.isLoggable(INFO)) {
LOG.info("Trim memory: unknown level " + level);
}

View File

@@ -3,29 +3,22 @@ package org.briarproject.briar.android;
import android.app.NotificationManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.fragment.ErrorFragment;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class StartupFailureActivity extends BaseActivity implements
BaseFragmentListener {
@Override
public void onCreate(@Nullable Bundle state) {
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_fragment_container);
@@ -45,7 +38,7 @@ public class StartupFailureActivity extends BaseActivity implements
// cancel notification
if (notificationId > -1) {
Object o = getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) requireNonNull(o);
NotificationManager nm = (NotificationManager) o;
nm.cancel(notificationId);
}
@@ -73,7 +66,7 @@ public class StartupFailureActivity extends BaseActivity implements
}
@Override
public void runOnDbThread(@NonNull Runnable runnable) {
public void runOnDbThread(Runnable runnable) {
throw new AssertionError("Deprecated and should not be used");
}

View File

@@ -30,10 +30,4 @@ public interface TestingConstants {
long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ?
BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L :
Long.MAX_VALUE;
/**
* Feature flag for enabling image attachments.
*/
boolean FEATURE_FLAG_IMAGE_ATTACHMENTS = IS_DEBUG_BUILD;
}

View File

@@ -15,12 +15,10 @@ import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.ContactModule;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ImageActivity;
import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment;
@@ -31,6 +29,7 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment;
import org.briarproject.briar.android.keyagreement.IntroFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
import org.briarproject.briar.android.login.AuthorNameFragment;
@@ -48,6 +47,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupMessageFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupModule;
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
@@ -68,8 +68,10 @@ import org.briarproject.briar.android.sharing.ForumInvitationActivity;
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
import org.briarproject.briar.android.sharing.ShareBlogActivity;
import org.briarproject.briar.android.sharing.ShareBlogFragment;
import org.briarproject.briar.android.sharing.ShareBlogMessageFragment;
import org.briarproject.briar.android.sharing.ShareForumActivity;
import org.briarproject.briar.android.sharing.ShareForumFragment;
import org.briarproject.briar.android.sharing.ShareForumMessageFragment;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.TestDataActivity;
@@ -108,8 +110,6 @@ public interface ActivityComponent {
void inject(ConversationActivity activity);
void inject(ImageActivity activity);
void inject(ForumInvitationActivity activity);
void inject(BlogInvitationActivity activity);
@@ -179,6 +179,8 @@ public interface ActivityComponent {
void inject(CreateGroupFragment fragment);
void inject(CreateGroupMessageFragment fragment);
void inject(GroupListFragment fragment);
void inject(GroupInviteFragment fragment);
@@ -189,14 +191,20 @@ public interface ActivityComponent {
void inject(FeedFragment fragment);
void inject(IntroFragment fragment);
void inject(KeyAgreementFragment fragment);
void inject(ContactChooserFragment fragment);
void inject(ShareForumFragment fragment);
void inject(ShareForumMessageFragment fragment);
void inject(ShareBlogFragment fragment);
void inject(ShareBlogMessageFragment fragment);
void inject(IntroductionMessageFragment fragment);
void inject(SettingsFragment fragment);
@@ -207,6 +215,4 @@ public interface ActivityComponent {
void inject(AliasDialogFragment aliasDialogFragment);
void inject(ImageFragment imageFragment);
}

View File

@@ -15,8 +15,6 @@ import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication;
@@ -53,8 +51,6 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
* Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity}
*/
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseActivity extends AppCompatActivity
implements DestroyableContext, OnTapFilteredListener {
@@ -81,17 +77,6 @@ public abstract class BaseActivity extends AppCompatActivity
@Override
public void onCreate(@Nullable Bundle state) {
// create the ActivityComponent *before* calling super.onCreate()
// because it already attaches fragments which need access
// to the component for their own injection
AndroidComponent applicationComponent =
((BriarApplication) getApplication()).getApplicationComponent();
activityComponent = DaggerActivityComponent.builder()
.androidComponent(applicationComponent)
.activityModule(getActivityModule())
.forumModule(getForumModule())
.build();
injectActivity(activityComponent);
super.onCreate(state);
// WARNING: When removing this or making it possible to turn it off,
@@ -101,6 +86,17 @@ public abstract class BaseActivity extends AppCompatActivity
// unlock screen is shown.
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
AndroidComponent applicationComponent =
((BriarApplication) getApplication()).getApplicationComponent();
activityComponent = DaggerActivityComponent.builder()
.androidComponent(applicationComponent)
.activityModule(getActivityModule())
.forumModule(getForumModule())
.build();
injectActivity(activityComponent);
for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityCreate(this);
}

View File

@@ -1,16 +1,16 @@
package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.support.annotation.RequiresApi;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition;
import android.view.Gravity;
import android.view.Window;
import android.widget.CheckBox;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.DbController;
@@ -33,12 +33,10 @@ import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@SuppressLint("Registered")
public abstract class BriarActivity extends BaseActivity {
public static final String GROUP_ID = "briar.GROUP_ID";
@@ -62,8 +60,7 @@ public abstract class BriarActivity extends BaseActivity {
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_PASSWORD) {
// The result can be RESULT_CANCELED if there's no account
@@ -92,7 +89,7 @@ public abstract class BriarActivity extends BaseActivity {
} else if (lockManager.isLocked() && !isFinishing()) {
// Also check that the activity isn't finishing already.
// This is possible if we finished in onActivityResult().
// Launching another UnlockActivity would cause a loop.
// Lauching another UnlockActivity would cause a loop.
Intent i = new Intent(this, UnlockActivity.class);
startActivityForResult(i, REQUEST_UNLOCK);
} else if (SDK_INT >= 23) {
@@ -114,32 +111,21 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop();
}
protected boolean signedIn() {
return briarController.accountSignedIn();
}
/**
* Sets the transition animations.
* @param enterTransition used to move views into initial positions
* @param exitTransition used to move views out when starting a <b>new</b> activity.
* @param returnTransition used when window is closing, because the activity is finishing.
*/
@RequiresApi(api = 21)
public void setSceneTransitionAnimation(
@Nullable Transition enterTransition,
@Nullable Transition exitTransition,
@Nullable Transition returnTransition) {
public void setSceneTransitionAnimation() {
if (SDK_INT < 21) return;
// workaround for #1007
if (isSamsung7()) {
return;
}
if (enterTransition != null) excludeSystemUi(enterTransition);
if (exitTransition != null) excludeSystemUi(exitTransition);
if (returnTransition != null) excludeSystemUi(returnTransition);
Transition slide = new Slide(Gravity.RIGHT);
slide.excludeTarget(android.R.id.statusBarBackground, true);
slide.excludeTarget(android.R.id.navigationBarBackground, true);
Window window = getWindow();
window.setEnterTransition(enterTransition);
window.setExitTransition(exitTransition);
window.setReturnTransition(returnTransition);
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
window.setExitTransition(slide);
window.setEnterTransition(slide);
window.setTransitionBackgroundFadeDuration(getResources()
.getInteger(android.R.integer.config_longAnimTime));
}
/**
@@ -185,15 +171,13 @@ public abstract class BriarActivity extends BaseActivity {
b.show();
}
protected void signOut(boolean removeFromRecentApps,
boolean deleteAccount) {
protected void signOut(boolean removeFromRecentApps) {
if (briarController.accountSignedIn()) {
// Don't use UiResultHandler because we want the result even if
// this activity has been destroyed
briarController.signOut(result -> runOnUiThread(
() -> exit(removeFromRecentApps)), deleteAccount);
() -> exit(removeFromRecentApps)));
} else {
if (deleteAccount) briarController.deleteAccount();
exit(removeFromRecentApps);
}
}

View File

@@ -14,7 +14,5 @@ public interface RequestCodes {
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
int REQUEST_SAVE_ATTACHMENT = 14;
}

View File

@@ -24,8 +24,6 @@ import javax.annotation.Nullable;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@@ -37,7 +35,7 @@ abstract class BasePostFragment extends BaseFragment {
static final String POST_ID = "briar.POST_ID";
private static final Logger LOG =
getLogger(BasePostFragment.class.getName());
Logger.getLogger(BasePostFragment.class.getName());
private final Handler handler = new Handler(Looper.getMainLooper());
@@ -54,7 +52,7 @@ abstract class BasePostFragment extends BaseFragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// retrieve MessageId of blog post from arguments
byte[] p = requireNonNull(getArguments()).getByteArray(POST_ID);
byte[] p = getArguments().getByteArray(POST_ID);
if (p == null) throw new IllegalStateException("No post ID in args");
postId = new MessageId(p);
@@ -70,7 +68,6 @@ abstract class BasePostFragment extends BaseFragment {
@Override
public void onAuthorClick(BlogPostItem post) {
if (getContext() == null) return;
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);

View File

@@ -3,14 +3,12 @@ package org.briarproject.briar.android.blog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -45,7 +43,6 @@ import javax.inject.Inject;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@@ -64,17 +61,15 @@ public class BlogFragment extends BaseFragment
BlogController blogController;
@Inject
SharingController sharingController;
@Nullable
private Parcelable layoutManagerState;
private GroupId groupId;
private BlogPostAdapter adapter;
private LayoutManager layoutManager;
private BriarRecyclerView list;
private MenuItem writeButton, deleteButton;
private boolean isMyBlog = false, canDeleteBlog = false;
static BlogFragment newInstance(GroupId groupId) {
BlogFragment f = new BlogFragment();
Bundle bundle = new Bundle();
@@ -84,42 +79,36 @@ public class BlogFragment extends BaseFragment
return f;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setBlogSharingListener(this);
sharingController.setSharingListener(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments());
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
groupId = new GroupId(b);
View v = inflater.inflate(R.layout.fragment_blog, container, false);
adapter = new BlogPostAdapter(requireNonNull(getActivity()), this,
getFragmentManager());
adapter =
new BlogPostAdapter(getActivity(), this, getFragmentManager());
list = v.findViewById(R.id.postList);
layoutManager = new LinearLayoutManager(getActivity());
list.setLayoutManager(layoutManager);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.showProgressBar();
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
if (savedInstanceState != null) {
layoutManagerState =
savedInstanceState.getParcelable("layoutManager");
}
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
blogController.setBlogSharingListener(this);
sharingController.setSharingListener(this);
}
@Override
public void onStart() {
super.onStart();
@@ -137,15 +126,6 @@ public class BlogFragment extends BaseFragment
list.stopPeriodicUpdate();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (layoutManager != null) {
layoutManagerState = layoutManager.onSaveInstanceState();
outState.putParcelable("layoutManager", layoutManagerState);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.blogs_blog_actions, menu);
@@ -238,10 +218,7 @@ public class BlogFragment extends BaseFragment
@Override
public void onAuthorClick(BlogPostItem post) {
if (post.getGroupId().equals(groupId) || getContext() == null) {
// We're already there
return;
}
if (post.getGroupId().equals(groupId)) return; // We're already there
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
@@ -258,12 +235,7 @@ public class BlogFragment extends BaseFragment
list.showData();
} else {
adapter.addAll(posts);
if (reload || layoutManagerState == null) {
list.scrollToPosition(0);
} else {
layoutManager.onRestoreInstanceState(
layoutManagerState);
}
if (reload) list.scrollToPosition(0);
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
@@ -36,7 +35,6 @@ import javax.inject.Inject;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@@ -55,10 +53,7 @@ public class FeedFragment extends BaseFragment implements
private BlogPostAdapter adapter;
private LinearLayoutManager layoutManager;
private BriarRecyclerView list;
@Nullable
private Blog personalBlog;
@Nullable
private Parcelable layoutManagerState;
private Blog personalBlog = null;
public static FeedFragment newInstance() {
FeedFragment f = new FeedFragment();
@@ -69,18 +64,13 @@ public class FeedFragment extends BaseFragment implements
return f;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
feedController.setFeedListener(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.blogs_button);
getActivity().setTitle(R.string.blogs_button);
View v = inflater.inflate(R.layout.fragment_blog, container, false);
@@ -95,14 +85,15 @@ public class FeedFragment extends BaseFragment implements
list.setEmptyText(R.string.blogs_feed_empty_state);
list.setEmptyAction(R.string.blogs_feed_empty_state_action);
if (savedInstanceState != null) {
layoutManagerState =
savedInstanceState.getParcelable("layoutManager");
}
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
feedController.setFeedListener(this);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
@@ -132,15 +123,6 @@ public class FeedFragment extends BaseFragment implements
// TODO save list position in database/preferences?
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (layoutManager != null) {
layoutManagerState = layoutManager.onSaveInstanceState();
outState.putParcelable("layoutManager", layoutManagerState);
}
}
private void loadPersonalBlog() {
feedController.loadPersonalBlog(
new UiResultExceptionHandler<Blog, DbException>(this) {
@@ -168,12 +150,6 @@ public class FeedFragment extends BaseFragment implements
if (clear) adapter.setItems(posts);
else adapter.addAll(posts);
if (posts.isEmpty()) list.showData();
if (layoutManagerState == null) {
list.scrollToPosition(0); // Scroll to the top
} else {
layoutManager.onRestoreInstanceState(
layoutManagerState);
}
} else {
LOG.info("Concurrent update, reloading");
loadBlogPosts(clear);

View File

@@ -17,7 +17,6 @@ import org.briarproject.briar.android.controller.handler.UiResultExceptionHandle
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@UiThread
@@ -43,17 +42,13 @@ public class FeedPostFragment extends BasePostFragment {
return f;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments());
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
blogId = new GroupId(b);
@@ -66,6 +61,11 @@ public class FeedPostFragment extends BasePostFragment {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onStart() {
super.onStart();

View File

@@ -18,6 +18,7 @@ public class ReblogActivity extends BriarActivity implements
@Override
public void onCreate(Bundle savedInstanceState) {
setSceneTransitionAnimation();
super.onCreate(savedInstanceState);
Intent intent = getIntent();

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.blog;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -19,10 +18,7 @@ import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import java.util.List;
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -31,18 +27,18 @@ import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ReblogFragment extends BaseFragment implements SendListener {
public class ReblogFragment extends BaseFragment implements TextInputListener {
public static final String TAG = ReblogFragment.class.getName();
private ViewHolder ui;
private GroupId blogId;
private MessageId postId;
private BlogPostItem item;
@Inject
@@ -74,22 +70,24 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments());
GroupId blogId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
Bundle args = getArguments();
blogId = new GroupId(args.getByteArray(GROUP_ID));
postId = new MessageId(args.getByteArray(POST_ID));
View v = inflater.inflate(R.layout.fragment_reblog, container, false);
ui = new ViewHolder(v);
ui.post.setTransitionName(postId);
TextSendController sendController =
new TextSendController(ui.input, this, true);
ui.input.setSendController(sendController);
ui.input.setEnabled(false);
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
ui.input.setSendButtonEnabled(false);
showProgressBar();
return v;
}
@Override
public void onStart() {
super.onStart();
// TODO: Load blog post when fragment is created. #631
feedController.loadBlogPost(blogId, postId,
new UiResultExceptionHandler<BlogPostItem, DbException>(
this) {
@@ -104,8 +102,6 @@ public class ReblogFragment extends BaseFragment implements SendListener {
handleDbException(exception);
}
});
return v;
}
private void bindViewHolder() {
@@ -116,14 +112,16 @@ public class ReblogFragment extends BaseFragment implements SendListener {
ui.post.bindItem(item);
ui.post.hideReblogButton();
ui.input.setEnabled(true);
ui.input.setListener(this);
ui.input.setSendButtonEnabled(true);
ui.scrollView.post(() -> ui.scrollView.fullScroll(FOCUS_DOWN));
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(String text) {
ui.input.hideSoftKeyboard();
feedController.repeatPost(item, text,
String comment = getComment();
feedController.repeatPost(item, comment,
new UiExceptionHandler<DbException>(this) {
@Override
public void onExceptionUi(DbException exception) {
@@ -133,6 +131,12 @@ public class ReblogFragment extends BaseFragment implements SendListener {
finish();
}
@Nullable
private String getComment() {
if (ui.input.getText().length() == 0) return null;
return ui.input.getText().toString();
}
private void showProgressBar() {
ui.progressBar.setVisibility(VISIBLE);
ui.input.setVisibility(GONE);

View File

@@ -29,7 +29,6 @@ import javax.inject.Inject;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -73,15 +72,6 @@ public class RssFeedImportActivity extends BriarActivity {
enableOrDisableImportButton();
}
});
urlInput.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == IME_ACTION_DONE && importButton.isEnabled() &&
importButton.getVisibility() == VISIBLE) {
publish();
hideSoftKeyboard(urlInput);
return true;
}
return false;
});
importButton = findViewById(R.id.importButton);
importButton.setOnClickListener(v -> publish());

View File

@@ -1,9 +1,9 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.widget.ProgressBar;
@@ -14,22 +14,19 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPost;
import org.briarproject.briar.api.blog.BlogPostFactory;
import java.security.GeneralSecurityException;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -38,13 +35,10 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class WriteBlogPostActivity extends BriarActivity
implements OnEditorActionListener, SendListener {
implements OnEditorActionListener, TextInputListener {
private static final Logger LOG =
Logger.getLogger(WriteBlogPostActivity.class.getName());
@@ -64,8 +58,9 @@ public class WriteBlogPostActivity extends BriarActivity
@Inject
volatile BlogManager blogManager;
@SuppressWarnings("ConstantConditions")
@Override
public void onCreate(@Nullable Bundle state) {
public void onCreate(Bundle state) {
super.onCreate(state);
Intent i = getIntent();
@@ -76,10 +71,24 @@ public class WriteBlogPostActivity extends BriarActivity
setContentView(R.layout.activity_write_blog_post);
input = findViewById(R.id.textInput);
TextSendController sendController =
new TextSendController(input, this, false);
input.setSendController(sendController);
input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
input.setSendButtonEnabled(false);
input.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
@Override
public void afterTextChanged(Editable s) {
enableOrDisablePublishButton();
}
});
input.setListener(this);
progressBar = findViewById(R.id.progressBar);
}
@@ -118,15 +127,18 @@ public class WriteBlogPostActivity extends BriarActivity
return true;
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
if (isNullOrEmpty(text)) throw new AssertionError();
private void enableOrDisablePublishButton() {
input.setSendButtonEnabled(input.getText().length() > 0);
}
@Override
public void onSendClick(String text) {
// hide publish button, show progress bar
input.hideSoftKeyboard();
input.setVisibility(GONE);
progressBar.setVisibility(VISIBLE);
text = StringUtils.truncateUtf8(text, MAX_BLOG_POST_TEXT_LENGTH);
storePost(text);
}

View File

@@ -38,7 +38,7 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
@Override
public boolean areItemsTheSame(I c1, I c2) {
return c1.getContact().equals(c2.getContact());
return c1.getContact().getId().equals(c2.getContact().getId());
}
@Override
@@ -47,7 +47,8 @@ public abstract class BaseContactListAdapter<I extends ContactItem, VH extends C
}
int findItemPosition(ContactId c) {
for (int i = 0; i < getItemCount(); i++) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
I item = getItemAt(i);
if (item != null && item.getContact().getId().equals(c))
return i;

View File

@@ -5,10 +5,8 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
@NotNullByDefault
public class ContactListAdapter extends
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
@@ -30,9 +28,6 @@ public class ContactListAdapter extends
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
// check for all properties that influence visual
// representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false;
}
@@ -44,7 +39,11 @@ public class ContactListAdapter extends
@Override
public int compare(ContactListItem c1, ContactListItem c2) {
return Long.compare(c2.getTimestamp(), c1.getTimestamp());
long time1 = c1.getTimestamp();
long time2 = c2.getTimestamp();
if (time1 < time2) return 1;
if (time1 > time2) return -1;
return 0;
}
}

View File

@@ -51,13 +51,11 @@ import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.getTransitionName;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
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.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -104,7 +102,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
getActivity().setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.list, container, false);
@@ -115,7 +114,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
if (SDK_INT >= 23 && !isSamsung7()) {
if (SDK_INT >= 23) {
ContactListItemViewHolder holder =
(ContactListItemViewHolder) list
.getRecyclerView()
@@ -281,7 +280,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.setConnected(connected);
adapter.updateItemAt(position, item);
adapter.notifyItemChanged(position);
}
});
}

View File

@@ -25,7 +25,6 @@ import java.util.Collection;
import javax.annotation.Nullable;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS;
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds;
@@ -51,10 +50,10 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = requireNonNull(getArguments());
Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b);
@@ -73,7 +72,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
list.setEmptyText(getString(R.string.no_contacts_selector));
list.setEmptyAction(getString(R.string.no_contacts_selector_action));
adapter = getAdapter(requireNonNull(getContext()), this);
adapter = getAdapter(getContext(), this);
list.setAdapter(adapter);
// restore selected contacts if available

View File

@@ -16,8 +16,5 @@ public interface BriarController extends ActivityLifecycleController {
void doNotAskAgainForDozeWhiteListing();
void signOut(ResultHandler<Void> eventHandler, boolean deleteAccount);
void deleteAccount();
void signOut(ResultHandler<Void> eventHandler);
}

View File

@@ -120,8 +120,7 @@ public class BriarControllerImpl implements BriarController {
}
@Override
public void signOut(ResultHandler<Void> eventHandler,
boolean deleteAccount) {
public void signOut(ResultHandler<Void> eventHandler) {
new Thread(() -> {
try {
// Wait for the service to finish starting up
@@ -135,18 +134,11 @@ public class BriarControllerImpl implements BriarController {
service.waitForShutdown();
} catch (InterruptedException e) {
LOG.warning("Interrupted while waiting for service");
} finally {
if (deleteAccount) accountManager.deleteAccount();
}
eventHandler.onResult(null);
}).start();
}
@Override
public void deleteAccount() {
accountManager.deleteAccount();
}
private void unbindService() {
if (bound) activity.unbindService(serviceConnection);
}

View File

@@ -39,8 +39,8 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
BriarActivity a = (BriarActivity) requireNonNull(getActivity());
a.getActivityComponent().inject(this);
if (getActivity() == null) return;
((BriarActivity) getActivity()).getActivityComponent().inject(this);
viewModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(ConversationViewModel.class);
}
@@ -48,6 +48,7 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_alias_dialog, container,
false);
@@ -68,5 +69,4 @@ public class AliasDialogFragment extends AppCompatDialogFragment {
return v;
}
}

View File

@@ -1,24 +1,20 @@
package org.briarproject.briar.android.conversation;
import android.content.res.Resources;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.support.annotation.Nullable;
import android.support.media.ExifInterface;
import android.webkit.MimeTypeMap;
import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.conversation.ImageHelper.DecodeResult;
import org.briarproject.briar.R;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -46,10 +42,8 @@ class AttachmentController {
private static final Logger LOG =
getLogger(AttachmentController.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final MessagingManager messagingManager;
private final ImageHelper imageHelper;
private final int defaultSize;
private final int minWidth, maxWidth;
private final int minHeight, maxHeight;
@@ -57,38 +51,18 @@ class AttachmentController {
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>();
AttachmentController(MessagingManager messagingManager,
AttachmentDimensions dimensions, ImageHelper imageHelper) {
AttachmentController(MessagingManager messagingManager, Resources res) {
this.messagingManager = messagingManager;
this.imageHelper = imageHelper;
defaultSize = dimensions.defaultSize;
minWidth = dimensions.minWidth;
maxWidth = dimensions.maxWidth;
minHeight = dimensions.minHeight;
maxHeight = dimensions.maxHeight;
}
AttachmentController(MessagingManager messagingManager,
AttachmentDimensions dimensions) {
this(messagingManager, dimensions, new ImageHelper() {
@Override
public DecodeResult decodeStream(InputStream is) {
Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
String mimeType = options.outMimeType;
if (mimeType == null) mimeType = "";
return new DecodeResult(options.outWidth, options.outHeight,
mimeType);
}
@Nullable
@Override
public String getExtensionFromMimeType(String mimeType) {
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
return mimeTypeMap.getExtensionFromMimeType(mimeType);
}
});
defaultSize =
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
minWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_width);
maxWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_width);
minHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_height);
maxHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_height);
}
void put(MessageId messageId, List<AttachmentItem> attachments) {
@@ -107,53 +81,31 @@ class AttachmentController {
List<Pair<AttachmentHeader, Attachment>> attachments =
new ArrayList<>(headers.size());
for (AttachmentHeader h : headers) {
Attachment a = messagingManager.getAttachment(h.getMessageId());
Attachment a =
messagingManager.getAttachment(h.getMessageId());
attachments.add(new Pair<>(h, a));
}
logDuration(LOG, "Loading attachment", start);
return attachments;
}
/**
* Creates {@link AttachmentItem}s from the passed headers and Attachments.
* <p>
* Note: This closes the {@link Attachment}'s {@link InputStream}.
*/
List<AttachmentItem> getAttachmentItems(
List<Pair<AttachmentHeader, Attachment>> attachments) {
boolean needsSize = attachments.size() == 1;
List<AttachmentItem> items = new ArrayList<>(attachments.size());
for (Pair<AttachmentHeader, Attachment> a : attachments) {
AttachmentItem item =
getAttachmentItem(a.getFirst(), a.getSecond(), needsSize);
getAttachmentItem(a.getFirst(), a.getSecond());
items.add(item);
}
return items;
}
/**
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
* {@link InputStream} which will be closed when this method returns.
*/
AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a,
boolean needsSize) {
private AttachmentItem getAttachmentItem(AttachmentHeader h, Attachment a) {
MessageId messageId = h.getMessageId();
if (!needsSize) {
String mimeType = h.getContentType();
String extension = imageHelper.getExtensionFromMimeType(mimeType);
boolean hasError = false;
if (extension == null) {
extension = "";
hasError = true;
}
return new AttachmentItem(messageId, 0, 0, mimeType, extension, 0,
0, hasError);
}
Size size = new Size();
InputStream is = new MarkEnforcingInputStream(
new BufferedInputStream(a.getStream()));
is.mark(READ_LIMIT);
InputStream is = a.getStream();
is.mark(Integer.MAX_VALUE);
try {
// use exif to get size
if (h.getContentType().equals("image/jpeg")) {
@@ -166,8 +118,6 @@ class AttachmentController {
// use BitmapFactory to get size
if (size.error) {
is.reset();
// need to mark again to re-add read limit
is.mark(READ_LIMIT);
size = getSizeFromBitmap(is);
}
} catch (IOException e) {
@@ -177,24 +127,19 @@ class AttachmentController {
}
// calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
Size thumbnailSize = new Size(defaultSize, defaultSize);
if (!size.error) {
thumbnailSize =
getThumbnailSize(size.width, size.height, size.mimeType);
thumbnailSize = getThumbnailSize(size.width, size.height);
}
// get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType);
boolean hasError = extension == null || size.error;
if (extension == null) extension = "";
return new AttachmentItem(messageId, size.width, size.height,
size.mimeType, extension, thumbnailSize.width,
thumbnailSize.height, hasError);
thumbnailSize.width, thumbnailSize.height, size.error);
}
/**
* Gets the size of a JPEG {@link InputStream} if EXIF info is available.
*/
private Size getSizeFromExif(InputStream is) throws IOException {
private static Size getSizeFromExif(InputStream is)
throws IOException {
ExifInterface exif = new ExifInterface(is);
// these can return 0 independent of default value
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
@@ -206,21 +151,24 @@ class AttachmentController {
orientation == ORIENTATION_TRANSVERSE ||
orientation == ORIENTATION_TRANSPOSE) {
//noinspection SuspiciousNameCombination
return new Size(height, width, "image/jpeg");
return new Size(height, width);
}
return new Size(width, height, "image/jpeg");
return new Size(width, height);
}
/**
* Gets the size of any image {@link InputStream}.
*/
private Size getSizeFromBitmap(InputStream is) {
DecodeResult result = imageHelper.decodeStream(is);
if (result.width < 1 || result.height < 1) return new Size();
return new Size(result.width, result.height, result.mimeType);
private static Size getSizeFromBitmap(InputStream is) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
if (options.outWidth < 1 || options.outHeight < 1)
return new Size();
return new Size(options.outWidth, options.outHeight);
}
private Size getThumbnailSize(int width, int height, String mimeType) {
private Size getThumbnailSize(int width, int height) {
float widthPercentage = maxWidth / (float) width;
float heightPercentage = maxHeight / (float) height;
float scaleFactor = Math.min(widthPercentage, heightPercentage);
@@ -236,27 +184,24 @@ class AttachmentController {
if (thumbnailWidth > maxWidth) thumbnailWidth = maxWidth;
if (thumbnailHeight > maxHeight) thumbnailHeight = maxHeight;
}
return new Size(thumbnailWidth, thumbnailHeight, mimeType);
return new Size(thumbnailWidth, thumbnailHeight);
}
private static class Size {
private final int width;
private final int height;
private final String mimeType;
private final boolean error;
private Size(int width, int height, String mimeType) {
private Size(int width, int height) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.error = false;
}
private Size() {
this.width = 0;
this.height = 0;
this.mimeType = "";
this.error = true;
}
}

View File

@@ -1,39 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.res.Resources;
import android.support.annotation.VisibleForTesting;
import org.briarproject.briar.R;
class AttachmentDimensions {
final int defaultSize;
final int minWidth, maxWidth;
final int minHeight, maxHeight;
@VisibleForTesting
AttachmentDimensions(int defaultSize, int minWidth, int maxWidth,
int minHeight, int maxHeight) {
this.defaultSize = defaultSize;
this.minWidth = minWidth;
this.maxWidth = maxWidth;
this.minHeight = minHeight;
this.maxHeight = maxHeight;
}
static AttachmentDimensions getAttachmentDimensions(Resources res) {
int defaultSize =
res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
int minWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_width);
int maxWidth = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_width);
int minHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_height);
int maxHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_height);
return new AttachmentDimensions(defaultSize, minWidth, maxWidth,
minHeight, minHeight);
}
}

View File

@@ -1,68 +1,27 @@
package org.briarproject.briar.android.conversation;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class AttachmentItem implements Parcelable {
public class AttachmentItem {
private final MessageId messageId;
private final int width, height;
private final String mimeType, extension;
private final int thumbnailWidth, thumbnailHeight;
private final boolean hasError;
private final long instanceId;
public static final Creator<AttachmentItem> CREATOR =
new Creator<AttachmentItem>() {
@Override
public AttachmentItem createFromParcel(Parcel in) {
return new AttachmentItem(in);
}
@Override
public AttachmentItem[] newArray(int size) {
return new AttachmentItem[size];
}
};
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
AttachmentItem(MessageId messageId, int width, int height, String mimeType,
String extension, int thumbnailWidth, int thumbnailHeight,
boolean hasError) {
AttachmentItem(MessageId messageId, int width, int height,
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
this.messageId = messageId;
this.width = width;
this.height = height;
this.mimeType = mimeType;
this.extension = extension;
this.thumbnailWidth = thumbnailWidth;
this.thumbnailHeight = thumbnailHeight;
this.hasError = hasError;
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
}
protected AttachmentItem(Parcel in) {
byte[] messageIdByte = new byte[MessageId.LENGTH];
in.readByteArray(messageIdByte);
messageId = new MessageId(messageIdByte);
width = in.readInt();
height = in.readInt();
mimeType = in.readString();
extension = in.readString();
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
hasError = in.readByte() != 0;
instanceId = in.readLong();
}
public MessageId getMessageId() {
@@ -77,14 +36,6 @@ public class AttachmentItem implements Parcelable {
return height;
}
String getMimeType() {
return mimeType;
}
String getExtension() {
return extension;
}
int getThumbnailWidth() {
return thumbnailWidth;
}
@@ -97,32 +48,4 @@ public class AttachmentItem implements Parcelable {
return hasError;
}
String getTransitionName() {
return String.valueOf(instanceId);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(messageId.getBytes());
dest.writeInt(width);
dest.writeInt(height);
dest.writeString(mimeType);
dest.writeString(extension);
dest.writeInt(thumbnailWidth);
dest.writeInt(thumbnailHeight);
dest.writeByte((byte) (hasError ? 1 : 0));
dest.writeLong(instanceId);
}
@Override
public boolean equals(@Nullable Object o) {
return o instanceof AttachmentItem &&
instanceId == ((AttachmentItem) o).instanceId;
}
}

View File

@@ -1,26 +1,19 @@
package org.briarproject.briar.android.conversation;
import android.annotation.SuppressLint;
import android.arch.lifecycle.Observer;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar;
import android.transition.Slide;
import android.transition.Transition;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuInflater;
@@ -30,6 +23,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
@@ -46,10 +40,14 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
@@ -60,12 +58,8 @@ import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.view.ImagePreview;
import org.briarproject.briar.android.view.TextAttachmentController;
import org.briarproject.briar.android.view.TextAttachmentController.AttachImageListener;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.client.ProtocolStateException;
@@ -80,6 +74,7 @@ import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
@@ -101,12 +96,8 @@ import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
import static android.arch.lifecycle.Lifecycle.State.STARTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.setTransitionName;
import static android.support.v7.util.SortedList.INVALID_POSITION;
import static android.view.Gravity.RIGHT;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Collections.emptyList;
import static java.util.Collections.sort;
@@ -116,14 +107,8 @@ import static java.util.logging.Level.WARNING;
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.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.TestingConstants.FEATURE_FLAG_IMAGE_ATTACHMENTS;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_ATTACH_IMAGE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@@ -134,16 +119,15 @@ import static uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.S
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationActivity extends BriarActivity
implements EventListener, ConversationListener, SendListener,
TextCache, AttachmentCache, AttachImageListener {
implements EventListener, ConversationListener, TextInputListener,
TextCache, AttachmentCache {
public static final String CONTACT_ID = "briar.CONTACT_ID";
private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName());
private static final int TRANSITION_DURATION_MS = 500;
private static final int ONBOARDING_DELAY_MS = 250;
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@Inject
AndroidNotificationManager notificationManager;
@@ -152,8 +136,20 @@ public class ConversationActivity extends BriarActivity
@Inject
@CryptoExecutor
Executor cryptoExecutor;
@Inject
ViewModelProvider.Factory viewModelFactory;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private AttachmentController attachmentController;
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
private ConversationAdapter adapter;
private Toolbar toolbar;
private CircleImageView toolbarAvatar;
private ImageView toolbarStatus;
private TextView toolbarTitle;
private BriarRecyclerView list;
private LinearLayoutManager layoutManager;
private TextInputView textInputView;
// Fields that are accessed from background threads must be volatile
@Inject
@@ -176,39 +172,21 @@ public class ConversationActivity extends BriarActivity
volatile BlogSharingManager blogSharingManager;
@Inject
volatile GroupInvitationManager groupInvitationManager;
@Inject
ViewModelProvider.Factory viewModelFactory;
private volatile ContactId contactId;
@Nullable
private volatile GroupId messagingGroupId;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Observer<String> contactNameObserver = name -> {
requireNonNull(name);
loadMessages();
};
private AttachmentController attachmentController;
private ConversationViewModel viewModel;
private ConversationVisitor visitor;
private ConversationAdapter adapter;
private Toolbar toolbar;
private CircleImageView toolbarAvatar;
private ImageView toolbarStatus;
private TextView toolbarTitle;
private BriarRecyclerView list;
private LinearLayoutManager layoutManager;
private TextInputView textInputView;
private TextSendController sendController;
@Nullable
private Parcelable layoutManagerState;
private volatile ContactId contactId;
@Override
public void onCreate(@Nullable Bundle state) {
if (SDK_INT >= 21) {
// Spurious lint warning - using END causes a crash
@SuppressLint("RtlHardcoded")
Transition slide = new Slide(RIGHT);
slide.setDuration(TRANSITION_DURATION_MS);
setSceneTransitionAnimation(slide, null, slide);
}
setSceneTransitionAnimation();
super.onCreate(state);
Intent i = getIntent();
@@ -218,6 +196,7 @@ public class ConversationActivity extends BriarActivity
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
viewModel.setContactId(contactId);
attachmentController = viewModel.getAttachmentController();
setContentView(R.layout.activity_conversation);
@@ -241,8 +220,6 @@ public class ConversationActivity extends BriarActivity
requireNonNull(deleted);
if (deleted) finish();
});
viewModel.getAddedPrivateMessage().observe(this,
this::onAddedPrivateMessage);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
@@ -255,34 +232,9 @@ public class ConversationActivity extends BriarActivity
list.setLayoutManager(layoutManager);
list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_private_messages));
ConversationScrollListener scrollListener =
new ConversationScrollListener(adapter, viewModel);
list.getRecyclerView().addOnScrollListener(scrollListener);
textInputView = findViewById(R.id.text_input_container);
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, this);
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
if (hasSupport != null && hasSupport) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController)
.setImagesSupported();
}
});
} else {
sendController = new TextSendController(textInputView, this, false);
}
textInputView.setSendController(sendController);
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
textInputView.setEnabled(false);
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
}
private void scrollToBottom() {
int items = adapter.getItemCount();
if (items > 0) list.scrollToPosition(items - 1);
textInputView.setListener(this);
}
@Override
@@ -291,8 +243,7 @@ public class ConversationActivity extends BriarActivity
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
protected void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
@@ -300,9 +251,6 @@ public class ConversationActivity extends BriarActivity
Snackbar.LENGTH_SHORT);
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.show();
} else if (request == REQUEST_ATTACH_IMAGE && result == RESULT_OK) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController).onImageReceived(data);
}
}
@@ -317,16 +265,6 @@ public class ConversationActivity extends BriarActivity
list.startPeriodicUpdate();
}
@Override
public void onResume() {
super.onResume();
// Trigger loading of contact data, noop if data was loaded already.
//
// We can only start loading data *after* we are sure
// the user has signed in. After sign-in, onCreate() isn't run again.
if (signedIn()) viewModel.setContactId(contactId);
}
@Override
public void onStop() {
super.onStop();
@@ -336,39 +274,16 @@ public class ConversationActivity extends BriarActivity
list.stopPeriodicUpdate();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (layoutManager != null) {
layoutManagerState = layoutManager.onSaveInstanceState();
outState.putParcelable("layoutManager", layoutManagerState);
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu items for use in the action bar
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.conversation_actions, menu);
// enable introduction action if available
observeOnce(viewModel.showIntroductionAction(), this, enable -> {
if (enable != null && enable) {
menu.findItem(R.id.action_introduction).setEnabled(true);
// show introduction onboarding, if needed
observeOnce(viewModel.showIntroductionOnboarding(), this,
this::showIntroductionOnboarding);
}
});
// enable alias action if available
observeOnce(viewModel.getContact(), this, contact ->
menu.findItem(R.id.action_set_alias).setEnabled(true));
enableIntroductionActionIfAvailable(
menu.findItem(R.id.action_introduction));
enableAliasActionIfAvailable(
menu.findItem(R.id.action_set_alias));
return super.onCreateOptionsMenu(menu);
}
@@ -431,10 +346,33 @@ public class ConversationActivity extends BriarActivity
Long.compare(b.getTimestamp(), a.getTimestamp()));
if (!sorted.isEmpty()) {
// If the latest header is a private message, eagerly load
// its size so we can set the scroll position correctly
// its text so we can set the scroll position correctly
ConversationMessageHeader latest = sorted.get(0);
if (latest instanceof PrivateMessageHeader) {
eagerlyLoadMessageSize((PrivateMessageHeader) latest);
MessageId id = latest.getId();
PrivateMessageHeader h = (PrivateMessageHeader) latest;
if (h.hasText()) {
String text = textCache.get(id);
if (text == null) {
LOG.info(
"Eagerly loading text of latest message");
text = messagingManager.getMessageText(id);
textCache.put(id, text);
}
}
if (!h.getAttachmentHeaders().isEmpty()) {
List<AttachmentItem> items =
attachmentController.get(id);
if (items == null) {
LOG.info(
"Eagerly loading image size for latest message");
items = attachmentController.getAttachmentItems(
attachmentController
.getMessageAttachments(
h.getAttachmentHeaders()));
attachmentController.put(id, items);
}
}
}
}
displayMessages(revision, sorted);
@@ -446,51 +384,17 @@ public class ConversationActivity extends BriarActivity
});
}
private void eagerlyLoadMessageSize(PrivateMessageHeader h)
throws DbException {
MessageId id = h.getId();
// If the message has text, load it
if (h.hasText()) {
String text = textCache.get(id);
if (text == null) {
LOG.info("Eagerly loading text for latest message");
text = messagingManager.getMessageText(id);
textCache.put(id, text);
}
}
// If the message has a single image, load its size - for multiple
// images we use a grid so the size is fixed
if (h.getAttachmentHeaders().size() == 1) {
List<AttachmentItem> items = attachmentController.get(id);
if (items == null) {
LOG.info("Eagerly loading image size for latest message");
items = attachmentController.getAttachmentItems(
attachmentController.getMessageAttachments(
h.getAttachmentHeaders()));
attachmentController.put(id, items);
}
}
}
private void displayMessages(int revision,
Collection<ConversationMessageHeader> headers) {
runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
textInputView.setEnabled(true);
// start observing onboarding after enabling (only once, because
// we only update this when an onboarding should be shown)
observeOnce(viewModel.showImageOnboarding(), this,
this::showImageOnboarding);
textInputView.setSendButtonEnabled(true);
List<ConversationItem> items = createItems(headers);
adapter.addAll(items);
list.showData();
if (layoutManagerState == null) {
scrollToBottom();
} else {
// Restore the previous scroll position
layoutManager.onRestoreInstanceState(layoutManagerState);
}
// Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1);
} else {
LOG.info("Concurrent update, reloading");
loadMessages();
@@ -531,28 +435,21 @@ public class ConversationActivity extends BriarActivity
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setText(text);
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
}
});
}
// When a message's text or attachments are loaded, scroll to the bottom
// if the conversation is visible and we were previously at the bottom
private boolean shouldScrollWhenUpdatingMessage() {
return getLifecycle().getCurrentState().isAtLeast(STARTED)
&& adapter.isScrolledToBottom(layoutManager);
}
private void loadMessageAttachments(MessageId messageId,
List<AttachmentHeader> headers) {
runOnDbThread(() -> {
try {
List<Pair<AttachmentHeader, Attachment>> attachments =
attachmentController.getMessageAttachments(headers);
// TODO move getting the items off to IoExecutor, if size == 1
// TODO move getting the items off to the IoExecutor
List<AttachmentItem> items =
attachmentController.getAttachmentItems(attachments);
displayMessageAttachments(messageId, items);
@@ -569,10 +466,10 @@ public class ConversationActivity extends BriarActivity
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setAttachments(items);
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
}
});
}
@@ -621,13 +518,10 @@ public class ConversationActivity extends BriarActivity
private void addConversationItem(ConversationItem item) {
runOnUiThreadUnlessDestroyed(() -> {
boolean bottom = adapter.isScrolledToBottom(layoutManager);
adapter.incrementRevision();
adapter.add(item);
// When adding a new message, scroll to the bottom if the
// conversation is visible, even if we're not currently at
// the bottom
if (getLifecycle().getCurrentState().isAtLeast(STARTED))
scrollToBottom();
if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
});
}
@@ -663,18 +557,14 @@ public class ConversationActivity extends BriarActivity
}
@Override
public void onAttachImage(Intent intent) {
startActivityForResult(intent, REQUEST_ATTACH_IMAGE);
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
if (isNullOrEmpty(text) && imageUris.isEmpty())
throw new AssertionError();
public void onSendClick(String text) {
if (text.isEmpty()) return;
text = StringUtils.truncateUtf8(text, MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, imageUris, timestamp);
textInputView.clearText();
if (messagingGroupId == null) loadGroupId(text, timestamp);
else createMessage(text, timestamp);
textInputView.setText("");
}
private long getMinTimestampForNewMessage() {
@@ -683,10 +573,48 @@ public class ConversationActivity extends BriarActivity
return item == null ? 0 : item.getTime() + 1;
}
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
if (h == null) return;
addConversationItem(h.accept(visitor));
viewModel.onAddedPrivateMessageSeen();
private void loadGroupId(String text, long timestamp) {
runOnDbThread(() -> {
try {
messagingGroupId =
messagingManager.getConversationId(contactId);
createMessage(text, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void createMessage(String text, long timestamp) {
cryptoExecutor.execute(() -> {
try {
//noinspection ConstantConditions init in loadGroupId()
storeMessage(privateMessageFactory.createPrivateMessage(
messagingGroupId, timestamp, text, emptyList()), text);
} catch (FormatException e) {
throw new RuntimeException(e);
}
});
}
private void storeMessage(PrivateMessage m, String text) {
runOnDbThread(() -> {
try {
long start = now();
messagingManager.addLocalMessage(m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, false, false, false,
true, emptyList());
textCache.put(message.getId(), text);
addConversationItem(h.accept(visitor));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void askToRemoveContact() {
@@ -723,70 +651,91 @@ public class ConversationActivity extends BriarActivity
});
}
private void showImageOnboarding(@Nullable Boolean show) {
if (show == null || !show) return;
if (SDK_INT >= 21) {
// show onboarding only after the enter transition has ended
// otherwise the tap target animation won't play
textInputView.postDelayed(this::showImageOnboarding,
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
} else {
showImageOnboarding();
}
private void enableIntroductionActionIfAvailable(MenuItem item) {
runOnDbThread(() -> {
try {
if (contactManager.getActiveContacts().size() > 1) {
enableIntroductionAction(item);
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
true)) {
showIntroductionOnboarding();
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void showImageOnboarding() {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController)
.showImageOnboarding(this, () ->
viewModel.onImageOnboardingSeen());
private void enableAliasActionIfAvailable(MenuItem item) {
observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
}
private void showIntroductionOnboarding(@Nullable Boolean show) {
if (show == null || !show) return;
if (SDK_INT >= 21) {
// show onboarding only after the enter transition has ended
// otherwise the tap target animation won't play
textInputView.postDelayed(this::showIntroductionOnboarding,
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS);
} else {
showIntroductionOnboarding();
}
private void enableIntroductionAction(MenuItem item) {
runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
}
private void showIntroductionOnboarding() {
// find view of overflow icon
View target = null;
for (int i = 0; i < toolbar.getChildCount(); i++) {
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i);
// The overflow icon should be the last child of the menu
target = menu.getChildAt(menu.getChildCount() - 1);
// If the menu hasn't been populated yet, use the menu itself
// as the target
if (target == null) target = menu;
break;
runOnUiThreadUnlessDestroyed(() -> {
// find view of overflow icon
View target = null;
for (int i = 0; i < toolbar.getChildCount(); i++) {
if (toolbar.getChildAt(i) instanceof ActionMenuView) {
ActionMenuView menu =
(ActionMenuView) toolbar.getChildAt(i);
target = menu.getChildAt(menu.getChildCount() - 1);
break;
}
}
if (target == null) {
LOG.warning("No Overflow Icon found!");
return;
}
}
if (target == null) {
LOG.warning("No Overflow Icon found!");
return;
}
PromptStateChangeListener listener = (prompt, state) -> {
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
viewModel.onIntroductionOnboardingSeen();
PromptStateChangeListener listener = (prompt, state) -> {
if (state == STATE_DISMISSED || state == STATE_FINISHED) {
introductionOnboardingSeen();
}
};
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
R.style.OnboardingDialogTheme).setTarget(target)
.setPrimaryText(R.string.introduction_onboarding_title)
.setSecondaryText(R.string.introduction_onboarding_text)
.setIcon(R.drawable.ic_more_vert_accent)
.setPromptStateChangeListener(listener)
.show();
});
}
private void introductionOnboardingSeen() {
runOnDbThread(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(SHOW_ONBOARDING_INTRODUCTION, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
};
new MaterialTapTargetPrompt.Builder(ConversationActivity.this,
R.style.OnboardingDialogTheme).setTarget(target)
.setPrimaryText(R.string.introduction_onboarding_title)
.setSecondaryText(R.string.introduction_onboarding_text)
.setIcon(R.drawable.ic_more_vert_accent)
.setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary))
.setPromptStateChangeListener(listener)
.show();
});
}
@Override
public void onItemVisible(ConversationItem item) {
if (!item.isRead()) markMessageRead(item.getGroupId(), item.getId());
}
private void markMessageRead(GroupId g, MessageId m) {
runOnDbThread(() -> {
try {
long start = now();
messagingManager.setReadFlag(g, m, true);
logDuration(LOG, "Marking read", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@UiThread
@@ -853,30 +802,6 @@ public class ConversationActivity extends BriarActivity
startActivity(i);
}
@Override
public void onAttachmentClicked(View view,
ConversationMessageItem messageItem, AttachmentItem item) {
String name;
if (messageItem.isIncoming()) {
// must be available when items are being displayed
name = viewModel.getContactDisplayName().getValue();
} else {
name = getString(R.string.you);
}
ArrayList<AttachmentItem> attachments =
new ArrayList<>(messageItem.getAttachments());
Intent i = new Intent(this, ImageActivity.class);
i.putParcelableArrayListExtra(ATTACHMENTS, attachments);
i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item));
i.putExtra(NAME, name);
i.putExtra(DATE, messageItem.getTime());
// restoring list position should not trigger android bug #224270
String transitionName = item.getTransitionName();
ActivityOptionsCompat options =
makeSceneTransitionAnimation(this, view, transitionName);
ActivityCompat.startActivity(this, i, options.toBundle());
}
@DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept, long time) throws DbException {
@@ -920,5 +845,4 @@ public class ConversationActivity extends BriarActivity
}
return attachments;
}
}

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView.RecycledViewPool;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -14,27 +13,19 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
import org.briarproject.briar.android.util.ItemReturningAdapter;
import javax.annotation.Nullable;
@NotNullByDefault
class ConversationAdapter
extends BriarAdapter<ConversationItem, ConversationItemViewHolder>
implements ItemReturningAdapter<ConversationItem> {
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
private ConversationListener listener;
private final RecycledViewPool imageViewPool;
private final ImageItemDecoration imageItemDecoration;
ConversationAdapter(Context ctx,
ConversationListener conversationListener) {
super(ctx, ConversationItem.class);
listener = conversationListener;
// This shares the same pool for view recycling between all image lists
imageViewPool = new RecycledViewPool();
// Share the item decoration as well
imageItemDecoration = new ImageItemDecoration(ctx);
}
@LayoutRes
@@ -51,17 +42,15 @@ class ConversationAdapter
type, viewGroup, false);
switch (type) {
case R.layout.list_item_conversation_msg_in:
return new ConversationMessageViewHolder(v, listener, true,
imageViewPool, imageItemDecoration);
return new ConversationMessageViewHolder(v, true);
case R.layout.list_item_conversation_msg_out:
return new ConversationMessageViewHolder(v, listener, false,
imageViewPool, imageItemDecoration);
return new ConversationMessageViewHolder(v, false);
case R.layout.list_item_conversation_notice_in:
return new ConversationNoticeViewHolder(v, listener, true);
return new ConversationNoticeViewHolder(v, true);
case R.layout.list_item_conversation_notice_out:
return new ConversationNoticeViewHolder(v, listener, false);
return new ConversationNoticeViewHolder(v, false);
case R.layout.list_item_conversation_request:
return new ConversationRequestViewHolder(v, listener, true);
return new ConversationRequestViewHolder(v, true);
default:
throw new IllegalArgumentException("Unknown ConversationItem");
}
@@ -70,7 +59,8 @@ class ConversationAdapter
@Override
public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
ConversationItem item = items.get(position);
ui.bind(item);
ui.bind(item, listener);
listener.onItemVisible(item);
}
@Override

View File

@@ -69,10 +69,6 @@ abstract class ConversationItem {
return read;
}
void markRead() {
read = true;
}
/**
* Only useful for outgoing messages.
*/

View File

@@ -18,17 +18,14 @@ import static org.briarproject.briar.android.util.UiUtils.formatDate;
@NotNullByDefault
abstract class ConversationItemViewHolder extends ViewHolder {
protected final ConversationListener listener;
protected final ConstraintLayout layout;
@Nullable
private final OutItemViewHolder outViewHolder;
private final TextView text;
protected final TextView time;
ConversationItemViewHolder(View v, ConversationListener listener,
boolean isIncoming) {
ConversationItemViewHolder(View v, boolean isIncoming) {
super(v);
this.listener = listener;
this.outViewHolder = isIncoming ? null : new OutItemViewHolder(v);
layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text);
@@ -36,7 +33,7 @@ abstract class ConversationItemViewHolder extends ViewHolder {
}
@CallSuper
void bind(ConversationItem item) {
void bind(ConversationItem item, ConversationListener listener) {
if (item.getText() != null) {
text.setText(trim(item.getText()));
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.conversation;
import android.support.annotation.UiThread;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -9,11 +8,10 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface ConversationListener {
void onItemVisible(ConversationItem item);
void respondToRequest(ConversationRequestItem item, boolean accept);
void openRequestedShareable(ConversationRequestItem item);
void onAttachmentClicked(View view, ConversationMessageItem messageItem,
AttachmentItem attachmentItem);
}

View File

@@ -1,45 +1,63 @@
package org.briarproject.briar.android.conversation;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.support.annotation.DrawableRes;
import android.support.annotation.UiThread;
import android.support.constraint.ConstraintSet;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.RecycledViewPool;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.bumptech.glide.load.Transformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import static android.support.constraint.ConstraintSet.WRAP_CONTENT;
import static android.support.v4.content.ContextCompat.getColor;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_RTL;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
@UiThread
@NotNullByDefault
class ConversationMessageViewHolder extends ConversationItemViewHolder {
private final ImageAdapter adapter;
@DrawableRes
private static final int ERROR_RES = R.drawable.ic_image_broken;
private final ImageView imageView;
private final ViewGroup statusLayout;
private final int timeColor, timeColorBubble;
private final int radiusBig, radiusSmall;
private final boolean isRtl;
private final ConstraintSet textConstraints = new ConstraintSet();
private final ConstraintSet imageConstraints = new ConstraintSet();
private final ConstraintSet imageTextConstraints = new ConstraintSet();
ConversationMessageViewHolder(View v, ConversationListener listener,
boolean isIncoming, RecycledViewPool imageViewPool,
ImageItemDecoration imageItemDecoration) {
super(v, listener, isIncoming);
ConversationMessageViewHolder(View v, boolean isIncoming) {
super(v, isIncoming);
imageView = v.findViewById(R.id.imageView);
statusLayout = v.findViewById(R.id.statusLayout);
// image list
RecyclerView list = v.findViewById(R.id.imageList);
list.setRecycledViewPool(imageViewPool);
adapter = new ImageAdapter(v.getContext(), listener);
list.setAdapter(adapter);
list.addItemDecoration(imageItemDecoration);
radiusBig = v.getContext().getResources()
.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
radiusSmall = v.getContext().getResources()
.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
// remember original status text color
timeColor = time.getCurrentTextColor();
timeColorBubble = getColor(v.getContext(), R.color.briar_white);
timeColorBubble =
ContextCompat.getColor(v.getContext(), R.color.briar_white);
// find out if we are showing a RTL language, Use the configuration,
// because getting the layout direction of views is not reliable
Configuration config =
imageView.getContext().getResources().getConfiguration();
isRtl = SDK_INT >= 17 &&
config.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
// clone constraint sets from layout files
textConstraints
@@ -59,8 +77,9 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
}
@Override
void bind(ConversationItem conversationItem) {
super.bind(conversationItem);
void bind(ConversationItem conversationItem,
ConversationListener listener) {
super.bind(conversationItem, listener);
ConversationMessageItem item =
(ConversationMessageItem) conversationItem;
if (item.getAttachments().isEmpty()) {
@@ -71,43 +90,68 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
}
private void bindTextItem() {
resetStatusLayoutForText();
textConstraints.applyTo(layout);
adapter.clear();
}
private void bindImageItem(ConversationMessageItem item) {
ConstraintSet constraintSet;
if (item.getText() == null) {
statusLayout.setBackgroundResource(R.drawable.msg_status_bubble);
time.setTextColor(timeColorBubble);
constraintSet = imageConstraints;
} else {
resetStatusLayoutForText();
constraintSet = imageTextConstraints;
}
if (item.getAttachments().size() == 1) {
// apply image size constraints for a single image
AttachmentItem attachment = item.getAttachments().get(0);
int width = attachment.getThumbnailWidth();
int height = attachment.getThumbnailHeight();
constraintSet.constrainWidth(R.id.imageList, width);
constraintSet.constrainHeight(R.id.imageList, height);
} else {
// bubble adapts to size of image list
constraintSet.constrainWidth(R.id.imageList, WRAP_CONTENT);
constraintSet.constrainHeight(R.id.imageList, WRAP_CONTENT);
}
constraintSet.applyTo(layout);
adapter.setConversationItem(item);
}
private void resetStatusLayoutForText() {
clearImage();
statusLayout.setBackgroundResource(0);
// also reset padding (the background drawable defines some)
statusLayout.setPadding(0, 0, 0, 0);
time.setTextColor(timeColor);
textConstraints.applyTo(layout);
}
private void bindImageItem(ConversationMessageItem item) {
// TODO show more than just the first image
AttachmentItem attachment = item.getAttachments().get(0);
ConstraintSet constraintSet;
if (item.getText() == null) {
statusLayout
.setBackgroundResource(R.drawable.msg_status_bubble);
time.setTextColor(timeColorBubble);
constraintSet = imageConstraints;
} else {
statusLayout.setBackgroundResource(0);
// also reset padding (the background drawable defines some)
statusLayout.setPadding(0, 0, 0, 0);
time.setTextColor(timeColor);
constraintSet = imageTextConstraints;
}
// apply image size constraints, so glides picks them up for scaling
int width = attachment.getThumbnailWidth();
int height = attachment.getThumbnailHeight();
constraintSet.constrainWidth(R.id.imageView, width);
constraintSet.constrainHeight(R.id.imageView, height);
constraintSet.applyTo(layout);
if (attachment.hasError()) {
clearImage();
imageView.setImageResource(ERROR_RES);
} else {
loadImage(item, attachment);
}
}
private void clearImage() {
GlideApp.with(imageView)
.clear(imageView);
}
private void loadImage(ConversationMessageItem item,
AttachmentItem attachment) {
boolean leftCornerSmall =
(isIncoming() && !isRtl) || (!isIncoming() && isRtl);
boolean bottomRound = item.getText() == null;
Transformation<Bitmap> transformation = new BriarImageTransformation(
radiusSmall, radiusBig, leftCornerSmall, bottomRound);
GlideApp.with(imageView)
.load(attachment)
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.transform(transformation)
.transition(withCrossFade())
.into(imageView)
.waitForLayout();
}
}

View File

@@ -19,17 +19,16 @@ class ConversationNoticeViewHolder extends ConversationItemViewHolder {
private final TextView msgText;
ConversationNoticeViewHolder(View v, ConversationListener listener,
boolean isIncoming) {
super(v, listener, isIncoming);
ConversationNoticeViewHolder(View v, boolean isIncoming) {
super(v, isIncoming);
msgText = v.findViewById(R.id.msgText);
}
@Override
@CallSuper
void bind(ConversationItem item) {
void bind(ConversationItem item, ConversationListener listener) {
ConversationNoticeItem notice = (ConversationNoticeItem) item;
super.bind(notice);
super.bind(notice, listener);
String text = notice.getMsgText();
if (isNullOrEmpty(text)) {

View File

@@ -17,17 +17,16 @@ class ConversationRequestViewHolder extends ConversationNoticeViewHolder {
private final Button acceptButton;
private final Button declineButton;
ConversationRequestViewHolder(View v, ConversationListener listener,
boolean isIncoming) {
super(v, listener, isIncoming);
ConversationRequestViewHolder(View v, boolean isIncoming) {
super(v, isIncoming);
acceptButton = v.findViewById(R.id.acceptButton);
declineButton = v.findViewById(R.id.declineButton);
}
@Override
void bind(ConversationItem item) {
void bind(ConversationItem item, ConversationListener listener) {
ConversationRequestItem request = (ConversationRequestItem) item;
super.bind(request);
super.bind(request, listener);
if (request.wasAnswered() && request.canBeOpened()) {
acceptButton.setVisibility(VISIBLE);

View File

@@ -1,26 +0,0 @@
package org.briarproject.briar.android.conversation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.view.BriarRecyclerViewScrollListener;
@NotNullByDefault
class ConversationScrollListener extends
BriarRecyclerViewScrollListener<ConversationAdapter, ConversationItem> {
private final ConversationViewModel viewModel;
protected ConversationScrollListener(ConversationAdapter adapter,
ConversationViewModel viewModel) {
super(adapter);
this.viewModel = viewModel;
}
@Override
protected void onItemVisible(ConversationItem item) {
if (!item.isRead()) {
viewModel.markMessageRead(item.getGroupId(), item.getId());
item.markRead();
}
}
}

View File

@@ -5,41 +5,19 @@ import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Transformations;
import android.content.ContentResolver;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -48,34 +26,19 @@ import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
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.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel {
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
"showOnboardingImage";
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@DatabaseExecutor
private final Executor dbExecutor;
@CryptoExecutor
private final Executor cryptoExecutor;
// TODO replace with TransactionManager once it exists
private final DatabaseComponent db;
private final MessagingManager messagingManager;
private final ContactManager contactManager;
private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentController attachmentController;
@Nullable
@@ -85,64 +48,38 @@ public class ConversationViewModel extends AndroidViewModel {
Transformations.map(contact, c -> c.getAuthor().getId());
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showImageOnboarding =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showIntroductionOnboarding =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showIntroductionAction =
new MutableLiveData<>();
private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>();
private final MutableLiveData<GroupId> messagingGroupId =
new MutableLiveData<>();
private final MutableLiveData<PrivateMessageHeader> addedHeader =
new MutableLiveData<>();
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db,
MessagingManager messagingManager, ContactManager contactManager,
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory) {
ContactManager contactManager, MessagingManager messagingManager) {
super(application);
this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.messagingManager = messagingManager;
this.contactManager = contactManager;
this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory;
this.attachmentController = new AttachmentController(messagingManager,
getAttachmentDimensions(application.getResources()));
application.getResources());
contactDeleted.setValue(false);
}
/**
* Setting the {@link ContactId} automatically triggers loading of other
* data.
*/
void setContactId(ContactId contactId) {
if (this.contactId == null) {
this.contactId = contactId;
loadContact(contactId);
loadContact();
} else if (!contactId.equals(this.contactId)) {
throw new IllegalStateException();
}
}
private void loadContact(ContactId contactId) {
private void loadContact() {
dbExecutor.execute(() -> {
try {
long start = now();
Contact c = contactManager.getContact(contactId);
Contact c =
contactManager.getContact(requireNonNull(contactId));
contact.postValue(c);
logDuration(LOG, "Loading contact", start);
start = now();
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) {
contactDeleted.postValue(true);
} catch (DbException e) {
@@ -151,196 +88,18 @@ public class ConversationViewModel extends AndroidViewModel {
});
}
void markMessageRead(GroupId g, MessageId m) {
dbExecutor.execute(() -> {
try {
long start = now();
messagingManager.setReadFlag(g, m, true);
logDuration(LOG, "Marking read", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setContactAlias(String alias) {
dbExecutor.execute(() -> {
try {
contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias);
loadContact(contactId);
loadContact();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void sendMessage(@Nullable String text, List<Uri> uris, long timestamp) {
if (messagingGroupId.getValue() == null) loadGroupId();
observeForeverOnce(messagingGroupId, groupId -> {
if (groupId == null) return;
// calls through to creating and storing the message
storeAttachments(groupId, text, uris, timestamp);
});
}
private void loadGroupId() {
if (contactId == null) throw new IllegalStateException();
dbExecutor.execute(() -> {
try {
messagingGroupId.postValue(
messagingManager.getConversationId(contactId));
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images are supported
boolean imagesSupported = db.transactionWithResult(true, txn ->
messagingManager.contactSupportsImages(txn, c));
imageSupport.postValue(imagesSupported);
// check if introductions are supported
Collection<Contact> contacts = contactManager.getActiveContacts();
boolean introductionSupported = contacts.size() > 1;
showIntroductionAction.postValue(introductionSupported);
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
// check if we should show onboarding, only if images are supported
showImageOnboarding.postValue(true);
// allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false);
} else {
// allow observer to stop listening for changes
showImageOnboarding.postValue(false);
// we only show one onboarding dialog at a time
if (introductionSupported &&
settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION, true)) {
showIntroductionOnboarding.postValue(true);
} else {
// allow observer to stop listening for changes
showIntroductionOnboarding.postValue(false);
}
}
}
void onImageOnboardingSeen() {
onOnboardingSeen(SHOW_ONBOARDING_IMAGE);
}
void onIntroductionOnboardingSeen() {
onOnboardingSeen(SHOW_ONBOARDING_INTRODUCTION);
}
private void onOnboardingSeen(String key) {
dbExecutor.execute(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(key, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void storeAttachments(GroupId groupId, @Nullable String text,
List<Uri> uris, long timestamp) {
dbExecutor.execute(() -> {
long start = now();
List<AttachmentHeader> attachments = new ArrayList<>();
List<AttachmentItem> items = new ArrayList<>();
boolean needsSize = uris.size() == 1;
for (Uri uri : uris) {
Pair<AttachmentHeader, AttachmentItem> pair =
createAttachmentHeader(groupId, uri, timestamp,
needsSize);
if (pair == null) continue;
attachments.add(pair.getFirst());
items.add(pair.getSecond());
}
logDuration(LOG, "Storing attachments", start);
createMessage(groupId, text, attachments, items, timestamp);
});
}
@Nullable
@DatabaseExecutor
private Pair<AttachmentHeader, AttachmentItem> createAttachmentHeader(
GroupId groupId, Uri uri, long timestamp, boolean needsSize) {
InputStream is = null;
try {
ContentResolver contentResolver =
getApplication().getContentResolver();
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
String contentType = contentResolver.getType(uri);
if (contentType == null) throw new IOException("null content type");
AttachmentHeader h = messagingManager
.addLocalAttachment(groupId, timestamp, contentType, is);
is.close();
// re-open stream to get AttachmentItem
is = contentResolver.openInputStream(uri);
if (is == null) throw new IOException();
AttachmentItem item = attachmentController
.getAttachmentItem(h, new Attachment(is), needsSize);
return new Pair<>(h, item);
} catch (DbException | IOException e) {
logException(LOG, WARNING, e);
return null;
} finally {
if (is != null) tryToClose(is, LOG, WARNING);
}
}
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> attachments, List<AttachmentItem> aItems,
long timestamp) {
cryptoExecutor.execute(() -> {
try {
// TODO remove when text can be null in the backend
String msgText = text == null ? "null" : text;
PrivateMessage pm = privateMessageFactory
.createPrivateMessage(groupId, timestamp, msgText,
attachments);
attachmentController.put(pm.getMessage().getId(), aItems);
storeMessage(pm, msgText, attachments);
} catch (FormatException e) {
throw new RuntimeException(e);
}
});
}
private void storeMessage(PrivateMessage m, @Nullable String text,
List<AttachmentHeader> attachments) {
dbExecutor.execute(() -> {
try {
long start = now();
messagingManager.addLocalMessage(m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
text != null, attachments);
// TODO add text to cache when available here
addedHeader.postValue(h);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@UiThread
void onAddedPrivateMessageSeen() {
addedHeader.setValue(null);
}
AttachmentController getAttachmentController() {
return attachmentController;
}
@@ -357,28 +116,8 @@ public class ConversationViewModel extends AndroidViewModel {
return contactName;
}
LiveData<Boolean> hasImageSupport() {
return imageSupport;
}
LiveData<Boolean> showImageOnboarding() {
return showImageOnboarding;
}
LiveData<Boolean> showIntroductionOnboarding() {
return showIntroductionOnboarding;
}
LiveData<Boolean> showIntroductionAction() {
return showIntroductionAction;
}
LiveData<Boolean> isContactDeleted() {
return contactDeleted;
}
LiveData<PrivateMessageHeader> getAddedPrivateMessage() {
return addedHeader;
}
}

View File

@@ -1,344 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.widget.Toolbar;
import android.transition.Fade;
import android.transition.Transition;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.view.PullDownLayout;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import static android.content.Intent.ACTION_CREATE_DOCUMENT;
import static android.content.Intent.CATEGORY_OPENABLE;
import static android.content.Intent.EXTRA_TITLE;
import static android.graphics.Color.TRANSPARENT;
import static android.os.Build.VERSION.SDK_INT;
import static android.support.design.widget.Snackbar.LENGTH_LONG;
import static android.view.View.GONE;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ImageActivity extends BriarActivity
implements PullDownLayout.Callback, OnGlobalLayoutListener {
final static String ATTACHMENTS = "attachments";
final static String ATTACHMENT_POSITION = "position";
final static String NAME = "name";
final static String DATE = "date";
@RequiresApi(api = 16)
private final static int UI_FLAGS_DEFAULT =
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
@Inject
ViewModelProvider.Factory viewModelFactory;
private ImageViewModel viewModel;
private PullDownLayout layout;
private AppBarLayout appBarLayout;
private ViewPager viewPager;
private List<AttachmentItem> attachments;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
// Transitions
if (state == null) supportPostponeEnterTransition();
Window window = getWindow();
if (SDK_INT >= 21) {
Transition transition = new Fade();
setSceneTransitionAnimation(transition, null, transition);
}
// get View Model
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ImageViewModel.class);
viewModel.getSaveState().observe(this, this::onImageSaveStateChanged);
// inflate layout
setContentView(R.layout.activity_image);
layout = findViewById(R.id.layout);
layout.setCallback(this);
layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
// Status Bar
if (SDK_INT >= 21) {
window.setStatusBarColor(TRANSPARENT);
} else if (SDK_INT >= 19) {
// we can't make the status bar transparent, but translucent
window.addFlags(FLAG_TRANSLUCENT_STATUS);
}
// Toolbar
appBarLayout = findViewById(R.id.appBarLayout);
Toolbar toolbar = requireNonNull(setUpCustomToolbar(true));
TextView contactName = toolbar.findViewById(R.id.contactName);
TextView dateView = toolbar.findViewById(R.id.dateView);
// Intent Extras
Intent i = getIntent();
attachments = i.getParcelableArrayListExtra(ATTACHMENTS);
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
if (position == -1) throw new IllegalStateException();
String name = i.getStringExtra(NAME);
long time = i.getLongExtra(DATE, 0);
String date = formatDateAbsolute(this, time);
contactName.setText(name);
dateView.setText(date);
// Set up image ViewPager
viewPager = findViewById(R.id.viewPager);
ImagePagerAdapter pagerAdapter =
new ImagePagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(pagerAdapter);
viewPager.setCurrentItem(position);
if (SDK_INT >= 16) {
viewModel.getOnImageClicked().observe(this, this::onImageClicked);
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.image_actions, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.action_save_image:
showSaveImageDialog();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onGlobalLayout() {
viewModel.setToolbarPosition(
appBarLayout.getTop(), appBarLayout.getBottom()
);
if (SDK_INT >= 16) {
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
@Override
protected void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK &&
data != null) {
viewModel.saveImage(getVisibleAttachment(), data.getData());
}
}
@Override
public void onPullStart() {
appBarLayout.animate()
.alpha(0f)
.start();
}
@Override
public void onPull(float progress) {
}
@Override
public void onPullCancel() {
appBarLayout.animate()
.alpha(1f)
.start();
}
@Override
public void onPullComplete() {
showStatusBarBeforeFinishing();
supportFinishAfterTransition();
}
@Override
public void onBackPressed() {
showStatusBarBeforeFinishing();
super.onBackPressed();
}
@RequiresApi(api = 16)
private void onImageClicked(@Nullable Boolean clicked) {
if (clicked != null && clicked) {
toggleSystemUi();
viewModel.onOnImageClickSeen();
}
}
@RequiresApi(api = 16)
private void toggleSystemUi() {
View decorView = getWindow().getDecorView();
if (appBarLayout.getVisibility() == VISIBLE) {
hideSystemUi(decorView);
} else {
showSystemUi(decorView);
}
}
@RequiresApi(api = 16)
private void hideSystemUi(View decorView) {
decorView.setSystemUiVisibility(
SYSTEM_UI_FLAG_FULLSCREEN | UI_FLAGS_DEFAULT);
appBarLayout.animate()
.translationYBy(-1 * appBarLayout.getHeight())
.alpha(0f)
.withEndAction(() -> appBarLayout.setVisibility(GONE))
.start();
}
@RequiresApi(api = 16)
private void showSystemUi(View decorView) {
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT);
appBarLayout.animate()
.translationYBy(appBarLayout.getHeight())
.alpha(1f)
.withStartAction(() -> appBarLayout.setVisibility(VISIBLE))
.start();
}
/**
* If we don't show the status bar again before finishing this activity,
* the return transition will "jump" down the size of the status bar
* when the previous activity (with visible status bar) is shown.
*/
private void showStatusBarBeforeFinishing() {
if (SDK_INT >= 16 && appBarLayout.getVisibility() == GONE) {
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT);
}
}
private void showSaveImageDialog() {
OnClickListener okListener = (dialog, which) -> {
if (SDK_INT >= 19) {
Intent intent = getCreationIntent();
startActivityForResult(intent, REQUEST_SAVE_ATTACHMENT);
} else {
viewModel.saveImage(getVisibleAttachment());
}
};
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.dialog_title_save_image));
builder.setMessage(getString(R.string.dialog_message_save_image));
Drawable icon = ContextCompat.getDrawable(this, R.drawable.ic_security);
DrawableCompat.setTint(requireNonNull(icon),
ContextCompat.getColor(this, R.color.color_primary));
builder.setIcon(icon);
builder.setPositiveButton(R.string.save_image, okListener);
builder.setNegativeButton(R.string.cancel, null);
builder.show();
}
@RequiresApi(api = 19)
private Intent getCreationIntent() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
Locale.getDefault());
String fileName = sdf.format(new Date());
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addCategory(CATEGORY_OPENABLE);
intent.setType(getVisibleAttachment().getMimeType());
intent.putExtra(EXTRA_TITLE, fileName);
return intent;
}
private void onImageSaveStateChanged(@Nullable Boolean error) {
if (error == null) return;
int stringRes = error ?
R.string.save_image_error : R.string.save_image_success;
int colorRes = error ?
R.color.briar_red : R.color.briar_primary;
Snackbar s = Snackbar.make(layout, stringRes, LENGTH_LONG);
s.getView().setBackgroundResource(colorRes);
s.show();
viewModel.onSaveStateSeen();
}
AttachmentItem getVisibleAttachment() {
return attachments.get(viewPager.getCurrentItem());
}
private class ImagePagerAdapter extends FragmentStatePagerAdapter {
private boolean isFirst = true;
private ImagePagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
Fragment f = ImageFragment
.newInstance(attachments.get(position), isFirst);
isFirst = false;
return f;
}
@Override
public int getCount() {
return attachments.size();
}
}
}

View File

@@ -1,155 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView.Adapter;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.glide.Radii;
import java.util.ArrayList;
import java.util.List;
import static android.content.Context.WINDOW_SERVICE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.util.UiUtils.isRtl;
@NotNullByDefault
class ImageAdapter extends Adapter<ImageViewHolder> {
private final List<AttachmentItem> items = new ArrayList<>();
private final ConversationListener listener;
private final int imageSize;
private final int radiusBig, radiusSmall;
private final boolean isRtl;
@Nullable
private ConversationMessageItem conversationItem;
ImageAdapter(Context ctx, ConversationListener listener) {
this.listener = listener;
imageSize = getImageSize(ctx);
Resources res = ctx.getResources();
radiusBig =
res.getDimensionPixelSize(R.dimen.message_bubble_radius_big);
radiusSmall =
res.getDimensionPixelSize(R.dimen.message_bubble_radius_small);
isRtl = isRtl(ctx);
}
@Override
public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_image, viewGroup, false);
return new ImageViewHolder(v, imageSize);
}
@Override
public void onBindViewHolder(ImageViewHolder imageViewHolder,
int position) {
// get item
requireNonNull(conversationItem);
AttachmentItem item = items.get(position);
// set onClick listener
imageViewHolder.itemView.setOnClickListener(v ->
listener.onAttachmentClicked(v, conversationItem, item)
);
// bind view holder
int size = items.size();
boolean isIncoming = conversationItem.isIncoming();
boolean hasText = conversationItem.getText() != null;
Radii r = getRadii(position, size, isIncoming, hasText);
imageViewHolder.bind(item, r, size == 1, singleInRow(position, size));
}
@Override
public int getItemCount() {
return items.size();
}
void setConversationItem(ConversationMessageItem item) {
this.conversationItem = item;
this.items.clear();
this.items.addAll(item.getAttachments());
notifyDataSetChanged();
}
private int getImageSize(Context ctx) {
Resources res = ctx.getResources();
WindowManager windowManager =
(WindowManager) ctx.getSystemService(WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
if (windowManager == null) {
return res.getDimensionPixelSize(
R.dimen.message_bubble_image_default);
}
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
int imageSize = displayMetrics.widthPixels / 3;
int maxSize = res.getDimensionPixelSize(
R.dimen.message_bubble_image_max_width);
return Math.min(imageSize, maxSize);
}
private Radii getRadii(int pos, int num, boolean isIncoming,
boolean hasText) {
boolean left = isLeft(pos);
boolean single = num == 1;
// Top Row
int topLeft;
int topRight;
if (single) {
topLeft = isIncoming ? radiusSmall : radiusBig;
topRight = !isIncoming ? radiusSmall : radiusBig;
} else if (isTopRow(pos)) {
topLeft = left ? (isIncoming ? radiusSmall : radiusBig) : 0;
topRight = !left ? (!isIncoming ? radiusSmall : radiusBig) : 0;
} else {
topLeft = 0;
topRight = 0;
}
// Bottom Row
boolean singleInRow = singleInRow(pos, num);
int bottomLeft;
int bottomRight;
if (!hasText && isBottomRow(pos, num)) {
bottomLeft = singleInRow || left ? radiusBig : 0;
bottomRight = singleInRow || !left ? radiusBig : 0;
} else {
bottomLeft = 0;
bottomRight = 0;
}
if (isRtl) return new Radii(topRight, topLeft, bottomRight, bottomLeft);
return new Radii(topLeft, topRight, bottomLeft, bottomRight);
}
void clear() {
items.clear();
notifyDataSetChanged();
}
static boolean isTopRow(int pos) {
return pos < 2;
}
static boolean isLeft(int pos) {
return pos % 2 == 0;
}
static boolean isBottomRow(int pos, int num) {
return num % 2 == 0 ?
pos >= num - 2 : // last two, if even
pos > num - 2; // last one, if odd
}
static boolean singleInRow(int pos, int num) {
// last item of an odd number
return num % 2 != 0 && pos == num -1;
}
}

View File

@@ -1,133 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.arch.lifecycle.ViewModelProvider;
import android.arch.lifecycle.ViewModelProviders;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.github.chrisbanes.photoview.PhotoView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
import static android.widget.ImageView.ScaleType.FIT_START;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
@MethodsNotNullByDefault
@ParametersAreNonnullByDefault
public class ImageFragment extends Fragment {
private final static String IS_FIRST = "isFirst";
@Inject
ViewModelProvider.Factory viewModelFactory;
private AttachmentItem attachment;
private boolean isFirst;
private ImageViewModel viewModel;
private PhotoView photoView;
static ImageFragment newInstance(AttachmentItem a, boolean isFirst) {
ImageFragment f = new ImageFragment();
Bundle args = new Bundle();
args.putParcelable(ATTACHMENT_POSITION, a);
args.putBoolean(IS_FIRST, isFirst);
f.setArguments(args);
return f;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
BaseActivity a = (BaseActivity) requireNonNull(getActivity());
a.getActivityComponent().inject(this);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = requireNonNull(getArguments());
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
isFirst = args.getBoolean(IS_FIRST);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_image, container,
false);
viewModel = ViewModelProviders.of(requireNonNull(getActivity()),
viewModelFactory).get(ImageViewModel.class);
photoView = v.findViewById(R.id.photoView);
photoView.setScaleLevels(1, 2, 4);
photoView.setOnClickListener(view -> viewModel.clickImage());
// Request Listener
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
if (getActivity() != null && isFirst)
getActivity().supportStartPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource,
boolean isFirstResource) {
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
// set transition name only when not animatable,
// because the animation won't start otherwise
photoView.setTransitionName(
attachment.getTransitionName());
}
// Move image to the top if overlapping toolbar
if (viewModel.isOverlappingToolbar(photoView, resource)) {
photoView.setScaleType(FIT_START);
}
if (getActivity() != null && isFirst) {
getActivity().supportStartPostponedEnterTransition();
}
return false;
}
};
// Load Image
GlideApp.with(this)
.load(attachment)
// TODO allow if size < maxTextureSize ?
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE)
.error(R.drawable.ic_image_broken)
.addListener(listener)
.into(photoView);
return v;
}
}

View File

@@ -1,29 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.InputStream;
@NotNullByDefault
interface ImageHelper {
DecodeResult decodeStream(InputStream is);
@Nullable
String getExtensionFromMimeType(String mimeType);
class DecodeResult {
final int width;
final int height;
final String mimeType;
DecodeResult(int width, int height, String mimeType) {
this.width = width;
this.height = height;
this.mimeType = mimeType;
}
}
}

View File

@@ -1,54 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ItemDecoration;
import android.support.v7.widget.RecyclerView.State;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import static org.briarproject.briar.android.conversation.ImageAdapter.isBottomRow;
import static org.briarproject.briar.android.conversation.ImageAdapter.isLeft;
import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow;
import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow;
import static org.briarproject.briar.android.util.UiUtils.isRtl;
@NotNullByDefault
class ImageItemDecoration extends ItemDecoration {
private final int border;
private final boolean isRtl;
ImageItemDecoration(Context ctx) {
Resources res = ctx.getResources();
// for pixel perfection, add a pixel to the border if it has an odd size
int b = res.getDimensionPixelSize(R.dimen.message_bubble_border);
int realBorderSize = b % 2 == 0 ? b : b + 1;
// we are applying half the border around the insides of each image
// to prevent differently sized images looking slightly broken
border = realBorderSize / 2;
// find out if we are showing a RTL language
isRtl = isRtl(ctx);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
State state) {
if (state.getItemCount() == 1) return;
int pos = parent.getChildAdapterPosition(view);
int num = state.getItemCount();
boolean start = isLeft(pos) ^ isRtl;
outRect.top = isTopRow(pos) ? 0 : border;
outRect.left = start ? 0 : border;
outRect.right = start && !singleInRow(pos, num) ? border : 0;
outRect.bottom = isBottomRow(pos, num) ? 0 : border;
}
}

View File

@@ -1,74 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.graphics.Bitmap;
import android.support.annotation.DrawableRes;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams;
import android.view.View;
import android.widget.ImageView;
import com.bumptech.glide.load.Transformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
import org.briarproject.briar.android.conversation.glide.GlideApp;
import org.briarproject.briar.android.conversation.glide.Radii;
import static android.os.Build.VERSION.SDK_INT;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
@NotNullByDefault
class ImageViewHolder extends ViewHolder {
@DrawableRes
private static final int ERROR_RES = R.drawable.ic_image_broken;
protected final ImageView imageView;
private final int imageSize;
ImageViewHolder(View v, int imageSize) {
super(v);
imageView = v.findViewById(R.id.imageView);
this.imageSize = imageSize;
}
void bind(AttachmentItem attachment, Radii r, boolean single,
boolean needsStretch) {
if (attachment.hasError()) {
GlideApp.with(imageView)
.clear(imageView);
imageView.setImageResource(ERROR_RES);
} else {
setImageViewDimensions(attachment, single, needsStretch);
loadImage(attachment, r);
if (SDK_INT >= 21) {
imageView.setTransitionName(attachment.getTransitionName());
}
}
}
private void setImageViewDimensions(AttachmentItem a, boolean single,
boolean needsStretch) {
LayoutParams params = (LayoutParams) imageView.getLayoutParams();
int width = needsStretch ? imageSize * 2 : imageSize;
params.width = single ? a.getThumbnailWidth() : width;
params.height = single ? a.getThumbnailHeight() : imageSize;
params.setFullSpan(!single && needsStretch);
imageView.setLayoutParams(params);
}
private void loadImage(AttachmentItem a, Radii r) {
Transformation<Bitmap> transformation = new BriarImageTransformation(r);
GlideApp.with(imageView)
.load(a)
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.transform(transformation)
.transition(withCrossFade())
.into(imageView)
.waitForLayout();
}
}

View File

@@ -1,214 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.view.View;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static android.media.MediaScannerConnection.scanFile;
import static android.os.Environment.DIRECTORY_PICTURES;
import static android.os.Environment.getExternalStoragePublicDirectory;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class ImageViewModel extends AndroidViewModel {
private static Logger LOG = getLogger(ImageViewModel.class.getName());
private final MessagingManager messagingManager;
@DatabaseExecutor
private final Executor dbExecutor;
@IoExecutor
private final Executor ioExecutor;
/**
* true means there was an error saving the image, false if image was saved.
*/
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
private final MutableLiveData<Boolean> imageClicked =
new MutableLiveData<>();
private int toolbarTop, toolbarBottom;
@Inject
ImageViewModel(Application application,
MessagingManager messagingManager,
@DatabaseExecutor Executor dbExecutor,
@IoExecutor Executor ioExecutor) {
super(application);
this.messagingManager = messagingManager;
this.dbExecutor = dbExecutor;
this.ioExecutor = ioExecutor;
}
void clickImage() {
imageClicked.setValue(true);
}
/**
* A LiveData that is true if the image was clicked,
* false if it wasn't.
*
* Call {@link #onOnImageClickSeen()} after consuming an update.
*/
LiveData<Boolean> getOnImageClicked() {
return imageClicked;
}
@UiThread
void onOnImageClickSeen() {
imageClicked.setValue(false);
}
void setToolbarPosition(int top, int bottom) {
toolbarTop = top;
toolbarBottom = bottom;
}
boolean isOverlappingToolbar(View screenView, Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
float widthPercentage = screenView.getWidth() / (float) width;
float heightPercentage = screenView.getHeight() / (float) height;
float scaleFactor = Math.min(widthPercentage, heightPercentage);
int realWidth = (int) (width * scaleFactor);
int realHeight = (int) (height * scaleFactor);
// return if image doesn't use the full width,
// because it will be moved to the right otherwise
if (realWidth < screenView.getWidth()) return false;
int drawableTop = (screenView.getHeight() - realHeight) / 2;
return drawableTop < toolbarBottom && drawableTop != toolbarTop;
}
/**
* A LiveData that is true if there was an error
* and false if the image was saved.
* It can be null otherwise, if no image was saved recently.
*
* Call {@link #onSaveStateSeen()} after consuming an update.
*/
LiveData<Boolean> getSaveState() {
return saveState;
}
@UiThread
void onSaveStateSeen() {
saveState.setValue(null);
}
/**
* Saves the attachment to a writeable {@link Uri}.
*/
@UiThread
void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
if (uri == null) {
saveState.setValue(true);
} else {
saveImage(attachment, () -> getOutputStream(uri), null);
}
}
/**
* Saves the attachment on external storage,
* assuming the permission was granted during install time.
*/
void saveImage(AttachmentItem attachment) {
File file = getImageFile(attachment);
saveImage(attachment, () -> getOutputStream(file), () -> scanFile(
getApplication(), new String[] {file.toString()}, null, null));
}
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
@Nullable Runnable afterCopy) {
MessageId messageId = attachment.getMessageId();
dbExecutor.execute(() -> {
try {
Attachment a = messagingManager.getAttachment(messageId);
copyImageFromDb(a, osp, afterCopy);
} catch (DbException e) {
logException(LOG, WARNING, e);
saveState.postValue(true);
}
});
}
private void copyImageFromDb(Attachment a, OutputStreamProvider osp,
@Nullable Runnable afterCopy) {
ioExecutor.execute(() -> {
try {
InputStream is = a.getStream();
OutputStream os = osp.getOutputStream();
copyAndClose(is, os);
if (afterCopy != null) afterCopy.run();
saveState.postValue(false);
} catch (IOException e) {
logException(LOG, WARNING, e);
saveState.postValue(true);
}
});
}
private String getFileName() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss",
Locale.getDefault());
return sdf.format(new Date());
}
private File getImageFile(AttachmentItem attachment) {
File path = getExternalStoragePublicDirectory(DIRECTORY_PICTURES);
//noinspection ResultOfMethodCallIgnored
path.mkdirs();
String fileName = getFileName();
String ext = "." + attachment.getExtension();
File file = new File(path, fileName + ext);
int i = 1;
while (file.exists()) {
file = new File(path, fileName + " (" + i + ")" + ext);
}
return file;
}
private OutputStream getOutputStream(File file) throws IOException {
return new FileOutputStream(file);
}
private OutputStream getOutputStream(Uri uri) throws IOException {
OutputStream os =
getApplication().getContentResolver().openOutputStream(uri);
if (os == null) throw new IOException();
return os;
}
private interface OutputStreamProvider {
OutputStream getOutputStream() throws IOException;
}
}

View File

@@ -40,7 +40,7 @@ class BriarDataFetcher implements DataFetcher<InputStream> {
private volatile boolean cancel = false;
@Inject
BriarDataFetcher(MessagingManager messagingManager,
public BriarDataFetcher(MessagingManager messagingManager,
@DatabaseExecutor Executor dbExecutor, AttachmentItem attachment) {
this.messagingManager = messagingManager;
this.dbExecutor = dbExecutor;

View File

@@ -7,8 +7,10 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop;
public class BriarImageTransformation extends MultiTransformation<Bitmap> {
public BriarImageTransformation(Radii r) {
super(new CenterCrop(), new CustomCornersTransformation(r));
public BriarImageTransformation(int smallRadius, int radius,
boolean leftCornerSmall, boolean bottomRound) {
super(new CenterCrop(), new ImageCornerTransformation(
smallRadius, radius, leftCornerSmall, bottomRound));
}
}

View File

@@ -22,7 +22,7 @@ public final class BriarModelLoader
@Inject
BriarDataFetcherFactory dataFetcherFactory;
BriarModelLoader(BriarApplication app) {
public BriarModelLoader(BriarApplication app) {
app.getApplicationComponent().inject(this);
}

View File

@@ -16,7 +16,7 @@ class BriarModelLoaderFactory
private final BriarApplication app;
BriarModelLoaderFactory(BriarApplication app) {
public BriarModelLoaderFactory(BriarApplication app) {
this.app = app;
}

View File

@@ -1,129 +0,0 @@
package org.briarproject.briar.android.conversation.glide;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.MessageDigest;
import javax.annotation.concurrent.Immutable;
import static android.graphics.Bitmap.Config.ARGB_8888;
import static android.graphics.Shader.TileMode.CLAMP;
@Immutable
@NotNullByDefault
class CustomCornersTransformation extends BitmapTransformation {
private static final String ID =
CustomCornersTransformation.class.getName();
private final Radii radii;
CustomCornersTransformation(Radii radii) {
this.radii = radii;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
int width = toTransform.getWidth();
int height = toTransform.getHeight();
Bitmap bitmap = pool.get(width, height, ARGB_8888);
bitmap.setHasAlpha(true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP));
drawRect(canvas, paint, width, height);
return bitmap;
}
private void drawRect(Canvas canvas, Paint paint, float width,
float height) {
drawTopLeft(canvas, paint, radii.topLeft, width, height);
drawTopRight(canvas, paint, radii.topRight, width, height);
drawBottomLeft(canvas, paint, radii.bottomLeft, width, height);
drawBottomRight(canvas, paint, radii.bottomRight, width, height);
}
private void drawTopLeft(Canvas canvas, Paint paint, int radius,
float width, float height) {
RectF rect = new RectF(
0,
0,
width / 2 + radius + 1,
height / 2 + radius + 1
);
if (radius == 0) canvas.drawRect(rect, paint);
else canvas.drawRoundRect(rect, radius, radius, paint);
}
private void drawTopRight(Canvas canvas, Paint paint, int radius,
float width, float height) {
RectF rect = new RectF(
width / 2 - radius,
0,
width,
height / 2 + radius + 1
);
if (radius == 0) canvas.drawRect(rect, paint);
else canvas.drawRoundRect(rect, radius, radius, paint);
}
private void drawBottomLeft(Canvas canvas, Paint paint, int radius,
float width, float height) {
RectF rect = new RectF(
0,
height / 2 - radius,
width / 2 + radius + 1,
height
);
if (radius == 0) canvas.drawRect(rect, paint);
else canvas.drawRoundRect(rect, radius, radius, paint);
}
private void drawBottomRight(Canvas canvas, Paint paint, int radius,
float width, float height) {
RectF rect = new RectF(
width / 2 - radius,
height / 2 - radius,
width,
height
);
if (radius == 0) canvas.drawRect(rect, paint);
else canvas.drawRoundRect(rect, radius, radius, paint);
}
@Override
public String toString() {
return "ImageCornerTransformation(" + radii + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof CustomCornersTransformation &&
radii.equals(((CustomCornersTransformation) o).radii);
}
@Override
public int hashCode() {
return ID.hashCode() + radii.hashCode();
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update((ID + radii).getBytes(CHARSET));
}
}

View File

@@ -0,0 +1,111 @@
package org.briarproject.briar.android.conversation.glide;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.MessageDigest;
import javax.annotation.concurrent.Immutable;
import static android.graphics.Bitmap.Config.ARGB_8888;
import static android.graphics.Shader.TileMode.CLAMP;
@Immutable
@NotNullByDefault
class ImageCornerTransformation extends BitmapTransformation {
private static final String ID = ImageCornerTransformation.class.getName();
private final int smallRadius, radius;
private final boolean leftCornerSmall, bottomRound;
ImageCornerTransformation(int smallRadius, int radius,
boolean leftCornerSmall, boolean bottomRound) {
this.smallRadius = smallRadius;
this.radius = radius;
this.leftCornerSmall = leftCornerSmall;
this.bottomRound = bottomRound;
}
@Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform,
int outWidth, int outHeight) {
int width = toTransform.getWidth();
int height = toTransform.getHeight();
Bitmap bitmap = pool.get(width, height, ARGB_8888);
bitmap.setHasAlpha(true);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setShader(new BitmapShader(toTransform, CLAMP, CLAMP));
drawRect(canvas, paint, width, height);
return bitmap;
}
private void drawRect(Canvas canvas, Paint paint, float width,
float height) {
drawSmallCorner(canvas, paint, width);
drawBigCorners(canvas, paint, width, height);
}
private void drawSmallCorner(Canvas canvas, Paint paint, float width) {
float left = leftCornerSmall ? 0 : width - radius;
float right = leftCornerSmall ? radius : width;
canvas.drawRoundRect(new RectF(left, 0, right, radius),
smallRadius, smallRadius, paint);
}
private void drawBigCorners(Canvas canvas, Paint paint, float width,
float height) {
float top = bottomRound ? 0 : radius;
RectF rect = new RectF(0, top, width, height);
if (bottomRound) {
canvas.drawRoundRect(rect, radius, radius, paint);
} else {
canvas.drawRect(rect, paint);
canvas.drawRoundRect(new RectF(0, 0, width, radius * 2),
radius, radius, paint);
}
}
@Override
public String toString() {
return "ImageCornerTransformation(smallRadius=" + smallRadius +
", radius=" + radius + ", leftCornerSmall=" + leftCornerSmall +
", bottomRound=" + bottomRound + ")";
}
@Override
public boolean equals(Object o) {
return o instanceof ImageCornerTransformation &&
((ImageCornerTransformation) o).smallRadius == smallRadius &&
((ImageCornerTransformation) o).radius == radius &&
((ImageCornerTransformation) o).leftCornerSmall ==
leftCornerSmall &&
((ImageCornerTransformation) o).bottomRound == bottomRound;
}
@Override
public int hashCode() {
return ID.hashCode() + (smallRadius << 16) ^ (radius << 2) ^
(leftCornerSmall ? 2 : 0) ^ (bottomRound ? 1 : 0);
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update((ID + '|' + smallRadius + '|' + radius + '|' +
leftCornerSmall + '|' + bottomRound).getBytes(CHARSET));
}
}

View File

@@ -1,41 +0,0 @@
package org.briarproject.briar.android.conversation.glide;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public class Radii {
public final int topLeft, topRight, bottomLeft, bottomRight;
public Radii(int topLeft, int topRight, int bottomLeft, int bottomRight) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomLeft = bottomLeft;
this.bottomRight = bottomRight;
}
@Override
public boolean equals(@Nullable Object o) {
return o instanceof Radii &&
topLeft == ((Radii) o).topLeft &&
topRight == ((Radii) o).topRight &&
bottomLeft == ((Radii) o).bottomLeft &&
bottomRight == ((Radii) o).bottomRight;
}
@Override
public int hashCode() {
return topLeft << 24 ^ topRight << 16 ^ bottomLeft << 8 ^ bottomRight;
}
@Override
public String toString() {
return "Radii(topLeft=" + topLeft +
",topRight=" + topRight +
",bottomLeft=" + bottomLeft +
",bottomRight=" + bottomRight;
}
}

View File

@@ -134,6 +134,12 @@ public class ForumActivity extends
return MAX_FORUM_POST_TEXT_LENGTH;
}
@Override
@StringRes
protected int getItemPostedString() {
return R.string.forum_new_entry_posted;
}
private void showUnsubscribeDialog() {
OnClickListener okListener = (dialog, which) -> deleteForum();
AlertDialog.Builder builder = new AlertDialog.Builder(this,

View File

@@ -44,7 +44,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -81,18 +80,13 @@ public class ForumListFragment extends BaseEventFragment implements
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.forums_button);
getActivity().setTitle(R.string.forums_button);
View contentView =
inflater.inflate(R.layout.fragment_forum_list, container,
@@ -108,7 +102,7 @@ public class ForumListFragment extends BaseEventFragment implements
snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.setAction(R.string.show, this);
snackbar.setActionTextColor(ContextCompat
.getColor(getActivity(), R.color.briar_button_text_positive));
.getColor(getContext(), R.color.briar_button_text_positive));
return contentView;
}
@@ -118,6 +112,11 @@ public class ForumListFragment extends BaseEventFragment implements
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onStart() {
super.onStart();

View File

@@ -10,15 +10,11 @@ import android.support.v4.app.FragmentActivity;
import android.view.MenuItem;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext;
import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseFragment extends Fragment
implements DestroyableContext {
@@ -26,15 +22,12 @@ public abstract class BaseFragment extends Fragment
public abstract String getUniqueTag();
public abstract void injectFragment(ActivityComponent component);
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (BaseFragmentListener) context;
injectFragment(listener.getActivityComponent());
}
public void injectFragment(ActivityComponent component) {
// fragments that need to inject, can override this method
}
@Override
@@ -45,6 +38,12 @@ public abstract class BaseFragment extends Fragment
setHasOptionsMenu(true);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
injectFragment(listener.getActivityComponent());
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

View File

@@ -10,6 +10,7 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
@MethodsNotNullByDefault
@@ -56,4 +57,9 @@ public class ErrorFragment extends BaseFragment {
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
// not necessary
}
}

View File

@@ -1,8 +1,7 @@
package org.briarproject.briar.android.introduction;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v7.widget.LinearLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -12,8 +11,6 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
@@ -31,14 +28,10 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactChooserFragment extends BaseFragment {
public static final String TAG = ContactChooserFragment.class.getName();
@@ -58,6 +51,7 @@ public class ContactChooserFragment extends BaseFragment {
volatile ConnectionRegistry connectionRegistry;
public static ContactChooserFragment newInstance(ContactId id) {
Bundle args = new Bundle();
ContactChooserFragment fragment = new ContactChooserFragment();
@@ -67,13 +61,13 @@ public class ContactChooserFragment extends BaseFragment {
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
public void onAttach(Context context) {
super.onAttach(context);
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false);
@@ -83,16 +77,14 @@ public class ContactChooserFragment extends BaseFragment {
Contact c2 = item.getContact();
showMessageScreen(c1, c2);
};
adapter = new ContactListAdapter(requireNonNull(getActivity()),
onContactClickListener);
adapter = new ContactListAdapter(getActivity(), onContactClickListener);
list = contentView.findViewById(R.id.list);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter);
list.setEmptyText(R.string.no_contacts);
contactId = new ContactId(
requireNonNull(getArguments()).getInt(CONTACT_ID));
contactId = new ContactId(getArguments().getInt(CONTACT_ID));
return contentView;
}
@@ -115,6 +107,11 @@ public class ContactChooserFragment extends BaseFragment {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
private void loadContacts() {
listener.runOnDbThread(() -> {
try {

View File

@@ -1,8 +1,8 @@
package org.briarproject.briar.android.introduction;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.view.LayoutInflater;
@@ -17,17 +17,14 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.view.TextInputView;
import org.briarproject.briar.android.view.TextSendController;
import org.briarproject.briar.android.view.TextSendController.SendListener;
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
import org.briarproject.briar.api.introduction.IntroductionManager;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
@@ -39,16 +36,13 @@ import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_TEXT_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class IntroductionMessageFragment extends BaseFragment
implements SendListener {
implements TextInputListener {
public static final String TAG =
IntroductionMessageFragment.class.getName();
@@ -78,6 +72,11 @@ public class IntroductionMessageFragment extends BaseFragment
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -85,14 +84,8 @@ public class IntroductionMessageFragment extends BaseFragment
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// change toolbar text
ActionBar actionBar = introductionActivity.getSupportActionBar();
@@ -100,26 +93,11 @@ public class IntroductionMessageFragment extends BaseFragment
actionBar.setTitle(R.string.introduction_message_title);
}
// get contact IDs from fragment arguments
Bundle args = requireNonNull(getArguments());
int contactId1 = args.getInt(CONTACT_ID_1, -1);
int contactId2 = args.getInt(CONTACT_ID_2, -1);
if (contactId1 == -1 || contactId2 == -1) {
throw new AssertionError("Use newInstance() to instantiate");
}
// inflate view
View v = inflater.inflate(R.layout.introduction_message, container,
false);
ui = new ViewHolder(v);
TextSendController sendController =
new TextSendController(ui.message, this, true);
ui.message.setSendController(sendController);
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
ui.message.setEnabled(false);
// get contacts and then show view
prepareToSetUpViews(contactId1, contactId2);
ui.message.setSendButtonEnabled(false);
return v;
}
@@ -127,6 +105,16 @@ public class IntroductionMessageFragment extends BaseFragment
@Override
public void onStart() {
super.onStart();
// get contact IDs from fragment arguments
int contactId1 = getArguments().getInt(CONTACT_ID_1, -1);
int contactId2 = getArguments().getInt(CONTACT_ID_2, -1);
if (contactId1 == -1 || contactId2 == -1) {
throw new java.lang.InstantiationError(
"You need to use newInstance() to instantiate");
}
// get contacts and then show view
prepareToSetUpViews(contactId1, contactId2);
}
@Override
@@ -168,10 +156,13 @@ public class IntroductionMessageFragment extends BaseFragment
ui.progressBar.setVisibility(GONE);
if (possible) {
// set button action
ui.message.setListener(IntroductionMessageFragment.this);
// show views
ui.notPossible.setVisibility(GONE);
ui.message.setVisibility(VISIBLE);
ui.message.setEnabled(true);
ui.message.setSendButtonEnabled(true);
ui.message.showSoftKeyboard();
} else {
ui.notPossible.setVisibility(VISIBLE);
@@ -193,11 +184,14 @@ public class IntroductionMessageFragment extends BaseFragment
}
@Override
public void onSendClick(@Nullable String text, List<Uri> imageUris) {
public void onSendClick(@NonNull String text) {
// disable button to prevent accidental double invitations
ui.message.setEnabled(false);
ui.message.setSendButtonEnabled(false);
makeIntroduction(contact1, contact2, text);
String txt = ui.message.getText().toString();
if (txt.isEmpty()) txt = null;
else txt = StringUtils.truncateUtf8(txt, MAX_INTRODUCTION_TEXT_LENGTH);
makeIntroduction(contact1, contact2, txt);
// don't wait for the introduction to be made before finishing activity
introductionActivity.hideSoftKeyboard(ui.message);

View File

@@ -4,12 +4,9 @@ import android.os.Bundle;
import android.support.annotation.UiThread;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask;
import org.briarproject.bramble.api.contact.event.ContactExchangeFailedEvent;
import org.briarproject.bramble.api.contact.event.ContactExchangeSucceededEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -31,7 +28,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ContactExchangeActivity extends KeyAgreementActivity implements
EventListener {
ContactExchangeListener {
private static final Logger LOG =
Logger.getLogger(ContactExchangeActivity.class.getName());
@@ -53,20 +50,6 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
getSupportActionBar().setTitle(R.string.add_contact_title);
}
@Override
public void onStart() {
super.onStart();
// Listen to updates from contactExchangeTask
eventBus.addListener(this);
}
@Override
protected void onStop() {
super.onStop();
// Stop listen to updates from contactExchangeTask
eventBus.addListener(this);
}
private void startContactExchange(KeyAgreementResult result) {
runOnDbThread(() -> {
LocalAuthor localAuthor;
@@ -80,28 +63,15 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
}
// Exchange contact details
contactExchangeTask.startExchange(localAuthor,
result.getMasterKey(), result.getConnection(),
result.getTransportId(), result.wasAlice());
contactExchangeTask.startExchange(ContactExchangeActivity.this,
localAuthor, result.getMasterKey(),
result.getConnection(), result.getTransportId(),
result.wasAlice());
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactExchangeSucceededEvent) {
contactExchangeSucceeded(
((ContactExchangeSucceededEvent) e).getRemoteAuthor());
} else if (e instanceof ContactExchangeFailedEvent) {
ContactExchangeFailedEvent fe = (ContactExchangeFailedEvent) e;
if (fe.wasDuplicateContact()) {
duplicateContact(fe.getDuplicateRemoteAuthor());
} else {
contactExchangeFailed();
}
}
}
private void contactExchangeSucceeded(Author remoteAuthor) {
public void contactExchangeSucceeded(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(R.string.contact_added_toast);
@@ -112,7 +82,8 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
});
}
private void duplicateContact(Author remoteAuthor) {
@Override
public void duplicateContact(Author remoteAuthor) {
runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName();
String format = getString(R.string.contact_already_exists);
@@ -123,7 +94,8 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
});
}
private void contactExchangeFailed() {
@Override
public void contactExchangeFailed() {
runOnUiThreadUnlessDestroyed(() -> {
showErrorFragment(R.string.connection_error_explanation);
});

View File

@@ -44,11 +44,6 @@ public class ContactExchangeErrorFragment extends BaseFragment {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@@ -79,6 +74,11 @@ public class ContactExchangeErrorFragment extends BaseFragment {
return v;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
private void triggerFeedback() {
finish();
UiUtils.triggerFeedback(androidExecutor);

View File

@@ -10,6 +10,7 @@ import android.widget.ScrollView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
@@ -38,6 +39,11 @@ public class IntroFragment extends BaseFragment {
return fragment;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);

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