Compare commits

..

4 Commits

Author SHA1 Message Date
Torsten Grote
e6e077ff40 [android] enable image shared element transition for API 21+22
There's an Android framework bug (#224270) on these APIs that causes a NPE
when the shared element is not visible anymore when returning.
Since we know restore the list position, the shared element should be
visible and thus not produce NPEs anymore.
2018-12-13 14:20:52 -02:00
Torsten Grote
4e5e354af2 [android] Fix enter transition to fullscreen ImageActivity 2018-12-13 14:20:52 -02:00
Torsten Grote
9ea3623c85 [android] Save and restore list position of conversation across restarts 2018-12-13 14:20:51 -02:00
Torsten Grote
84089f5e16 DO NOT MERGE: Return multiple fake image attachements in MessagingManager
(cherry picked from commit d9cb675)
2018-12-13 14:20:04 -02:00
187 changed files with 1221 additions and 4639 deletions

View File

@@ -39,6 +39,31 @@
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </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> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>

View File

@@ -11,8 +11,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 26 targetSdkVersion 26
versionCode 10106 versionCode 10105
versionName "1.1.6" versionName "1.1.5"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@@ -30,8 +30,8 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.8@zip' tor 'org.briarproject:tor-android:0.3.4.8@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.9@zip' tor 'org.briarproject:obfs4proxy-android:0.0.7@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.19' 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 org.briarproject.bramble.util.IoUtils;
import java.io.File; import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.os.Build.VERSION.SDK_INT;
class AndroidAccountManager extends AccountManagerImpl class AndroidAccountManager extends AccountManagerImpl
implements AccountManager { implements AccountManager {
@@ -93,42 +89,20 @@ class AndroidAccountManager extends AccountManagerImpl
LOG.warning("Could not clear shared preferences"); LOG.warning("Could not clear shared preferences");
} }
// Delete files, except lib and shared_prefs directories // Delete files, except lib and shared_prefs directories
Set<File> files = new HashSet<>();
File dataDir = new File(appContext.getApplicationInfo().dataDir); File dataDir = new File(appContext.getApplicationInfo().dataDir);
@Nullable File[] children = dataDir.listFiles();
File[] fileArray = dataDir.listFiles(); if (children == null) {
if (fileArray == null) {
LOG.warning("Could not list files in app data dir"); LOG.warning("Could not list files in app data dir");
} else { } else {
for (File file : fileArray) { for (File child : children) {
String name = file.getName(); String name = child.getName();
if (!name.equals("lib") && !name.equals("shared_prefs")) { 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 // 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"); 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_BATTERY_CHANGED;
import static android.content.Intent.ACTION_POWER_CONNECTED; import static android.content.Intent.ACTION_POWER_CONNECTED;
import static android.content.Intent.ACTION_POWER_DISCONNECTED; 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.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -46,8 +48,9 @@ class AndroidBatteryManager implements BatteryManager, Service {
IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED); IntentFilter filter = new IntentFilter(ACTION_BATTERY_CHANGED);
Intent i = appContext.registerReceiver(null, filter); Intent i = appContext.registerReceiver(null, filter);
if (i == null) return false; if (i == null) return false;
int status = i.getIntExtra(EXTRA_PLUGGED, 0); int status = i.getIntExtra(EXTRA_STATUS, -1);
return status != 0; return status == BATTERY_STATUS_CHARGING ||
status == BATTERY_STATUS_FULL;
} }
@Override @Override

View File

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

View File

@@ -1,45 +1,46 @@
dependencyVerification { dependencyVerification {
verify = [ verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861', '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:protos:26.2.1:protos-26.2.1.jar:2f371f5b1f551e85ab08be4d6a2873471b3d44afd1ebf6aa3298f3b796bf691f',
'com.android.tools.analytics-library:shared:26.3.2:shared-26.3.2.jar:ddd80dcf21905018b7c0583ba72b7282f446084d4952422609a09fbf8237ef71', 'com.android.tools.analytics-library:shared:26.2.1:shared-26.2.1.jar:4c1e4e705fa4d45f23aaea230557f6508155012d9c296337787c1d7b26a97f5a',
'com.android.tools.analytics-library:tracker:26.3.2:tracker-26.3.2.jar:28c575d2d1af003e96d7b375a668ee10b2673a2dd0f6438750aa8c3b42e7d0ad', 'com.android.tools.analytics-library:tracker:26.2.1:tracker-26.2.1.jar:4a624ecc976539f755ddb0bb8dfc2dd3d08326cfec59a098dbd70f701ca7fb75',
'com.android.tools.build:aapt2:3.3.2-5309881:aapt2-3.3.2-5309881-linux.jar:9beead7e8b1057359049f450538aadcd3eb2a5bc3fbdd5e3296cb8fdc2a22412', 'com.android.tools.build:aapt2:3.2.1-4818971:aapt2-3.2.1-4818971-linux.jar:f431b6f96c91a2c155144b091a9c97d9805c589fe8efc9c930b6cd346cb60a1e',
'com.android.tools.build:apksig:3.3.2:apksig-3.3.2.jar:84c4aaa20127c6c1fe6bdd334b3f5df71f54ad080be9029c8a10f43b6a908acd', 'com.android.tools.build:apksig:3.2.1:apksig-3.2.1.jar:2b46f2feffea66037aab29e4261b2433c190194a6ef97b958511eb157f2ccba5',
'com.android.tools.build:apkzlib:3.3.2:apkzlib-3.3.2.jar:d34e523278e5dff565eba3ef3c089d515b2b5cc7b47dc77e2f3465e5e47176ac', 'com.android.tools.build:apkzlib:3.2.1:apkzlib-3.2.1.jar:c39ad0313905932431fe81c8899c2cf39a4d92ad6c4edcaa4b25432f461452aa',
'com.android.tools.build:builder-model:3.3.2:builder-model-3.3.2.jar:055e3db0ecee9e06b9f024034999a29cd92cb1885207b37542126bd8bcc57f46', 'com.android.tools.build:builder-model:3.2.1:builder-model-3.2.1.jar:a9f68e6abcec122f9cb5ad352d3f05a3eb03acbcdca95e4d25c16310c2c965ff',
'com.android.tools.build:builder-test-api:3.3.2:builder-test-api-3.3.2.jar:0b2e4cd7615bbcad14a3c91fe45ae26693508d06e40ba06c5968b8bc24416618', 'com.android.tools.build:builder-test-api:3.2.1:builder-test-api-3.2.1.jar:533ac6c2b5884bb54967a33791f2628dfdfac7981af39417a333b43d4379b6be',
'com.android.tools.build:builder:3.3.2:builder-3.3.2.jar:65649704da7aef0487235fd326f0f2e99ed5cf958e80f204496e6e08a42bd9f5', 'com.android.tools.build:builder:3.2.1:builder-3.2.1.jar:aedcbfd115dbe91d09b4113e66ef50589b558d0aa3b2f133b1d867c9b87fae83',
'com.android.tools.build:gradle-api:3.3.2:gradle-api-3.3.2.jar:3cbd47e41bb70330dd72ec2c9fe51e6173554b484a03829b5a2de9e00841e040', 'com.android.tools.build:gradle-api:3.2.1:gradle-api-3.2.1.jar:57cf0ac5ac1dca8afdb3f62b94265e776e7dcfa641cc3844fb53a05193de208d',
'com.android.tools.build:manifest-merger:26.3.2:manifest-merger-26.3.2.jar:05c4a6d8b02fb9f08744876477d0a68547c03a8a9069b1f086684fa04af97c33', 'com.android.tools.build:manifest-merger:26.2.1:manifest-merger-26.2.1.jar:8830573263361035d38cfdcb51e2db94029c93865b21334f5fbf8a27984281a6',
'com.android.tools.ddms:ddmlib:26.3.2:ddmlib-26.3.2.jar:d248da8a563d6e46d2c7ebbf371a4877e00510f4ca763c0bb272d5a281bf8b85', 'com.android.tools.ddms:ddmlib:26.2.1:ddmlib-26.2.1.jar:a4bf0a29a19980bf27269465cc782064656750b77c26728f82f9e148b705218b',
'com.android.tools.external.com-intellij:intellij-core:26.3.2:intellij-core-26.3.2.jar:6c5ecc968230e9f4dcd0fef28885379feace1f0cd8130de6f61d649c86139bf3', '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.3.2:kotlin-compiler-26.3.2.jar:1007d9b07ccb49cd8eaf30fda10ed4681d4714f2f9ab2ecda39b4e539cc51bbe', '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.3.2:uast-26.3.2.jar:5d1833e562ea4f38a89708dfde695f0a162cbd39d003d3dde818c3fdc2b05317', 'com.android.tools.external.org-jetbrains:uast:26.2.1:uast-26.2.1.jar:f10f7258d2ab9189562cc0f9ad838c0378fdba439229173390a99de02ebac75b',
'com.android.tools.layoutlib:layoutlib-api:26.3.2:layoutlib-api-26.3.2.jar:d7e61e874ab95f5c350dd38b6a95b5c9dbe0083a02001884264cdb390cb255b8', 'com.android.tools.layoutlib:layoutlib-api:26.2.1:layoutlib-api-26.2.1.jar:ddbf4fca123733fa011595b1cc1f4ac2937ed327b60990711fafc33c775c2ade',
'com.android.tools.lint:lint-api:26.3.2:lint-api-26.3.2.jar:5867dfd7fb4a4e161a816a5d29d045f9b542d34594c00a1efec46fb4cd0e1033', 'com.android.tools.lint:lint-api:26.2.1:lint-api-26.2.1.jar:3b57e739de567b98bc9ab56c2c0ee66fc026b4adf5843e8f9804ca0666a6f66e',
'com.android.tools.lint:lint-checks:26.3.2:lint-checks-26.3.2.jar:4b163b9c93790d2771e92ba8de58a0d9e0671ffcf2ccef3cf496efd442e27517', 'com.android.tools.lint:lint-checks:26.2.1:lint-checks-26.2.1.jar:c86f4cc9aaee722ee4ad70062f7b5af91e9b041914af27adc09f545ab0fb3bc6',
'com.android.tools.lint:lint-gradle-api:26.3.2:lint-gradle-api-26.3.2.jar:54cb282e0c054f9bed3f51302ce08b003c8ab7961dfd5a4f6de26c23cc23062f', '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.3.2:lint-gradle-26.3.2.jar:bb139615f4ce97d42cc394b9389b49b76a6eb85be6785a5d272991543b519013', 'com.android.tools.lint:lint-gradle:26.2.1:lint-gradle-26.2.1.jar:8fd90b2f3ec788cbb9801c07ab3e1ea2255aa31a6093157d7ea0ff13d0315ecb',
'com.android.tools.lint:lint:26.3.2:lint-26.3.2.jar:ef7b369f8a56a92ccb0f4c1c357666b9339e4a711a9d84747d446441746cfe4e', 'com.android.tools.lint:lint-kotlin:26.2.1:lint-kotlin-26.2.1.jar:7a6a5d2b18f69cf1b900d857c2632b4c683713c533295933b8b759f8cab4a877',
'com.android.tools:annotations:26.3.2:annotations-26.3.2.jar:5bcce8e98b6a2f5ccf13ebcefd8f734e0b35f8b19e456575665631442ce1f7a1', 'com.android.tools.lint:lint:26.2.1:lint-26.2.1.jar:7848b82ae988b90dee259ae7c7e86e05cbf52db6cd21c8bbd38ce7df08f3f8c5',
'com.android.tools:common:26.3.2:common-26.3.2.jar:d9f8e7f0669e9a701568e3db6a87c89cf12d8fa6811c9991e969f950215ecfac', 'com.android.tools:annotations:26.2.1:annotations-26.2.1.jar:7391c6a1e080174b96e64ceb078dadd31ce4d8a2d2fee0ec65be202126f90f24',
'com.android.tools:dvlib:26.3.2:dvlib-26.3.2.jar:d84aad56161c7773579303d69714ded6897c64c6ddfd7d456e453231e4dfe811', 'com.android.tools:common:26.2.1:common-26.2.1.jar:a50aab2d6411ff68f4004a87c7e93d87d8e980a0ec3b352246549897ea2d78e5',
'com.android.tools:repository:26.3.2:repository-26.3.2.jar:da611eeb06e9ab8750d25b9e2901e10db8e5ec6304eb4c8b7103d39e0921ea40', 'com.android.tools:dvlib:26.2.1:dvlib-26.2.1.jar:72a83bf2839b1df9b1fbf67ba45d1bfb9f966cd774da4320c762b2be8f1688aa',
'com.android.tools:sdk-common:26.3.2:sdk-common-26.3.2.jar:82823a3bf25e64fac33a286490f0cf5ac50c2cdb3c540149b030896bb44bf96c', 'com.android.tools:repository:26.2.1:repository-26.2.1.jar:fa74dae09103faef703df38550ad8fa244c5b6d1bf90d6198be932292b3d9cc1',
'com.android.tools:sdklib:26.3.2:sdklib-26.3.2.jar:424d15492af67321900963238646d27495ab60de2a5b19e6a416963bc5d6932b', '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: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.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-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-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-spi:2.19:dagger-spi-2.19.jar:e7a6379d82c841f6aac2866948ad1eed716528707814602842a8d844ce04e2e1',
'com.google.dagger:dagger:2.19:dagger-2.19.jar:514b6f1e0727c6572e1d65cb27e4ae668b7aeaeb93a29515182965265b609939', '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: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.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.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: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.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:40ceb7157feb263949e0f503fe5f71689333a621021aa20ce0d0acee3badaa0f',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd', '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', '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.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.9:obfs4proxy-android-0.0.9.zip:9b7e9181535ea8d8bbe8ae6338e08cf4c5fc1e357a779393e0ce49586d459ae0', 'org.briarproject:obfs4proxy-android:0.0.7:obfs4proxy-android-0.0.7.zip:abdfb5d889d848de9bf214f9276abbf454808a505b870819eccc9a9e985bf617',
'org.briarproject:tor-android:0.3.5.8:tor-android-0.3.5.8.zip:42a13a6f185be1a62f42e3f30ce66a3c099ac5ec890a65e7593111b65b44a54a', '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-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.12:groovy-all-2.4.12.jar:6a56af4bd48903d56bec62821876cadefafd007360cc6bd0d8f7aa8d72b38be4',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.codehaus.mojo:animal-sniffer-annotations:1.14:animal-sniffer-annotations-1.14.jar:2068320bd6bad744c3673ab048f67e30bef8f518996fa380033556600669905d', '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-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: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.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-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c', '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-reflect:1.2.0:kotlin-reflect-1.2.0.jar:4f48a872bad6e4d9c053f4ad610d11e4012ad7e58dc19a03dd5eb811f36069dd',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.21:kotlin-stdlib-common-1.3.21.jar:cea61f7b611895e64f58569a9757fc0ab0d582f107211e1930e0ce2a0add52a7', 'org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71:kotlin-stdlib-common-1.2.71.jar:63999687ff2fce8a592dd180ffbbf8f1d21c26b4044c55cdc74ff3cf3b3cf328',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.21:kotlin-stdlib-jdk7-1.3.21.jar:a87875604fd42140da6938ae4d35ee61081f4482536efc6d2615b8b626a198af', 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71:kotlin-stdlib-jdk7-1.2.71.jar:b136bd61b240e07d4d92ce00d3bd1dbf584400a7bf5f220c2f3cd22446858082',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.21:kotlin-stdlib-jdk8-1.3.21.jar:5823ed66ac122a1c55442ebca5a209a843ccd87f562edc31a787f3d2e47f74d4', 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71:kotlin-stdlib-jdk8-1.2.71.jar:ac3c8abf47790b64b4f7e2509a53f0c145e061ac1612a597520535d199946ea9',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.21:kotlin-stdlib-1.3.21.jar:38ba2370d9f06f50433e06b2ca775b94473c2e2785f410926079ab793c72b034', '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.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478', 'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c', '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. * 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, DuplexTransportConnection conn, TransportId transportId,
boolean alice); boolean alice);
} }

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

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

View File

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

View File

@@ -17,24 +17,16 @@ public interface CircumventionProvider {
String[] BLOCKED = {"CN", "IR", "EG", "BY", "TR", "SY", "VE"}; 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}. * Should be a subset of {@link #BLOCKED}.
*/ */
String[] BRIDGES = { "CN", "IR", "EG", "BY", "TR", "SY", "VE" }; String[] BRIDGES = { "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"};
boolean isTorProbablyBlocked(String countryCode); boolean isTorProbablyBlocked(String countryCode);
boolean doBridgesWork(String countryCode); boolean doBridgesWork(String countryCode);
boolean needsMeek(String countryCode);
@IoExecutor @IoExecutor
List<String> getBridges(boolean meek); List<String> getBridges();
} }

View File

@@ -22,8 +22,6 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(BLOCKED)); new HashSet<>(asList(BLOCKED));
private static final Set<String> BRIDGES_WORK_IN_COUNTRIES = private static final Set<String> BRIDGES_WORK_IN_COUNTRIES =
new HashSet<>(asList(BRIDGES)); new HashSet<>(asList(BRIDGES));
private static final Set<String> BRIDGES_NEED_MEEK =
new HashSet<>(asList(NEEDS_MEEK));
@Nullable @Nullable
private volatile List<String> bridges = null; private volatile List<String> bridges = null;
@@ -42,14 +40,9 @@ class CircumventionProviderImpl implements CircumventionProvider {
return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode); return BRIDGES_WORK_IN_COUNTRIES.contains(countryCode);
} }
@Override
public boolean needsMeek(String countryCode) {
return BRIDGES_NEED_MEEK.contains(countryCode);
}
@Override @Override
@IoExecutor @IoExecutor
public List<String> getBridges(boolean useMeek) { public List<String> getBridges() {
List<String> bridges = this.bridges; List<String> bridges = this.bridges;
if (bridges != null) return new ArrayList<>(bridges); if (bridges != null) return new ArrayList<>(bridges);
@@ -60,8 +53,6 @@ class CircumventionProviderImpl implements CircumventionProvider {
bridges = new ArrayList<>(); bridges = new ArrayList<>();
while (scanner.hasNextLine()) { while (scanner.hasNextLine()) {
String line = scanner.nextLine(); String line = scanner.nextLine();
boolean isMeekBridge = line.startsWith("Bridge meek");
if (useMeek && !isMeekBridge || !useMeek && isMeekBridge) continue;
if (!line.startsWith("#")) bridges.add(line); if (!line.startsWith("#")) bridges.add(line);
} }
scanner.close(); 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_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_NEVER; 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_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.PREF_TOR_PORT;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2; import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V2;
import static org.briarproject.bramble.api.plugin.TorConstants.PROP_ONION_V3; 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(); if (!enable) callback.transportDisabled();
} }
private void enableBridges(boolean enable, boolean needsMeek) private void enableBridges(boolean enable) throws IOException {
throws IOException {
if (enable) { if (enable) {
Collection<String> conf = new ArrayList<>(); Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1"); conf.add("UseBridges 1");
if (needsMeek) { conf.add("ClientTransportPlugin obfs4 exec " +
conf.add("ClientTransportPlugin meek_lite exec " + obfs4File.getAbsolutePath());
obfs4File.getAbsolutePath()); conf.addAll(circumventionProvider.getBridges());
} else {
conf.add("ClientTransportPlugin obfs4 exec " +
obfs4File.getAbsolutePath());
}
conf.addAll(circumventionProvider.getBridges(needsMeek));
controlConnection.setConf(conf); controlConnection.setConf(conf);
} else { } else {
controlConnection.setConf("UseBridges", "0"); controlConnection.setConf("UseBridges", "0");
@@ -655,10 +648,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (s.getNamespace().equals(ID.getString())) { if (s.getNamespace().equals(ID.getString())) {
LOG.info("Tor settings updated"); LOG.info("Tor settings updated");
settings = s.getSettings(); settings = s.getSettings();
// Works around a bug introduced in Tor 0.3.4.8. // Works around a bug introduced in Tor 0.3.4.8. Could be
// https://trac.torproject.org/projects/tor/ticket/28027 // replaced with callback.transportDisabled() when fixed.
// Could be replaced with callback.transportDisabled()
// when fixed.
disableNetwork(); disableNetwork();
updateConnectionStatus(networkManager.getNetworkStatus(), updateConnectionStatus(networkManager.getNetworkStatus(),
batteryManager.isCharging()); batteryManager.isCharging());
@@ -694,8 +685,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
int network = settings.getInt(PREF_TOR_NETWORK, int network = settings.getInt(PREF_TOR_NETWORK,
PREF_TOR_NETWORK_AUTOMATIC); PREF_TOR_NETWORK_AUTOMATIC);
boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true); boolean useMobile = settings.getBoolean(PREF_TOR_MOBILE, true);
boolean onlyWhenCharging =
settings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
boolean bridgesWork = circumventionProvider.doBridgesWork(country); boolean bridgesWork = circumventionProvider.doBridgesWork(country);
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC; boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
@@ -710,29 +699,21 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!online) { if (!online) {
LOG.info("Disabling network, device is offline"); LOG.info("Disabling network, device is offline");
enableNetwork(false); enableNetwork(false);
} else if (!charging && onlyWhenCharging) {
LOG.info("Disabling network, device is on battery");
enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_NEVER || } else if (network == PREF_TOR_NETWORK_NEVER ||
(!useMobile && !wifi)) { (!useMobile && !wifi)) {
LOG.info("Disabling network, device is using mobile data"); LOG.info("Disabling network due to setting");
enableNetwork(false); enableNetwork(false);
} else if (automatic && blocked && !bridgesWork) { } else if (automatic && blocked && !bridgesWork) {
LOG.info("Disabling network, country is blocked"); LOG.info("Disabling network, country is blocked");
enableNetwork(false); enableNetwork(false);
} else if (network == PREF_TOR_NETWORK_WITH_BRIDGES || } else if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) { LOG.info("Enabling network, using bridges");
LOG.info("Enabling network, using meek bridges"); enableBridges(true);
enableBridges(true, true);
} else {
LOG.info("Enabling network, using obfs4 bridges");
enableBridges(true, false);
}
enableNetwork(true); enableNetwork(true);
} else { } else {
LOG.info("Enabling network, not using bridges"); LOG.info("Enabling network, not using bridges");
enableBridges(false, false); enableBridges(false);
enableNetwork(true); enableNetwork(true);
} }
if (online && wifi && charging) { if (online && wifi && charging) {

View File

@@ -1,5 +1,4 @@
Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0 Bridge obfs4 78.46.188.239:37356 5A2D2F4158D0453E00C7C176978D3F41D69C45DB cert=3c0SwxpOisbohNxEc4tb875RVW8eOu1opRTVXJhafaKA/PNNtI7ElQIVOVZg1AdL5bxGCw iat-mode=0
Bridge obfs4 52.15.78.72:9443 02069A3C5362476936B62BA6F5ACC41ABD573A9B cert=ijYG/OKc7kqu2YzKNFfeXN7/BG2BOgfEP2KyYEiGDQthnHbsOiTWHeIG0WJVW+BckzDgKw 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 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 obfs4 45.33.37.112:9443 60A609BB4ABE8D46E634AE81ED29ADAB7776B399 cert=t5v19WmNv5Sc2YPNr8RQids365W7MY8zJwQVkOxBjUMFomMWARDzsbYpcWLLcw0J9Gm+BQ iat-mode=0
Bridge meek_lite 0.0.2.0:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642', 'org.briarproject:obfs4proxy:0.0.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.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.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', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',

View File

@@ -22,8 +22,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 26 targetSdkVersion 26
versionCode 10106 versionCode 10105
versionName "1.1.6" versionName "1.1.5"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash", buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -105,6 +105,7 @@ dependencies {
implementation "com.android.support:cardview-v7:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.android.support:support-annotations:$supportVersion" implementation "com.android.support:support-annotations:$supportVersion"
implementation "com.android.support:exifinterface:$supportVersion" implementation "com.android.support:exifinterface:$supportVersion"
implementation "com.android.support:palette-v7:$supportVersion"
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation "android.arch.lifecycle:extensions:1.1.1" implementation "android.arch.lifecycle:extensions:1.1.1"
@@ -116,7 +117,7 @@ dependencies {
implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.google.zxing:core:3.3.3' 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' implementation 'com.vanniktech:emoji-google:0.5.1'
def glideVersion = '4.8.0' def glideVersion = '4.8.0'
implementation("com.github.bumptech.glide:glide:$glideVersion") { implementation("com.github.bumptech.glide:glide:$glideVersion") {
@@ -134,7 +135,7 @@ dependencies {
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'org.robolectric:robolectric:4.0.1' testImplementation 'org.robolectric:robolectric:4.0.1'
testImplementation 'org.robolectric:shadows-support-v4:3.3.2' 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 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2" testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2" testImplementation "org.jmock:jmock-junit4:2.8.2"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 317 B

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

View File

@@ -231,7 +231,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
showForumPostNotification(f.getGroupId()); showForumPostNotification(f.getGroupId());
} else if (e instanceof BlogPostAddedEvent) { } else if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent b = (BlogPostAddedEvent) e; BlogPostAddedEvent b = (BlogPostAddedEvent) e;
if (!b.isLocal()) showBlogPostNotification(b.getGroupId()); showBlogPostNotification(b.getGroupId());
} else if (e instanceof IntroductionSucceededEvent) { } else if (e instanceof IntroductionSucceededEvent) {
showIntroductionNotification(); 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(); AndroidComponent getApplicationComponent();
SharedPreferences getDefaultSharedPreferences(); SharedPreferences getDefaultSharedPreferences();
boolean isRunningInBackground();
} }

View File

@@ -1,7 +1,5 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@@ -32,8 +30,6 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
import java.util.logging.Logger; 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.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static org.acra.ReportField.ANDROID_VERSION; import static org.acra.ReportField.ANDROID_VERSION;
@@ -83,7 +79,6 @@ public class BriarApplicationImpl extends Application
Logger.getLogger(BriarApplicationImpl.class.getName()); Logger.getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler(); private final CachingLogHandler logHandler = new CachingLogHandler();
private final BackgroundMonitor backgroundMonitor = new BackgroundMonitor();
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs; private volatile SharedPreferences prefs;
@@ -120,9 +115,6 @@ public class BriarApplicationImpl extends Application
applicationComponent = createApplicationComponent(); applicationComponent = createApplicationComponent();
EmojiManager.install(new GoogleEmojiProvider()); EmojiManager.install(new GoogleEmojiProvider());
if (SDK_INT < 16)
registerActivityLifecycleCallbacks(backgroundMonitor);
} }
protected AndroidComponent createApplicationComponent() { protected AndroidComponent createApplicationComponent() {
@@ -181,15 +173,4 @@ public class BriarApplicationImpl extends Application
public SharedPreferences getDefaultSharedPreferences() { public SharedPreferences getDefaultSharedPreferences() {
return prefs; 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; package org.briarproject.briar.android;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@@ -33,6 +35,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; 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_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
@@ -71,7 +74,6 @@ public class BriarService extends Service {
@Nullable @Nullable
private BroadcastReceiver receiver = null; private BroadcastReceiver receiver = null;
private BriarApplication app;
@Inject @Inject
AndroidNotificationManager notificationManager; AndroidNotificationManager notificationManager;
@@ -91,8 +93,8 @@ public class BriarService extends Service {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
app = (BriarApplication) getApplication(); BriarApplication application = (BriarApplication) getApplication();
app.getApplicationComponent().inject(this); application.getApplicationComponent().inject(this);
LOG.info("Created"); LOG.info("Created");
if (created.getAndSet(true)) { if (created.getAndSet(true)) {
@@ -218,8 +220,8 @@ public class BriarService extends Service {
public void onLowMemory() { public void onLowMemory() {
super.onLowMemory(); super.onLowMemory();
LOG.warning("Memory is low"); LOG.warning("Memory is low");
// If we're not in the foreground, clear the UI to save memory // Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16
if (app.isRunningInBackground()) hideUi(); if (SDK_INT < 16) hideUi();
} }
@Override @Override
@@ -233,16 +235,20 @@ public class BriarService extends Service {
LOG.info("Trim memory: near middle of LRU list"); LOG.info("Trim memory: near middle of LRU list");
} else if (level == TRIM_MEMORY_COMPLETE) { } else if (level == TRIM_MEMORY_COMPLETE) {
LOG.info("Trim memory: near end of LRU list"); LOG.info("Trim memory: near end of LRU list");
} else if (level == TRIM_MEMORY_RUNNING_MODERATE) { } else if (SDK_INT >= 16) {
LOG.info("Trim memory: running moderately low"); if (level == TRIM_MEMORY_RUNNING_MODERATE) {
} else if (level == TRIM_MEMORY_RUNNING_LOW) { LOG.info("Trim memory: running moderately low");
LOG.info("Trim memory: running low"); } else if (level == TRIM_MEMORY_RUNNING_LOW) {
} else if (level == TRIM_MEMORY_RUNNING_CRITICAL) { LOG.info("Trim memory: running low");
// This level may be received if SDK_INT < 16, although the } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) {
// constant isn't declared until API level 16 LOG.info("Trim memory: running critically low");
LOG.warning("Trim memory: running critically low"); // If we're not in the foreground, clear the UI to save memory
// If we're not in the foreground, clear the UI to save memory RunningAppProcessInfo info = new RunningAppProcessInfo();
if (app.isRunningInBackground()) hideUi(); 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)) { } else if (LOG.isLoggable(INFO)) {
LOG.info("Trim memory: unknown level " + level); LOG.info("Trim memory: unknown level " + level);
} }

View File

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

View File

@@ -31,6 +31,7 @@ import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment; import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment; 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.KeyAgreementActivity;
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.AuthorNameFragment;
@@ -48,6 +49,7 @@ import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule; import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule;
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity; import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.creation.CreateGroupFragment; 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.CreateGroupModule;
import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity; import org.briarproject.briar.android.privategroup.creation.GroupInviteActivity;
import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment; import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
@@ -68,8 +70,10 @@ import org.briarproject.briar.android.sharing.ForumInvitationActivity;
import org.briarproject.briar.android.sharing.ForumSharingStatusActivity; import org.briarproject.briar.android.sharing.ForumSharingStatusActivity;
import org.briarproject.briar.android.sharing.ShareBlogActivity; import org.briarproject.briar.android.sharing.ShareBlogActivity;
import org.briarproject.briar.android.sharing.ShareBlogFragment; 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.ShareForumActivity;
import org.briarproject.briar.android.sharing.ShareForumFragment; 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.sharing.SharingModule;
import org.briarproject.briar.android.splash.SplashScreenActivity; import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.TestDataActivity; import org.briarproject.briar.android.test.TestDataActivity;
@@ -179,6 +183,8 @@ public interface ActivityComponent {
void inject(CreateGroupFragment fragment); void inject(CreateGroupFragment fragment);
void inject(CreateGroupMessageFragment fragment);
void inject(GroupListFragment fragment); void inject(GroupListFragment fragment);
void inject(GroupInviteFragment fragment); void inject(GroupInviteFragment fragment);
@@ -189,14 +195,20 @@ public interface ActivityComponent {
void inject(FeedFragment fragment); void inject(FeedFragment fragment);
void inject(IntroFragment fragment);
void inject(KeyAgreementFragment fragment); void inject(KeyAgreementFragment fragment);
void inject(ContactChooserFragment fragment); void inject(ContactChooserFragment fragment);
void inject(ShareForumFragment fragment); void inject(ShareForumFragment fragment);
void inject(ShareForumMessageFragment fragment);
void inject(ShareBlogFragment fragment); void inject(ShareBlogFragment fragment);
void inject(ShareBlogMessageFragment fragment);
void inject(IntroductionMessageFragment fragment); void inject(IntroductionMessageFragment fragment);
void inject(SettingsFragment fragment); void inject(SettingsFragment fragment);

View File

@@ -15,8 +15,6 @@ import android.view.ViewGroup.LayoutParams;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import org.briarproject.bramble.api.db.DbException; 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.R;
import org.briarproject.briar.android.AndroidComponent; import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.BriarApplication; 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}. * Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity} * E.g. {@link DevReportActivity}
*/ */
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public abstract class BaseActivity extends AppCompatActivity public abstract class BaseActivity extends AppCompatActivity
implements DestroyableContext, OnTapFilteredListener { implements DestroyableContext, OnTapFilteredListener {
@@ -81,17 +77,6 @@ public abstract class BaseActivity extends AppCompatActivity
@Override @Override
public void onCreate(@Nullable Bundle state) { 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); super.onCreate(state);
// WARNING: When removing this or making it possible to turn it off, // 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. // unlock screen is shown.
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); 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) { for (ActivityLifecycleController alc : lifecycleControllers) {
alc.onActivityCreate(this); alc.onActivityCreate(this);
} }

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.activity; package org.briarproject.briar.android.activity;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
@@ -9,8 +10,6 @@ import android.transition.Transition;
import android.view.Window; import android.view.Window;
import android.widget.CheckBox; 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.R;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.DbController; import org.briarproject.briar.android.controller.DbController;
@@ -37,8 +36,7 @@ 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.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7; import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @SuppressLint("Registered")
@ParametersNotNullByDefault
public abstract class BriarActivity extends BaseActivity { public abstract class BriarActivity extends BaseActivity {
public static final String GROUP_ID = "briar.GROUP_ID"; public static final String GROUP_ID = "briar.GROUP_ID";
@@ -62,8 +60,7 @@ public abstract class BriarActivity extends BaseActivity {
} }
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result, Intent data) {
@Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_PASSWORD) { if (request == REQUEST_PASSWORD) {
// The result can be RESULT_CANCELED if there's no account // 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()) { } else if (lockManager.isLocked() && !isFinishing()) {
// Also check that the activity isn't finishing already. // Also check that the activity isn't finishing already.
// This is possible if we finished in onActivityResult(). // 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); Intent i = new Intent(this, UnlockActivity.class);
startActivityForResult(i, REQUEST_UNLOCK); startActivityForResult(i, REQUEST_UNLOCK);
} else if (SDK_INT >= 23) { } else if (SDK_INT >= 23) {
@@ -114,10 +111,6 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop(); lockManager.onActivityStop();
} }
protected boolean signedIn() {
return briarController.accountSignedIn();
}
/** /**
* Sets the transition animations. * Sets the transition animations.
* @param enterTransition used to move views into initial positions * @param enterTransition used to move views into initial positions
@@ -185,15 +178,13 @@ public abstract class BriarActivity extends BaseActivity {
b.show(); b.show();
} }
protected void signOut(boolean removeFromRecentApps, protected void signOut(boolean removeFromRecentApps) {
boolean deleteAccount) {
if (briarController.accountSignedIn()) { if (briarController.accountSignedIn()) {
// Don't use UiResultHandler because we want the result even if // Don't use UiResultHandler because we want the result even if
// this activity has been destroyed // this activity has been destroyed
briarController.signOut(result -> runOnUiThread( briarController.signOut(result -> runOnUiThread(
() -> exit(removeFromRecentApps)), deleteAccount); () -> exit(removeFromRecentApps)));
} else { } else {
if (deleteAccount) briarController.deleteAccount();
exit(removeFromRecentApps); exit(removeFromRecentApps);
} }
} }

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,6 @@ import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; 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.android.blog.BasePostFragment.POST_ID;
import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH; import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_POST_TEXT_LENGTH;
@@ -43,6 +42,8 @@ public class ReblogFragment extends BaseFragment implements SendListener {
public static final String TAG = ReblogFragment.class.getName(); public static final String TAG = ReblogFragment.class.getName();
private ViewHolder ui; private ViewHolder ui;
private GroupId blogId;
private MessageId postId;
private BlogPostItem item; private BlogPostItem item;
@Inject @Inject
@@ -74,11 +75,9 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments()); Bundle args = getArguments();
GroupId blogId = blogId = new GroupId(args.getByteArray(GROUP_ID));
new GroupId(requireNonNull(args.getByteArray(GROUP_ID))); postId = new MessageId(args.getByteArray(POST_ID));
MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
View v = inflater.inflate(R.layout.fragment_reblog, container, false); View v = inflater.inflate(R.layout.fragment_reblog, container, false);
ui = new ViewHolder(v); ui = new ViewHolder(v);
@@ -90,6 +89,14 @@ public class ReblogFragment extends BaseFragment implements SendListener {
ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH); ui.input.setMaxTextLength(MAX_BLOG_POST_TEXT_LENGTH);
showProgressBar(); showProgressBar();
return v;
}
@Override
public void onStart() {
super.onStart();
// TODO: Load blog post when fragment is created. #631
feedController.loadBlogPost(blogId, postId, feedController.loadBlogPost(blogId, postId,
new UiResultExceptionHandler<BlogPostItem, DbException>( new UiResultExceptionHandler<BlogPostItem, DbException>(
this) { this) {
@@ -104,8 +111,6 @@ public class ReblogFragment extends BaseFragment implements SendListener {
handleDbException(exception); handleDbException(exception);
} }
}); });
return v;
} }
private void bindViewHolder() { private void bindViewHolder() {

View File

@@ -29,7 +29,6 @@ import javax.inject.Inject;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -73,15 +72,6 @@ public class RssFeedImportActivity extends BriarActivity {
enableOrDisableImportButton(); 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 = findViewById(R.id.importButton);
importButton.setOnClickListener(v -> publish()); importButton.setOnClickListener(v -> publish());

View File

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

View File

@@ -5,10 +5,8 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@NotNullByDefault
public class ContactListAdapter extends public class ContactListAdapter extends
BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> { BaseContactListAdapter<ContactListItem, ContactListItemViewHolder> {
@@ -30,9 +28,6 @@ public class ContactListAdapter extends
public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) { public boolean areContentsTheSame(ContactListItem c1, ContactListItem c2) {
// check for all properties that influence visual // check for all properties that influence visual
// representation of contact // representation of contact
if (c1.isEmpty() != c2.isEmpty()) {
return false;
}
if (c1.getUnreadCount() != c2.getUnreadCount()) { if (c1.getUnreadCount() != c2.getUnreadCount()) {
return false; return false;
} }
@@ -44,7 +39,11 @@ public class ContactListAdapter extends
@Override @Override
public int compare(ContactListItem c1, ContactListItem c2) { 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.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.getTransitionName; import static android.support.v4.view.ViewCompat.getTransitionName;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID; import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -104,7 +102,8 @@ public class ContactListFragment extends BaseFragment implements EventListener {
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @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); 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(); ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt()); i.putExtra(CONTACT_ID, contactId.getInt());
if (SDK_INT >= 23 && !isSamsung7()) { if (SDK_INT >= 23) {
ContactListItemViewHolder holder = ContactListItemViewHolder holder =
(ContactListItemViewHolder) list (ContactListItemViewHolder) list
.getRecyclerView() .getRecyclerView()
@@ -281,7 +280,7 @@ public class ContactListFragment extends BaseFragment implements EventListener {
ContactListItem item = adapter.getItemAt(position); ContactListItem item = adapter.getItemAt(position);
if (item != null) { if (item != null) {
item.setConnected(connected); 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 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.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS; import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.CONTACTS;
import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds; import static org.briarproject.briar.android.contactselection.ContactSelectorActivity.getContactsFromIds;
@@ -51,10 +50,10 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
} }
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle args = requireNonNull(getArguments()); Bundle args = getArguments();
byte[] b = args.getByteArray(GROUP_ID); byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No GroupId"); if (b == null) throw new IllegalStateException("No GroupId");
groupId = new GroupId(b); 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.setEmptyImage(R.drawable.ic_empty_state_contact_list);
list.setEmptyText(getString(R.string.no_contacts_selector)); list.setEmptyText(getString(R.string.no_contacts_selector));
list.setEmptyAction(getString(R.string.no_contacts_selector_action)); list.setEmptyAction(getString(R.string.no_contacts_selector_action));
adapter = getAdapter(requireNonNull(getContext()), this); adapter = getAdapter(getContext(), this);
list.setAdapter(adapter); list.setAdapter(adapter);
// restore selected contacts if available // restore selected contacts if available

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,17 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.conversation;
import android.content.res.Resources;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.media.ExifInterface; import android.support.media.ExifInterface;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import com.bumptech.glide.util.MarkEnforcingInputStream;
import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; 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.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager; import org.briarproject.briar.api.messaging.MessagingManager;
@@ -46,10 +44,8 @@ class AttachmentController {
private static final Logger LOG = private static final Logger LOG =
getLogger(AttachmentController.class.getName()); getLogger(AttachmentController.class.getName());
private static final int READ_LIMIT = 1024 * 8192;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ImageHelper imageHelper;
private final int defaultSize; private final int defaultSize;
private final int minWidth, maxWidth; private final int minWidth, maxWidth;
private final int minHeight, maxHeight; private final int minHeight, maxHeight;
@@ -57,38 +53,18 @@ class AttachmentController {
private final Map<MessageId, List<AttachmentItem>> attachmentCache = private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>(); new ConcurrentHashMap<>();
AttachmentController(MessagingManager messagingManager, AttachmentController(MessagingManager messagingManager, Resources res) {
AttachmentDimensions dimensions, ImageHelper imageHelper) {
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.imageHelper = imageHelper; defaultSize =
defaultSize = dimensions.defaultSize; res.getDimensionPixelSize(R.dimen.message_bubble_image_default);
minWidth = dimensions.minWidth; minWidth = res.getDimensionPixelSize(
maxWidth = dimensions.maxWidth; R.dimen.message_bubble_image_min_width);
minHeight = dimensions.minHeight; maxWidth = res.getDimensionPixelSize(
maxHeight = dimensions.maxHeight; R.dimen.message_bubble_image_max_width);
} minHeight = res.getDimensionPixelSize(
R.dimen.message_bubble_image_min_height);
AttachmentController(MessagingManager messagingManager, maxHeight = res.getDimensionPixelSize(
AttachmentDimensions dimensions) { R.dimen.message_bubble_image_max_height);
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);
}
});
} }
void put(MessageId messageId, List<AttachmentItem> attachments) { void put(MessageId messageId, List<AttachmentItem> attachments) {
@@ -107,7 +83,8 @@ class AttachmentController {
List<Pair<AttachmentHeader, Attachment>> attachments = List<Pair<AttachmentHeader, Attachment>> attachments =
new ArrayList<>(headers.size()); new ArrayList<>(headers.size());
for (AttachmentHeader h : headers) { for (AttachmentHeader h : headers) {
Attachment a = messagingManager.getAttachment(h.getMessageId()); Attachment a =
messagingManager.getAttachment(h.getMessageId());
attachments.add(new Pair<>(h, a)); attachments.add(new Pair<>(h, a));
} }
logDuration(LOG, "Loading attachment", start); logDuration(LOG, "Loading attachment", start);
@@ -116,7 +93,7 @@ class AttachmentController {
/** /**
* Creates {@link AttachmentItem}s from the passed headers and Attachments. * Creates {@link AttachmentItem}s from the passed headers and Attachments.
* <p> *
* Note: This closes the {@link Attachment}'s {@link InputStream}. * Note: This closes the {@link Attachment}'s {@link InputStream}.
*/ */
List<AttachmentItem> getAttachmentItems( List<AttachmentItem> getAttachmentItems(
@@ -140,7 +117,7 @@ class AttachmentController {
MessageId messageId = h.getMessageId(); MessageId messageId = h.getMessageId();
if (!needsSize) { if (!needsSize) {
String mimeType = h.getContentType(); String mimeType = h.getContentType();
String extension = imageHelper.getExtensionFromMimeType(mimeType); String extension = getExtensionFromMimeType(mimeType);
boolean hasError = false; boolean hasError = false;
if (extension == null) { if (extension == null) {
extension = ""; extension = "";
@@ -151,9 +128,8 @@ class AttachmentController {
} }
Size size = new Size(); Size size = new Size();
InputStream is = new MarkEnforcingInputStream( InputStream is = new BufferedInputStream(a.getStream());
new BufferedInputStream(a.getStream())); is.mark(Integer.MAX_VALUE);
is.mark(READ_LIMIT);
try { try {
// use exif to get size // use exif to get size
if (h.getContentType().equals("image/jpeg")) { if (h.getContentType().equals("image/jpeg")) {
@@ -166,8 +142,6 @@ class AttachmentController {
// use BitmapFactory to get size // use BitmapFactory to get size
if (size.error) { if (size.error) {
is.reset(); is.reset();
// need to mark again to re-add read limit
is.mark(READ_LIMIT);
size = getSizeFromBitmap(is); size = getSizeFromBitmap(is);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -183,18 +157,26 @@ class AttachmentController {
getThumbnailSize(size.width, size.height, size.mimeType); getThumbnailSize(size.width, size.height, size.mimeType);
} }
// get file extension // get file extension
String extension = imageHelper.getExtensionFromMimeType(size.mimeType); String extension = getExtensionFromMimeType(size.mimeType);
boolean hasError = extension == null || size.error; if (extension == null) {
if (extension == null) extension = ""; return new AttachmentItem(messageId, 0, 0, "", "", 0, 0, true);
}
return new AttachmentItem(messageId, size.width, size.height, return new AttachmentItem(messageId, size.width, size.height,
size.mimeType, extension, thumbnailSize.width, size.mimeType, extension, thumbnailSize.width,
thumbnailSize.height, hasError); thumbnailSize.height, size.error);
}
@Nullable
private String getExtensionFromMimeType(String mimeType) {
MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
return mimeTypeMap.getExtensionFromMimeType(mimeType);
} }
/** /**
* Gets the size of a JPEG {@link InputStream} if EXIF info is available. * 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); ExifInterface exif = new ExifInterface(is);
// these can return 0 independent of default value // these can return 0 independent of default value
int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0); int width = exif.getAttributeInt(TAG_IMAGE_WIDTH, 0);
@@ -214,10 +196,14 @@ class AttachmentController {
/** /**
* Gets the size of any image {@link InputStream}. * Gets the size of any image {@link InputStream}.
*/ */
private Size getSizeFromBitmap(InputStream is) { private static Size getSizeFromBitmap(InputStream is) {
DecodeResult result = imageHelper.decodeStream(is); BitmapFactory.Options options = new BitmapFactory.Options();
if (result.width < 1 || result.height < 1) return new Size(); options.inJustDecodeBounds = true;
return new Size(result.width, result.height, result.mimeType); BitmapFactory.decodeStream(is, null, options);
if (options.outWidth < 1 || options.outHeight < 1)
return new Size();
return new Size(options.outWidth, options.outHeight,
options.outMimeType);
} }
private Size getThumbnailSize(int width, int height, String mimeType) { private Size getThumbnailSize(int width, int height, String mimeType) {

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

@@ -7,8 +7,6 @@ import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
@Immutable @Immutable
@@ -20,7 +18,6 @@ public class AttachmentItem implements Parcelable {
private final String mimeType, extension; private final String mimeType, extension;
private final int thumbnailWidth, thumbnailHeight; private final int thumbnailWidth, thumbnailHeight;
private final boolean hasError; private final boolean hasError;
private final long instanceId;
public static final Creator<AttachmentItem> CREATOR = public static final Creator<AttachmentItem> CREATOR =
new Creator<AttachmentItem>() { new Creator<AttachmentItem>() {
@@ -35,8 +32,6 @@ public class AttachmentItem implements Parcelable {
} }
}; };
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
AttachmentItem(MessageId messageId, int width, int height, String mimeType, AttachmentItem(MessageId messageId, int width, int height, String mimeType,
String extension, int thumbnailWidth, int thumbnailHeight, String extension, int thumbnailWidth, int thumbnailHeight,
boolean hasError) { boolean hasError) {
@@ -48,7 +43,6 @@ public class AttachmentItem implements Parcelable {
this.thumbnailWidth = thumbnailWidth; this.thumbnailWidth = thumbnailWidth;
this.thumbnailHeight = thumbnailHeight; this.thumbnailHeight = thumbnailHeight;
this.hasError = hasError; this.hasError = hasError;
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
} }
protected AttachmentItem(Parcel in) { protected AttachmentItem(Parcel in) {
@@ -62,7 +56,6 @@ public class AttachmentItem implements Parcelable {
thumbnailWidth = in.readInt(); thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt(); thumbnailHeight = in.readInt();
hasError = in.readByte() != 0; hasError = in.readByte() != 0;
instanceId = in.readLong();
} }
public MessageId getMessageId() { public MessageId getMessageId() {
@@ -97,8 +90,9 @@ public class AttachmentItem implements Parcelable {
return hasError; return hasError;
} }
// TODO use counter instead, because in theory one attachment can appear in more than one messages
String getTransitionName() { String getTransitionName() {
return String.valueOf(instanceId); return String.valueOf(messageId.hashCode());
} }
@Override @Override
@@ -116,13 +110,12 @@ public class AttachmentItem implements Parcelable {
dest.writeInt(thumbnailWidth); dest.writeInt(thumbnailWidth);
dest.writeInt(thumbnailHeight); dest.writeInt(thumbnailHeight);
dest.writeByte((byte) (hasError ? 1 : 0)); dest.writeByte((byte) (hasError ? 1 : 0));
dest.writeLong(instanceId);
} }
@Override @Override
public boolean equals(@Nullable Object o) { public boolean equals(@Nullable Object o) {
return o instanceof AttachmentItem && return o instanceof AttachmentItem &&
instanceId == ((AttachmentItem) o).instanceId; messageId.equals(((AttachmentItem) o).messageId);
} }
} }

View File

@@ -46,7 +46,9 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent; import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
import org.briarproject.bramble.api.sync.event.MessagesSentEvent; import org.briarproject.bramble.api.sync.event.MessagesSentEvent;
@@ -101,7 +103,6 @@ import im.delight.android.identicons.IdenticonDrawable;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt; import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener; 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.os.Build.VERSION.SDK_INT;
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation; import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static android.support.v4.view.ViewCompat.setTransitionName; import static android.support.v4.view.ViewCompat.setTransitionName;
@@ -124,6 +125,7 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION; 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.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME; 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.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName; import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce; import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@@ -141,9 +143,8 @@ public class ConversationActivity extends BriarActivity
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ConversationActivity.class.getName()); Logger.getLogger(ConversationActivity.class.getName());
private static final String SHOW_ONBOARDING_INTRODUCTION =
private static final int TRANSITION_DURATION_MS = 500; "showOnboardingIntroduction";
private static final int ONBOARDING_DELAY_MS = 250;
@Inject @Inject
AndroidNotificationManager notificationManager; AndroidNotificationManager notificationManager;
@@ -152,8 +153,21 @@ public class ConversationActivity extends BriarActivity
@Inject @Inject
@CryptoExecutor @CryptoExecutor
Executor 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;
private TextSendController sendController;
// Fields that are accessed from background threads must be volatile // Fields that are accessed from background threads must be volatile
@Inject @Inject
@@ -176,37 +190,24 @@ public class ConversationActivity extends BriarActivity
volatile BlogSharingManager blogSharingManager; volatile BlogSharingManager blogSharingManager;
@Inject @Inject
volatile GroupInvitationManager groupInvitationManager; volatile GroupInvitationManager groupInvitationManager;
@Inject
ViewModelProvider.Factory viewModelFactory;
private volatile ContactId contactId;
@Nullable
private Parcelable layoutManagerState;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Observer<String> contactNameObserver = name -> { private final Observer<String> contactNameObserver = name -> {
requireNonNull(name); requireNonNull(name);
loadMessages(); 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 @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
if (SDK_INT >= 21) { if (SDK_INT >= 21) {
// Spurious lint warning - using END causes a crash // Spurious lint warning - using END causes a crash
@SuppressLint("RtlHardcoded") @SuppressLint("RtlHardcoded")
Transition slide = new Slide(RIGHT); Transition slide = new Slide(RIGHT);
slide.setDuration(TRANSITION_DURATION_MS);
setSceneTransitionAnimation(slide, null, slide); setSceneTransitionAnimation(slide, null, slide);
} }
super.onCreate(state); super.onCreate(state);
@@ -218,6 +219,7 @@ public class ConversationActivity extends BriarActivity
viewModel = ViewModelProviders.of(this, viewModelFactory) viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class); .get(ConversationViewModel.class);
viewModel.setContactId(contactId);
attachmentController = viewModel.getAttachmentController(); attachmentController = viewModel.getAttachmentController();
setContentView(R.layout.activity_conversation); setContentView(R.layout.activity_conversation);
@@ -241,8 +243,8 @@ public class ConversationActivity extends BriarActivity
requireNonNull(deleted); requireNonNull(deleted);
if (deleted) finish(); if (deleted) finish();
}); });
viewModel.getAddedPrivateMessage().observe(this, viewModel.getAddedPrivateMessage()
this::onAddedPrivateMessage); .observe(this, this::onAddedPrivateMessage);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId)); setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId)); setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
@@ -255,34 +257,18 @@ public class ConversationActivity extends BriarActivity
list.setLayoutManager(layoutManager); list.setLayoutManager(layoutManager);
list.setAdapter(adapter); list.setAdapter(adapter);
list.setEmptyText(getString(R.string.no_private_messages)); 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); textInputView = findViewById(R.id.text_input_container);
if (FEATURE_FLAG_IMAGE_ATTACHMENTS) { if (FEATURE_FLAG_IMAGE_ATTACHMENTS) {
ImagePreview imagePreview = findViewById(R.id.imagePreview); ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView, sendController = new TextAttachmentController(textInputView,
imagePreview, this, this); imagePreview, this, this);
observeOnce(viewModel.hasImageSupport(), this, hasSupport -> {
if (hasSupport != null && hasSupport) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS
((TextAttachmentController) sendController)
.setImagesSupported();
}
});
} else { } else {
sendController = new TextSendController(textInputView, this, false); sendController = new TextSendController(textInputView, this, false);
} }
textInputView.setSendController(sendController); textInputView.setSendController(sendController);
textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH); textInputView.setMaxTextLength(MAX_PRIVATE_MESSAGE_TEXT_LENGTH);
textInputView.setEnabled(false); textInputView.setEnabled(false);
textInputView.addOnKeyboardShownListener(this::scrollToBottom);
}
private void scrollToBottom() {
int items = adapter.getItemCount();
if (items > 0) list.scrollToPosition(items - 1);
} }
@Override @Override
@@ -291,8 +277,7 @@ public class ConversationActivity extends BriarActivity
} }
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result, Intent data) {
@Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) { if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
@@ -317,16 +302,6 @@ public class ConversationActivity extends BriarActivity
list.startPeriodicUpdate(); 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 @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
@@ -357,18 +332,10 @@ public class ConversationActivity extends BriarActivity
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.conversation_actions, menu); inflater.inflate(R.menu.conversation_actions, menu);
// enable introduction action if available enableIntroductionActionIfAvailable(
observeOnce(viewModel.showIntroductionAction(), this, enable -> { menu.findItem(R.id.action_introduction));
if (enable != null && enable) { enableAliasActionIfAvailable(
menu.findItem(R.id.action_introduction).setEnabled(true); menu.findItem(R.id.action_set_alias));
// 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));
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
} }
@@ -431,10 +398,33 @@ public class ConversationActivity extends BriarActivity
Long.compare(b.getTimestamp(), a.getTimestamp())); Long.compare(b.getTimestamp(), a.getTimestamp()));
if (!sorted.isEmpty()) { if (!sorted.isEmpty()) {
// If the latest header is a private message, eagerly load // 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); ConversationMessageHeader latest = sorted.get(0);
if (latest instanceof PrivateMessageHeader) { 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().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);
}
}
} }
} }
displayMessages(revision, sorted); displayMessages(revision, sorted);
@@ -446,49 +436,19 @@ 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, private void displayMessages(int revision,
Collection<ConversationMessageHeader> headers) { Collection<ConversationMessageHeader> headers) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) { if (revision == adapter.getRevision()) {
adapter.incrementRevision(); adapter.incrementRevision();
textInputView.setEnabled(true); 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);
List<ConversationItem> items = createItems(headers); List<ConversationItem> items = createItems(headers);
adapter.addAll(items); adapter.addAll(items);
list.showData(); list.showData();
if (layoutManagerState == null) { if (layoutManagerState == null) {
scrollToBottom(); // Scroll to the bottom
list.scrollToPosition(adapter.getItemCount() - 1);
} else { } else {
// Restore the previous scroll position
layoutManager.onRestoreInstanceState(layoutManagerState); layoutManager.onRestoreInstanceState(layoutManagerState);
} }
} else { } else {
@@ -531,21 +491,15 @@ public class ConversationActivity extends BriarActivity
Pair<Integer, ConversationMessageItem> pair = Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m); adapter.getMessageItem(m);
if (pair != null) { if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setText(text); pair.getSecond().setText(text);
boolean bottom = layoutManagerState == null &&
adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst()); 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, private void loadMessageAttachments(MessageId messageId,
List<AttachmentHeader> headers) { List<AttachmentHeader> headers) {
runOnDbThread(() -> { runOnDbThread(() -> {
@@ -569,10 +523,11 @@ public class ConversationActivity extends BriarActivity
Pair<Integer, ConversationMessageItem> pair = Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m); adapter.getMessageItem(m);
if (pair != null) { if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setAttachments(items); pair.getSecond().setAttachments(items);
boolean bottom = layoutManagerState == null &&
adapter.isScrolledToBottom(layoutManager);
adapter.notifyItemChanged(pair.getFirst()); adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom(); if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
} }
}); });
} }
@@ -621,13 +576,11 @@ public class ConversationActivity extends BriarActivity
private void addConversationItem(ConversationItem item) { private void addConversationItem(ConversationItem item) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
boolean bottom = layoutManagerState == null &&
adapter.isScrolledToBottom(layoutManager);
adapter.incrementRevision(); adapter.incrementRevision();
adapter.add(item); adapter.add(item);
// When adding a new message, scroll to the bottom if the if (bottom) list.scrollToPosition(adapter.getItemCount() - 1);
// conversation is visible, even if we're not currently at
// the bottom
if (getLifecycle().getCurrentState().isAtLeast(STARTED))
scrollToBottom();
}); });
} }
@@ -723,70 +676,91 @@ public class ConversationActivity extends BriarActivity
}); });
} }
private void showImageOnboarding(@Nullable Boolean show) { private void enableIntroductionActionIfAvailable(MenuItem item) {
if (show == null || !show) return; runOnDbThread(() -> {
if (SDK_INT >= 21) { try {
// show onboarding only after the enter transition has ended if (contactManager.getActiveContacts().size() > 1) {
// otherwise the tap target animation won't play enableIntroductionAction(item);
textInputView.postDelayed(this::showImageOnboarding, Settings settings =
TRANSITION_DURATION_MS + ONBOARDING_DELAY_MS); settingsManager.getSettings(SETTINGS_NAMESPACE);
} else { if (settings.getBoolean(SHOW_ONBOARDING_INTRODUCTION,
showImageOnboarding(); true)) {
} showIntroductionOnboarding();
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
private void showImageOnboarding() { private void enableAliasActionIfAvailable(MenuItem item) {
// remove cast when removing FEATURE_FLAG_IMAGE_ATTACHMENTS observeOnce(viewModel.getContact(), this, c -> item.setEnabled(true));
((TextAttachmentController) sendController)
.showImageOnboarding(this, () ->
viewModel.onImageOnboardingSeen());
} }
private void showIntroductionOnboarding(@Nullable Boolean show) { private void enableIntroductionAction(MenuItem item) {
if (show == null || !show) return; runOnUiThreadUnlessDestroyed(() -> item.setEnabled(true));
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 showIntroductionOnboarding() { private void showIntroductionOnboarding() {
// find view of overflow icon runOnUiThreadUnlessDestroyed(() -> {
View target = null; // find view of overflow icon
for (int i = 0; i < toolbar.getChildCount(); i++) { View target = null;
if (toolbar.getChildAt(i) instanceof ActionMenuView) { for (int i = 0; i < toolbar.getChildCount(); i++) {
ActionMenuView menu = (ActionMenuView) toolbar.getChildAt(i); if (toolbar.getChildAt(i) instanceof ActionMenuView) {
// The overflow icon should be the last child of the menu ActionMenuView menu =
target = menu.getChildAt(menu.getChildCount() - 1); (ActionMenuView) toolbar.getChildAt(i);
// If the menu hasn't been populated yet, use the menu itself target = menu.getChildAt(menu.getChildCount() - 1);
// as the target break;
if (target == null) target = menu; }
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) -> { PromptStateChangeListener listener = (prompt, state) -> {
if (state == STATE_DISMISSED || state == STATE_FINISHED) { if (state == STATE_DISMISSED || state == STATE_FINISHED) {
viewModel.onIntroductionOnboardingSeen(); 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) @Override
.setSecondaryText(R.string.introduction_onboarding_text) public void onItemVisible(ConversationItem item) {
.setIcon(R.drawable.ic_more_vert_accent) if (!item.isRead()) markMessageRead(item.getGroupId(), item.getId());
.setBackgroundColour( }
ContextCompat.getColor(this, R.color.briar_primary))
.setPromptStateChangeListener(listener) private void markMessageRead(GroupId g, MessageId m) {
.show(); runOnDbThread(() -> {
try {
long start = now();
messagingManager.setReadFlag(g, m, true);
logDuration(LOG, "Marking read", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
} }
@UiThread @UiThread

View File

@@ -14,14 +14,12 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter; import org.briarproject.briar.android.util.BriarAdapter;
import org.briarproject.briar.android.util.ItemReturningAdapter;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@NotNullByDefault @NotNullByDefault
class ConversationAdapter class ConversationAdapter
extends BriarAdapter<ConversationItem, ConversationItemViewHolder> extends BriarAdapter<ConversationItem, ConversationItemViewHolder> {
implements ItemReturningAdapter<ConversationItem> {
private ConversationListener listener; private ConversationListener listener;
private final RecycledViewPool imageViewPool; private final RecycledViewPool imageViewPool;
@@ -71,6 +69,7 @@ class ConversationAdapter
public void onBindViewHolder(ConversationItemViewHolder ui, int position) { public void onBindViewHolder(ConversationItemViewHolder ui, int position) {
ConversationItem item = items.get(position); ConversationItem item = items.get(position);
ui.bind(item); ui.bind(item);
listener.onItemVisible(item);
} }
@Override @Override

View File

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

View File

@@ -9,6 +9,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault @NotNullByDefault
interface ConversationListener { interface ConversationListener {
void onItemVisible(ConversationItem item);
void respondToRequest(ConversationRequestItem item, boolean accept); void respondToRequest(ConversationRequestItem item, boolean accept);
void openRequestedShareable(ConversationRequestItem item); void openRequestedShareable(ConversationRequestItem item);

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

@@ -16,17 +16,13 @@ import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor; 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.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException; import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.GroupId;
import org.briarproject.bramble.api.sync.Message; 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.android.util.UiUtils;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
@@ -38,7 +34,6 @@ import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -52,8 +47,6 @@ import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.conversation.AttachmentDimensions.getAttachmentDimensions;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
@NotNullByDefault @NotNullByDefault
@@ -61,20 +54,13 @@ public class ConversationViewModel extends AndroidViewModel {
private static Logger LOG = private static Logger LOG =
getLogger(ConversationViewModel.class.getName()); getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
"showOnboardingImage";
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@DatabaseExecutor @DatabaseExecutor
private final Executor dbExecutor; private final Executor dbExecutor;
@CryptoExecutor @CryptoExecutor
private final Executor cryptoExecutor; private final Executor cryptoExecutor;
// TODO replace with TransactionManager once it exists
private final DatabaseComponent db;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final ContactManager contactManager; private final ContactManager contactManager;
private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory; private final PrivateMessageFactory privateMessageFactory;
private final AttachmentController attachmentController; private final AttachmentController attachmentController;
@@ -85,14 +71,6 @@ public class ConversationViewModel extends AndroidViewModel {
Transformations.map(contact, c -> c.getAuthor().getId()); Transformations.map(contact, c -> c.getAuthor().getId());
private final LiveData<String> contactName = private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName); 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 = private final MutableLiveData<Boolean> contactDeleted =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<GroupId> messagingGroupId = private final MutableLiveData<GroupId> messagingGroupId =
@@ -103,46 +81,38 @@ public class ConversationViewModel extends AndroidViewModel {
@Inject @Inject
ConversationViewModel(Application application, ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor, DatabaseComponent db, @CryptoExecutor Executor cryptoExecutor,
MessagingManager messagingManager, ContactManager contactManager, MessagingManager messagingManager,
SettingsManager settingsManager, ContactManager contactManager,
PrivateMessageFactory privateMessageFactory) { PrivateMessageFactory privateMessageFactory) {
super(application); super(application);
this.dbExecutor = dbExecutor; this.dbExecutor = dbExecutor;
this.cryptoExecutor = cryptoExecutor; this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory; this.privateMessageFactory = privateMessageFactory;
this.attachmentController = new AttachmentController(messagingManager, this.attachmentController = new AttachmentController(messagingManager,
getAttachmentDimensions(application.getResources())); application.getResources());
contactDeleted.setValue(false); contactDeleted.setValue(false);
} }
/**
* Setting the {@link ContactId} automatically triggers loading of other
* data.
*/
void setContactId(ContactId contactId) { void setContactId(ContactId contactId) {
if (this.contactId == null) { if (this.contactId == null) {
this.contactId = contactId; this.contactId = contactId;
loadContact(contactId); loadContact();
} else if (!contactId.equals(this.contactId)) { } else if (!contactId.equals(this.contactId)) {
throw new IllegalStateException(); throw new IllegalStateException();
} }
} }
private void loadContact(ContactId contactId) { private void loadContact() {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
long start = now(); long start = now();
Contact c = contactManager.getContact(contactId); Contact c =
contactManager.getContact(requireNonNull(contactId));
contact.postValue(c); contact.postValue(c);
logDuration(LOG, "Loading contact", start); logDuration(LOG, "Loading contact", start);
start = now();
checkFeaturesAndOnboarding(contactId);
logDuration(LOG, "Checking for image support", start);
} catch (NoSuchContactException e) { } catch (NoSuchContactException e) {
contactDeleted.postValue(true); contactDeleted.postValue(true);
} catch (DbException e) { } catch (DbException e) {
@@ -151,24 +121,12 @@ 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) { void setContactAlias(String alias) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
try { try {
contactManager.setContactAlias(requireNonNull(contactId), contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias); alias.isEmpty() ? null : alias);
loadContact(contactId); loadContact();
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
@@ -196,59 +154,6 @@ public class ConversationViewModel extends AndroidViewModel {
}); });
} }
@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, private void storeAttachments(GroupId groupId, @Nullable String text,
List<Uri> uris, long timestamp) { List<Uri> uris, long timestamp) {
dbExecutor.execute(() -> { dbExecutor.execute(() -> {
@@ -357,22 +262,6 @@ public class ConversationViewModel extends AndroidViewModel {
return contactName; 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() { LiveData<Boolean> isContactDeleted() {
return contactDeleted; return contactDeleted;
} }

View File

@@ -27,8 +27,6 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.Window; import android.view.Window;
import android.widget.TextView; 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.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
@@ -57,8 +55,6 @@ import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SAVE_ATTACHMENT;
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute; import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ImageActivity extends BriarActivity public class ImageActivity extends BriarActivity
implements PullDownLayout.Callback, OnGlobalLayoutListener { implements PullDownLayout.Callback, OnGlobalLayoutListener {
@@ -67,10 +63,6 @@ public class ImageActivity extends BriarActivity
final static String NAME = "name"; final static String NAME = "name";
final static String DATE = "date"; 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 @Inject
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
@@ -105,6 +97,7 @@ public class ImageActivity extends BriarActivity
// inflate layout // inflate layout
setContentView(R.layout.activity_image); setContentView(R.layout.activity_image);
layout = findViewById(R.id.layout); layout = findViewById(R.id.layout);
layout.getBackground().setAlpha(255);
layout.setCallback(this); layout.setCallback(this);
layout.getViewTreeObserver().addOnGlobalLayoutListener(this); layout.getViewTreeObserver().addOnGlobalLayoutListener(this);
@@ -142,7 +135,9 @@ public class ImageActivity extends BriarActivity
if (SDK_INT >= 16) { if (SDK_INT >= 16) {
viewModel.getOnImageClicked().observe(this, this::onImageClicked); viewModel.getOnImageClicked().observe(this, this::onImageClicked);
window.getDecorView().setSystemUiVisibility(UI_FLAGS_DEFAULT); window.getDecorView().setSystemUiVisibility(
SYSTEM_UI_FLAG_LAYOUT_STABLE |
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
} }
} }
@@ -179,11 +174,9 @@ public class ImageActivity extends BriarActivity
} }
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result, Intent data) {
@Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK && if (request == REQUEST_SAVE_ATTACHMENT && result == RESULT_OK) {
data != null) {
viewModel.saveImage(getVisibleAttachment(), data.getData()); viewModel.saveImage(getVisibleAttachment(), data.getData());
} }
} }
@@ -197,6 +190,7 @@ public class ImageActivity extends BriarActivity
@Override @Override
public void onPull(float progress) { public void onPull(float progress) {
layout.getBackground().setAlpha(Math.round((1 - progress) * 255));
} }
@Override @Override
@@ -208,16 +202,9 @@ public class ImageActivity extends BriarActivity
@Override @Override
public void onPullComplete() { public void onPullComplete() {
showStatusBarBeforeFinishing();
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
@Override
public void onBackPressed() {
showStatusBarBeforeFinishing();
super.onBackPressed();
}
@RequiresApi(api = 16) @RequiresApi(api = 16)
private void onImageClicked(@Nullable Boolean clicked) { private void onImageClicked(@Nullable Boolean clicked) {
if (clicked != null && clicked) { if (clicked != null && clicked) {
@@ -238,8 +225,9 @@ public class ImageActivity extends BriarActivity
@RequiresApi(api = 16) @RequiresApi(api = 16)
private void hideSystemUi(View decorView) { private void hideSystemUi(View decorView) {
decorView.setSystemUiVisibility( decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_FULLSCREEN |
SYSTEM_UI_FLAG_FULLSCREEN | UI_FLAGS_DEFAULT); SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
appBarLayout.animate() appBarLayout.animate()
.translationYBy(-1 * appBarLayout.getHeight()) .translationYBy(-1 * appBarLayout.getHeight())
.alpha(0f) .alpha(0f)
@@ -249,7 +237,9 @@ public class ImageActivity extends BriarActivity
@RequiresApi(api = 16) @RequiresApi(api = 16)
private void showSystemUi(View decorView) { private void showSystemUi(View decorView) {
decorView.setSystemUiVisibility(UI_FLAGS_DEFAULT); decorView.setSystemUiVisibility(
SYSTEM_UI_FLAG_LAYOUT_STABLE | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
);
appBarLayout.animate() appBarLayout.animate()
.translationYBy(appBarLayout.getHeight()) .translationYBy(appBarLayout.getHeight())
.alpha(1f) .alpha(1f)
@@ -257,18 +247,6 @@ public class ImageActivity extends BriarActivity
.start(); .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() { private void showSaveImageDialog() {
OnClickListener okListener = (dialog, which) -> { OnClickListener okListener = (dialog, which) -> {
if (SDK_INT >= 19) { if (SDK_INT >= 19) {

View File

@@ -82,7 +82,6 @@ public class ImageFragment extends Fragment {
viewModelFactory).get(ImageViewModel.class); viewModelFactory).get(ImageViewModel.class);
photoView = v.findViewById(R.id.photoView); photoView = v.findViewById(R.id.photoView);
photoView.setScaleLevels(1, 2, 4);
photoView.setOnClickListener(view -> viewModel.clickImage()); photoView.setOnClickListener(view -> viewModel.clickImage());
// Request Listener // Request Listener
@@ -120,10 +119,9 @@ public class ImageFragment extends Fragment {
// Load Image // Load Image
GlideApp.with(this) GlideApp.with(this)
.load(attachment) .load(attachment)
// TODO allow if size < maxTextureSize ?
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE) .diskCacheStrategy(NONE)
.error(R.drawable.ic_image_broken) .error(R.drawable.ic_image_broken)
.dontTransform()
.addListener(listener) .addListener(listener)
.into(photoView); .into(photoView);

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

@@ -10,12 +10,12 @@ import android.view.View;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.UiUtils;
import static org.briarproject.briar.android.conversation.ImageAdapter.isBottomRow; 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.isLeft;
import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow; import static org.briarproject.briar.android.conversation.ImageAdapter.isTopRow;
import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow; import static org.briarproject.briar.android.conversation.ImageAdapter.singleInRow;
import static org.briarproject.briar.android.util.UiUtils.isRtl;
@NotNullByDefault @NotNullByDefault
class ImageItemDecoration extends ItemDecoration { class ImageItemDecoration extends ItemDecoration {
@@ -35,7 +35,7 @@ class ImageItemDecoration extends ItemDecoration {
border = realBorderSize / 2; border = realBorderSize / 2;
// find out if we are showing a RTL language // find out if we are showing a RTL language
isRtl = isRtl(ctx); isRtl = UiUtils.isRtl(ctx);
} }
@Override @Override

View File

@@ -50,12 +50,9 @@ public class ImageViewModel extends AndroidViewModel {
@IoExecutor @IoExecutor
private final Executor 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 = private final MutableLiveData<Boolean> imageClicked =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveData<Boolean> saveState = new MutableLiveData<>();
private int toolbarTop, toolbarBottom; private int toolbarTop, toolbarBottom;
@Inject @Inject
@@ -109,9 +106,8 @@ public class ImageViewModel extends AndroidViewModel {
} }
/** /**
* A LiveData that is true if there was an error * A LiveData that is true if the image was saved,
* and false if the image was saved. * false if there was an error and null otherwise.
* It can be null otherwise, if no image was saved recently.
* *
* Call {@link #onSaveStateSeen()} after consuming an update. * Call {@link #onSaveStateSeen()} after consuming an update.
*/ */
@@ -130,7 +126,7 @@ public class ImageViewModel extends AndroidViewModel {
@UiThread @UiThread
void saveImage(AttachmentItem attachment, @Nullable Uri uri) { void saveImage(AttachmentItem attachment, @Nullable Uri uri) {
if (uri == null) { if (uri == null) {
saveState.setValue(true); saveState.setValue(false);
} else { } else {
saveImage(attachment, () -> getOutputStream(uri), null); saveImage(attachment, () -> getOutputStream(uri), null);
} }

View File

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

View File

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

View File

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

View File

@@ -21,14 +21,13 @@ import static android.graphics.Shader.TileMode.CLAMP;
@Immutable @Immutable
@NotNullByDefault @NotNullByDefault
class CustomCornersTransformation extends BitmapTransformation { public class CustomCornersTransformation extends BitmapTransformation {
private static final String ID = private static final String ID = CustomCornersTransformation.class.getName();
CustomCornersTransformation.class.getName();
private final Radii radii; private final Radii radii;
CustomCornersTransformation(Radii radii) { public CustomCornersTransformation(Radii radii) {
this.radii = radii; this.radii = radii;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,7 +39,6 @@ import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@@ -79,14 +78,14 @@ public class IntroductionMessageFragment extends BaseFragment
} }
@Override @Override
public void onAttach(Context context) { public void injectFragment(ActivityComponent component) {
super.onAttach(context); component.inject(this);
introductionActivity = (IntroductionActivity) context;
} }
@Override @Override
public void injectFragment(ActivityComponent component) { public void onAttach(Context context) {
component.inject(this); super.onAttach(context);
introductionActivity = (IntroductionActivity) context;
} }
@Override @Override
@@ -100,14 +99,6 @@ public class IntroductionMessageFragment extends BaseFragment
actionBar.setTitle(R.string.introduction_message_title); 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 // inflate view
View v = inflater.inflate(R.layout.introduction_message, container, View v = inflater.inflate(R.layout.introduction_message, container,
false); false);
@@ -118,15 +109,22 @@ public class IntroductionMessageFragment extends BaseFragment
ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH); ui.message.setMaxTextLength(MAX_INTRODUCTION_TEXT_LENGTH);
ui.message.setEnabled(false); ui.message.setEnabled(false);
// get contacts and then show view
prepareToSetUpViews(contactId1, contactId2);
return v; return v;
} }
@Override @Override
public void onStart() { public void onStart() {
super.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 @Override

View File

@@ -4,12 +4,9 @@ import android.os.Bundle;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.widget.Toast; import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactExchangeListener;
import org.briarproject.bramble.api.contact.ContactExchangeTask; 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.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.Author;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor; import org.briarproject.bramble.api.identity.LocalAuthor;
@@ -31,7 +28,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ContactExchangeActivity extends KeyAgreementActivity implements public class ContactExchangeActivity extends KeyAgreementActivity implements
EventListener { ContactExchangeListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ContactExchangeActivity.class.getName()); Logger.getLogger(ContactExchangeActivity.class.getName());
@@ -53,20 +50,6 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
getSupportActionBar().setTitle(R.string.add_contact_title); 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) { private void startContactExchange(KeyAgreementResult result) {
runOnDbThread(() -> { runOnDbThread(() -> {
LocalAuthor localAuthor; LocalAuthor localAuthor;
@@ -80,28 +63,15 @@ public class ContactExchangeActivity extends KeyAgreementActivity implements
} }
// Exchange contact details // Exchange contact details
contactExchangeTask.startExchange(localAuthor, contactExchangeTask.startExchange(ContactExchangeActivity.this,
result.getMasterKey(), result.getConnection(), localAuthor, result.getMasterKey(),
result.getTransportId(), result.wasAlice()); result.getConnection(), result.getTransportId(),
result.wasAlice());
}); });
} }
@Override @Override
public void eventOccurred(Event e) { public void contactExchangeSucceeded(Author remoteAuthor) {
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) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName(); String contactName = remoteAuthor.getName();
String format = getString(R.string.contact_added_toast); 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(() -> { runOnUiThreadUnlessDestroyed(() -> {
String contactName = remoteAuthor.getName(); String contactName = remoteAuthor.getName();
String format = getString(R.string.contact_already_exists); 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(() -> { runOnUiThreadUnlessDestroyed(() -> {
showErrorFragment(R.string.connection_error_explanation); showErrorFragment(R.string.connection_error_explanation);
}); });

View File

@@ -44,11 +44,6 @@ public class ContactExchangeErrorFragment extends BaseFragment {
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@@ -79,6 +74,11 @@ public class ContactExchangeErrorFragment extends BaseFragment {
return v; return v;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
private void triggerFeedback() { private void triggerFeedback() {
finish(); finish();
UiUtils.triggerFeedback(androidExecutor); 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.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -38,6 +39,11 @@ public class IntroFragment extends BaseFragment {
return fragment; return fragment;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);

View File

@@ -53,7 +53,6 @@ import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL; import static android.widget.LinearLayout.HORIZONTAL;
import static android.widget.Toast.LENGTH_LONG; import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@@ -137,8 +136,7 @@ public class KeyAgreementFragment extends BaseEventFragment
@Override @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
requireNonNull(getActivity()) getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
cameraView.setPreviewConsumer(new QrCodeDecoder(this)); cameraView.setPreviewConsumer(new QrCodeDecoder(this));
} }

View File

@@ -18,7 +18,6 @@ import javax.annotation.Nullable;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -36,16 +35,11 @@ public class AuthorNameFragment extends SetupFragment {
return new AuthorNameFragment(); return new AuthorNameFragment();
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(getString(R.string.setup_title)); getActivity().setTitle(getString(R.string.setup_title));
View v = inflater.inflate(R.layout.fragment_setup_author_name, View v = inflater.inflate(R.layout.fragment_setup_author_name,
container, false); container, false);
authorNameWrapper = v.findViewById(R.id.nickname_entry_wrapper); authorNameWrapper = v.findViewById(R.id.nickname_entry_wrapper);
@@ -63,6 +57,11 @@ public class AuthorNameFragment extends SetupFragment {
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
protected String getHelpText() { protected String getHelpText() {
return getString(R.string.setup_name_explanation); return getString(R.string.setup_name_explanation);

View File

@@ -19,7 +19,6 @@ import org.briarproject.briar.android.util.UiUtils;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@@ -40,16 +39,11 @@ public class DozeFragment extends SetupFragment
return new DozeFragment(); return new DozeFragment();
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(getString(R.string.setup_doze_title)); getActivity().setTitle(getString(R.string.setup_doze_title));
setHasOptionsMenu(false); setHasOptionsMenu(false);
View v = inflater.inflate(R.layout.fragment_setup_doze, container, View v = inflater.inflate(R.layout.fragment_setup_doze, container,
false); false);
@@ -71,6 +65,11 @@ public class DozeFragment extends SetupFragment
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
protected String getHelpText() { protected String getHelpText() {
return getString(R.string.setup_doze_explanation); return getString(R.string.setup_doze_explanation);

View File

@@ -23,7 +23,6 @@ import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -44,16 +43,11 @@ public class PasswordFragment extends SetupFragment {
return new PasswordFragment(); return new PasswordFragment();
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro)); getActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container, View v = inflater.inflate(R.layout.fragment_setup_password, container,
false); false);
@@ -70,11 +64,6 @@ public class PasswordFragment extends SetupFragment {
passwordConfirmation.addTextChangedListener(this); passwordConfirmation.addTextChangedListener(this);
nextButton.setOnClickListener(this); nextButton.setOnClickListener(this);
if (!setupController.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
return v; return v;
} }
@@ -83,6 +72,17 @@ public class PasswordFragment extends SetupFragment {
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
// the controller is not yet available in onCreateView()
if (!setupController.needToShowDozeFragment()) {
nextButton.setText(R.string.create_account_button);
passwordConfirmation.setImeOptions(IME_ACTION_DONE);
}
}
@Override @Override
protected String getHelpText() { protected String getHelpText() {
return getString(R.string.setup_password_explanation); return getString(R.string.setup_password_explanation);

View File

@@ -11,8 +11,7 @@ import android.view.View.OnClickListener;
import android.widget.TextView; import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener; import android.widget.TextView.OnEditorActionListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
@@ -23,8 +22,7 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT;
import static org.briarproject.briar.android.util.UiUtils.enterPressed; import static org.briarproject.briar.android.util.UiUtils.enterPressed;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog; import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault @NotNullByDefault
@ParametersNotNullByDefault
abstract class SetupFragment extends BaseFragment implements TextWatcher, abstract class SetupFragment extends BaseFragment implements TextWatcher,
OnEditorActionListener, OnClickListener { OnEditorActionListener, OnClickListener {

View File

@@ -1,13 +1,14 @@
package org.briarproject.briar.android.logout; package org.briarproject.briar.android.logout;
import android.app.Activity; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import java.util.logging.Logger; import java.util.logging.Logger;
import static android.os.Build.VERSION.SDK_INT; public class ExitActivity extends BaseActivity {
public class ExitActivity extends Activity {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ExitActivity.class.getName()); Logger.getLogger(ExitActivity.class.getName());
@@ -15,9 +16,14 @@ public class ExitActivity extends Activity {
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
if (SDK_INT >= 21) finishAndRemoveTask(); if (Build.VERSION.SDK_INT >= 21) finishAndRemoveTask();
else finish(); else finish();
LOG.info("Exiting"); LOG.info("Exiting");
System.exit(0); System.exit(0);
} }
@Override
public void injectActivity(ActivityComponent component) {
}
} }

View File

@@ -1,13 +1,20 @@
package org.briarproject.briar.android.logout; package org.briarproject.briar.android.logout;
import android.app.Activity;
import android.os.Bundle; import android.os.Bundle;
public class HideUiActivity extends Activity { import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
public class HideUiActivity extends BaseActivity {
@Override @Override
public void onCreate(Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
finish(); finish();
} }
@Override
public void injectActivity(ActivityComponent component) {
}
} }

View File

@@ -5,21 +5,19 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class SignOutFragment extends BaseFragment { public class SignOutFragment extends BaseFragment {
public static final String TAG = SignOutFragment.class.getName(); public static final String TAG = SignOutFragment.class.getName();
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(@Nonnull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_sign_out, container, false); return inflater.inflate(R.layout.fragment_sign_out, container, false);
@@ -29,4 +27,9 @@ public class SignOutFragment extends BaseFragment {
public String getUniqueTag() { public String getUniqueTag() {
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
// no need to inject
}
} }

View File

@@ -5,7 +5,6 @@ import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.NavigationView; import android.support.design.widget.NavigationView;
import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener; import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
@@ -13,7 +12,6 @@ import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -27,8 +25,6 @@ import android.widget.TextView;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TorConstants;
@@ -58,7 +54,6 @@ import static android.support.v4.view.GravityCompat.START;
import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING;
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
@@ -66,8 +61,6 @@ import static org.briarproject.briar.android.navdrawer.NavDrawerController.Expir
import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE; import static org.briarproject.briar.android.navdrawer.NavDrawerController.ExpiryWarning.UPDATE;
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry; import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class NavDrawerActivity extends BriarActivity implements public class NavDrawerActivity extends BriarActivity implements
BaseFragmentListener, TransportStateListener, BaseFragmentListener, TransportStateListener,
OnNavigationItemSelectedListener { OnNavigationItemSelectedListener {
@@ -109,7 +102,7 @@ public class NavDrawerActivity extends BriarActivity implements
} else if (intent.getBooleanExtra(INTENT_BLOGS, false)) { } else if (intent.getBooleanExtra(INTENT_BLOGS, false)) {
startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs); startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs);
} else if (intent.getBooleanExtra(INTENT_SIGN_OUT, false)) { } else if (intent.getBooleanExtra(INTENT_SIGN_OUT, false)) {
signOut(false, false); signOut(false);
} }
setIntent(null); setIntent(null);
} }
@@ -119,8 +112,9 @@ public class NavDrawerActivity extends BriarActivity implements
component.inject(this); component.inject(this);
} }
@SuppressWarnings("ConstantConditions")
@Override @Override
public void onCreate(@Nullable Bundle state) { public void onCreate(Bundle state) {
super.onCreate(state); super.onCreate(state);
exitIfStartupFailed(getIntent()); exitIfStartupFailed(getIntent());
setContentView(R.layout.activity_nav_drawer); setContentView(R.layout.activity_nav_drawer);
@@ -131,9 +125,8 @@ public class NavDrawerActivity extends BriarActivity implements
GridView transportsView = findViewById(R.id.transportsView); GridView transportsView = findViewById(R.id.transportsView);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
ActionBar actionBar = requireNonNull(getSupportActionBar()); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true);
actionBar.setHomeButtonEnabled(true);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.nav_drawer_open_description, R.string.nav_drawer_open_description,
@@ -172,8 +165,7 @@ public class NavDrawerActivity extends BriarActivity implements
} }
@Override @Override
protected void onActivityResult(int request, int result, protected void onActivityResult(int request, int result, Intent data) {
@Nullable Intent data) {
super.onActivityResult(request, result, data); super.onActivityResult(request, result, data);
if (request == REQUEST_PASSWORD && result == RESULT_OK) { if (request == REQUEST_PASSWORD && result == RESULT_OK) {
controller.shouldAskForDozeWhitelisting(this, controller.shouldAskForDozeWhitelisting(this,
@@ -261,7 +253,7 @@ public class NavDrawerActivity extends BriarActivity implements
} }
@Override @Override
public void onPostCreate(@Nullable Bundle savedInstanceState) { public void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState); super.onPostCreate(savedInstanceState);
drawerToggle.syncState(); drawerToggle.syncState();
} }
@@ -279,7 +271,7 @@ public class NavDrawerActivity extends BriarActivity implements
private void signOut() { private void signOut() {
drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED); drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED);
signOut(false, false); signOut(false);
finish(); finish();
} }

View File

@@ -2,19 +2,17 @@ package org.briarproject.briar.android.panic;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.preference.PreferenceManager; import android.support.v7.preference.PreferenceManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import info.guardianproject.GuardianProjectRSA4096; import info.guardianproject.GuardianProjectRSA4096;
@@ -22,24 +20,21 @@ import info.guardianproject.panic.Panic;
import info.guardianproject.panic.PanicResponder; import info.guardianproject.panic.PanicResponder;
import info.guardianproject.trustedintents.TrustedIntents; import info.guardianproject.trustedintents.TrustedIntents;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_LOCK; import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_LOCK;
import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_PURGE; import static org.briarproject.briar.android.panic.PanicPreferencesFragment.KEY_PURGE;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class PanicResponderActivity extends BriarActivity { public class PanicResponderActivity extends BriarActivity {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(PanicResponderActivity.class.getName()); Logger.getLogger(PanicResponderActivity.class.getName());
@Inject @Inject
protected LifecycleManager lifecycleManager; protected AccountManager accountManager;
@Inject @Inject
protected AndroidExecutor androidExecutor; protected AndroidExecutor androidExecutor;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
TrustedIntents trustedIntents = TrustedIntents.get(this); TrustedIntents trustedIntents = TrustedIntents.get(this);
@@ -69,12 +64,12 @@ public class PanicResponderActivity extends BriarActivity {
// non-destructive actions are allowed by non-connected trusted apps // non-destructive actions are allowed by non-connected trusted apps
if (sharedPref.getBoolean(KEY_LOCK, true)) { if (sharedPref.getBoolean(KEY_LOCK, true)) {
LOG.info("Signing out..."); LOG.info("Signing out...");
signOut(true, false); signOut(true);
} }
} }
} }
if (SDK_INT >= 21) { if (Build.VERSION.SDK_INT >= 21) {
finishAndRemoveTask(); finishAndRemoveTask();
} else { } else {
finish(); finish();
@@ -88,8 +83,14 @@ public class PanicResponderActivity extends BriarActivity {
private void deleteAllData() { private void deleteAllData() {
androidExecutor.runOnBackgroundThread(() -> { androidExecutor.runOnBackgroundThread(() -> {
accountManager.deleteAccount();
// TODO somehow delete/shred the database more thoroughly
PanicResponder.deleteAllAppData(PanicResponderActivity.this);
// nothing left to do after everything is deleted,
// so still sign out
LOG.info("Signing out..."); LOG.info("Signing out...");
signOut(true, true); signOut(true);
}); });
} }
} }

View File

@@ -179,16 +179,14 @@ public class GroupActivity extends
} }
@Override @Override
public void onItemReceived(GroupMessageItem item) { protected int getMaxTextLength() {
super.onItemReceived(item); return MAX_GROUP_POST_TEXT_LENGTH;
if (item instanceof JoinMessageItem) {
if (((JoinMessageItem) item).isInitial()) loadSharingContacts();
}
} }
@Override @Override
protected int getMaxTextLength() { @StringRes
return MAX_GROUP_POST_TEXT_LENGTH; protected int getItemPostedString() {
return R.string.groups_message_sent;
} }
@Override @Override

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.privategroup.creation;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@@ -13,8 +12,6 @@ import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
@@ -26,8 +23,6 @@ import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static org.briarproject.briar.android.util.UiUtils.enterPressed; import static org.briarproject.briar.android.util.UiUtils.enterPressed;
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH; import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CreateGroupFragment extends BaseFragment { public class CreateGroupFragment extends BaseFragment {
public final static String TAG = CreateGroupFragment.class.getName(); public final static String TAG = CreateGroupFragment.class.getName();
@@ -45,13 +40,8 @@ public class CreateGroupFragment extends BaseFragment {
} }
@Override @Override
public void injectFragment(ActivityComponent component) { public View onCreateView(LayoutInflater inflater, ViewGroup container,
component.inject(this); Bundle savedInstanceState) {
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_create_group, container, View v = inflater.inflate(R.layout.fragment_create_group, container,
false); false);
@@ -97,6 +87,11 @@ public class CreateGroupFragment extends BaseFragment {
listener.showSoftKeyboard(nameEntry); listener.showSoftKeyboard(nameEntry);
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public String getUniqueTag() { public String getUniqueTag() {
return TAG; return TAG;

View File

@@ -2,13 +2,10 @@ package org.briarproject.briar.android.privategroup.creation;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.sharing.BaseMessageFragment; import org.briarproject.briar.android.sharing.BaseMessageFragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CreateGroupMessageFragment extends BaseMessageFragment { public class CreateGroupMessageFragment extends BaseMessageFragment {
private final static String TAG = private final static String TAG =
@@ -31,4 +28,9 @@ public class CreateGroupMessageFragment extends BaseMessageFragment {
return TAG; return TAG;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.privategroup.creation; package org.briarproject.briar.android.privategroup.creation;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -14,7 +13,6 @@ import org.briarproject.briar.android.contactselection.SelectableContactItem;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -35,14 +33,14 @@ public class GroupInviteFragment extends ContactSelectorFragment {
} }
@Override @Override
public void injectFragment(ActivityComponent component) { public void onCreate(Bundle savedInstanceState) {
component.inject(this); super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.groups_invite_members);
} }
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void injectFragment(ActivityComponent component) {
super.onCreate(savedInstanceState); component.inject(this);
requireNonNull(getActivity()).setTitle(R.string.groups_invite_members);
} }
@Override @Override

View File

@@ -37,7 +37,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE; import static android.support.design.widget.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -58,23 +57,17 @@ public class GroupListFragment extends BaseFragment implements
private GroupListAdapter adapter; private GroupListAdapter adapter;
private Snackbar snackbar; private Snackbar snackbar;
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
controller.setGroupListListener(this);
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.groups_button); getActivity().setTitle(R.string.groups_button);
View v = inflater.inflate(R.layout.list, container, false); View v = inflater.inflate(R.layout.list, container, false);
adapter = new GroupListAdapter(getActivity(), this); adapter = new GroupListAdapter(getContext(), this);
list = v.findViewById(R.id.list); list = v.findViewById(R.id.list);
list.setEmptyImage(R.drawable.ic_empty_state_group_list); list.setEmptyImage(R.drawable.ic_empty_state_group_list);
list.setEmptyText(R.string.groups_list_empty); list.setEmptyText(R.string.groups_list_empty);
@@ -86,11 +79,17 @@ public class GroupListFragment extends BaseFragment implements
snackbar.getView().setBackgroundResource(R.color.briar_primary); snackbar.getView().setBackgroundResource(R.color.briar_primary);
snackbar.setAction(R.string.show, this); snackbar.setAction(R.string.show, this);
snackbar.setActionTextColor(ContextCompat snackbar.setActionTextColor(ContextCompat
.getColor(getActivity(), R.color.briar_button_text_positive)); .getColor(getContext(), R.color.briar_button_text_positive));
return v; return v;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
controller.setGroupListListener(this);
}
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();

View File

@@ -7,20 +7,14 @@ import android.view.MenuItem;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler; import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
import java.util.Collection; import java.util.Collection;
@@ -29,13 +23,10 @@ import javax.inject.Inject;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class GroupMemberListActivity extends BriarActivity public class GroupMemberListActivity extends BriarActivity {
implements EventListener {
@Inject @Inject
GroupMemberListController controller; GroupMemberListController controller;
@Inject
EventBus eventBus;
private MemberListAdapter adapter; private MemberListAdapter adapter;
private BriarRecyclerView list; private BriarRecyclerView list;
@@ -70,50 +61,6 @@ public class GroupMemberListActivity extends BriarActivity
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
loadMembers();
eventBus.addListener(this);
list.startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
eventBus.removeListener(this);
list.stopPeriodicUpdate();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof GroupMessageAddedEvent) {
// we can't use GroupInvitationResponseReceivedEvent, because
// a peer only becomes a member after joining the group by message
GroupMessageAddedEvent ge = (GroupMessageAddedEvent) e;
if (ge.getGroupId().equals(groupId) &&
ge.getHeader() instanceof JoinMessageHeader) {
loadMembers();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getId().equals(groupId)) {
runOnUiThreadUnlessDestroyed(
this::supportFinishAfterTransition);
}
}
// TODO ContactConnectedEvent and ContactDisconnectedEvent
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void loadMembers() {
controller.loadMembers(groupId, controller.loadMembers(groupId,
new UiResultExceptionHandler<Collection<MemberListItem>, DbException>( new UiResultExceptionHandler<Collection<MemberListItem>, DbException>(
this) { this) {
@@ -127,6 +74,24 @@ public class GroupMemberListActivity extends BriarActivity
handleDbException(exception); handleDbException(exception);
} }
}); });
list.startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
list.stopPeriodicUpdate();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
default:
return super.onOptionsItemSelected(item);
}
} }
} }

View File

@@ -75,7 +75,6 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENA
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_AUTOMATIC;
import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -112,8 +111,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in";
public static final String TOR_NETWORK = "pref_key_tor_network"; public static final String TOR_NETWORK = "pref_key_tor_network";
public static final String TOR_MOBILE = "pref_key_tor_mobile_data"; public static final String TOR_MOBILE = "pref_key_tor_mobile_data";
public static final String TOR_ONLY_WHEN_CHARGING =
"pref_key_tor_only_when_charging";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SettingsFragment.class.getName()); Logger.getLogger(SettingsFragment.class.getName());
@@ -123,7 +120,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
private ListPreference enableBluetooth; private ListPreference enableBluetooth;
private ListPreference torNetwork; private ListPreference torNetwork;
private SwitchPreference torMobile; private SwitchPreference torMobile;
private SwitchPreference torOnlyWhenCharging;
private SwitchPreference screenLock; private SwitchPreference screenLock;
private ListPreference screenLockTimeout; private ListPreference screenLockTimeout;
private SwitchPreference notifyPrivateMessages; private SwitchPreference notifyPrivateMessages;
@@ -169,8 +165,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
torNetwork = (ListPreference) findPreference(TOR_NETWORK); torNetwork = (ListPreference) findPreference(TOR_NETWORK);
torMobile = (SwitchPreference) findPreference(TOR_MOBILE); torMobile = (SwitchPreference) findPreference(TOR_MOBILE);
torOnlyWhenCharging =
(SwitchPreference) findPreference(TOR_ONLY_WHEN_CHARGING);
screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK); screenLock = (SwitchPreference) findPreference(PREF_SCREEN_LOCK);
screenLockTimeout = screenLockTimeout =
(ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT); (ListPreference) findPreference(PREF_SCREEN_LOCK_TIMEOUT);
@@ -208,7 +202,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this);
torNetwork.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this);
torMobile.setOnPreferenceChangeListener(this); torMobile.setOnPreferenceChangeListener(this);
torOnlyWhenCharging.setOnPreferenceChangeListener(this);
screenLock.setOnPreferenceChangeListener(this); screenLock.setOnPreferenceChangeListener(this);
screenLockTimeout.setOnPreferenceChangeListener(this); screenLockTimeout.setOnPreferenceChangeListener(this);
@@ -369,10 +362,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
torSettings.getBoolean(PREF_TOR_MOBILE, true); torSettings.getBoolean(PREF_TOR_MOBILE, true);
torMobile.setChecked(torMobileSetting); torMobile.setChecked(torMobileSetting);
boolean torChargingSetting =
torSettings.getBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false);
torOnlyWhenCharging.setChecked(torChargingSetting);
displayScreenLockSetting(); displayScreenLockSetting();
if (SDK_INT < 26) { if (SDK_INT < 26) {
@@ -434,7 +423,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
enableBluetooth.setEnabled(enabled); enableBluetooth.setEnabled(enabled);
torNetwork.setEnabled(enabled); torNetwork.setEnabled(enabled);
torMobile.setEnabled(enabled); torMobile.setEnabled(enabled);
torOnlyWhenCharging.setEnabled(enabled);
if (!enabled) screenLock.setEnabled(false); if (!enabled) screenLock.setEnabled(false);
notifyPrivateMessages.setEnabled(enabled); notifyPrivateMessages.setEnabled(enabled);
notifyGroupMessages.setEnabled(enabled); notifyGroupMessages.setEnabled(enabled);
@@ -531,9 +519,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
} else if (preference == torMobile) { } else if (preference == torMobile) {
boolean torMobileSetting = (Boolean) newValue; boolean torMobileSetting = (Boolean) newValue;
storeTorMobileSetting(torMobileSetting); storeTorMobileSetting(torMobileSetting);
} else if (preference == torOnlyWhenCharging) {
boolean torChargingSetting = (Boolean) newValue;
storeTorChargingSetting(torChargingSetting);
} else if (preference == screenLock) { } else if (preference == screenLock) {
Settings s = new Settings(); Settings s = new Settings();
s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue); s.putBoolean(PREF_SCREEN_LOCK, (Boolean) newValue);
@@ -600,12 +585,6 @@ public class SettingsFragment extends PreferenceFragmentCompat
mergeSettings(s, TOR_NAMESPACE); mergeSettings(s, TOR_NAMESPACE);
} }
private void storeTorChargingSetting(boolean torChargingSetting) {
Settings s = new Settings();
s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, torChargingSetting);
mergeSettings(s, TOR_NAMESPACE);
}
private void storeBluetoothSettings(boolean btSetting) { private void storeBluetoothSettings(boolean btSetting) {
Settings s = new Settings(); Settings s = new Settings();
s.putBoolean(PREF_BT_ENABLE, btSetting); s.putBoolean(PREF_BT_ENABLE, btSetting);

View File

@@ -37,7 +37,7 @@ public abstract class BaseMessageFragment extends BaseFragment
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(@Nullable LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {

View File

@@ -3,14 +3,11 @@ package org.briarproject.briar.android.sharing;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.api.blog.BlogInvitationResponse;
import org.briarproject.briar.api.blog.BlogSharingManager; import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
import java.util.Collection; import java.util.Collection;
@@ -29,19 +26,6 @@ public class BlogSharingStatusActivity extends SharingStatusActivity {
component.inject(this); component.inject(this);
} }
@Override
public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof BlogInvitationResponseReceivedEvent) {
BlogInvitationResponseReceivedEvent r =
(BlogInvitationResponseReceivedEvent) e;
BlogInvitationResponse h = r.getMessageHeader();
if (h.getShareableId().equals(getGroupId()) && h.wasAccepted()) {
loadSharedWith();
}
}
}
@Override @Override
int getInfoText() { int getInfoText() {
return R.string.sharing_status_blog; return R.string.sharing_status_blog;

View File

@@ -3,14 +3,11 @@ package org.briarproject.briar.android.sharing;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.api.forum.ForumInvitationResponse;
import org.briarproject.briar.api.forum.ForumSharingManager; import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
import java.util.Collection; import java.util.Collection;
@@ -29,19 +26,6 @@ public class ForumSharingStatusActivity extends SharingStatusActivity {
component.inject(this); component.inject(this);
} }
@Override
public void eventOccurred(Event e) {
super.eventOccurred(e);
if (e instanceof ForumInvitationResponseReceivedEvent) {
ForumInvitationResponseReceivedEvent r =
(ForumInvitationResponseReceivedEvent) e;
ForumInvitationResponse h = r.getMessageHeader();
if (h.getShareableId().equals(getGroupId()) && h.wasAccepted()) {
loadSharedWith();
}
}
}
@Override @Override
int getInfoText() { int getInfoText() {
return R.string.sharing_status_forum; return R.string.sharing_status_forum;

View File

@@ -1,18 +1,14 @@
package org.briarproject.briar.android.sharing; package org.briarproject.briar.android.sharing;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareBlogMessageFragment extends BaseMessageFragment { public class ShareBlogMessageFragment extends BaseMessageFragment {
public final static String TAG = ShareBlogMessageFragment.class.getName(); public final static String TAG = ShareBlogMessageFragment.class.getName();
@@ -22,9 +18,9 @@ public class ShareBlogMessageFragment extends BaseMessageFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Nullable ViewGroup container, Bundle savedInstanceState) {
@Nullable Bundle savedInstanceState) {
setTitle(R.string.blogs_sharing_share); setTitle(R.string.blogs_sharing_share);
return super.onCreateView(inflater, container, savedInstanceState); return super.onCreateView(inflater, container, savedInstanceState);
} }
@@ -41,6 +37,11 @@ public class ShareBlogMessageFragment extends BaseMessageFragment {
return R.string.forum_share_message; return R.string.forum_share_message;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public String getUniqueTag() { public String getUniqueTag() {
return TAG; return TAG;

View File

@@ -1,18 +1,14 @@
package org.briarproject.briar.android.sharing; package org.briarproject.briar.android.sharing;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ShareForumMessageFragment extends BaseMessageFragment { public class ShareForumMessageFragment extends BaseMessageFragment {
public final static String TAG = ShareForumMessageFragment.class.getName(); public final static String TAG = ShareForumMessageFragment.class.getName();
@@ -22,9 +18,8 @@ public class ShareForumMessageFragment extends BaseMessageFragment {
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Nullable ViewGroup container, Bundle savedInstanceState) {
@Nullable Bundle savedInstanceState) {
setTitle(R.string.forum_share_button); setTitle(R.string.forum_share_button);
return super.onCreateView(inflater, container, savedInstanceState); return super.onCreateView(inflater, container, savedInstanceState);
@@ -42,6 +37,11 @@ public class ShareForumMessageFragment extends BaseMessageFragment {
return R.string.forum_share_message; return R.string.forum_share_message;
} }
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override @Override
public String getUniqueTag() { public String getUniqueTag() {
return TAG; return TAG;

View File

@@ -2,7 +2,6 @@ package org.briarproject.briar.android.sharing;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.view.MenuItem; import android.view.MenuItem;
@@ -11,19 +10,14 @@ import android.widget.TextView;
import org.briarproject.bramble.api.contact.Contact; import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry; import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.contact.ContactItem; import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.sharing.event.ContactLeftShareableEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -38,13 +32,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
abstract class SharingStatusActivity extends BriarActivity abstract class SharingStatusActivity extends BriarActivity {
implements EventListener {
@Inject @Inject
ConnectionRegistry connectionRegistry; ConnectionRegistry connectionRegistry;
@Inject
EventBus eventBus;
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(SharingStatusActivity.class.getName()); Logger.getLogger(SharingStatusActivity.class.getName());
@@ -77,7 +68,6 @@ abstract class SharingStatusActivity extends BriarActivity
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
eventBus.addListener(this);
loadSharedWith(); loadSharedWith();
} }
@@ -85,28 +75,9 @@ abstract class SharingStatusActivity extends BriarActivity
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
adapter.clear(); adapter.clear();
eventBus.removeListener(this);
list.showProgressBar(); list.showProgressBar();
} }
@Override
@CallSuper
public void eventOccurred(Event e) {
if (e instanceof ContactLeftShareableEvent) {
ContactLeftShareableEvent c = (ContactLeftShareableEvent) e;
if (c.getGroupId().equals(getGroupId())) {
loadSharedWith();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getId().equals(getGroupId())) {
runOnUiThreadUnlessDestroyed(
this::supportFinishAfterTransition);
}
}
// TODO ContactConnectedEvent and ContactDisconnectedEvent
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items // Handle presses on the action bar items
@@ -129,7 +100,7 @@ abstract class SharingStatusActivity extends BriarActivity
return groupId; return groupId;
} }
protected void loadSharedWith() { private void loadSharedWith() {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
List<ContactItem> contactItems = new ArrayList<>(); List<ContactItem> contactItems = new ArrayList<>();
@@ -147,7 +118,6 @@ abstract class SharingStatusActivity extends BriarActivity
private void displaySharedWith(List<ContactItem> contacts) { private void displaySharedWith(List<ContactItem> contacts) {
runOnUiThreadUnlessDestroyed(() -> { runOnUiThreadUnlessDestroyed(() -> {
adapter.clear();
if (contacts.isEmpty()) list.showData(); if (contacts.isEmpty()) list.showData();
else adapter.addAll(contacts); else adapter.addAll(contacts);
}); });

View File

@@ -51,6 +51,7 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
} else if (!item.isRead()) { } else if (!item.isRead()) {
layout.setActivated(true); layout.setActivated(true);
animateFadeOut(); animateFadeOut();
listener.onUnreadItemVisible(item);
} else { } else {
layout.setActivated(false); layout.setActivated(false);
} }

View File

@@ -10,7 +10,6 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ItemReturningAdapter;
import org.briarproject.briar.android.util.VersionedAdapter; import org.briarproject.briar.android.util.VersionedAdapter;
import java.util.Collection; import java.util.Collection;
@@ -22,7 +21,7 @@ import static android.support.v7.widget.RecyclerView.NO_POSITION;
@UiThread @UiThread
public class ThreadItemAdapter<I extends ThreadItem> public class ThreadItemAdapter<I extends ThreadItem>
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>> extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
implements VersionedAdapter, ItemReturningAdapter<I> { implements VersionedAdapter {
static final int UNDEFINED = -1; static final int UNDEFINED = -1;
@@ -137,6 +136,30 @@ public class ThreadItemAdapter<I extends ThreadItem>
return null; return null;
} }
/**
* Gets the number of unread items above and below the current view port.
*
* Attention: Do not call this when the list is still scrolling,
* because then the view port is unknown.
*/
public UnreadCount getUnreadCount() {
int positionTop = layoutManager.findFirstVisibleItemPosition();
int positionBottom = layoutManager.findLastVisibleItemPosition();
if (positionTop == NO_POSITION && positionBottom == NO_POSITION)
return new UnreadCount(0, 0);
int unreadCounterTop = 0, unreadCounterBottom = 0;
for (int i = 0; i < items.size(); i++) {
I item = items.get(i);
if (i < positionTop && !item.isRead()) {
unreadCounterTop++;
} else if (i > positionBottom && !item.isRead()) {
unreadCounterBottom++;
}
}
return new UnreadCount(unreadCounterTop, unreadCounterBottom);
}
/** /**
* Returns the position of the first unread item below the current viewport * Returns the position of the first unread item below the current viewport
*/ */
@@ -165,7 +188,20 @@ public class ThreadItemAdapter<I extends ThreadItem>
return NO_POSITION; return NO_POSITION;
} }
static class UnreadCount {
final int top, bottom;
private UnreadCount(int top, int bottom) {
this.top = top;
this.bottom = bottom;
}
}
public interface ThreadItemListener<I> { public interface ThreadItemListener<I> {
void onUnreadItemVisible(I item);
void onReplyClick(I item); void onReplyClick(I item);
} }

View File

@@ -3,13 +3,13 @@ package org.briarproject.briar.android.threaded;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.CallSuper; import android.support.annotation.CallSuper;
import android.support.annotation.StringRes; import android.support.annotation.StringRes;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
@@ -43,7 +43,10 @@ import javax.inject.Inject;
import static android.support.design.widget.Snackbar.make; import static android.support.design.widget.Snackbar.make;
import static android.support.v7.widget.RecyclerView.NO_POSITION; import static android.support.v7.widget.RecyclerView.NO_POSITION;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCount;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -58,13 +61,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
Logger.getLogger(ThreadListActivity.class.getName()); Logger.getLogger(ThreadListActivity.class.getName());
protected A adapter; protected A adapter;
private ThreadScrollListener<I> scrollListener;
protected BriarRecyclerView list; protected BriarRecyclerView list;
private LinearLayoutManager layoutManager; private LinearLayoutManager layoutManager;
protected TextInputView textInput; protected TextInputView textInput;
protected GroupId groupId; protected GroupId groupId;
@Nullable private UnreadMessageButton upButton, downButton;
private Parcelable layoutManagerState;
@Nullable @Nullable
private MessageId replyId; private MessageId replyId;
@@ -75,6 +76,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@CallSuper @CallSuper
@Override @Override
@SuppressWarnings("ConstantConditions")
public void onCreate(@Nullable Bundle state) { public void onCreate(@Nullable Bundle state) {
super.onCreate(state); super.onCreate(state);
@@ -91,18 +93,37 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
new TextSendController(textInput, this, false); new TextSendController(textInput, this, false);
textInput.setSendController(sendController); textInput.setSendController(sendController);
textInput.setMaxTextLength(getMaxTextLength()); textInput.setMaxTextLength(getMaxTextLength());
UnreadMessageButton upButton = findViewById(R.id.upButton);
UnreadMessageButton downButton = findViewById(R.id.downButton);
list = findViewById(R.id.list); list = findViewById(R.id.list);
layoutManager = new LinearLayoutManager(this); layoutManager = new LinearLayoutManager(this);
// FIXME pre-fetching messes with read state, find better solution #1289
layoutManager.setItemPrefetchEnabled(false);
list.setLayoutManager(layoutManager); list.setLayoutManager(layoutManager);
adapter = createAdapter(layoutManager); adapter = createAdapter(layoutManager);
list.setAdapter(adapter); list.setAdapter(adapter);
scrollListener = new ThreadScrollListener<>(adapter, getController(),
upButton, downButton);
list.getRecyclerView().addOnScrollListener(scrollListener);
list.getRecyclerView().addOnScrollListener(
new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx,
int dy) {
super.onScrolled(recyclerView, dx, dy);
if (dx == 0 && dy == 0) {
// scrollToPosition has been called and finished
updateUnreadCount();
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == SCROLL_STATE_IDLE) {
updateUnreadCount();
}
}
});
upButton = findViewById(R.id.upButton);
downButton = findViewById(R.id.downButton);
upButton.setOnClickListener(v -> { upButton.setOnClickListener(v -> {
int position = adapter.getVisibleUnreadPosTop(); int position = adapter.getVisibleUnreadPosTop();
if (position != NO_POSITION) { if (position != NO_POSITION) {
@@ -190,12 +211,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
MessageId messageId = items.getFirstVisibleItemId(); MessageId messageId = items.getFirstVisibleItemId();
if (messageId != null) if (messageId != null)
adapter.setItemWithIdVisible(messageId); adapter.setItemWithIdVisible(messageId);
updateUnreadCount();
list.showData(); list.showData();
if (layoutManagerState == null) {
list.scrollToPosition(0); // Scroll to the top
} else {
layoutManager.onRestoreInstanceState(layoutManagerState);
}
} }
protected void loadSharingContacts() { protected void loadSharingContacts() {
@@ -236,21 +253,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (layoutManager != null) {
layoutManagerState = layoutManager.onSaveInstanceState();
outState.putParcelable("layoutManager", layoutManagerState);
}
if (replyId != null) { if (replyId != null) {
outState.putByteArray(KEY_REPLY_ID, replyId.getBytes()); outState.putByteArray(KEY_REPLY_ID, replyId.getBytes());
} }
} }
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
layoutManagerState = savedInstanceState.getParcelable("layoutManager");
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@@ -273,6 +280,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
} }
} }
@Override
public void onUnreadItemVisible(I item) {
if (!item.isRead()) {
item.setRead(true);
getController().markItemRead(item);
}
}
@Override @Override
public void onReplyClick(I item) { public void onReplyClick(I item) {
replyId = item.getId(); replyId = item.getId();
@@ -386,10 +401,24 @@ public abstract class ThreadListActivity<G extends NamedGroup, I extends ThreadI
adapter.add(item); adapter.add(item);
if (isLocal) { if (isLocal) {
displaySnackbar(getItemPostedString());
scrollToItemAtTop(item); scrollToItemAtTop(item);
} else { } else {
scrollListener.updateUnreadButtons(layoutManager); updateUnreadCount();
} }
} }
private void updateUnreadCount() {
UnreadCount unreadCount = adapter.getUnreadCount();
if (LOG.isLoggable(INFO)) {
LOG.info("Updating unread count: top=" + unreadCount.top +
" bottom=" + unreadCount.bottom);
}
upButton.setUnreadCount(unreadCount.top);
downButton.setUnreadCount(unreadCount.bottom);
}
@StringRes
protected abstract int getItemPostedString();
} }

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