Compare commits

..

2 Commits

316 changed files with 2793 additions and 8960 deletions

View File

@@ -28,20 +28,6 @@
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>

View File

@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-android" />
<module name="briar-android" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-android" />
<module name="bramble-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-android" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-api" />
<module name="bramble-api" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,6 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-java" />
<module name="bramble-java" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
@@ -8,8 +10,11 @@
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-android" />
<module name="briar-android" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-core" />
<module name="briar-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="package" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-headless" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.briar-headless" />
<module name="briar-headless" />
<option name="PACKAGE_NAME" value="org.briarproject.briar.headless" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.H2DatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,14 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
<module name="briar.bramble-core" />
<module name="bramble-core" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.db" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.db.HyperSqlDatabasePerformanceTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea" />
<option name="PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
<option name="PASS_PARENT_ENVS" value="true" />
<option name="TEST_SEARCH_SCOPE">
<value defaultName="singleModule" />
</option>
<patterns />
<method />
</configuration>
</component>

View File

@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="briar-headless" type="JetRunConfigurationType" singleton="true">
<module name="briar.briar-headless" />
<configuration default="false" name="briar-headless" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="-v" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
@@ -8,8 +8,9 @@
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.briar.headless.MainKt" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Make" enabled="true" />
<module name="briar-headless" />
<envs />
<method>
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jar" externalProjectPath="$PROJECT_DIR$/briar-headless" vmOptions="" scriptParameters="" />
</method>
</configuration>

View File

@@ -3,4 +3,3 @@ gen
build
.settings
src/main/res/raw/*.zip
src/main/jniLibs

View File

@@ -5,14 +5,14 @@ apply plugin: 'witness'
apply from: 'witness.gradle'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion 29
buildToolsVersion '29.0.2'
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
minSdkVersion 16
targetSdkVersion 28
versionCode 10209
versionName "1.2.9"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -38,7 +38,7 @@ configurations {
dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:tor-android:0.3.5.10@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
@@ -53,12 +53,10 @@ dependencies {
}
def torBinariesDir = 'src/main/res/raw'
def torLibsDir = 'src/main/jniLibs'
task cleanTorBinaries {
doLast {
delete fileTree(torBinariesDir) { include '*.zip' }
delete fileTree(torLibsDir) { include '**/*.so' }
}
}
@@ -69,36 +67,8 @@ task unpackTorBinaries {
copy {
from configurations.tor.collect { zipTree(it) }
into torBinariesDir
include 'geoip.zip'
}
configurations.tor.each { outer ->
zipTree(outer).each { inner ->
if (inner.name.endsWith('_arm_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'armeabi-v7a/lib$1.so'
}
} else if (inner.name.endsWith('_arm64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'arm64-v8a/lib$1.so'
}
} else if (inner.name.endsWith('_x86_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86/lib$1.so'
}
} else if (inner.name.endsWith('_x86_64_pie.zip')) {
copy {
from zipTree(inner)
into torLibsDir
rename '(.*)', 'x86_64/lib$1.so'
}
}
}
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
include 'geoip.zip', '*_pie.zip'
}
}
dependsOn cleanTorBinaries
@@ -106,6 +76,5 @@ task unpackTorBinaries {
tasks.withType(MergeResources) {
inputs.dir torBinariesDir
inputs.dir torLibsDir
dependsOn unpackTorBinaries
}

View File

@@ -71,6 +71,7 @@ class AndroidBluetoothPlugin
private final Application app;
private final Clock clock;
private volatile boolean wasEnabledByUs = false;
private volatile BluetoothStateReceiver receiver = null;
// Non-null if the plugin started successfully
@@ -132,10 +133,41 @@ class AndroidBluetoothPlugin
return adapter != null && adapter.isEnabled();
}
@Override
void enableAdapter() {
if (adapter != null && !adapter.isEnabled()) {
if (adapter.enable()) {
LOG.info("Enabling Bluetooth");
wasEnabledByUs = true;
} else {
LOG.info("Could not enable Bluetooth");
}
}
}
@Override
void disableAdapterIfEnabledByUs() {
if (isAdapterEnabled() && wasEnabledByUs) {
if (adapter.disable()) LOG.info("Disabling Bluetooth");
else LOG.info("Could not disable Bluetooth");
wasEnabledByUs = false;
}
}
@Override
void setEnabledByUs() {
wasEnabledByUs = true;
}
@Override
void onAdapterDisabled() {
super.onAdapterDisabled();
wasEnabledByUs = false;
}
@Override
@Nullable
String getBluetoothAddress() {
if (adapter == null) return null;
String address = AndroidUtils.getBluetoothAddress(app, adapter);
return address.isEmpty() ? null : address;
}

View File

@@ -16,42 +16,19 @@ import org.briarproject.bramble.api.system.AndroidWakeLockManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.util.AndroidUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.net.SocketFactory;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AndroidTorPlugin extends TorPlugin {
private static final List<String> LIBRARY_ARCHITECTURES =
asList("armeabi-v7a", "arm64-v8a", "x86", "x86_64");
private static final String TOR_LIB_NAME = "libtor.so";
private static final String OBFS4_LIB_NAME = "libobfs4proxy.so";
private static final Logger LOG =
getLogger(AndroidTorPlugin.class.getName());
private final Application app;
private final AndroidWakeLock wakeLock;
private final File torLib, obfs4Lib;
AndroidTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
@@ -78,9 +55,6 @@ class AndroidTorPlugin extends TorPlugin {
maxIdleTime, torDirectory);
this.app = app;
wakeLock = wakeLockManager.createWakeLock("TorPlugin");
String nativeLibDir = app.getApplicationInfo().nativeLibraryDir;
torLib = new File(nativeLibDir, TOR_LIB_NAME);
obfs4Lib = new File(nativeLibDir, OBFS4_LIB_NAME);
}
@Override
@@ -111,112 +85,4 @@ class AndroidTorPlugin extends TorPlugin {
super.stop();
wakeLock.release();
}
@Override
protected File getTorExecutableFile() {
return torLib.exists() ? torLib : super.getTorExecutableFile();
}
@Override
protected File getObfs4ExecutableFile() {
return obfs4Lib.exists() ? obfs4Lib : super.getObfs4ExecutableFile();
}
@Override
protected void installTorExecutable() throws IOException {
File extracted = super.getTorExecutableFile();
if (torLib.exists()) {
// If an older version left behind a Tor binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted Tor binary");
else LOG.info("Failed to delete Tor binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(TOR_LIB_NAME, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(torLib.getAbsolutePath());
}
}
@Override
protected void installObfs4Executable() throws IOException {
File extracted = super.getObfs4ExecutableFile();
if (obfs4Lib.exists()) {
// If an older version left behind an obfs4 binary, delete it
if (extracted.exists()) {
if (extracted.delete()) LOG.info("Deleted obfs4 binary");
else LOG.info("Failed to delete obfs4 binary");
}
} else if (SDK_INT < 29) {
// The binary wasn't extracted at install time. Try to extract it
extractLibraryFromApk(OBFS4_LIB_NAME, extracted);
} else {
// No point extracting the binary, we won't be allowed to execute it
throw new FileNotFoundException(obfs4Lib.getAbsolutePath());
}
}
private void extractLibraryFromApk(String libName, File dest)
throws IOException {
File sourceDir = new File(app.getApplicationInfo().sourceDir);
if (sourceDir.isFile()) {
// Look for other APK files in the same directory, if we're allowed
File parent = sourceDir.getParentFile();
if (parent != null) sourceDir = parent;
}
List<String> libPaths = getSupportedLibraryPaths(libName);
for (File apk : findApkFiles(sourceDir)) {
ZipInputStream zin = new ZipInputStream(new FileInputStream(apk));
for (ZipEntry e = zin.getNextEntry(); e != null;
e = zin.getNextEntry()) {
if (libPaths.contains(e.getName())) {
if (LOG.isLoggable(INFO)) {
LOG.info("Extracting " + e.getName()
+ " from " + apk.getAbsolutePath());
}
extract(zin, dest); // Zip input stream will be closed
return;
}
}
zin.close();
}
throw new FileNotFoundException(libName);
}
/**
* Returns all files with the extension .apk or .APK under the given root.
*/
private List<File> findApkFiles(File root) {
List<File> files = new ArrayList<>();
findApkFiles(root, files);
return files;
}
private void findApkFiles(File f, List<File> files) {
if (f.isFile() && f.getName().toLowerCase().endsWith(".apk")) {
files.add(f);
} else if (f.isDirectory()) {
File[] children = f.listFiles();
if (children != null) {
for (File child : children) findApkFiles(child, files);
}
}
}
/**
* Returns the paths at which libraries with the given name would be found
* inside an APK file, for all architectures supported by the device, in
* order of preference.
*/
private List<String> getSupportedLibraryPaths(String libName) {
List<String> architectures = new ArrayList<>();
for (String abi : AndroidUtils.getSupportedArchitectures()) {
if (LIBRARY_ARCHITECTURES.contains(abi)) {
architectures.add("lib/" + abi + "/" + libName);
}
}
return architectures;
}
}

View File

@@ -1,36 +1,33 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'com.android.tools.analytics-library:protos:27.1.1:protos-27.1.1.jar:13f77e73762e58ab372d140b3a6be6903aea9775b62dd14fbc62d4cc7069c9a4',
'com.android.tools.analytics-library:shared:27.1.1:shared-27.1.1.jar:82930a52001410e97d809930b670f4de3002286975f046b9de5f6b777b06d366',
'com.android.tools.analytics-library:tracker:27.1.1:tracker-27.1.1.jar:31bc5a00be0055bac89c9b2f34751883e987cd89e3ac1783720645c164f591d9',
'com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524:aapt2-proto-4.1.0-alpha01-6193524.jar:17e75523e1e92dd4f222c7368ee41df9e964a508232f591e265d0c499baf9dca',
'com.android.tools.build:apksig:4.1.1:apksig-4.1.1.jar:e0a69da9e5a03986d608b45bbf954ef0e6a0b3f58c1b8315bd169ec08b279e72',
'com.android.tools.build:apkzlib:4.1.1:apkzlib-4.1.1.jar:ba4b5e419b6be0130eae7f8301c3a551ad3976f487d2e0c6852ebb175ac41127',
'com.android.tools.build:builder-model:4.1.1:builder-model-4.1.1.jar:e95c99cc298ad67b8deb6ced99c51abc8f59afebedad044b1a10dde14646a4dd',
'com.android.tools.build:builder-test-api:4.1.1:builder-test-api-4.1.1.jar:464f596ab261c051c3847406748e843770dea123f6fa5fee8a9390644e709b7a',
'com.android.tools.build:builder:4.1.1:builder-4.1.1.jar:0f78d4759d2f7b57b95865522ec34596ba419b9982f3b25e3449213f9c98b80d',
'com.android.tools.build:gradle-api:4.1.1:gradle-api-4.1.1.jar:d42e6b539e4c1353ad3546e75ec8ce11a017b97481023e8ea18577eefe374358',
'com.android.tools.build:manifest-merger:27.1.1:manifest-merger-27.1.1.jar:7a45fa143687859bb2e5a961dcf6ee88094d3853de0cb543dc03dbcb0f4b554b',
'com.android.tools.ddms:ddmlib:27.1.1:ddmlib-27.1.1.jar:da6e4bd834b6a85dae8019039849d8bd96933347dfbf460df74913ddade6e40a',
'com.android.tools.external.com-intellij:intellij-core:27.1.1:intellij-core-27.1.1.jar:2591a7363c4443c59bf9f793730acafce9d6ec3076e2f46716edaf53a41b6fb6',
'com.android.tools.external.com-intellij:kotlin-compiler:27.1.1:kotlin-compiler-27.1.1.jar:5054ae770ba788f110303c65abd6b1fa28eccf52dee1274510e201b2b81885c8',
'com.android.tools.external.org-jetbrains:uast:27.1.1:uast-27.1.1.jar:54cd8f6886a9d2f5641659dd5c91f626629672cd48301f7f0bd6aad9bd448714',
'com.android.tools.layoutlib:layoutlib-api:27.1.1:layoutlib-api-27.1.1.jar:8a9a22e3b309521ea83b724e5a89cfdac6076f52d675c0e17d77b05527bc0f8c',
'com.android.tools.lint:lint-api:27.1.1:lint-api-27.1.1.jar:c1d8176094cb0478786070d40533efb578ebc53529a82f6ef5bee879bdca418b',
'com.android.tools.lint:lint-checks:27.1.1:lint-checks-27.1.1.jar:3899c91e00bd059b40c31a9ca00cd0f8303191947608735ae1b657323693fb61',
'com.android.tools.lint:lint-gradle-api:27.1.1:lint-gradle-api-27.1.1.jar:26aa89d38b9825cc73229daa82a68875801c8b8491f30497ce62aff1f206eb0d',
'com.android.tools.lint:lint-gradle:27.1.1:lint-gradle-27.1.1.jar:f7355823ead869f4d28184ba28b7a0c693b507519a2d3705bb9848a0f35b3756',
'com.android.tools.lint:lint-model:27.1.1:lint-model-27.1.1.jar:bc23c0c413bdfca59dac2cd56b870d8360d009e9ec0d365e71f774bcf127971d',
'com.android.tools.lint:lint:27.1.1:lint-27.1.1.jar:2f6038a5398a42bd591883c3f5e5894f4ec52ca1c3683bf94fa8553c1700af81',
'com.android.tools:annotations:27.1.1:annotations-27.1.1.jar:ff28c504d2acb9fd1a5ffbd97ae85cf59ee18c76927525aad250509bccf2cab1',
'com.android.tools:common:27.1.1:common-27.1.1.jar:63d9a2a9ad6d278db319f3749b9f50bdf5457ef7020074a1bebe124e714b535c',
'com.android.tools:dvlib:27.1.1:dvlib-27.1.1.jar:998a54201fc1cefee5f2399215e95c42b1f64f9e1d8f4452eb8255c68ba5440f',
'com.android.tools:repository:27.1.1:repository-27.1.1.jar:d25b74ccabf4d876903efb375e9af6fb380d8ae0445bb74bbdcc225c1e37fa1d',
'com.android.tools:sdk-common:27.1.1:sdk-common-27.1.1.jar:4473ae97d0ef7061ee1de61041d5aa97405ae08e44c09cf7bb278b42e4b97c7c',
'com.android.tools:sdklib:27.1.1:sdklib-27.1.1.jar:08e6b83961ac9724b3c1e3d0eff971f13be6701292c77914b8794480f3391250',
'com.android:signflinger:4.1.1:signflinger-4.1.1.jar:0c66825988873ec2d51057fa463f54a8f18fc7326ff4530b9da363b71e97ce60',
'com.android:zipflinger:4.1.1:zipflinger-4.1.1.jar:0a8c3e52ac13dd031236f9fb5ba4408b1d5dcd12325a05440b36da09d8881446',
'com.android.tools.analytics-library:protos:26.5.1:protos-26.5.1.jar:8dde1130725461fe827f2a343d353f2b51e8870661fc860d7d5ebddb097ead4e',
'com.android.tools.analytics-library:shared:26.5.1:shared-26.5.1.jar:ccc2f3b00ec17b11401610ba68553544fc8fc517120e84439ac6eb86b875e18d',
'com.android.tools.analytics-library:tracker:26.5.1:tracker-26.5.1.jar:3a76984c0fe2e847ca7a8b35b4780ef0447a9d1666946cb8e60466318e0ab5ae',
'com.android.tools.build:aapt2-proto:0.4.0:aapt2-proto-0.4.0.jar:fac0435e08898f89eeeb9ca236bea707155ff816c12205ced285ad53604133ca',
'com.android.tools.build:apksig:3.5.1:apksig-3.5.1.jar:1fd33e7f009a2a0da766cfeec4211a09f548034b015c289a66d75dd8a9302f4a',
'com.android.tools.build:apkzlib:3.5.1:apkzlib-3.5.1.jar:9f330167cbe973b7db407692f74f4f6453b7ffa5f2048934b06280c2ceee60fa',
'com.android.tools.build:builder-model:3.5.1:builder-model-3.5.1.jar:39ea3c82b76b6e0c9f9fa88d93e0edc1dd4a0f1dfae0ef6fbf2d451da47e5450',
'com.android.tools.build:builder-test-api:3.5.1:builder-test-api-3.5.1.jar:a1b59305584cbcaa078fdc9cfb80871012755b822dd32e8da19add6f7bbcb762',
'com.android.tools.build:builder:3.5.1:builder-3.5.1.jar:e3a8d382434c5f60990730c4719fc814e85a898a33a1e96c1df8d627d3c6eea6',
'com.android.tools.build:gradle-api:3.5.1:gradle-api-3.5.1.jar:be9b41859bace11998f66b04ed944f87e413f3ad6da3c4665587699da125addc',
'com.android.tools.build:manifest-merger:26.5.1:manifest-merger-26.5.1.jar:dcad9ecb967251f4d750f55a4204a2b400e8fbfe5cb930a1d0d5dbe10ae8bdfc',
'com.android.tools.ddms:ddmlib:26.5.1:ddmlib-26.5.1.jar:b081aef2a4ed3f4d47cae4cdb128469735f25a114e026d37123bf9ffdec742a8',
'com.android.tools.external.com-intellij:intellij-core:26.5.1:intellij-core-26.5.1.jar:20eced30adc124805bd93488d9cd9d3e33e6bf7b48e9fe5a703d4983f894d450',
'com.android.tools.external.com-intellij:kotlin-compiler:26.5.1:kotlin-compiler-26.5.1.jar:5aed762dd54875b77ae7018d97c05756ff0c5b9fd02ec595dd396ccd14cc22cb',
'com.android.tools.external.org-jetbrains:uast:26.5.1:uast-26.5.1.jar:4bc8653d6c0943f40fee963a149e36c6baa45683d2530968a13f5007e3c40740',
'com.android.tools.layoutlib:layoutlib-api:26.5.1:layoutlib-api-26.5.1.jar:88732f11396c427273e515d23042e35633f4fe4295528a99b866aa2adf0efd9c',
'com.android.tools.lint:lint-api:26.5.1:lint-api-26.5.1.jar:ec33fcd72bfaf70dd841e03fbfd93f109c2e575aec146067c606689c3972f0de',
'com.android.tools.lint:lint-checks:26.5.1:lint-checks-26.5.1.jar:a1b9607d484aaae7a71dcecdc76f8003d8239af226c776894a2cf63f9e6c60d7',
'com.android.tools.lint:lint-gradle-api:26.5.1:lint-gradle-api-26.5.1.jar:82453fd98a8394cc84ed995c04d2cd744abd1d6589403427ba7eef53115406f3',
'com.android.tools.lint:lint-gradle:26.5.1:lint-gradle-26.5.1.jar:59465b56cf7db77c656d5f8195d721c3d48b6bdd0502d774de335bfe4baff00b',
'com.android.tools.lint:lint:26.5.1:lint-26.5.1.jar:336e4b04ec6f8b0f25879131b7a7862d77df83a1879ee5b71be26128755f8e2e',
'com.android.tools:annotations:26.5.1:annotations-26.5.1.jar:2c43c82f8c59d8f7a61e3239e1a2dc9f69dc342ec09af9b7c9f69b25337c0b6e',
'com.android.tools:common:26.5.1:common-26.5.1.jar:eccfa54486ed54c4e3123cc42195d023bd0dd21bcd2f0e4868e8c6fc70f8ef6b',
'com.android.tools:dvlib:26.5.1:dvlib-26.5.1.jar:46f93ad498b4756e7d867d2fe38c38890a80e7407a4ae459e4a8c8d5c5aeacfe',
'com.android.tools:repository:26.5.1:repository-26.5.1.jar:2b3ee791aa4c3e8ce60498c161a27ca7228816fc630eed4d9f25f2f36a106dce',
'com.android.tools:sdk-common:26.5.1:sdk-common-26.5.1.jar:365f749676c3574676fd465177c8a492f340816db2b520d6ed114d3b6e77bea7',
'com.android.tools:sdklib:26.5.1:sdklib-26.5.1.jar:007da104afb27c8c682a1628023fe9ec438249c8d15ef0fd6624c5bb8e23b696',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
@@ -38,30 +35,27 @@ dependencyVerification {
'com.google.dagger:dagger-spi:2.24:dagger-spi-2.24.jar:c038445d14dbcb4054e61bf49e05009edf26fce4fdc7ec1a9db544784f68e718',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64',
'com.google.errorprone:error_prone_annotations:2.2.0:error_prone_annotations-2.2.0.jar:6ebd22ca1b9d8ec06d41de8d64e0596981d9607b42035f9ed374f9de271a481a',
'com.google.errorprone:error_prone_annotations:2.3.2:error_prone_annotations-2.3.2.jar:357cd6cfb067c969226c442451502aee13800a24e950fdfde77bcdb4565a668d',
'com.google.errorprone:javac-shaded:9-dev-r4023-3:javac-shaded-9-dev-r4023-3.jar:65bfccf60986c47fbc17c9ebab0be626afc41741e0a6ec7109e0768817a36f30',
'com.google.googlejavaformat:google-java-format:1.5:google-java-format-1.5.jar:aa19ad7850fb85178aa22f2fddb163b84d6ce4d0035872f30d4408195ca1144e',
'com.google.guava:failureaccess:1.0.1:failureaccess-1.0.1.jar:a171ee4c734dd2da837e4b16be9df4661afab72a41adaf31eb84dfdaf936ca26',
'com.google.guava:guava:27.0.1-jre:guava-27.0.1-jre.jar:e1c814fd04492a27c38e0317eabeaa1b3e950ec8010239e400fe90ad6c9107b4',
'com.google.guava:guava:27.1-jre:guava-27.1-jre.jar:4a5aa70cc968a4d137e599ad37553e5cfeed2265e8c193476d7119036c536fe7',
'com.google.guava:guava:28.1-jre:guava-28.1-jre.jar:30beb8b8527bd07c6e747e77f1a92122c2f29d57ce347461a4a55eb26e382da4',
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.google.j2objc:j2objc-annotations:1.3:j2objc-annotations-1.3.jar:21af30c92267bd6122c0e0b4d20cccb6641a37eaf956c6540ec471d584e64a7b',
'com.google.jimfs:jimfs:1.1:jimfs-1.1.jar:c4828e28d7c0a930af9387510b3bada7daa5c04d7c25a75c7b8b081f1c257ddd',
'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
'com.google.protobuf:protobuf-java:3.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
'com.googlecode.json-simple:json-simple:1.1:json-simple-1.1.jar:2d9484f4c649f708f47f9a479465fc729770ee65617dca3011836602264f6439',
'com.squareup:javapoet:1.11.1:javapoet-1.11.1.jar:9cbf2107be499ec6e95afd36b58e3ca122a24166cdd375732e51267d64058e90',
'com.squareup:javawriter:2.5.0:javawriter-2.5.0.jar:fcfb09fb0ea0aa97d3cfe7ea792398081348e468f126b3603cb3803f240197f0',
'com.sun.activation:javax.activation:1.2.0:javax.activation-1.2.0.jar:993302b16cd7056f21e779cc577d175a810bb4900ef73cd8fbf2b50f928ba9ce',
'com.sun.istack:istack-commons-runtime:3.0.7:istack-commons-runtime-3.0.7.jar:6443e10ba2e259fb821d9b6becf10db5316285fc30c53cec9d7b19a3877e7fdf',
'com.sun.xml.fastinfoset:FastInfoset:1.2.15:FastInfoset-1.2.15.jar:785861db11ca1bd0d1956682b974ad73eb19cd3e01a4b3fa82d62eca97210aec',
'com.sun.istack:istack-commons-runtime:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
'javax.activation:javax.activation-api:1.2.0:javax.activation-api-1.2.0.jar:43fdef0b5b6ceb31b0424b208b930c74ab58fac2ceeb7b3f6fd3aeb8b5ca4393',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'javax.xml.bind:jaxb-api:2.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
@@ -76,35 +70,34 @@ dependencyVerification {
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.11-2:obfs4proxy-android-0.0.11-2.zip:57e55cbe87aa2aac210fdbb6cd8cdeafe15f825406a08ebf77a8b787aa2c6a8a',
'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
'org.briarproject:tor-android:0.3.5.10:tor-android-0.3.5.10.zip:edd83bf557fcff2105eaa0bdb3f607a6852ebe7360920929ae3039dd5f4774c5',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',
'org.codehaus.groovy:groovy-all:2.4.15:groovy-all-2.4.15.jar:51d6c4e71782e85674239189499854359d380fb75e1a703756e3aaa5b98a5af0',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.glassfish.jaxb:jaxb-core:2.2.11:jaxb-core-2.2.11.jar:37bcaee8ebb04362c8352a5bf6221b86967ecdab5164c696b10b9a2bb587b2aa',
'org.glassfish.jaxb:jaxb-runtime:2.2.11:jaxb-runtime-2.2.11.jar:a874f2351cfba8e2946be3002d10c18a6da8f21b52ba2acf52f2b85d5520ed70',
'org.glassfish.jaxb:txw2:2.2.11:txw2-2.2.11.jar:272a3ccad45a4511351920cd2a8633c53cab8d5220c7a92954da5526bb5eafea',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72:kotlin-stdlib-jdk8-1.3.72.jar:133da70cfc07b56094282eac5c59bccd59f167ee2ead22e5282876d8bc10bf95',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.kotlin:kotlin-reflect:1.3.50:kotlin-reflect-1.3.50.jar:64583199ea5a54aefd1bd1595288925f784226ee562d1dd279011c6075b3d7a4',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50:kotlin-stdlib-common-1.3.50.jar:8ce678e88e4ba018b66dacecf952471e4d7dfee156a8a819760a5a5ff29d323c',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50:kotlin-stdlib-jdk7-1.3.50.jar:9a026639e76212f8d57b86d55b075394c2e009f1979110751d34c05c5f75d57b',
'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50:kotlin-stdlib-jdk8-1.3.50.jar:1b351fb6e09c14b55525c74c1f4cf48942eae43c348b7bc764a5e6e423d4da0c',
'org.jetbrains.kotlin:kotlin-stdlib:1.3.50:kotlin-stdlib-1.3.50.jar:e6f05746ee0366d0b52825a090fac474dcf44082c9083bbb205bd16976488d6c',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.jvnet.staxex:stax-ex:1.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm-analysis:6.0:asm-analysis-6.0.jar:2f1a6387219c3a6cc4856481f221b03bd9f2408a326d416af09af5d6f608c1f4',
'org.ow2.asm:asm-commons:6.0:asm-commons-6.0.jar:f1bce5c648a96a017bdcd01fe5d59af9845297fd7b79b81c015a6fbbd9719abf',
'org.ow2.asm:asm-tree:6.0:asm-tree-6.0.jar:887998fb69727c8759e4d253f856822801e33f9fd4caa566b3ac58ee92106215',
'org.ow2.asm:asm-util:6.0:asm-util-6.0.jar:356afebdb0f870175262e5188f8709a3b17aa2a5a6a4b0340b04d4b449bca5f6',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:6.0:asm-6.0.jar:dd8971c74a4e697899a8e95caae4ea8760ea6c486dc6b97b1795e75760420461',
]
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.api.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
@@ -120,17 +119,4 @@ public interface ClientHelper {
Map<TransportId, TransportProperties> parseAndValidateTransportPropertiesMap(
BdfDictionary properties) throws FormatException;
/**
* Retrieves the contact ID from the group metadata of the given contact
* group.
*/
ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException, FormatException;
/**
* Stores the given contact ID in the group metadata of the given contact
* group.
*/
void setContactId(Transaction txn, GroupId contactGroupId, ContactId c)
throws DbException;
}

View File

@@ -1,9 +0,0 @@
package org.briarproject.bramble.api.client;
public interface ContactGroupConstants {
/**
* Group metadata key for associating a contact ID with a contact group.
*/
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -5,6 +5,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
/**
* Interface for strengthening a password-based key, for example by using a
* key stored in a key management service or hardware security module.
*
* TODO: Remove after a reasonable migration period unless we can work around
* Android keymaster bugs. Added 2020-02-24
*/
@NotNullByDefault
public interface KeyStrengthener {

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that informs the Bluetooth plugin that we have enabled the
* Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class BluetoothEnabledEvent extends Event {
}

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that asks the Bluetooth plugin to disable the Bluetooth adapter if
* we previously enabled it.
*/
@Immutable
@NotNullByDefault
public class DisableBluetoothEvent extends Event {
}

View File

@@ -0,0 +1,14 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that asks the Bluetooth plugin to enable the Bluetooth adapter.
*/
@Immutable
@NotNullByDefault
public class EnableBluetoothEvent extends Event {
}

View File

@@ -6,9 +6,7 @@ import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@Immutable
@NotNullByDefault
public class ValidationUtils {
@@ -66,9 +64,4 @@ public class ValidationUtils {
if (dictionary != null && dictionary.size() != size)
throw new FormatException();
}
public static void checkRange(@Nullable Long l, long min, long max)
throws FormatException {
if (l != null && (l < min || l > max)) throw new FormatException();
}
}

View File

@@ -179,8 +179,9 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock")
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes();
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password,
databaseConfig.getKeyStrengthener());
// Don't use a key strengthener as the Android keymaster isn't reliable
byte[] ciphertext =
crypto.encryptWithPassword(plaintext, password, null);
return storeEncryptedDatabaseKey(toHexString(ciphertext));
}
@@ -197,13 +198,13 @@ class AccountManagerImpl implements AccountManager {
@Override
public void signIn(String password) throws DecryptionException {
synchronized (stateChangeLock) {
databaseKey = loadAndDecryptDatabaseKey(password);
databaseKey = loadAndDecryptDatabaseKey(password, false);
}
}
@GuardedBy("stateChangeLock")
private SecretKey loadAndDecryptDatabaseKey(String password)
throws DecryptionException {
private SecretKey loadAndDecryptDatabaseKey(String password,
boolean changing) throws DecryptionException {
String hex = loadEncryptedDatabaseKey();
if (hex == null) {
LOG.warning("Failed to load encrypted database key");
@@ -214,11 +215,11 @@ class AccountManagerImpl implements AccountManager {
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener);
SecretKey key = new SecretKey(plaintext);
// If the DB key was encrypted with a weak key and a key strengthener
// is now available, re-encrypt the DB key with a strengthened key
if (keyStrengthener != null &&
!crypto.isEncryptedWithStrengthenedKey(ciphertext)) {
LOG.info("Re-encrypting database key with strengthened key");
// If the DB key was encrypted with a hardware-backed key, re-encrypt
// it without the hardware-backed key so keymaster bugs don't delete
// the user's account
if (!changing && crypto.isEncryptedWithStrengthenedKey(ciphertext)) {
LOG.info("Re-encrypting database key without strengthened key");
encryptAndStoreDatabaseKey(key, password);
}
return key;
@@ -228,7 +229,7 @@ class AccountManagerImpl implements AccountManager {
public void changePassword(String oldPassword, String newPassword)
throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
SecretKey key = loadAndDecryptDatabaseKey(oldPassword, true);
encryptAndStoreDatabaseKey(key, newPassword);
}
}

View File

@@ -2,13 +2,11 @@ package org.briarproject.bramble.client;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.data.BdfDictionary;
import org.briarproject.bramble.api.data.BdfEntry;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.data.BdfReader;
import org.briarproject.bramble.api.data.BdfReaderFactory;
@@ -41,7 +39,6 @@ import java.util.Map.Entry;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.client.ContactGroupConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
@@ -392,27 +389,4 @@ class ClientHelperImpl implements ClientHelper {
return tpMap;
}
@Override
public ContactId getContactId(Transaction txn, GroupId contactGroupId)
throws DbException {
try {
BdfDictionary meta =
getGroupMetadataAsDictionary(txn, contactGroupId);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e); // Invalid group metadata
}
}
@Override
public void setContactId(Transaction txn, GroupId contactGroupId,
ContactId c) throws DbException {
BdfDictionary meta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, c.getInt()));
try {
mergeGroupMetadata(txn, contactGroupId, meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.keyagreement;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.data.BdfList;
@@ -9,8 +8,6 @@ import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.Payload;
import org.briarproject.bramble.api.keyagreement.TransportDescriptor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -22,9 +19,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
@@ -33,10 +28,8 @@ import java.util.logging.Logger;
import javax.annotation.Nullable;
import static java.util.Arrays.asList;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.CONNECTION_TIMEOUT;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -48,10 +41,7 @@ class KeyAgreementConnector {
}
private static final Logger LOG =
getLogger(KeyAgreementConnector.class.getName());
private static final List<TransportId> PREFERRED_TRANSPORTS =
asList(BluetoothConstants.ID, LanTcpConstants.ID);
Logger.getLogger(KeyAgreementConnector.class.getName());
private final Callbacks callbacks;
private final KeyAgreementCrypto keyAgreementCrypto;
@@ -115,35 +105,24 @@ class KeyAgreementConnector {
this.alice = alice;
aliceLatch.countDown();
// Start connecting over supported transports in order of preference
// Start connecting over supported transports
if (LOG.isLoggable(INFO)) {
LOG.info("Starting outgoing BQP connections as "
+ (alice ? "Alice" : "Bob"));
}
Map<TransportId, TransportDescriptor> descriptors = new HashMap<>();
for (TransportDescriptor d : remotePayload.getTransportDescriptors()) {
descriptors.put(d.getId(), d);
}
List<Pair<DuplexPlugin, BdfList>> transports = new ArrayList<>();
for (TransportId id : PREFERRED_TRANSPORTS) {
TransportDescriptor d = descriptors.get(id);
Plugin p = pluginManager.getPlugin(id);
if (d != null && p instanceof DuplexPlugin) {
Plugin p = pluginManager.getPlugin(d.getId());
if (p instanceof DuplexPlugin) {
if (LOG.isLoggable(INFO))
LOG.info("Connecting via " + id);
transports.add(new Pair<>((DuplexPlugin) p, d.getDescriptor()));
LOG.info("Connecting via " + d.getId());
DuplexPlugin plugin = (DuplexPlugin) p;
byte[] commitment = remotePayload.getCommitment();
BdfList descriptor = d.getDescriptor();
connectionChooser.submit(new ReadableTask(
new ConnectorTask(plugin, commitment, descriptor)));
}
}
// TODO: If we don't have any transports in common with the peer,
// warn the user and give up (#1224)
if (!transports.isEmpty()) {
byte[] commitment = remotePayload.getCommitment();
connectionChooser.submit(new ReadableTask(new ConnectorTask(
transports, commitment)));
}
// Get chosen connection
try {
KeyAgreementConnection chosen =
@@ -169,13 +148,15 @@ class KeyAgreementConnector {
private class ConnectorTask implements Callable<KeyAgreementConnection> {
private final List<Pair<DuplexPlugin, BdfList>> transports;
private final byte[] commitment;
private final BdfList descriptor;
private final DuplexPlugin plugin;
private ConnectorTask(List<Pair<DuplexPlugin, BdfList>> transports,
byte[] commitment) {
this.transports = transports;
private ConnectorTask(DuplexPlugin plugin, byte[] commitment,
BdfList descriptor) {
this.plugin = plugin;
this.commitment = commitment;
this.descriptor = descriptor;
}
@Nullable
@@ -183,18 +164,13 @@ class KeyAgreementConnector {
public KeyAgreementConnection call() throws Exception {
// Repeat attempts until we connect, get stopped, or get interrupted
while (!stopped) {
for (Pair<DuplexPlugin, BdfList> pair : transports) {
if (stopped) return null;
DuplexPlugin plugin = pair.getFirst();
BdfList descriptor = pair.getSecond();
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment,
descriptor);
if (conn != null) {
if (LOG.isLoggable(INFO))
LOG.info(plugin.getId() + ": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId());
}
DuplexTransportConnection conn =
plugin.createKeyAgreementConnection(commitment,
descriptor);
if (conn != null) {
if (LOG.isLoggable(INFO))
LOG.info(plugin.getId() + ": Outgoing connection");
return new KeyAgreementConnection(conn, plugin.getId());
}
// Wait 2s before retry (to circumvent transient failures)
Thread.sleep(2000);

View File

@@ -53,7 +53,6 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -355,10 +354,6 @@ class PluginManagerImpl implements PluginManager, Service {
} else if (oldState == ACTIVE) {
eventBus.broadcast(new TransportInactiveEvent(id));
}
} else if (newState == DISABLED) {
// Broadcast an event even though the state hasn't changed, as
// the reasons for the plugin being disabled may have changed
eventBus.broadcast(new TransportStateEvent(id, newState));
}
}

View File

@@ -21,6 +21,9 @@ import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.bramble.api.plugin.event.DisableBluetoothEvent;
import org.briarproject.bramble.api.plugin.event.EnableBluetoothEvent;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.properties.event.RemoteTransportPropertiesUpdatedEvent;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
@@ -90,6 +93,12 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
abstract boolean isAdapterEnabled();
abstract void enableAdapter();
abstract void disableAdapterIfEnabledByUs();
abstract void setEnabledByUs();
/**
* Returns the local Bluetooth address, or null if no valid address can
* be found.
@@ -180,7 +189,10 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
throw new PluginException(e);
}
updateProperties();
if (enabledByUser && isAdapterEnabled()) bind();
if (enabledByUser) {
if (isAdapterEnabled()) bind();
else enableAdapter();
}
}
private void bind() {
@@ -307,6 +319,7 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
public void stop() {
SS ss = state.setStopped();
tryToClose(ss);
disableAdapterIfEnabledByUs();
}
@Override
@@ -436,10 +449,8 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
String uuid = UUID.nameUUIDFromBytes(commitment).toString();
DuplexTransportConnection conn;
if (descriptor.size() == 1) {
if (LOG.isLoggable(INFO)) {
LOG.info("Discovering address for key agreement UUID " +
uuid);
}
if (LOG.isLoggable(INFO))
LOG.info("Discovering address for key agreement UUID " + uuid);
conn = discoverAndConnect(uuid);
} else {
String address;
@@ -479,7 +490,13 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) {
if (e instanceof EnableBluetoothEvent) {
ioExecutor.execute(this::enableAdapter);
} else if (e instanceof DisableBluetoothEvent) {
ioExecutor.execute(this::disableAdapterIfEnabledByUs);
} else if (e instanceof BluetoothEnabledEvent) {
setEnabledByUs();
} else if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(ID.getString()))
ioExecutor.execute(() -> onSettingsUpdated(s.getSettings()));
@@ -505,13 +522,11 @@ abstract class BluetoothPlugin<S, SS> implements DuplexPlugin, EventListener {
if (ss != null) {
LOG.info("Disabled by user, closing server socket");
tryToClose(ss);
disableAdapterIfEnabledByUs();
} else if (s == INACTIVE) {
if (isAdapterEnabled()) {
LOG.info("Enabled by user, opening server socket");
bind();
} else {
LOG.info("Enabled by user but adapter is disabled");
}
LOG.info("Enabled by user, opening server socket");
if (isAdapterEnabled()) bind();
else enableAdapter();
}
}

View File

@@ -132,7 +132,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, geoIpFile, configFile;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
@@ -181,7 +181,9 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
this.torDirectory = torDirectory;
torFile = new File(torDirectory, "tor");
geoIpFile = new File(torDirectory, "geoip");
obfs4File = new File(torDirectory, "obfs4proxy");
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
@@ -190,14 +192,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
new PoliteExecutor("TorPlugin", ioExecutor, 1);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor");
}
protected File getObfs4ExecutableFile() {
return new File(torDirectory, "obfs4proxy");
}
@Override
public TransportId getId() {
return TorConstants.ID;
@@ -230,7 +224,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
@@ -329,43 +322,44 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void installAssets() throws PluginException {
InputStream in = null;
OutputStream out = null;
try {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
installTorExecutable();
installObfs4Executable();
extract(getGeoIpInputStream(), geoIpFile);
extract(getConfigInputStream(), configFile);
// Unzip the Tor binary to the filesystem
in = getTorInputStream();
out = new FileOutputStream(torFile);
copyAndClose(in, out);
// Make the Tor binary executable
if (!torFile.setExecutable(true, true)) throw new IOException();
// Unzip the GeoIP database to the filesystem
in = getGeoIpInputStream();
out = new FileOutputStream(geoIpFile);
copyAndClose(in, out);
// Unzip the Obfs4 proxy to the filesystem
in = getObfs4InputStream();
out = new FileOutputStream(obfs4File);
copyAndClose(in, out);
// Make the Obfs4 proxy executable
if (!obfs4File.setExecutable(true, true)) throw new IOException();
// Copy the config file to the filesystem
in = getConfigInputStream();
out = new FileOutputStream(configFile);
copyAndClose(in, out);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
tryToClose(in, LOG, WARNING);
tryToClose(out, LOG, WARNING);
throw new PluginException(e);
}
}
protected void extract(InputStream in, File dest) throws IOException {
OutputStream out = new FileOutputStream(dest);
copyAndClose(in, out);
}
protected void installTorExecutable() throws IOException {
private InputStream getTorInputStream() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing Tor binary for " + architecture);
File torFile = getTorExecutableFile();
extract(getTorInputStream(), torFile);
if (!torFile.setExecutable(true, true)) throw new IOException();
}
protected void installObfs4Executable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
File obfs4File = getObfs4ExecutableFile();
extract(getObfs4InputStream(), obfs4File);
if (!obfs4File.setExecutable(true, true)) throw new IOException();
}
private InputStream getTorInputStream() throws IOException {
InputStream in = resourceProvider
.getResourceInputStream("tor_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
@@ -382,6 +376,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private InputStream getObfs4InputStream() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
InputStream in = resourceProvider
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
@@ -573,7 +569,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (needsMeek) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());

View File

@@ -42,7 +42,6 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.api.system.Wakeful;
import java.security.GeneralSecurityException;
@@ -69,7 +68,6 @@ import static org.briarproject.bramble.api.contact.PendingContactState.ADDING_CO
import static org.briarproject.bramble.api.contact.PendingContactState.FAILED;
import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.api.contact.PendingContactState.WAITING_FOR_CONNECTION;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNull;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.POLLING_INTERVAL_MS;
import static org.briarproject.bramble.rendezvous.RendezvousConstants.RENDEZVOUS_TIMEOUT_MS;
@@ -104,8 +102,6 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
new HashMap<>();
@Nullable
private KeyPair handshakeKeyPair = null;
@Nullable
private Cancellable pollTask = null;
@Inject
RendezvousPollerImpl(@IoExecutor Executor ioExecutor,
@@ -148,6 +144,8 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
} catch (DbException e) {
throw new ServiceException(e);
}
scheduler.scheduleWithFixedDelay(this::poll, worker,
POLLING_INTERVAL_MS, POLLING_INTERVAL_MS, MILLISECONDS);
}
@EventExecutor
@@ -188,12 +186,6 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
}
if (cs.numEndpoints == 0) broadcastState(p.getId(), OFFLINE);
else broadcastState(p.getId(), WAITING_FOR_CONNECTION);
if (cryptoStates.size() == 1) {
LOG.info("Starting poller");
requireNull(pollTask);
pollTask = scheduler.scheduleWithFixedDelay(this::poll, worker,
POLLING_INTERVAL_MS, POLLING_INTERVAL_MS, MILLISECONDS);
}
} catch (DbException | GeneralSecurityException e) {
logException(LOG, WARNING, e);
}
@@ -216,7 +208,6 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
// Worker
@Wakeful
private void poll() {
LOG.info("Polling");
removeExpiredPendingContacts();
for (PluginState ps : pluginStates.values()) poll(ps);
}
@@ -243,11 +234,6 @@ class RendezvousPollerImpl implements RendezvousPoller, Service, EventListener {
RendezvousEndpoint endpoint = ps.endpoints.remove(p);
if (endpoint != null) tryToClose(endpoint, LOG, INFO);
}
if (cryptoStates.isEmpty()) {
LOG.info("Stopping poller");
requireNonNull(pollTask).cancel();
pollTask = null;
}
}
// Worker

View File

@@ -5,5 +5,6 @@ interface ClientVersioningConstants {
// Metadata keys
String MSG_KEY_UPDATE_VERSION = "version";
String MSG_KEY_LOCAL = "local";
String GROUP_KEY_CONTACT_ID = "contactId";
}

View File

@@ -50,6 +50,7 @@ import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
@@ -160,7 +161,13 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
db.addGroup(txn, g);
db.setGroupVisibility(txn, c.getId(), g.getId(), SHARED);
// Attach the contact ID to the group
clientHelper.setContactId(txn, g.getId(), c.getId());
BdfDictionary meta = new BdfDictionary();
meta.put(GROUP_KEY_CONTACT_ID, c.getId().getInt());
try {
clientHelper.mergeGroupMetadata(txn, g.getId(), meta);
} catch (FormatException e) {
throw new AssertionError(e);
}
// Create and store the first local update
List<ClientVersion> versions = new ArrayList<>(clients);
Collections.sort(versions);
@@ -222,7 +229,7 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
Map<ClientMajorVersion, Visibility> after =
getVisibilities(newLocalStates, newRemoteStates);
// Call hooks for any visibilities that have changed
ContactId c = clientHelper.getContactId(txn, m.getGroupId());
ContactId c = getContactId(txn, m.getGroupId());
if (!before.equals(after)) {
Contact contact = db.getContact(txn, c);
callVisibilityHooks(txn, contact, before, after);
@@ -514,6 +521,17 @@ class ClientVersioningManagerImpl implements ClientVersioningManager,
storeUpdate(txn, g, states, 1);
}
private ContactId getContactId(Transaction txn, GroupId g)
throws DbException {
try {
BdfDictionary meta =
clientHelper.getGroupMetadataAsDictionary(txn, g);
return new ContactId(meta.getLong(GROUP_KEY_CONTACT_ID).intValue());
} catch (FormatException e) {
throw new DbException(e);
}
}
private List<ClientState> updateStatesFromRemoteStates(
List<ClientState> oldLocalStates, List<ClientState> remoteStates) {
Set<ClientMajorVersion> remoteSet = new HashSet<>();

View File

@@ -134,7 +134,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(true));
will(returnValue(false));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
@@ -160,9 +160,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(false));
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
keyStrengthener);
will(returnValue(true));
oneOf(crypto).encryptWithPassword(key.getBytes(), password, null);
will(returnValue(newEncryptedKey));
}});
@@ -262,8 +261,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
oneOf(identityManager).registerIdentity(identity);
oneOf(crypto).generateSecretKey();
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
keyStrengthener);
oneOf(crypto).encryptWithPassword(key.getBytes(), password, null);
will(returnValue(encryptedKey));
}});
@@ -323,10 +321,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
oneOf(crypto).decryptWithPassword(encryptedKey, password,
keyStrengthener);
will(returnValue(key.getBytes()));
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
will(returnValue(true));
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword,
keyStrengthener);
null);
will(returnValue(newEncryptedKey));
}});

View File

@@ -27,7 +27,6 @@ import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedE
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
@@ -80,7 +79,6 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.mock(KeyMaterialSource.class);
private final RendezvousEndpoint rendezvousEndpoint =
context.mock(RendezvousEndpoint.class);
private final Cancellable cancellable = context.mock(Cancellable.class);
private final Executor ioExecutor = new ImmediateExecutor();
private final PendingContact pendingContact = getPendingContact();
@@ -109,7 +107,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
long beforeExpiry = pendingContact.getTimestamp()
+ RENDEZVOUS_TIMEOUT_MS - 1000;
long afterExpiry = beforeExpiry + POLLING_INTERVAL_MS;
AtomicReference<Runnable> capturePollTask;
AtomicReference<Runnable> capturePollTask = new AtomicReference<>();
// Start the service
context.checking(new DbExpectations() {{
@@ -123,17 +121,21 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == OFFLINE)));
// Capture the poll task
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(any(Executor.class)), with(POLLING_INTERVAL_MS),
with(POLLING_INTERVAL_MS), with(MILLISECONDS));
will(new CaptureArgumentAction<>(capturePollTask, Runnable.class,
0));
}});
expectDeriveRendezvousKey();
capturePollTask = expectSchedulePolling();
rendezvousPoller.startService();
context.assertIsSatisfied();
// Run the poll task - pending contact expires, polling is cancelled
// Run the poll task - pending contact expires
expectPendingContactExpires(afterExpiry);
expectCancelPolling();
capturePollTask.get().run();
}
@@ -155,6 +157,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == FAILED)));
// Schedule the poll task
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(any(Executor.class)), with(POLLING_INTERVAL_MS),
with(POLLING_INTERVAL_MS), with(MILLISECONDS));
}});
rendezvousPoller.startService();
@@ -177,8 +183,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled,
// polling should be scheduled
// Add the pending contact - endpoint should be created and polled
expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
expectDeriveRendezvousKey();
expectCreateEndpoint();
@@ -195,16 +200,12 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
any(ConnectionHandler.class)))));
}});
expectSchedulePolling();
rendezvousPoller.eventOccurred(
new PendingContactAddedEvent(pendingContact));
context.assertIsSatisfied();
// Remove the pending contact - endpoint should be closed,
// polling should be cancelled
// Remove the pending contact - endpoint should be closed
expectCloseEndpoint();
expectCancelPolling();
rendezvousPoller.eventOccurred(
new PendingContactRemovedEvent(pendingContact.getId()));
@@ -220,10 +221,10 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
long beforeExpiry = pendingContact.getTimestamp()
+ RENDEZVOUS_TIMEOUT_MS - 1000;
long afterExpiry = beforeExpiry + POLLING_INTERVAL_MS;
AtomicReference<Runnable> capturePollTask;
// Start the service
expectStartupWithNoPendingContacts();
// Start the service, capturing the poll task
AtomicReference<Runnable> capturePollTask =
expectStartupWithNoPendingContacts();
rendezvousPoller.startService();
context.assertIsSatisfied();
@@ -234,8 +235,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.eventOccurred(new TransportActiveEvent(transportId));
context.assertIsSatisfied();
// Add the pending contact - endpoint should be created and polled,
// polling should be scheduled
// Add the pending contact - endpoint should be created and polled
expectAddPendingContact(beforeExpiry, WAITING_FOR_CONNECTION);
expectDeriveRendezvousKey();
expectCreateEndpoint();
@@ -252,17 +252,13 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
any(ConnectionHandler.class)))));
}});
capturePollTask = expectSchedulePolling();
rendezvousPoller.eventOccurred(
new PendingContactAddedEvent(pendingContact));
context.assertIsSatisfied();
// Run the poll task - pending contact expires, endpoint is closed,
// polling is cancelled
// Run the poll task - pending contact expires, endpoint is closed
expectPendingContactExpires(afterExpiry);
expectCloseEndpoint();
expectCancelPolling();
capturePollTask.get().run();
context.assertIsSatisfied();
@@ -290,7 +286,6 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
// Add the pending contact - no endpoints should be created yet
expectAddPendingContact(beforeExpiry, OFFLINE);
expectDeriveRendezvousKey();
expectSchedulePolling();
rendezvousPoller.eventOccurred(
new PendingContactAddedEvent(pendingContact));
@@ -312,8 +307,6 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied();
// Remove the pending contact - endpoint is already closed
expectCancelPolling();
rendezvousPoller.eventOccurred(
new PendingContactRemovedEvent(pendingContact.getId()));
}
@@ -355,9 +348,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
rendezvousPoller.startService();
context.assertIsSatisfied();
// Run the poll task - pending contact expires, polling is cancelled
// Run the poll task - pending contact expires
expectPendingContactExpires(afterExpiry);
expectCancelPolling();
capturePollTask.get().run();
context.assertIsSatisfied();
@@ -393,9 +385,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new RendezvousConnectionOpenedEvent(pendingContact.getId()));
context.assertIsSatisfied();
// Run the poll task - pending contact expires, polling is cancelled
// Run the poll task - pending contact expires
expectPendingContactExpires(afterExpiry);
expectCancelPolling();
capturePollTask.get().run();
context.assertIsSatisfied();
@@ -426,9 +417,8 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
new RendezvousConnectionOpenedEvent(pendingContact.getId()));
context.assertIsSatisfied();
// Run the poll task - pending contact expires, polling is cancelled
// Run the poll task - pending contact expires
expectPendingContactExpires(afterExpiry);
expectCancelPolling();
capturePollTask.get().run();
context.assertIsSatisfied();
@@ -457,8 +447,6 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
context.assertIsSatisfied();
// Pending contact is removed - no event should be broadcast
expectCancelPolling();
rendezvousPoller.eventOccurred(
new PendingContactRemovedEvent(pendingContact.getId()));
context.assertIsSatisfied();
@@ -468,35 +456,25 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
pendingContact.getId(), false));
}
private AtomicReference<Runnable> expectSchedulePolling() {
AtomicReference<Runnable> capturePollTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(any(Executor.class)), with(POLLING_INTERVAL_MS),
with(POLLING_INTERVAL_MS), with(MILLISECONDS));
will(doAll(new CaptureArgumentAction<>(capturePollTask,
Runnable.class, 0), returnValue(cancellable)));
}});
return capturePollTask;
}
private void expectCancelPolling() {
context.checking(new Expectations() {{
oneOf(cancellable).cancel();
}});
}
private void expectStartupWithNoPendingContacts() throws Exception {
private AtomicReference<Runnable> expectStartupWithNoPendingContacts()
throws Exception {
Transaction txn = new Transaction(null, true);
AtomicReference<Runnable> capturePollTask = new AtomicReference<>();
context.checking(new DbExpectations() {{
// Load the pending contacts
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).getPendingContacts(txn);
will(returnValue(emptyList()));
// Capture the poll task
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(any(Executor.class)), with(POLLING_INTERVAL_MS),
with(POLLING_INTERVAL_MS), with(MILLISECONDS));
will(new CaptureArgumentAction<>(capturePollTask, Runnable.class,
0));
}});
return capturePollTask;
}
private void expectAddPendingContact(long now,
@@ -552,6 +530,7 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
private AtomicReference<Runnable> expectStartupWithPendingContact(long now)
throws Exception {
Transaction txn = new Transaction(null, true);
AtomicReference<Runnable> capturePollTask = new AtomicReference<>();
context.checking(new DbExpectations() {{
// Load the pending contacts
@@ -564,10 +543,17 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
oneOf(eventBus).broadcast(with(new PredicateMatcher<>(
PendingContactStateChangedEvent.class, e ->
e.getPendingContactState() == OFFLINE)));
// Capture the poll task
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(any(Executor.class)), with(POLLING_INTERVAL_MS),
with(POLLING_INTERVAL_MS), with(MILLISECONDS));
will(new CaptureArgumentAction<>(capturePollTask, Runnable.class,
0));
}});
expectDeriveRendezvousKey();
return expectSchedulePolling();
return capturePollTask;
}
private void expectPendingContactExpires(long now) {

View File

@@ -38,6 +38,7 @@ import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getGroup;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL;
import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION;
import static org.junit.Assert.assertEquals;
@@ -59,6 +60,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
private final ClientId clientId = getClientId();
private final long now = System.currentTimeMillis();
private final Transaction txn = new Transaction(null, false);
private final BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
private ClientVersioningManagerImpl createInstance() {
context.checking(new Expectations() {{
@@ -120,8 +123,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).addGroup(txn, contactGroup);
oneOf(db).setGroupVisibility(txn, contact.getId(),
contactGroup.getId(), SHARED);
oneOf(clientHelper).setContactId(txn, contactGroup.getId(),
contact.getId());
oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(),
groupMeta);
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(clientHelper).createMessage(contactGroup.getId(), now,
@@ -457,8 +460,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(db).deleteMessage(txn, oldRemoteUpdateId);
oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId);
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -488,9 +492,10 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
// Load the latest local update
oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId);
will(returnValue(oldLocalUpdateBody));
// Get contact ID
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
// Get client ID
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
// No states or visibilities have changed
}});
@@ -541,6 +546,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -570,8 +577,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, visibility);
@@ -611,6 +619,8 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
BdfDictionary newLocalUpdateMeta = BdfDictionary.of(
new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L),
new BdfEntry(MSG_KEY_LOCAL, true));
BdfDictionary groupMeta = BdfDictionary.of(
new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt()));
context.checking(new Expectations() {{
oneOf(clientHelper).toList(newRemoteUpdate);
@@ -640,8 +650,9 @@ public class ClientVersioningManagerImplTest extends BrambleMockTestCase {
oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate,
newLocalUpdateMeta, true, false);
// The client's visibility has changed
oneOf(clientHelper).getContactId(txn, contactGroup.getId());
will(returnValue(contact.getId()));
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
contactGroup.getId());
will(returnValue(groupMeta));
oneOf(db).getContact(txn, contact.getId());
will(returnValue(contact));
oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE);

View File

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

View File

@@ -62,6 +62,22 @@ class JavaBluetoothPlugin
return localDevice != null && LocalDevice.isPowerOn();
}
@Override
void enableAdapter() {
// Nothing we can do on this platform
LOG.info("Could not enable Bluetooth");
}
@Override
void disableAdapterIfEnabledByUs() {
// We didn't enable it so we don't need to disable it
}
@Override
void setEnabledByUs() {
// Irrelevant on this platform
}
@Nullable
@Override
String getBluetoothAddress() {

View File

@@ -24,7 +24,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
'org.briarproject:tor:0.3.5.10:tor-0.3.5.10.zip:7b387d3523ae8af289c23be59dc4c64ec5d3721385d7825a09705095e3318d5c',
'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.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -16,14 +16,14 @@ def getStdout = { command, defaultValue ->
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion 29
buildToolsVersion '29.0.2'
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
minSdkVersion 16
targetSdkVersion 28
versionCode 10209
versionName "1.2.9"
applicationId "org.briarproject.briar.android"
buildConfigField "String", "GitHash",
"\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\""
@@ -93,12 +93,12 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android')
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01'
implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0'
@@ -106,7 +106,7 @@ dependencies {
implementation 'de.hdodenhof:circleimageview:3.0.1'
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.0.0'
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.vanniktech:emoji-google:0.6.0'
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
def glideVersion = '4.10.0'
@@ -120,13 +120,13 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0'
def espressoVersion = '3.3.0'
def espressoVersion = '3.2.0'
def jmockVersion = '2.8.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'androidx.fragment:fragment-testing:1.2.5'
testImplementation 'androidx.test:runner:1.2.0'
testImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'androidx.fragment:fragment-testing:1.1.0'
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.mockito:mockito-core:3.1.0'
@@ -136,7 +136,7 @@ dependencies {
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"

View File

@@ -37,6 +37,3 @@
# Glide
-dontwarn com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper
# Dependency injection annotations, needed for UI tests on older API levels
-keep class javax.inject.**

View File

@@ -13,9 +13,8 @@ import java.util.Random;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import static androidx.test.InstrumentationRegistry.getContext;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(AndroidJUnit4.class)
@@ -28,7 +27,7 @@ public class AttachmentRetrieverIntegrationTest {
private final ImageHelper imageHelper = new ImageHelperImpl();
private final AttachmentRetriever retriever =
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
new ImageSizeCalculator(imageHelper));
@Test
@@ -36,7 +35,7 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_small.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(160, item.getWidth());
assertEquals(240, item.getHeight());
@@ -44,7 +43,7 @@ public class AttachmentRetrieverIntegrationTest {
assertEquals(240, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertJpgOrJpeg(item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -52,7 +51,7 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("kitten_original.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(1728, item.getWidth());
assertEquals(2592, item.getHeight());
@@ -60,7 +59,7 @@ public class AttachmentRetrieverIntegrationTest {
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
assertEquals("image/jpeg", item.getMimeType());
assertJpgOrJpeg(item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -68,7 +67,7 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getAssetInputStream("kitten.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(a, true);
assertEquals(msgId, item.getMessageId());
assertEquals(737, item.getWidth());
assertEquals(510, item.getHeight());
@@ -76,7 +75,7 @@ public class AttachmentRetrieverIntegrationTest {
assertEquals(138, item.getThumbnailHeight());
assertEquals("image/png", item.getMimeType());
assertEquals("png", item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -84,14 +83,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("uber.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -99,14 +98,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("lottapixel.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertJpgOrJpeg(item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -114,14 +113,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/png");
InputStream is = getAssetInputStream("image_io_crash.png");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -129,14 +128,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("gimp_crash.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -144,14 +143,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("opti_png_afl.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -159,8 +158,8 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("libraw_error.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
assertEquals(ERROR, item.getState());
AttachmentItem item = retriever.getAttachmentItem(a, true);
assertTrue(item.hasError());
}
@Test
@@ -168,14 +167,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -183,14 +182,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("animated2.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -198,14 +197,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/gif");
InputStream is = getAssetInputStream("error_large.gif");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -213,14 +212,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_high.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertJpgOrJpeg(item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
@Test
@@ -228,14 +227,14 @@ public class AttachmentRetrieverIntegrationTest {
AttachmentHeader h = new AttachmentHeader(msgId, "image/jpeg");
InputStream is = getAssetInputStream("error_wide.jpg");
Attachment a = new Attachment(h, is);
AttachmentItem item = retriever.createAttachmentItem(a, true);
AttachmentItem item = retriever.getAttachmentItem(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());
assertJpgOrJpeg(item.getExtension());
assertEquals(AVAILABLE, item.getState());
assertFalse(item.hasError());
}
private InputStream getAssetInputStream(String name) throws Exception {

View File

@@ -1,46 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.briar">
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
android:required="false"/>
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
android:required="false"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false"
android:banner="@mipmap/tv_banner"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:banner="@mipmap/tv_banner"
android:supportsRtl="true"
android:theme="@style/BriarTheme"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"
@@ -50,8 +50,8 @@
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
@@ -59,13 +59,14 @@
android:name="org.briarproject.briar.android.BriarService"
android:exported="false">
<intent-filter>
<action android:name="org.briarproject.briar.android.BriarService" />
<action android:name="org.briarproject.briar.android.BriarService"/>
</intent-filter>
</service>
<service
android:name="org.briarproject.briar.android.NotificationCleanupService"
android:exported="false"></service>
android:exported="false">
</service>
<activity
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
@@ -75,29 +76,32 @@
android:label="@string/crash_report_title"
android:launchMode="singleInstance"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden"></activity>
android:windowSoftInputMode="adjustResize|stateHidden">
</activity>
<activity
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name"></activity>
android:label="@string/app_name">
</activity>
<activity
android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name"></activity>
android:label="@string/app_name">
</activity>
<activity
android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"></activity>
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
</activity>
<activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
android:label="@string/app_name"
android:theme="@style/BriarTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
@@ -107,17 +111,17 @@
android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar">
<intent-filter android:label="@string/add_contact_remotely_title_case">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="briar" />
<data android:scheme="briar"/>
</intent-filter>
<intent-filter android:label="@string/add_contact_remotely_title_case">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
@@ -129,7 +133,7 @@
android:windowSoftInputMode="adjustResize|stateUnchanged">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -138,17 +142,7 @@
android:theme="@style/BriarTheme.ActionBarOverlay">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
android:name=".android.conversation.ConversationSettingsActivity"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
android:label="@string/disappearing_messages_title"
android:theme="@style/BriarTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
</activity>
<activity
@@ -158,7 +152,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -169,7 +163,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -178,7 +172,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -187,7 +181,7 @@
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
</activity>
<activity
@@ -196,7 +190,7 @@
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
</activity>
<activity
@@ -206,7 +200,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
</activity>
<activity
@@ -215,7 +209,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -224,7 +218,7 @@
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
</activity>
<activity
@@ -234,7 +228,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -245,7 +239,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -255,7 +249,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.forum.ForumActivity" />
android:value="org.briarproject.briar.android.forum.ForumActivity"/>
</activity>
<activity
@@ -265,7 +259,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity" />
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
</activity>
<activity
@@ -274,7 +268,8 @@
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.forum.ForumActivity" />
android:value="org.briarproject.briar.android.forum.ForumActivity"
/>
</activity>
<activity
@@ -283,7 +278,7 @@
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity" />
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
</activity>
<activity
@@ -292,7 +287,7 @@
android:theme="@style/BriarTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -302,7 +297,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity" />
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
</activity>
<activity
@@ -312,7 +307,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity" />
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
</activity>
<activity
@@ -322,7 +317,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -331,7 +326,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -341,7 +336,7 @@
android:theme="@style/BriarTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
</activity>
<activity
@@ -351,12 +346,13 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
</activity>
<activity
android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title"></activity>
android:label="@string/startup_failed_activity_title">
</activity>
<activity
android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -365,22 +361,13 @@
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name="org.briarproject.briar.android.navdrawer.TransportsActivity"
android:label="@string/network_settings_title"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
android:label="@string/change_password"
@@ -388,7 +375,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
</activity>
<activity
@@ -397,7 +384,7 @@
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
</activity>
<activity
@@ -406,7 +393,7 @@
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
</activity>
<activity
@@ -415,35 +402,37 @@
android:theme="@style/TranslucentTheme">
<!-- this can never have launchMode singleTask or singleInstance! -->
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity
android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay"></activity>
android:theme="@android:style/Theme.NoDisplay">
</activity>
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay"></activity>
android:theme="@android:style/Theme.NoDisplay">
</activity>
<activity
android:name=".android.account.UnlockActivity"
android:label="@string/lock_unlock"
android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar" />
android:theme="@style/BriarTheme.NoActionBar"/>
<activity
android:name=".android.contact.add.remote.AddContactActivity"
android:label="@string/add_contact_remotely_title_case"
android:theme="@style/BriarTheme"
android:windowSoftInputMode="adjustResize|stateHidden" />
android:windowSoftInputMode="adjustResize|stateHidden"/>
<activity
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme" />
android:theme="@style/BriarTheme"/>
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
@@ -40,7 +39,6 @@ import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostFactory;
import org.briarproject.briar.api.blog.BlogSharingManager;
@@ -171,10 +169,6 @@ public interface AndroidComponent
AndroidWakeLockManager wakeLockManager();
TransactionManager transactionManager();
AutoDeleteManager autoDeleteManager();
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);

View File

@@ -0,0 +1,93 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Logger;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.os.Environment.DIRECTORY_DOWNLOADS;
import static android.os.Environment.getExternalStoragePublicDirectory;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
public class AccountUtils {
private static final Logger LOG = getLogger(AccountUtils.class.getName());
private static final String[] BACKUP_DIRS =
{"app_db", "app_key", "shared_prefs"};
public static void exportAccount(Context ctx) {
try {
File dataDir = getDataDir(ctx);
File backupDir = getBackupDir(ctx);
for (String name : BACKUP_DIRS) {
copyRecursively(new File(dataDir, name),
new File(backupDir, name));
}
Toast.makeText(ctx, "Account exported to "
+ backupDir.getCanonicalPath(), LENGTH_LONG).show();
} catch (IOException e) {
logException(LOG, WARNING, e);
Toast.makeText(ctx, "Export failed", LENGTH_LONG).show();
}
}
public static void importAccount(Context ctx) {
try {
File dataDir = getDataDir(ctx);
File backupDir = getBackupDir(ctx);
for (String name : BACKUP_DIRS) {
copyRecursively(new File(backupDir, name),
new File(dataDir, name));
}
Toast.makeText(ctx, "Account imported from "
+ backupDir.getCanonicalPath(), LENGTH_LONG).show();
Intent intent = new Intent(ctx, ENTRY_ACTIVITY);
intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
intent.setData(SIGN_OUT_URI);
ctx.startActivity(intent);
} catch (IOException e) {
logException(LOG, WARNING, e);
Toast.makeText(ctx, "Import failed", LENGTH_LONG).show();
}
}
private static File getDataDir(Context ctx) {
return new File(ctx.getApplicationInfo().dataDir);
}
private static File getBackupDir(Context ctx) {
File downloads = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
return new File(downloads, ctx.getPackageName());
}
private static void copyRecursively(File src, File dest)
throws IOException {
if (src.isDirectory()) {
if (!dest.isDirectory() && !dest.mkdirs()) throw new IOException();
File[] children = src.listFiles();
if (children == null) throw new IOException();
for (File child : children) {
copyRecursively(child, new File(dest, child.getName()));
}
} else if (src.isFile()) {
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest);
copyAndClose(in, out);
}
}
}

View File

@@ -17,6 +17,7 @@ import org.briarproject.briar.android.activity.ActivityComponent;
import javax.annotation.Nullable;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.util.StringUtils.toUtf8;
import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -44,7 +45,7 @@ public class AuthorNameFragment extends SetupFragment {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_title));
requireNonNull(getActivity()).setTitle(getString(R.string.setup_title));
View v = inflater.inflate(R.layout.fragment_setup_author_name,
container, false);
authorNameWrapper = v.findViewById(R.id.nickname_entry_wrapper);

View File

@@ -20,6 +20,7 @@ import androidx.annotation.Nullable;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@@ -49,10 +50,10 @@ public class DozeFragment extends SetupFragment
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_doze_title));
requireNonNull(getActivity()).setTitle(getString(R.string.setup_doze_title));
setHasOptionsMenu(false);
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
false);
false);
dozeView = v.findViewById(R.id.dozeView);
dozeView.setOnCheckedChangedListener(this);
huaweiView = v.findViewById(R.id.huaweiView);
@@ -77,8 +78,7 @@ public class DozeFragment extends SetupFragment
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
public void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_DOZE_WHITELISTING) {
if (!dozeView.needsToBeShown() || secondAttempt) {
@@ -92,7 +92,11 @@ public class DozeFragment extends SetupFragment
@Override
public void onCheckedChanged() {
next.setEnabled(dozeView.isChecked() && huaweiView.isChecked());
if (dozeView.isChecked() && huaweiView.isChecked()) {
next.setEnabled(true);
} else {
next.setEnabled(false);
}
}
@SuppressLint("BatteryLife")

View File

@@ -24,6 +24,7 @@ import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
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.briar.android.util.UiUtils.setError;
@@ -54,7 +55,7 @@ public class SetPasswordFragment extends SetupFragment {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_password_intro));
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container,
false);

View File

@@ -117,24 +117,14 @@ public class UnlockActivity extends BaseActivity {
@RequiresApi(api = 28)
private void requestFingerprintUnlock() {
BiometricPrompt biometricPrompt;
if (SDK_INT >= 29) {
biometricPrompt = new Builder(this)
.setTitle(getString(R.string.lock_unlock))
.setDescription(getString(
R.string.lock_unlock_fingerprint_description))
.setDeviceCredentialAllowed(true)
.build();
} else {
biometricPrompt = new Builder(this)
.setTitle(getString(R.string.lock_unlock))
.setDescription(getString(
R.string.lock_unlock_fingerprint_description))
.setNegativeButton(getString(R.string.lock_unlock_password),
getMainExecutor(),
(dialog, which) -> requestKeyguardUnlock())
.build();
}
BiometricPrompt biometricPrompt = new Builder(this)
.setTitle(getString(R.string.lock_unlock))
.setDescription(
getString(R.string.lock_unlock_fingerprint_description))
.setNegativeButton(getString(R.string.lock_unlock_password),
getMainExecutor(),
(dialog, which) -> requestKeyguardUnlock())
.build();
CancellationSignal signal = new CancellationSignal();
AuthenticationCallback callback = new AuthenticationCallback() {
@Override

View File

@@ -28,8 +28,6 @@ import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsFragment;
import org.briarproject.briar.android.conversation.ImageActivity;
import org.briarproject.briar.android.conversation.ImageFragment;
import org.briarproject.briar.android.forum.CreateForumActivity;
@@ -49,7 +47,6 @@ import org.briarproject.briar.android.login.OpenDatabaseFragment;
import org.briarproject.briar.android.login.PasswordFragment;
import org.briarproject.briar.android.login.StartupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.navdrawer.TransportsActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
import org.briarproject.briar.android.panic.PanicResponderActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
@@ -166,8 +163,6 @@ public interface ActivityComponent {
void inject(SettingsActivity activity);
void inject(TransportsActivity activity);
void inject(TestDataActivity activity);
void inject(ChangePasswordActivity activity);
@@ -186,8 +181,6 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity);
void inject(ConversationSettingsActivity activity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -238,6 +231,4 @@ public interface ActivityComponent {
void inject(ImageFragment imageFragment);
void inject(ConversationSettingsFragment conversationSettingsFragment);
}

View File

@@ -34,7 +34,6 @@ import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_IMAGE_SIZE;
@@ -76,12 +75,8 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@UiThread
public LiveData<AttachmentResult> storeAttachments(
LiveData<GroupId> groupId, Collection<Uri> newUris) {
if (task != null || result != null || !uris.isEmpty()) {
if (task != null) LOG.warning("Task already exists!");
if (result != null) LOG.warning("Result already exists!");
if (!uris.isEmpty()) LOG.warning("Uris available: " + uris);
if (task != null || result != null || !uris.isEmpty())
throw new IllegalStateException();
}
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
this.result = result;
uris.addAll(newUris);
@@ -100,12 +95,8 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@UiThread
public LiveData<AttachmentResult> getLiveAttachments() {
MutableLiveData<AttachmentResult> result = this.result;
if (task == null || result == null || uris.isEmpty()) {
if (task == null) LOG.warning("No Task!");
if (result == null) LOG.warning("No Result!");
if (uris.isEmpty()) LOG.warning("Uris empty!");
if (task == null || result == null || uris.isEmpty())
throw new IllegalStateException();
}
// A task is already running. It will update the result LiveData.
// So nothing more to do here.
return result;
@@ -118,8 +109,8 @@ class AttachmentCreatorImpl implements AttachmentCreator {
// get and cache AttachmentItem for ImagePreview
try {
Attachment a = retriever.getMessageAttachment(h);
AttachmentItem item = retriever.createAttachmentItem(a, needsSize);
if (item.getState() == ERROR) throw new IOException();
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
if (item.hasError()) throw new IOException();
AttachmentItemResult itemResult =
new AttachmentItemResult(uri, item);
itemResults.add(itemResult);
@@ -176,13 +167,21 @@ class AttachmentCreatorImpl implements AttachmentCreator {
@Override
@UiThread
public void onAttachmentsSent(MessageId id) {
List<AttachmentItem> items = new ArrayList<>(itemResults.size());
for (AttachmentItemResult itemResult : itemResults) {
// check if we are trying to send attachment items with errors
if (itemResult.getItem() == null) throw new IllegalStateException();
items.add(itemResult.getItem());
}
retriever.cachePut(id, items);
resetState();
}
@Override
@UiThread
public void cancel() {
if (task != null) task.cancel();
if (task == null) throw new AssertionError();
task.cancel();
deleteUnsentAttachments();
resetState();
}

View File

@@ -7,33 +7,24 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.concurrent.Immutable;
import androidx.annotation.Nullable;
import static java.lang.System.arraycopy;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.LOADING;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.MISSING;
@Immutable
@NotNullByDefault
public class AttachmentItem implements Parcelable {
public enum State {
LOADING, MISSING, AVAILABLE, ERROR;
public boolean isFinal() {
return this == AVAILABLE || this == ERROR;
}
}
private final AttachmentHeader header;
private final int width, height;
private final String extension;
private final int thumbnailWidth, thumbnailHeight;
private final State state;
private final boolean hasError;
private final long instanceId;
public static final Creator<AttachmentItem> CREATOR =
new Creator<AttachmentItem>() {
@@ -48,33 +39,19 @@ public class AttachmentItem implements Parcelable {
}
};
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(0);
AttachmentItem(AttachmentHeader header, int width, int height,
String extension, int thumbnailWidth, int thumbnailHeight,
State state) {
boolean hasError) {
this.header = header;
this.width = width;
this.height = height;
this.extension = extension;
this.thumbnailWidth = thumbnailWidth;
this.thumbnailHeight = thumbnailHeight;
this.state = state;
}
/**
* Use only for {@link State MISSING} or {@link State LOADING} items.
*/
AttachmentItem(AttachmentHeader header, int width, int height,
State state) {
this(header, width, height, "", width, height, state);
if (state != MISSING && state != LOADING)
throw new IllegalArgumentException();
}
/**
* Use when the item does not need a size.
*/
AttachmentItem(AttachmentHeader header, String extension, State state) {
this(header, 0, 0, extension, 0, 0, state);
this.hasError = hasError;
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
}
protected AttachmentItem(Parcel in) {
@@ -87,7 +64,8 @@ public class AttachmentItem implements Parcelable {
extension = requireNonNull(in.readString());
thumbnailWidth = in.readInt();
thumbnailHeight = in.readInt();
state = State.valueOf(requireNonNull(in.readString()));
hasError = in.readByte() != 0;
instanceId = in.readLong();
header = new AttachmentHeader(messageId, mimeType);
}
@@ -123,16 +101,12 @@ public class AttachmentItem implements Parcelable {
return thumbnailHeight;
}
public State getState() {
return state;
public boolean hasError() {
return hasError;
}
public String getTransitionName(MessageId conversationItemId) {
int len = MessageId.LENGTH;
byte[] instanceId = new byte[len * 2];
arraycopy(header.getMessageId().getBytes(), 0, instanceId, 0, len);
arraycopy(conversationItemId.getBytes(), 0, instanceId, len, len);
return toHexString(instanceId);
public String getTransitionName() {
return String.valueOf(instanceId);
}
@Override
@@ -149,23 +123,14 @@ public class AttachmentItem implements Parcelable {
dest.writeString(extension);
dest.writeInt(thumbnailWidth);
dest.writeInt(thumbnailHeight);
dest.writeString(state.name());
dest.writeByte((byte) (hasError ? 1 : 0));
dest.writeLong(instanceId);
}
/**
* This is used to identity if two items are the same,
* irrespective of their state or size.
*/
@Override
public boolean equals(@Nullable Object o) {
return o instanceof AttachmentItem &&
header.getMessageId().equals(
((AttachmentItem) o).header.getMessageId()
);
instanceId == ((AttachmentItem) o).instanceId;
}
@Override
public int hashCode() {
return header.getMessageId().hashCode();
}
}

View File

@@ -1,63 +1,29 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.io.InputStream;
import java.util.List;
import androidx.lifecycle.LiveData;
import androidx.annotation.Nullable;
@NotNullByDefault
public interface AttachmentRetriever {
@DatabaseExecutor
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
@Nullable
List<AttachmentItem> cacheGet(MessageId messageId);
Attachment getMessageAttachment(AttachmentHeader h) throws DbException;
/**
* Returns a list of observable {@link LiveData}
* that get updated as the state of their {@link AttachmentItem}s changes.
*/
List<LiveData<AttachmentItem>> getAttachmentItems(
PrivateMessageHeader messageHeader);
/**
* Retrieves item size and adds the item to the cache, if available.
* <p>
* Use this to eagerly load the attachment size before it gets displayed.
* This is needed for messages containing a single attachment.
* Messages with more than one attachment use a standard size.
*/
@DatabaseExecutor
void cacheAttachmentItemWithSize(MessageId conversationMessageId,
AttachmentHeader h) throws DbException;
/**
* Creates an {@link AttachmentItem} from the {@link Attachment}'s
* {@link InputStream} which will be closed when this method returns.
*/
AttachmentItem createAttachmentItem(Attachment a, boolean needsSize);
/**
* Loads an {@link AttachmentItem}
* that arrived via an {@link AttachmentReceivedEvent}
* and notifies the associated {@link LiveData}.
*
* Note that you need to call {@link #getAttachmentItems(PrivateMessageHeader)}
* first to get the LiveData.
*
* It is possible that no LiveData is available,
* because the message of the AttachmentItem did not arrive, yet.
* In this case, the load wil be deferred until the message arrives.
*/
@DatabaseExecutor
void loadAttachmentItem(MessageId attachmentId);
AttachmentItem getAttachmentItem(Attachment a, boolean needsSize);
}

View File

@@ -1,39 +1,25 @@
package org.briarproject.briar.android.attachment;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem.State;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.annotation.Nullable;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.LOADING;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.MISSING;
@NotNullByDefault
class AttachmentRetrieverImpl implements AttachmentRetriever {
@@ -41,8 +27,6 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private static final Logger LOG =
getLogger(AttachmentRetrieverImpl.class.getName());
@DatabaseExecutor
private final Executor dbExecutor;
private final MessagingManager messagingManager;
private final ImageHelper imageHelper;
private final ImageSizeCalculator imageSizeCalculator;
@@ -50,17 +34,13 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
private final int minWidth, maxWidth;
private final int minHeight, maxHeight;
private final ConcurrentMap<MessageId, MutableLiveData<AttachmentItem>>
itemsWithSize = new ConcurrentHashMap<>();
private final ConcurrentMap<MessageId, MutableLiveData<AttachmentItem>>
itemsWithoutSize = new ConcurrentHashMap<>();
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
new ConcurrentHashMap<>();
@Inject
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
MessagingManager messagingManager,
AttachmentRetrieverImpl(MessagingManager messagingManager,
AttachmentDimensions dimensions, ImageHelper imageHelper,
ImageSizeCalculator imageSizeCalculator) {
this.dbExecutor = dbExecutor;
this.messagingManager = messagingManager;
this.imageHelper = imageHelper;
this.imageSizeCalculator = imageSizeCalculator;
@@ -72,143 +52,40 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
}
@Override
@DatabaseExecutor
public void cachePut(MessageId messageId,
List<AttachmentItem> attachments) {
attachmentCache.put(messageId, attachments);
}
@Override
@Nullable
public List<AttachmentItem> cacheGet(MessageId messageId) {
return attachmentCache.get(messageId);
}
@Override
public Attachment getMessageAttachment(AttachmentHeader h)
throws DbException {
return messagingManager.getAttachment(h);
}
@Override
public List<LiveData<AttachmentItem>> getAttachmentItems(
PrivateMessageHeader messageHeader) {
List<AttachmentHeader> headers = messageHeader.getAttachmentHeaders();
List<LiveData<AttachmentItem>> items = new ArrayList<>(headers.size());
boolean needsSize = headers.size() == 1;
for (AttachmentHeader h : headers) {
// try cache for existing item live data
MutableLiveData<AttachmentItem> liveData;
if (needsSize) liveData = itemsWithSize.get(h.getMessageId());
else {
// try items with size first, as they work as well
liveData = itemsWithSize.get(h.getMessageId());
if (liveData == null)
liveData = itemsWithoutSize.get(h.getMessageId());
}
// create new live data with LOADING item if cache miss
if (liveData == null) {
AttachmentItem item = new AttachmentItem(h,
defaultSize, defaultSize, LOADING);
liveData = new MutableLiveData<>(item);
// add new LiveData to cache, checking for concurrent updates
MutableLiveData<AttachmentItem> oldLiveData;
if (needsSize) {
oldLiveData = itemsWithSize.putIfAbsent(h.getMessageId(),
liveData);
} else {
oldLiveData = itemsWithoutSize.putIfAbsent(h.getMessageId(),
liveData);
}
if (oldLiveData == null) {
// kick-off loading of attachment, will post to live data
MutableLiveData<AttachmentItem> finalLiveData = liveData;
dbExecutor.execute(() ->
loadAttachmentItem(h, needsSize, finalLiveData));
} else {
// Concurrent cache update - use the existing live data
liveData = oldLiveData;
}
}
items.add(liveData);
}
return items;
}
@Override
@DatabaseExecutor
public void cacheAttachmentItemWithSize(MessageId conversationMessageId,
AttachmentHeader h) throws DbException {
// If a live data is already cached we don't need to do anything
if (itemsWithSize.containsKey(h.getMessageId())) return;
try {
Attachment a = messagingManager.getAttachment(h);
AttachmentItem item = createAttachmentItem(a, true);
MutableLiveData<AttachmentItem> liveData =
new MutableLiveData<>(item);
// If a live data was concurrently cached, don't replace it
itemsWithSize.putIfAbsent(h.getMessageId(), liveData);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
}
}
@Override
@DatabaseExecutor
public void loadAttachmentItem(MessageId attachmentId) {
// try to find LiveData for attachment in both caches
MutableLiveData<AttachmentItem> liveData;
boolean needsSize = true;
liveData = itemsWithSize.get(attachmentId);
if (liveData == null) {
needsSize = false;
liveData = itemsWithoutSize.get(attachmentId);
}
// If no LiveData for the attachment exists,
// its message did not yet arrive and we can ignore it for now.
if (liveData == null) return;
// actually load the attachment item
AttachmentHeader h = requireNonNull(liveData.getValue()).getHeader();
loadAttachmentItem(h, needsSize, liveData);
}
/**
* Loads an {@link AttachmentItem} from the database
* and notifies the given {@link LiveData}.
*/
@DatabaseExecutor
private void loadAttachmentItem(AttachmentHeader h, boolean needsSize,
MutableLiveData<AttachmentItem> liveData) {
Attachment a;
AttachmentItem item;
try {
a = messagingManager.getAttachment(h);
item = createAttachmentItem(a, needsSize);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
item = new AttachmentItem(h, defaultSize, defaultSize, MISSING);
} catch (DbException e) {
logException(LOG, WARNING, e);
item = new AttachmentItem(h, "", ERROR);
}
liveData.postValue(item);
}
@Override
public AttachmentItem createAttachmentItem(Attachment a,
boolean needsSize) {
AttachmentItem item;
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
AttachmentHeader h = a.getHeader();
if (needsSize) {
InputStream is = new BufferedInputStream(a.getStream());
Size size = imageSizeCalculator.getSize(is, h.getContentType());
tryToClose(is, LOG, WARNING);
item = createAttachmentItem(h, size);
} else {
if (!needsSize) {
String extension =
imageHelper.getExtensionFromMimeType(h.getContentType());
State state = AVAILABLE;
boolean hasError = false;
if (extension == null) {
extension = "";
state = ERROR;
hasError = true;
}
item = new AttachmentItem(h, extension, state);
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
}
return item;
}
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
InputStream is = new BufferedInputStream(a.getStream());
Size size = imageSizeCalculator.getSize(is, h.getContentType());
// calculate thumbnail size
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
if (!size.error) {
@@ -227,9 +104,8 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
hasError = true;
}
if (extension == null) extension = "";
State state = hasError ? ERROR : AVAILABLE;
return new AttachmentItem(h, size.width, size.height,
extension, thumbnailSize.width, thumbnailSize.height, state);
return new AttachmentItem(h, size.width, size.height, extension,
thumbnailSize.width, thumbnailSize.height, hasError);
}
private Size getThumbnailSize(int width, int height, String mimeType) {

View File

@@ -25,6 +25,7 @@ import androidx.annotation.UiThread;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@@ -54,7 +55,7 @@ abstract class BasePostFragment extends BaseFragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// retrieve MessageId of blog post from arguments
byte[] p = requireArguments().getByteArray(POST_ID);
byte[] p = requireNonNull(getArguments()).getByteArray(POST_ID);
if (p == null) throw new IllegalStateException("No post ID in args");
postId = new MessageId(p);

View File

@@ -46,6 +46,7 @@ import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.widget.Toast.LENGTH_SHORT;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@@ -96,14 +97,14 @@ public class BlogFragment extends BaseFragment
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
Bundle args = requireNonNull(getArguments());
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
groupId = new GroupId(b);
View v = inflater.inflate(R.layout.fragment_blog, container, false);
adapter = new BlogPostAdapter(requireActivity(), this,
adapter = new BlogPostAdapter(requireNonNull(getActivity()), this,
getFragmentManager());
list = v.findViewById(R.id.postList);
layoutManager = new LinearLayoutManager(getActivity());
@@ -195,8 +196,7 @@ public class BlogFragment extends BaseFragment
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
public void onActivityResult(int request, int result, Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {

View File

@@ -35,6 +35,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import static android.app.Activity.RESULT_OK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
@@ -78,7 +79,7 @@ public class FeedFragment extends BaseFragment implements
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(R.string.blogs_button);
requireNonNull(getActivity()).setTitle(R.string.blogs_button);
View v = inflater.inflate(R.layout.fragment_blog, container, false);
@@ -102,8 +103,7 @@ public class FeedFragment extends BaseFragment implements
}
@Override
public void onActivityResult(int requestCode, int resultCode,
@Nullable Intent data) {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// The BlogPostAddedEvent arrives when the controller is not listening

View File

@@ -18,6 +18,7 @@ import javax.inject.Inject;
import androidx.annotation.UiThread;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@UiThread
@@ -53,7 +54,7 @@ public class FeedPostFragment extends BasePostFragment {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
Bundle args = requireNonNull(getArguments());
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
blogId = new GroupId(b);

View File

@@ -74,7 +74,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
Bundle args = requireNonNull(getArguments());
GroupId blogId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =

View File

@@ -62,6 +62,7 @@ import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
import static androidx.core.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -121,7 +122,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(R.string.contact_list_button);
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.fragment_contact_list,
container, false);
@@ -273,8 +274,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactAddedEvent ||
@@ -316,7 +317,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@UiThread
private void showSnackBar() {
if (snackbar != null) return;
View v = requireView();
View v = requireNonNull(getView());
int stringRes = R.string.pending_contact_requests_snackbar;
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, view -> showPendingContactList())

View File

@@ -33,6 +33,7 @@ import androidx.lifecycle.ViewModelProviders;
import static android.content.Context.CLIPBOARD_SERVICE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
@@ -82,8 +83,8 @@ public class LinkExchangeFragment extends BaseFragment
linkInput.setText(viewModel.getRemoteHandshakeLink());
}
clipboard = (ClipboardManager)
requireContext().getSystemService(CLIPBOARD_SERVICE);
clipboard = (ClipboardManager) requireNonNull(
getContext().getSystemService(CLIPBOARD_SERVICE));
Button pasteButton = v.findViewById(R.id.pasteButton);
pasteButton.setOnClickListener(view -> {
@@ -106,7 +107,7 @@ public class LinkExchangeFragment extends BaseFragment
@Override
public void onGlobalLayout() {
ScrollView scrollView = (ScrollView) requireView();
ScrollView scrollView = (ScrollView) requireNonNull(getView());
View layout = scrollView.getChildAt(0);
int scrollBy = layout.getHeight() - scrollView.getHeight();
if (scrollBy > 0) {
@@ -120,7 +121,7 @@ public class LinkExchangeFragment extends BaseFragment
}
private void onHandshakeLinkLoaded(String link) {
View v = requireView();
View v = requireNonNull(getView());
TextView linkView = v.findViewById(R.id.linkView);
linkView.setText(link);

View File

@@ -31,7 +31,6 @@ import javax.inject.Inject;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -118,8 +117,7 @@ public class NicknameFragment extends BaseFragment {
addButton.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.getAddContactResult().observe(owner, result -> {
viewModel.getAddContactResult().observe(this, result -> {
if (result == null) return;
if (result.hasError())
handleException(name, requireNonNull(result.getException()));

View File

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

View File

@@ -28,6 +28,7 @@ import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
@@ -64,16 +65,17 @@ import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
import org.briarproject.briar.api.conversation.ConversationRequest;
import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import java.util.ArrayList;
@@ -95,7 +97,6 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -117,6 +118,8 @@ import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimati
import static androidx.core.view.ViewCompat.setTransitionName;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
@@ -133,15 +136,12 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRO
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES_AUTO_DELETE;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -185,6 +185,8 @@ public class ConversationActivity extends BriarActivity
volatile GroupInvitationManager groupInvitationManager;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
new ConcurrentHashMap<>();
private final Observer<String> contactNameObserver = name -> {
requireNonNull(name);
loadMessages();
@@ -275,11 +277,15 @@ public class ConversationActivity extends BriarActivity
ImagePreview imagePreview = findViewById(R.id.imagePreview);
sendController = new TextAttachmentController(textInputView,
imagePreview, this, viewModel);
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
if (format != TEXT_ONLY) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean hasSupport) {
if (hasSupport != null && hasSupport) {
// TODO: remove cast when removing feature flag
((TextAttachmentController) sendController)
.setImagesSupported();
viewModel.hasImageSupport().removeObserver(this);
}
}
});
} else {
@@ -382,12 +388,6 @@ public class ConversationActivity extends BriarActivity
// enable alias action if available
observeOnce(viewModel.getContact(), this, contact ->
menu.findItem(R.id.action_set_alias).setEnabled(true));
// show auto-delete timer setting only, if contacts supports it
observeOnce(viewModel.getPrivateMessageFormat(), this, format -> {
boolean visible = format == TEXT_IMAGES_AUTO_DELETE;
menu.findItem(R.id.action_conversation_settings)
.setVisible(visible);
});
return super.onCreateOptionsMenu(menu);
}
@@ -409,12 +409,6 @@ public class ConversationActivity extends BriarActivity
AliasDialogFragment.newInstance().show(
getSupportFragmentManager(), AliasDialogFragment.TAG);
return true;
case R.id.action_conversation_settings:
if (contactId == null) return false;
intent = new Intent(this, ConversationSettingsActivity.class);
intent.putExtra(CONTACT_ID, contactId.getInt());
startActivity(intent);
return true;
case R.id.action_delete_all_messages:
askToDeleteAllMessages();
return true;
@@ -546,7 +540,6 @@ public class ConversationActivity extends BriarActivity
});
}
@DatabaseExecutor
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
try {
MessageId id = h.getId();
@@ -563,11 +556,21 @@ public class ConversationActivity extends BriarActivity
// images we use a grid so the size is fixed
List<AttachmentHeader> headers = h.getAttachmentHeaders();
if (headers.size() == 1) {
LOG.info("Eagerly loading image size for latest message");
AttachmentHeader header = headers.get(0);
// get the item to retrieve its size
attachmentRetriever
.cacheAttachmentItemWithSize(h.getId(), header);
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
if (items == null) {
LOG.info("Eagerly loading image size for latest message");
AttachmentHeader header = headers.get(0);
try {
Attachment a = attachmentRetriever
.getMessageAttachment(header);
AttachmentItem item =
attachmentRetriever.getAttachmentItem(a, true);
attachmentRetriever.cachePut(id, singletonList(item));
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
}
}
}
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -648,18 +651,59 @@ public class ConversationActivity extends BriarActivity
&& adapter.isScrolledToBottom(layoutManager);
}
@UiThread
private void updateMessageAttachment(MessageId m, AttachmentItem item) {
Pair<Integer, ConversationMessageItem> pair = adapter.getMessageItem(m);
if (pair != null && pair.getSecond().updateAttachments(item)) {
boolean scroll = shouldScrollWhenUpdatingMessage();
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
}
private void loadMessageAttachments(PrivateMessageHeader h) {
// TODO: Use placeholders for missing/invalid attachments
runOnDbThread(() -> {
try {
// TODO move getting the items off to IoExecutor, if size == 1
List<AttachmentHeader> headers = h.getAttachmentHeaders();
boolean needsSize = headers.size() == 1;
List<AttachmentItem> items = new ArrayList<>(headers.size());
for (AttachmentHeader header : headers) {
try {
Attachment a = attachmentRetriever
.getMessageAttachment(header);
AttachmentItem item = attachmentRetriever
.getAttachmentItem(a, needsSize);
items.add(item);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
return;
}
}
// Don't cache items unless all are present and valid
attachmentRetriever.cachePut(h.getId(), items);
displayMessageAttachments(h.getId(), items);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayMessageAttachments(MessageId m,
List<AttachmentItem> items) {
runOnUiThreadUnlessDestroyed(() -> {
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setAttachments(items);
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received");
onAttachmentReceived(a.getMessageId());
}
}
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) {
@@ -667,8 +711,8 @@ public class ConversationActivity extends BriarActivity
supportFinishAfterTransition();
}
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
if (p.getContactId().equals(contactId)) {
LOG.info("Message received, adding");
onNewConversationMessage(p.getMessageHeader());
@@ -719,6 +763,15 @@ public class ConversationActivity extends BriarActivity
scrollToBottom();
}
@UiThread
private void onAttachmentReceived(MessageId attachmentId) {
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
if (h != null) {
LOG.info("Missing attachment received");
loadMessageAttachments(h);
}
}
@UiThread
private void onNewConversationMessage(ConversationMessageHeader h) {
if (h instanceof ConversationRequest ||
@@ -727,7 +780,7 @@ public class ConversationActivity extends BriarActivity
observeOnce(viewModel.getContactDisplayName(), this,
name -> addConversationItem(h.accept(visitor)));
} else {
// visitor also loads message text and attachments (if existing)
// visitor also loads message text (if existing)
addConversationItem(h.accept(visitor));
}
}
@@ -766,10 +819,18 @@ public class ConversationActivity extends BriarActivity
List<AttachmentHeader> attachmentHeaders) {
if (isNullOrEmpty(text) && attachmentHeaders.isEmpty())
throw new AssertionError();
viewModel.sendMessage(text, attachmentHeaders);
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
viewModel.sendMessage(text, attachmentHeaders, timestamp);
textInputView.clearText();
}
private long getMinTimestampForNewMessage() {
// Don't use an earlier timestamp than the newest message
ConversationItem item = adapter.getLastItem();
return item == null ? 0 : item.getTime() + 1;
}
private void onAddedPrivateMessage(@Nullable PrivateMessageHeader h) {
if (h == null) return;
addConversationItem(h.accept(visitor));
@@ -974,11 +1035,13 @@ public class ConversationActivity extends BriarActivity
adapter.notifyItemChanged(position, item);
}
runOnDbThread(() -> {
long timestamp = System.currentTimeMillis();
timestamp = Math.max(timestamp, getMinTimestampForNewMessage());
try {
switch (item.getRequestType()) {
case INTRODUCTION:
respondToIntroductionRequest(item.getSessionId(),
accept);
accept, timestamp);
break;
case FORUM:
respondToForumRequest(item.getSessionId(), accept);
@@ -1044,9 +1107,8 @@ public class ConversationActivity extends BriarActivity
i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item));
i.putExtra(NAME, name);
i.putExtra(DATE, messageItem.getTime());
i.putExtra(ITEM_ID, messageItem.getId().getBytes());
// restoring list position should not trigger android bug #224270
String transitionName = item.getTransitionName(messageItem.getId());
String transitionName = item.getTransitionName();
ActivityOptionsCompat options =
makeSceneTransitionAnimation(this, view, transitionName);
ActivityCompat.startActivity(this, i, options.toBundle());
@@ -1054,8 +1116,9 @@ public class ConversationActivity extends BriarActivity
@DatabaseExecutor
private void respondToIntroductionRequest(SessionId sessionId,
boolean accept) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, accept);
boolean accept, long time) throws DbException {
introductionManager.respondToIntroduction(contactId, sessionId, time,
accept);
}
@DatabaseExecutor
@@ -1084,41 +1147,15 @@ public class ConversationActivity extends BriarActivity
return text;
}
/**
* Called by {@link PrivateMessageHeader#accept(ConversationMessageVisitor)}
*/
@Override
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
List<LiveData<AttachmentItem>> liveDataList =
attachmentRetriever.getAttachmentItems(h);
List<AttachmentItem> items = new ArrayList<>(liveDataList.size());
for (LiveData<AttachmentItem> liveData : liveDataList) {
// first remove all our observers to avoid having more than one
// in case we reload the conversation, e.g. after deleting messages
liveData.removeObservers(this);
// add a new observer
liveData.observe(this, new AttachmentObserver(h.getId(), liveData));
items.add(requireNonNull(liveData.getValue()));
}
return items;
}
private class AttachmentObserver implements Observer<AttachmentItem> {
private final MessageId conversationMessageId;
private final LiveData<AttachmentItem> liveData;
private AttachmentObserver(MessageId conversationMessageId,
LiveData<AttachmentItem> liveData) {
this.conversationMessageId = conversationMessageId;
this.liveData = liveData;
}
@Override
public void onChanged(AttachmentItem attachmentItem) {
updateMessageAttachment(conversationMessageId, attachmentItem);
if (attachmentItem.getState().isFinal())
liveData.removeObserver(this);
List<AttachmentItem> attachments =
attachmentRetriever.cacheGet(h.getId());
if (attachments == null) {
loadMessageAttachments(h);
return emptyList();
}
return attachments;
}
}

View File

@@ -22,7 +22,7 @@ abstract class ConversationItem {
protected String text;
private final MessageId id;
private final GroupId groupId;
private final long time, autoDeleteTimer;
private final long time;
private final boolean isIncoming;
private boolean read, sent, seen;
@@ -32,7 +32,6 @@ abstract class ConversationItem {
this.id = h.getId();
this.groupId = h.getGroupId();
this.time = h.getTimestamp();
this.autoDeleteTimer = h.getAutoDeleteTimer();
this.read = h.isRead();
this.sent = h.isSent();
this.seen = h.isSeen();
@@ -69,10 +68,6 @@ abstract class ConversationItem {
return time;
}
public long getAutoDeleteTimer() {
return autoDeleteTimer;
}
/**
* Only useful for incoming messages.
*/

View File

@@ -12,11 +12,8 @@ import androidx.annotation.UiThread;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.util.StringUtils.trim;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@UiThread
@NotNullByDefault
@@ -29,7 +26,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
private final OutItemViewHolder outViewHolder;
private final TextView text;
protected final TextView time;
private final View bomb;
@Nullable
private String itemKey = null;
@@ -42,7 +38,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
layout = v.findViewById(R.id.layout);
text = v.findViewById(R.id.text);
time = v.findViewById(R.id.time);
bomb = v.findViewById(R.id.bomb);
}
@CallSuper
@@ -57,9 +52,6 @@ abstract class ConversationItemViewHolder extends ViewHolder {
long timestamp = item.getTime();
time.setText(formatDate(time.getContext(), timestamp));
boolean showBomb = item.getAutoDeleteTimer() != NO_AUTO_DELETE_TIMER;
bomb.setVisibility(showBomb ? VISIBLE : GONE);
if (outViewHolder != null) outViewHolder.bind(item);
}

View File

@@ -9,13 +9,12 @@ import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
import androidx.annotation.LayoutRes;
import androidx.annotation.UiThread;
@NotThreadSafe
@NotNullByDefault
class ConversationMessageItem extends ConversationItem {
private final List<AttachmentItem> attachments;
private List<AttachmentItem> attachments;
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
List<AttachmentItem> attachments) {
@@ -27,14 +26,8 @@ class ConversationMessageItem extends ConversationItem {
return attachments;
}
@UiThread
boolean updateAttachments(AttachmentItem item) {
int pos = attachments.indexOf(item);
if (pos != -1 && attachments.get(pos).getState() != item.getState()) {
attachments.set(pos, item);
return true;
}
return false;
void setAttachments(List<AttachmentItem> attachments) {
this.attachments = attachments;
}
}

View File

@@ -1,80 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsActivity extends BriarActivity implements
BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
private ContactId contactId;
@Override
public void onCreate(@Nullable Bundle bundle) {
super.onCreate(bundle);
Intent i = getIntent();
int id = i.getIntExtra(CONTACT_ID, -1);
if (id == -1) throw new IllegalStateException();
contactId = new ContactId(id);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
setContentView(R.layout.activity_conversation_settings);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ConversationViewModel.class);
}
@Override
public void onResume() {
super.onResume();
// Trigger loading of contact data, noop if data was loaded already.
//
// We can only start loading data *after* we are sure
// the user has signed in. After sign-in, onCreate() isn't run again.
if (signedIn()) viewModel.setContactId(contactId);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return false;
}
}

View File

@@ -1,158 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.widget.SwitchCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConversationSettingsFragment extends BaseFragment {
public static final String TAG =
ConversationSettingsFragment.class.getName();
private static final Logger LOG =
Logger.getLogger(ConversationSettingsFragment.class.getName());
@Inject
ViewModelProvider.Factory viewModelFactory;
@Inject
@DatabaseExecutor
Executor dbExecutor;
@Inject
TransactionManager db;
@Inject
AutoDeleteManager autoDeleteManager;
private ConversationSettingsActivity listener;
private ConversationViewModel viewModel;
private SwitchCompat switchDisappearingMessages;
private volatile boolean disappearingMessages = false;
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (ConversationSettingsActivity) context;
listener.getActivityComponent().inject(this);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView =
inflater.inflate(R.layout.fragment_conversation_settings,
container, false);
switchDisappearingMessages =
contentView.findViewById(R.id.switchDisappearingMessages);
switchDisappearingMessages
.setOnCheckedChangeListener((button, value) -> viewModel
.setAutoDeleteTimerEnabled(value));
TextView buttonLearnMore =
contentView.findViewById(R.id.buttonLearnMore);
buttonLearnMore.setOnClickListener(e -> showLearnMoreDialog());
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
return contentView;
}
@Override
public void onStart() {
super.onStart();
switchDisappearingMessages.setEnabled(false);
loadSettings();
}
private void loadSettings() {
observeOnce(viewModel.getContact(), this, c -> {
dbExecutor.execute(() -> {
try {
db.transaction(false, txn -> {
long timer = autoDeleteManager
.getAutoDeleteTimer(txn, c.getId());
disappearingMessages = timer != NO_AUTO_DELETE_TIMER;
});
listener.runOnUiThreadUnlessDestroyed(
this::displaySettings);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
});
}
private void displaySettings() {
switchDisappearingMessages.setChecked(disappearingMessages);
switchDisappearingMessages.setEnabled(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.help_action, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_help) {
showLearnMoreDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showLearnMoreDialog() {
ConversationSettingsLearnMoreDialog
dialog = new ConversationSettingsLearnMoreDialog();
dialog.show(requireActivity().getSupportFragmentManager(),
ConversationSettingsLearnMoreDialog.TAG);
}
}

View File

@@ -1,44 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
@MethodsNotNullByDefault
public class ConversationSettingsLearnMoreDialog extends DialogFragment {
final static String TAG =
ConversationSettingsLearnMoreDialog.class.getName();
static ConversationSettingsLearnMoreDialog newInstance() {
return new ConversationSettingsLearnMoreDialog();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
FragmentActivity activity = requireActivity();
AlertDialog.Builder builder =
new AlertDialog.Builder(activity, R.style.BriarDialogTheme);
LayoutInflater inflater = LayoutInflater.from(getContext());
View view = inflater.inflate(
R.layout.fragment_conversation_settings_learn_more, null);
builder.setView(view);
builder.setTitle(R.string.disappearing_messages_title);
builder.setNeutralButton(R.string.ok, null);
return builder.create();
}
}

View File

@@ -10,11 +10,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
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.identity.AuthorId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
@@ -29,15 +25,11 @@ import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageFormat;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.util.Collection;
import java.util.List;
@@ -54,7 +46,6 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
@@ -62,15 +53,12 @@ import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
import static org.briarproject.briar.api.autodelete.AutoDeleteConstants.NO_AUTO_DELETE_TIMER;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_IMAGES;
import static org.briarproject.briar.api.messaging.PrivateMessageFormat.TEXT_ONLY;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel
implements EventListener, AttachmentManager {
implements AttachmentManager {
private static final Logger LOG =
private static Logger LOG =
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
@@ -81,15 +69,12 @@ public class ConversationViewModel extends AndroidViewModel
@DatabaseExecutor
private final Executor dbExecutor;
private final TransactionManager db;
private final EventBus eventBus;
private final MessagingManager messagingManager;
private final ContactManager contactManager;
private final SettingsManager settingsManager;
private final PrivateMessageFactory privateMessageFactory;
private final AttachmentRetriever attachmentRetriever;
private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
@Nullable
private ContactId contactId = null;
@@ -99,7 +84,7 @@ public class ConversationViewModel extends AndroidViewModel
private final LiveData<String> contactName =
Transformations.map(contact, UiUtils::getContactDisplayName);
private final LiveData<GroupId> messagingGroupId;
private final MutableLiveData<PrivateMessageFormat> privateMessageFormat =
private final MutableLiveData<Boolean> imageSupport =
new MutableLiveData<>();
private final MutableLiveEvent<Boolean> showImageOnboarding =
new MutableLiveEvent<>();
@@ -116,51 +101,30 @@ public class ConversationViewModel extends AndroidViewModel
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
TransactionManager db,
EventBus eventBus,
MessagingManager messagingManager,
ContactManager contactManager,
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager) {
AttachmentCreator attachmentCreator) {
super(application);
this.dbExecutor = dbExecutor;
this.db = db;
this.eventBus = eventBus;
this.messagingManager = messagingManager;
this.contactManager = contactManager;
this.settingsManager = settingsManager;
this.privateMessageFactory = privateMessageFactory;
this.attachmentRetriever = attachmentRetriever;
this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
messagingGroupId = Transformations
.map(contact, c -> messagingManager.getContactGroup(c).getId());
contactDeleted.setValue(false);
eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
attachmentCreator.cancel(); // also deletes unsent attachments
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received");
dbExecutor.execute(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId()));
}
}
attachmentCreator.deleteUnsentAttachments();
}
/**
@@ -219,13 +183,16 @@ public class ConversationViewModel extends AndroidViewModel
}
@UiThread
void sendMessage(@Nullable String text, List<AttachmentHeader> headers) {
void sendMessage(@Nullable String text,
List<AttachmentHeader> headers, long timestamp) {
// messagingGroupId is loaded with the contact
observeForeverOnce(messagingGroupId, groupId -> {
requireNonNull(groupId);
observeForeverOnce(privateMessageFormat, format ->
storeMessage(requireNonNull(contactId), groupId, text,
headers, format));
observeForeverOnce(imageSupport, hasImageSupport -> {
requireNonNull(hasImageSupport);
createMessage(groupId, text, headers, timestamp,
hasImageSupport);
});
});
}
@@ -255,10 +222,10 @@ public class ConversationViewModel extends AndroidViewModel
@DatabaseExecutor
private void checkFeaturesAndOnboarding(ContactId c) throws DbException {
// check if images and auto-deletion are supported
PrivateMessageFormat format = db.transactionWithResult(true, txn ->
messagingManager.getContactMessageFormat(txn, c));
privateMessageFormat.postValue(format);
// 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.getContacts();
@@ -267,7 +234,7 @@ public class ConversationViewModel extends AndroidViewModel
// we only show one onboarding dialog at a time
Settings settings = settingsManager.getSettings(SETTINGS_NAMESPACE);
if (format != TEXT_ONLY &&
if (imagesSupported &&
settings.getBoolean(SHOW_ONBOARDING_IMAGE, true)) {
onOnboardingShown(SHOW_ONBOARDING_IMAGE);
showImageOnboarding.postEvent(true);
@@ -285,67 +252,38 @@ public class ConversationViewModel extends AndroidViewModel
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
}
private PrivateMessage createMessage(Transaction txn, ContactId c,
GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, PrivateMessageFormat format)
throws DbException {
long timestamp =
conversationManager.getTimestampForOutgoingMessage(txn, c);
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) {
try {
if (format == TEXT_ONLY) {
return privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
} else if (format == TEXT_IMAGES) {
return privateMessageFactory.createPrivateMessage(groupId,
PrivateMessage pm;
if (hasImageSupport) {
pm = privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers);
} else {
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
return privateMessageFactory.createPrivateMessage(groupId,
timestamp, text, headers, timer);
pm = privateMessageFactory.createLegacyPrivateMessage(
groupId, timestamp, requireNonNull(text));
}
storeMessage(pm);
} catch (FormatException e) {
throw new AssertionError(e);
}
}
@UiThread
private void storeMessage(ContactId c, GroupId groupId,
@Nullable String text, List<AttachmentHeader> headers,
PrivateMessageFormat format) {
private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
dbExecutor.execute(() -> {
try {
db.transaction(false, txn -> {
long start = now();
PrivateMessage m = createMessage(txn, c, groupId, text,
headers, format);
messagingManager.addLocalMessage(txn, m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders(),
m.getAutoDeleteTimer());
// TODO add text to cache when available here
MessageId id = message.getId();
txn.attach(() -> attachmentCreator.onAttachmentsSent(id));
addedHeader.postEvent(h);
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
void setAutoDeleteTimerEnabled(boolean enabled) {
final long timer = enabled ? DAYS.toMillis(7) : NO_AUTO_DELETE_TIMER;
// ContactId is set before menu gets inflated and UI interaction
final ContactId c = requireNonNull(contactId);
dbExecutor.execute(() -> {
try {
db.transaction(false, txn ->
autoDeleteManager.setAutoDeleteTimer(txn, c, timer));
long start = now();
messagingManager.addLocalMessage(m);
logDuration(LOG, "Storing message", start);
Message message = m.getMessage();
PrivateMessageHeader h = new PrivateMessageHeader(
message.getId(), message.getGroupId(),
message.getTimestamp(), true, true, false, false,
m.hasText(), m.getAttachmentHeaders());
// TODO add text to cache when available here
addedHeader.postEvent(h);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
@@ -368,8 +306,8 @@ public class ConversationViewModel extends AndroidViewModel
return contactName;
}
LiveData<PrivateMessageFormat> getPrivateMessageFormat() {
return privateMessageFormat;
LiveData<Boolean> hasImageSupport() {
return imageSupport;
}
LiveEvent<Boolean> showImageOnboarding() {

View File

@@ -16,7 +16,6 @@ import com.google.android.material.appbar.AppBarLayout;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
@@ -68,7 +67,6 @@ public class ImageActivity extends BriarActivity
final static String ATTACHMENT_POSITION = "position";
final static String NAME = "name";
final static String DATE = "date";
final static String ITEM_ID = "itemId";
@RequiresApi(api = 16)
private final static int UI_FLAGS_DEFAULT =
@@ -82,7 +80,6 @@ public class ImageActivity extends BriarActivity
private AppBarLayout appBarLayout;
private ViewPager viewPager;
private List<AttachmentItem> attachments;
private MessageId conversationMessageId;
@Override
public void injectActivity(ActivityComponent component) {
@@ -101,20 +98,9 @@ public class ImageActivity extends BriarActivity
setSceneTransitionAnimation(transition, null, transition);
}
// Intent Extras
Intent i = getIntent();
attachments =
requireNonNull(i.getParcelableArrayListExtra(ATTACHMENTS));
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
if (position == -1) throw new IllegalStateException();
String name = i.getStringExtra(NAME);
long time = i.getLongExtra(DATE, 0);
byte[] messageIdBytes = requireNonNull(i.getByteArrayExtra(ITEM_ID));
// get View Model
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ImageViewModel.class);
viewModel.expectAttachments(attachments);
viewModel.getSaveState().observeEvent(this,
this::onImageSaveStateChanged);
@@ -138,11 +124,16 @@ public class ImageActivity extends BriarActivity
TextView contactName = toolbar.findViewById(R.id.contactName);
TextView dateView = toolbar.findViewById(R.id.dateView);
// Set contact name and message time
// Intent Extras
Intent i = getIntent();
attachments = i.getParcelableArrayListExtra(ATTACHMENTS);
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
if (position == -1) throw new IllegalStateException();
String name = i.getStringExtra(NAME);
long time = i.getLongExtra(DATE, 0);
String date = formatDateAbsolute(this, time);
contactName.setText(name);
dateView.setText(date);
conversationMessageId = new MessageId(messageIdBytes);
// Set up image ViewPager
viewPager = findViewById(R.id.viewPager);
@@ -329,8 +320,8 @@ public class ImageActivity extends BriarActivity
@Override
public Fragment getItem(int position) {
Fragment f = ImageFragment.newInstance(
attachments.get(position), conversationMessageId, isFirst);
Fragment f = ImageFragment
.newInstance(attachments.get(position), isFirst);
isFirst = false;
return f;
}

View File

@@ -49,8 +49,7 @@ class ImageAdapter extends Adapter<ImageViewHolder> {
public ImageViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(
R.layout.list_item_image, viewGroup, false);
requireNonNull(conversationItem);
return new ImageViewHolder(v, imageSize, conversationItem.getId());
return new ImageViewHolder(v, imageSize);
}
@Override

View File

@@ -15,7 +15,6 @@ import com.bumptech.glide.request.target.Target;
import com.github.chrisbanes.photoview.PhotoView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.attachment.AttachmentItem;
@@ -24,10 +23,8 @@ import org.briarproject.briar.android.conversation.glide.GlideApp;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.inject.Inject;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -35,36 +32,27 @@ import static android.os.Build.VERSION.SDK_INT;
import static android.widget.ImageView.ScaleType.FIT_START;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
@MethodsNotNullByDefault
@ParametersAreNonnullByDefault
public class ImageFragment extends Fragment
implements RequestListener<Drawable> {
public class ImageFragment extends Fragment {
private final static String IS_FIRST = "isFirst";
@DrawableRes
private static final int ERROR_RES = R.drawable.ic_image_broken;
@Inject
ViewModelProvider.Factory viewModelFactory;
private AttachmentItem attachment;
private boolean isFirst;
private MessageId conversationItemId;
private ImageViewModel viewModel;
private PhotoView photoView;
static ImageFragment newInstance(AttachmentItem a,
MessageId conversationMessageId, boolean isFirst) {
static ImageFragment newInstance(AttachmentItem a, boolean isFirst) {
ImageFragment f = new ImageFragment();
Bundle args = new Bundle();
args.putParcelable(ATTACHMENT_POSITION, a);
args.putBoolean(IS_FIRST, isFirst);
args.putByteArray(ITEM_ID, conversationMessageId.getBytes());
f.setArguments(args);
return f;
}
@@ -79,11 +67,9 @@ public class ImageFragment extends Fragment
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = requireArguments();
Bundle args = requireNonNull(getArguments());
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
isFirst = args.getBoolean(IS_FIRST);
conversationItemId =
new MessageId(requireNonNull(args.getByteArray(ITEM_ID)));
}
@Nullable
@@ -94,76 +80,57 @@ public class ImageFragment extends Fragment
View v = inflater.inflate(R.layout.fragment_image, container,
false);
viewModel = ViewModelProviders.of(requireActivity(),
viewModel = ViewModelProviders.of(requireNonNull(getActivity()),
viewModelFactory).get(ImageViewModel.class);
photoView = v.findViewById(R.id.photoView);
photoView.setScaleLevels(1, 2, 4);
photoView.setOnClickListener(view -> viewModel.clickImage());
if (attachment.getState() == AVAILABLE) {
loadImage();
// postponed transition will be started when Image was loaded
} else if (attachment.getState() == ERROR) {
photoView.setImageResource(ERROR_RES);
startPostponedTransition();
} else {
photoView.setImageResource(R.drawable.ic_image_missing);
startPostponedTransition();
// state is not final, so observe state changes
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.getOnAttachmentReceived(attachment.getMessageId())
.observeEvent(owner, this::onAttachmentReceived);
}
// Request Listener
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
if (getActivity() != null && isFirst)
getActivity().supportStartPostponedEnterTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource,
boolean isFirstResource) {
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
// set transition name only when not animatable,
// because the animation won't start otherwise
photoView.setTransitionName(
attachment.getTransitionName());
}
// Move image to the top if overlapping toolbar
if (viewModel.isOverlappingToolbar(photoView, resource)) {
photoView.setScaleType(FIT_START);
}
if (getActivity() != null && isFirst) {
getActivity().supportStartPostponedEnterTransition();
}
return false;
}
};
// Load Image
GlideApp.with(this)
.load(attachment)
// TODO allow if size < maxTextureSize ?
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE)
.error(R.drawable.ic_image_broken)
.addListener(listener)
.into(photoView);
return v;
}
private void loadImage() {
GlideApp.with(this)
.load(attachment)
// TODO allow if size < maxTextureSize ?
// .override(SIZE_ORIGINAL)
.diskCacheStrategy(NONE)
.error(ERROR_RES)
.addListener(this)
.into(photoView);
}
private void onAttachmentReceived(Boolean received) {
if (received) loadImage();
}
@Override
public boolean onLoadFailed(@Nullable GlideException e,
Object model, Target<Drawable> target,
boolean isFirstResource) {
startPostponedTransition();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource,
boolean isFirstResource) {
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
// set transition name only when not animatable,
// because the animation won't start otherwise
photoView.setTransitionName(
attachment.getTransitionName(conversationItemId));
}
// Move image to the top if overlapping toolbar
if (viewModel.isOverlappingToolbar(photoView, resource)) {
photoView.setScaleType(FIT_START);
}
startPostponedTransition();
return false;
}
private void startPostponedTransition() {
if (getActivity() != null && isFirst) {
getActivity().supportStartPostponedEnterTransition();
}
}
}

View File

@@ -7,7 +7,6 @@ import android.widget.ImageView;
import com.bumptech.glide.load.Transformation;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.conversation.glide.BriarImageTransformation;
@@ -19,12 +18,8 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import androidx.recyclerview.widget.StaggeredGridLayoutManager.LayoutParams;
import static android.os.Build.VERSION.SDK_INT;
import static android.widget.ImageView.ScaleType.CENTER_CROP;
import static android.widget.ImageView.ScaleType.FIT_CENTER;
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.AVAILABLE;
import static org.briarproject.briar.android.attachment.AttachmentItem.State.ERROR;
@NotNullByDefault
class ImageViewHolder extends ViewHolder {
@@ -34,33 +29,25 @@ class ImageViewHolder extends ViewHolder {
protected final ImageView imageView;
private final int imageSize;
private final MessageId conversationItemId;
ImageViewHolder(View v, int imageSize, MessageId conversationItemId) {
ImageViewHolder(View v, int imageSize) {
super(v);
imageView = v.findViewById(R.id.imageView);
this.imageSize = imageSize;
this.conversationItemId = conversationItemId;
}
void bind(AttachmentItem attachment, Radii r, boolean single,
boolean needsStretch) {
setImageViewDimensions(attachment, single, needsStretch);
if (attachment.getState() != AVAILABLE) {
GlideApp.with(imageView).clear(imageView);
if (attachment.getState() == ERROR) {
imageView.setImageResource(ERROR_RES);
} else {
imageView.setImageResource(R.drawable.ic_image_missing);
}
imageView.setScaleType(FIT_CENTER);
if (attachment.hasError()) {
GlideApp.with(imageView)
.clear(imageView);
imageView.setImageResource(ERROR_RES);
} else {
setImageViewDimensions(attachment, single, needsStretch);
loadImage(attachment, r);
imageView.setScaleType(CENTER_CROP);
}
if (SDK_INT >= 21) {
imageView.setTransitionName(
attachment.getTransitionName(conversationItemId));
if (SDK_INT >= 21) {
imageView.setTransitionName(attachment.getTransitionName());
}
}
}

View File

@@ -7,18 +7,13 @@ import android.view.View;
import org.briarproject.bramble.api.db.DatabaseExecutor;
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.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.io.File;
import java.io.FileOutputStream;
@@ -27,8 +22,6 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -42,28 +35,22 @@ import androidx.lifecycle.AndroidViewModel;
import static android.media.MediaScannerConnection.scanFile;
import static android.os.Environment.DIRECTORY_PICTURES;
import static android.os.Environment.getExternalStoragePublicDirectory;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class ImageViewModel extends AndroidViewModel implements EventListener {
public class ImageViewModel extends AndroidViewModel {
private static Logger LOG = getLogger(ImageViewModel.class.getName());
private final MessagingManager messagingManager;
private final EventBus eventBus;
@DatabaseExecutor
private final Executor dbExecutor;
@IoExecutor
private final Executor ioExecutor;
private boolean receivedAttachmentsInitialized = false;
private HashMap<MessageId, MutableLiveEvent<Boolean>> receivedAttachments =
new HashMap<>();
/**
* true means there was an error saving the image, false if image was saved.
*/
@@ -75,60 +62,13 @@ public class ImageViewModel extends AndroidViewModel implements EventListener {
@Inject
ImageViewModel(Application application,
MessagingManager messagingManager, EventBus eventBus,
MessagingManager messagingManager,
@DatabaseExecutor Executor dbExecutor,
@IoExecutor Executor ioExecutor) {
super(application);
this.messagingManager = messagingManager;
this.eventBus = eventBus;
this.dbExecutor = dbExecutor;
this.ioExecutor = ioExecutor;
eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
@UiThread
@Override
public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
MessageId id = ((AttachmentReceivedEvent) e).getMessageId();
MutableLiveEvent<Boolean> oldEvent;
if (receivedAttachmentsInitialized) {
oldEvent = receivedAttachments.get(id);
if (oldEvent != null) oldEvent.postEvent(true);
} else {
receivedAttachments.put(id, new MutableLiveEvent<>(true));
}
}
}
@UiThread
public void expectAttachments(List<AttachmentItem> attachments) {
for (AttachmentItem item : attachments) {
// no need to track items that are in a final state already
if (item.getState().isFinal()) continue;
// add new live events, if not already added by eventOccurred()
MessageId id = item.getMessageId();
if (!receivedAttachments.containsKey(id)) {
receivedAttachments.put(id, new MutableLiveEvent<>());
}
}
receivedAttachmentsInitialized = true;
}
/**
* Returns a LiveData for attachments in a non-final state.
* Note that you need to call {@link #expectAttachments(List)} first.
*/
@UiThread
LiveEvent<Boolean> getOnAttachmentReceived(MessageId messageId) {
return requireNonNull(receivedAttachments.get(messageId));
}
void clickImage() {

View File

@@ -47,6 +47,7 @@ import androidx.annotation.UiThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -94,7 +95,7 @@ public class ForumListFragment extends BaseEventFragment implements
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(R.string.forums_button);
requireNonNull(getActivity()).setTitle(R.string.forums_button);
View contentView =
inflater.inflate(R.layout.fragment_forum_list, container,

View File

@@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@@ -83,7 +84,7 @@ public class ContactChooserFragment extends BaseFragment {
Contact c2 = item.getContact();
showMessageScreen(c1, c2);
};
adapter = new ContactListAdapter(requireActivity(),
adapter = new ContactListAdapter(requireNonNull(getActivity()),
onContactClickListener);
list = contentView.findViewById(R.id.list);
@@ -91,7 +92,8 @@ public class ContactChooserFragment extends BaseFragment {
list.setAdapter(adapter);
list.setEmptyText(R.string.no_contacts);
contactId = new ContactId(requireArguments().getInt(CONTACT_ID));
contactId = new ContactId(
requireNonNull(getArguments()).getInt(CONTACT_ID));
return contentView;
}

View File

@@ -39,6 +39,7 @@ import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@@ -101,7 +102,7 @@ public class IntroductionMessageFragment extends BaseFragment
}
// get contact IDs from fragment arguments
Bundle args = requireArguments();
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) {
@@ -211,7 +212,8 @@ public class IntroductionMessageFragment extends BaseFragment
introductionActivity.runOnDbThread(() -> {
// actually make the introduction
try {
introductionManager.makeIntroduction(c1, c2, text);
long timestamp = System.currentTimeMillis();
introductionManager.makeIntroduction(c1, c2, text, timestamp);
} catch (DbException e) {
logException(LOG, WARNING, e);
introductionError();

View File

@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.annotation.Nullable;
import javax.inject.Inject;
@@ -113,7 +114,9 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
return getString(R.string.exchanging_contact_details);
}
private void showErrorFragment() {
showNextFragment(new ContactExchangeErrorFragment());
protected void showErrorFragment() {
String errorMsg = getString(R.string.connection_error_explanation);
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
showNextFragment(f);
}
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android.keyagreement;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -19,10 +18,7 @@ import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.GONE;
import static org.briarproject.briar.android.util.UiUtils.onSingleLinkClick;
@MethodsNotNullByDefault
@@ -62,12 +58,13 @@ public class ContactExchangeErrorFragment extends BaseFragment {
View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
container, false);
// set optional error message
// set humanized error message
TextView explanation = v.findViewById(R.id.errorMessage);
Bundle args = getArguments();
String errorMessage = args == null ? null : args.getString(ERROR_MSG);
if (errorMessage == null) explanation.setVisibility(GONE);
else explanation.setText(args.getString(ERROR_MSG));
if (args == null) {
throw new IllegalArgumentException("Use newInstance()");
}
explanation.setText(args.getString(ERROR_MSG));
// make feedback link clickable
TextView sendFeedback = v.findViewById(R.id.sendFeedback);
@@ -76,11 +73,7 @@ public class ContactExchangeErrorFragment extends BaseFragment {
// buttons
Button tryAgain = v.findViewById(R.id.tryAgainButton);
tryAgain.setOnClickListener(view -> {
// Recreate the activity so we return to the intro fragment
FragmentActivity activity = requireActivity();
Intent i = new Intent(activity, ContactExchangeActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivity(i);
if (getActivity() != null) getActivity().onBackPressed();
});
Button cancel = v.findViewById(R.id.cancelButton);
cancel.setOnClickListener(view -> finish());

View File

@@ -18,6 +18,7 @@ import org.briarproject.bramble.api.plugin.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
@@ -26,6 +27,7 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.keyagreement.IntroFragment.IntroScreenSeenListener;
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment.KeyAgreementEventListener;
import org.briarproject.briar.android.util.UiUtils;
import java.util.logging.Logger;
@@ -45,7 +47,6 @@ import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
import static android.bluetooth.BluetoothAdapter.ACTION_SCAN_MODE_CHANGED;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
@@ -55,7 +56,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH_DISCOVERABLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PERMISSION_CAMERA_LOCATION;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -118,6 +118,13 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
*/
private boolean continueClicked = false;
/**
* Records whether the Bluetooth adapter was already enabled before we
* asked for Bluetooth discoverability, so we know whether to broadcast a
* {@link BluetoothEnabledEvent}.
*/
private boolean wasAdapterEnabled = false;
/**
* Records whether we've enabled the wifi plugin so we don't enable it more
* than once.
@@ -134,8 +141,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private Permission locationPermission = Permission.UNKNOWN;
private BluetoothDecision bluetoothDecision = BluetoothDecision.UNKNOWN;
private BroadcastReceiver bluetoothReceiver = null;
private Plugin wifiPlugin = null, bluetoothPlugin = null;
private BluetoothAdapter bt = null;
@Override
public void injectActivity(ActivityComponent component) {
@@ -155,9 +160,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
IntentFilter filter = new IntentFilter(ACTION_SCAN_MODE_CHANGED);
bluetoothReceiver = new BluetoothStateReceiver();
registerReceiver(bluetoothReceiver, filter);
wifiPlugin = pluginManager.getPlugin(LanTcpConstants.ID);
bluetoothPlugin = pluginManager.getPlugin(BluetoothConstants.ID);
bt = BluetoothAdapter.getDefaultAdapter();
}
@Override
@@ -193,7 +195,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
showQrCodeFragmentIfAllowed();
}
@SuppressWarnings("StatementWithEmptyBody")
private void showQrCodeFragmentIfAllowed() {
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
if (isWifiReady() && isBluetoothReady()) {
@@ -207,8 +208,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
}
if (bluetoothDecision == BluetoothDecision.UNKNOWN) {
requestBluetoothDiscoverable();
} else if (bluetoothDecision == BluetoothDecision.REFUSED) {
// Ask again when the user clicks "continue"
} else if (shouldEnableBluetooth()) {
LOG.info("Enabling Bluetooth plugin");
hasEnabledBluetooth = true;
@@ -219,50 +218,55 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
}
private boolean areEssentialPermissionsGranted() {
// If the camera permission has been granted, and the location
// permission has been granted or permanently denied, we can continue
return cameraPermission == Permission.GRANTED &&
(SDK_INT < 23 || locationPermission == Permission.GRANTED ||
!isBluetoothSupported());
}
private boolean isBluetoothSupported() {
return bt != null && bluetoothPlugin != null;
(locationPermission == Permission.GRANTED ||
locationPermission == Permission.PERMANENTLY_DENIED);
}
private boolean isWifiReady() {
if (wifiPlugin == null) return true; // Continue without wifi
State state = wifiPlugin.getState();
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return true; // Continue without wifi
State state = p.getState();
// Wait for plugin to become enabled
return state == ACTIVE || state == INACTIVE;
}
private boolean isBluetoothReady() {
if (!isBluetoothSupported()) {
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
bluetoothDecision == BluetoothDecision.WAITING) {
// Wait for decision
return false;
}
if (bluetoothDecision == BluetoothDecision.NO_ADAPTER
|| bluetoothDecision == BluetoothDecision.REFUSED) {
// Continue without Bluetooth
return true;
}
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
bluetoothDecision == BluetoothDecision.WAITING ||
bluetoothDecision == BluetoothDecision.REFUSED) {
// Wait for user to accept
return false;
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) return true; // Continue without Bluetooth
if (bt.getScanMode() != SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
// Wait for adapter to become discoverable
return false;
}
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
if (p == null) return true; // Continue without Bluetooth
// Wait for plugin to become active
return bluetoothPlugin.getState() == ACTIVE;
return p.getState() == ACTIVE;
}
private boolean shouldEnableWifi() {
if (hasEnabledWifi) return false;
if (wifiPlugin == null) return false;
State state = wifiPlugin.getState();
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
if (p == null) return false;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
}
private void requestBluetoothDiscoverable() {
if (!isBluetoothSupported()) {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
showQrCodeFragmentIfAllowed();
} else {
@@ -270,6 +274,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
if (i.resolveActivity(getPackageManager()) != null) {
LOG.info("Asking for Bluetooth discoverability");
bluetoothDecision = BluetoothDecision.WAITING;
wasAdapterEnabled = bt.isEnabled();
startActivityForResult(i, REQUEST_BLUETOOTH_DISCOVERABLE);
} else {
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
@@ -281,8 +286,9 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private boolean shouldEnableBluetooth() {
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
if (hasEnabledBluetooth) return false;
if (!isBluetoothSupported()) return false;
State state = bluetoothPlugin.getState();
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
if (p == null) return false;
State state = p.getState();
return state == STARTING_STOPPING || state == DISABLED;
}
@@ -301,9 +307,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
@Override
public void showNextScreen() {
continueClicked = true;
if (bluetoothDecision == BluetoothDecision.REFUSED) {
bluetoothDecision = BluetoothDecision.UNKNOWN; // Ask again
}
if (checkPermissions()) showQrCodeFragmentIfAllowed();
}
@@ -317,6 +320,11 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} else {
LOG.info("Bluetooth discoverability was accepted");
bluetoothDecision = BluetoothDecision.ACCEPTED;
if (!wasAdapterEnabled) {
LOG.info("Bluetooth adapter was enabled by us");
eventBus.broadcast(new BluetoothEnabledEvent());
wasAdapterEnabled = true;
}
}
showQrCodeFragmentIfAllowed();
} else super.onActivityResult(request, result, data);
@@ -347,17 +355,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
private boolean checkPermissions() {
if (areEssentialPermissionsGranted()) return true;
// If an essential permission has been permanently denied, ask the
// If the camera permission has been permanently denied, ask the
// user to change the setting
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.permission_camera_title,
R.string.permission_camera_denied_body);
return false;
}
if (isBluetoothSupported() &&
locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.permission_location_title,
R.string.permission_location_denied_body);
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(R.string.permission_camera_title);
builder.setMessage(R.string.permission_camera_denied_body);
builder.setPositiveButton(R.string.ok,
UiUtils.getGoToSettingsListener(this));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> supportFinishAfterTransition());
builder.show();
return false;
}
// Should we show the rationale for one or both permissions?
@@ -377,16 +385,6 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
return false;
}
private void showDenialDialog(@StringRes int title, @StringRes int body) {
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(title);
builder.setMessage(body);
builder.setPositiveButton(R.string.ok, getGoToSettingsListener(this));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> supportFinishAfterTransition());
builder.show();
}
private void showRationale(@StringRes int title, @StringRes int body) {
Builder builder = new Builder(this, R.style.BriarDialogTheme);
builder.setTitle(title);
@@ -397,13 +395,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
}
private void requestPermissions() {
String[] permissions;
if (isBluetoothSupported()) {
permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION};
} else {
permissions = new String[] {CAMERA};
}
ActivityCompat.requestPermissions(this, permissions,
ActivityCompat.requestPermissions(this,
new String[] {CAMERA, ACCESS_FINE_LOCATION},
REQUEST_PERMISSION_CAMERA_LOCATION);
}
@@ -420,15 +413,12 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
} else {
cameraPermission = Permission.PERMANENTLY_DENIED;
}
if (isBluetoothSupported()) {
if (gotPermission(ACCESS_FINE_LOCATION, permissions,
grantResults)) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
if (gotPermission(ACCESS_FINE_LOCATION, permissions, grantResults)) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRationale(ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
// If a permission dialog has been shown, showing the QR code fragment
// on this call path would cause a crash due to

View File

@@ -54,6 +54,7 @@ import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.widget.LinearLayout.HORIZONTAL;
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.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -66,7 +67,6 @@ public class KeyAgreementFragment extends BaseEventFragment
static final String TAG = KeyAgreementFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
@SuppressWarnings("CharsetObjectCanBeUsed") // Requires minSdkVersion >= 19
private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
@Inject
@@ -138,7 +138,8 @@ public class KeyAgreementFragment extends BaseEventFragment
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
requireNonNull(getActivity())
.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
}

View File

@@ -16,7 +16,6 @@ import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -52,8 +51,7 @@ public class OpenDatabaseFragment extends BaseFragment {
StartupViewModel viewModel = ViewModelProviders.of(requireActivity(),
viewModelFactory).get(StartupViewModel.class);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.getState().observe(owner, this::onStateChanged);
viewModel.getState().observe(this, this::onStateChanged);
return v;
}

View File

@@ -23,7 +23,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
@@ -68,8 +67,7 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
.get(StartupViewModel.class);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.getPasswordValidated().observeEvent(owner, result -> {
viewModel.getPasswordValidated().observeEvent(this, result -> {
if (result != SUCCESS) onPasswordInvalid(result);
});

View File

@@ -11,7 +11,6 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.google.android.material.navigation.NavigationView;
@@ -57,10 +56,8 @@ import androidx.core.content.ContextCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -78,8 +75,6 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
import static org.briarproject.briar.android.util.UiUtils.getDaysUntilExpiry;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.android.util.UiUtils.resolveColorAttribute;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -102,9 +97,6 @@ public class NavDrawerActivity extends BriarActivity implements
public static Uri SIGN_OUT_URI =
Uri.parse("briar-content://org.briarproject.briar/sign-out");
private final List<Transport> transports = new ArrayList<>(3);
private final MutableLiveData<ImageView> torIcon = new MutableLiveData<>();
private NavDrawerViewModel navDrawerViewModel;
private PluginViewModel pluginViewModel;
private ActionBarDrawerToggle drawerToggle;
@@ -118,6 +110,7 @@ public class NavDrawerActivity extends BriarActivity implements
private DrawerLayout drawerLayout;
private NavigationView navigation;
private List<Transport> transports;
private BaseAdapter transportsAdapter;
@Override
@@ -148,11 +141,6 @@ public class NavDrawerActivity extends BriarActivity implements
drawerLayout = findViewById(R.id.drawer_layout);
navigation = findViewById(R.id.navigation);
GridView transportsView = findViewById(R.id.transportsView);
LinearLayout transportsLayout = findViewById(R.id.transports);
transportsLayout.setOnClickListener(v -> {
LOG.info("Starting transports activity");
startActivity(new Intent(this, TransportsActivity.class));
});
setSupportActionBar(toolbar);
ActionBar actionBar = requireNonNull(getSupportActionBar());
@@ -161,23 +149,13 @@ public class NavDrawerActivity extends BriarActivity implements
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar,
R.string.nav_drawer_open_description,
R.string.nav_drawer_close_description) {
@Override
public void onDrawerOpened(View drawerView) {
super.onDrawerOpened(drawerView);
navDrawerViewModel.checkTransportsOnboarding();
}
};
R.string.nav_drawer_close_description);
drawerLayout.addDrawerListener(drawerToggle);
navigation.setNavigationItemSelectedListener(this);
initializeTransports();
transportsView.setAdapter(transportsAdapter);
observeOnce(navDrawerViewModel.showTransportsOnboarding(), this, show ->
observeOnce(torIcon, this, imageView ->
showTransportsOnboarding(show, imageView)));
lockManager.isLockable().observe(this, this::setLockVisible);
if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) {
@@ -402,6 +380,8 @@ public class NavDrawerActivity extends BriarActivity implements
}
private void initializeTransports() {
transports = new ArrayList<>(3);
transportsAdapter = new BaseAdapter() {
@Override
@@ -442,8 +422,6 @@ public class NavDrawerActivity extends BriarActivity implements
TextView text = view.findViewById(R.id.textView);
text.setText(getString(t.label));
if (t.id.equals(TorConstants.ID)) torIcon.setValue(icon);
return view;
}
};
@@ -466,7 +444,7 @@ public class NavDrawerActivity extends BriarActivity implements
private Transport createTransport(TransportId id,
@DrawableRes int iconDrawable, @StringRes int label) {
int iconColor = getIconColor(STARTING_STOPPING);
Transport transport = new Transport(id, iconDrawable, label, iconColor);
Transport transport = new Transport(iconDrawable, label, iconColor);
pluginViewModel.getPluginState(id).observe(this, state -> {
transport.iconColor = getIconColor(state);
transportsAdapter.notifyDataSetChanged();
@@ -474,25 +452,8 @@ public class NavDrawerActivity extends BriarActivity implements
return transport;
}
private void showTransportsOnboarding(boolean show, ImageView imageView) {
if (show) {
int color = resolveColorAttribute(this, R.attr.colorControlNormal);
new MaterialTapTargetPrompt.Builder(NavDrawerActivity.this,
R.style.OnboardingDialogTheme).setTarget(imageView)
.setPrimaryText(R.string.network_settings_title)
.setSecondaryText(R.string.transports_onboarding_text)
.setIcon(R.drawable.transport_tor)
.setIconDrawableColourFilter(color)
.setBackgroundColour(
ContextCompat.getColor(this, R.color.briar_primary))
.show();
navDrawerViewModel.transportsOnboardingShown();
}
}
private static class Transport {
private final TransportId id;
@DrawableRes
private final int iconDrawable;
@StringRes
@@ -501,9 +462,8 @@ public class NavDrawerActivity extends BriarActivity implements
@ColorRes
private int iconColor;
private Transport(TransportId id, @DrawableRes int iconDrawable,
@StringRes int label, @ColorRes int iconColor) {
this.id = id;
private Transport(@DrawableRes int iconDrawable, @StringRes int label,
@ColorRes int iconColor) {
this.iconDrawable = iconDrawable;
this.label = label;
this.iconColor = iconColor;

View File

@@ -34,8 +34,6 @@ public class NavDrawerViewModel extends AndroidViewModel {
getLogger(NavDrawerViewModel.class.getName());
private static final String EXPIRY_DATE_WARNING = "expiryDateWarning";
private static final String SHOW_TRANSPORTS_ONBOARDING =
"showTransportsOnboarding";
@DatabaseExecutor
private final Executor dbExecutor;
@@ -45,8 +43,6 @@ public class NavDrawerViewModel extends AndroidViewModel {
new MutableLiveData<>();
private final MutableLiveData<Boolean> shouldAskForDozeWhitelisting =
new MutableLiveData<>();
private final MutableLiveData<Boolean> showTransportsOnboarding =
new MutableLiveData<>();
@Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
@@ -132,39 +128,4 @@ public class NavDrawerViewModel extends AndroidViewModel {
}
});
}
@UiThread
LiveData<Boolean> showTransportsOnboarding() {
return showTransportsOnboarding;
}
@UiThread
void checkTransportsOnboarding() {
if (showTransportsOnboarding.getValue() != null) return;
dbExecutor.execute(() -> {
try {
Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE);
boolean show =
settings.getBoolean(SHOW_TRANSPORTS_ONBOARDING, true);
showTransportsOnboarding.postValue(show);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
@UiThread
void transportsOnboardingShown() {
showTransportsOnboarding.setValue(false);
dbExecutor.execute(() -> {
try {
Settings settings = new Settings();
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
}

View File

@@ -1,20 +1,8 @@
package org.briarproject.briar.android.navdrawer;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.bramble.api.db.DatabaseExecutor;
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.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BluetoothConstants;
import org.briarproject.bramble.api.plugin.LanTcpConstants;
@@ -24,44 +12,28 @@ import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault
public class PluginViewModel extends AndroidViewModel implements EventListener {
public class PluginViewModel extends ViewModel implements EventListener {
private static final Logger LOG =
getLogger(PluginViewModel.class.getName());
private final Application app;
private final Executor dbExecutor;
private final SettingsManager settingsManager;
private final PluginManager pluginManager;
private final EventBus eventBus;
private final BroadcastReceiver receiver;
private final MutableLiveData<State> torPluginState =
new MutableLiveData<>();
@@ -70,68 +42,24 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
private final MutableLiveData<State> btPluginState =
new MutableLiveData<>();
private final MutableLiveData<Boolean> torEnabledSetting =
new MutableLiveData<>(false);
private final MutableLiveData<Boolean> wifiEnabledSetting =
new MutableLiveData<>(false);
private final MutableLiveData<Boolean> btEnabledSetting =
new MutableLiveData<>(false);
private final MutableLiveData<NetworkStatus> networkStatus =
new MutableLiveData<>();
private final MutableLiveData<Boolean> bluetoothTurnedOn =
new MutableLiveData<>(false);
@Inject
PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, PluginManager pluginManager,
EventBus eventBus, NetworkManager networkManager) {
super(app);
this.app = app;
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager;
PluginViewModel(PluginManager pluginManager, EventBus eventBus) {
this.pluginManager = pluginManager;
this.eventBus = eventBus;
eventBus.addListener(this);
receiver = new BluetoothStateReceiver();
app.registerReceiver(receiver, new IntentFilter(ACTION_STATE_CHANGED));
networkStatus.setValue(networkManager.getNetworkStatus());
torPluginState.setValue(getTransportState(TorConstants.ID));
wifiPluginState.setValue(getTransportState(LanTcpConstants.ID));
btPluginState.setValue(getTransportState(BluetoothConstants.ID));
initialiseBluetoothState();
loadSettings();
}
@Override
protected void onCleared() {
eventBus.removeListener(this);
app.unregisterReceiver(receiver);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof NetworkStatusEvent) {
networkStatus.setValue(((NetworkStatusEvent) e).getStatus());
} else if (e instanceof SettingsUpdatedEvent) {
SettingsUpdatedEvent s = (SettingsUpdatedEvent) e;
if (s.getNamespace().equals(TorConstants.ID.getString())) {
boolean enable = s.getSettings().getBoolean(PREF_PLUGIN_ENABLE,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
torEnabledSetting.setValue(enable);
} else if (s.getNamespace().equals(
LanTcpConstants.ID.getString())) {
boolean enable = s.getSettings().getBoolean(PREF_PLUGIN_ENABLE,
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
wifiEnabledSetting.setValue(enable);
} else if (s.getNamespace().equals(
BluetoothConstants.ID.getString())) {
boolean enable = s.getSettings().getBoolean(PREF_PLUGIN_ENABLE,
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
btEnabledSetting.setValue(enable);
}
} else if (e instanceof TransportStateEvent) {
if (e instanceof TransportStateEvent) {
TransportStateEvent t = (TransportStateEvent) e;
TransportId id = t.getTransportId();
State state = t.getState();
@@ -149,62 +77,6 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
return liveData;
}
LiveData<Boolean> getPluginEnabledSetting(TransportId id) {
if (id.equals(TorConstants.ID)) return torEnabledSetting;
else if (id.equals(LanTcpConstants.ID)) return wifiEnabledSetting;
else if (id.equals(BluetoothConstants.ID)) return btEnabledSetting;
else throw new IllegalArgumentException();
}
LiveData<NetworkStatus> getNetworkStatus() {
return networkStatus;
}
LiveData<Boolean> getBluetoothTurnedOn() {
return bluetoothTurnedOn;
}
int getReasonsTorDisabled() {
Plugin plugin = pluginManager.getPlugin(TorConstants.ID);
return plugin == null ? 0 : plugin.getReasonsDisabled();
}
void enableTransport(TransportId id, boolean enable) {
Settings s = new Settings();
s.putBoolean(PREF_PLUGIN_ENABLE, enable);
mergeSettings(s, id.getString());
}
private void initialiseBluetoothState() {
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) bluetoothTurnedOn.setValue(false);
else bluetoothTurnedOn.setValue(bt.getState() == STATE_ON);
}
private void loadSettings() {
dbExecutor.execute(() -> {
try {
boolean tor = isPluginEnabled(TorConstants.ID,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
torEnabledSetting.postValue(tor);
boolean wifi = isPluginEnabled(LanTcpConstants.ID,
LanTcpConstants.DEFAULT_PREF_PLUGIN_ENABLE);
wifiEnabledSetting.postValue(wifi);
boolean bt = isPluginEnabled(BluetoothConstants.ID,
BluetoothConstants.DEFAULT_PREF_PLUGIN_ENABLE);
btEnabledSetting.postValue(bt);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private boolean isPluginEnabled(TransportId id, boolean defaultValue)
throws DbException {
Settings s = settingsManager.getSettings(id.getString());
return s.getBoolean(PREF_PLUGIN_ENABLE, defaultValue);
}
private State getTransportState(TransportId id) {
Plugin plugin = pluginManager.getPlugin(id);
return plugin == null ? STARTING_STOPPING : plugin.getState();
@@ -217,26 +89,4 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
else if (id.equals(BluetoothConstants.ID)) return btPluginState;
else return null;
}
private void mergeSettings(Settings s, String namespace) {
dbExecutor.execute(() -> {
try {
long start = now();
settingsManager.mergeSettings(s, namespace);
logDuration(LOG, "Merging settings", start);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private class BluetoothStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) bluetoothTurnedOn.postValue(true);
else bluetoothTurnedOn.postValue(false);
}
}
}

View File

@@ -1,360 +0,0 @@
package org.briarproject.briar.android.navdrawer;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.network.NetworkStatus;
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.LanTcpConstants;
import org.briarproject.bramble.api.plugin.Plugin.State;
import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED;
import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class TransportsActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private final List<Transport> transports = new ArrayList<>(3);
private PluginViewModel viewModel;
private BaseAdapter transportsAdapter;
@Override
public void onCreate(@Nullable Bundle state) {
super.onCreate(state);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setHomeButtonEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(true);
}
setContentView(R.layout.activity_transports);
ViewModelProvider provider =
ViewModelProviders.of(this, viewModelFactory);
viewModel = provider.get(PluginViewModel.class);
GridView grid = findViewById(R.id.grid);
initializeCards();
grid.setAdapter(transportsAdapter);
}
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
} else if (item.getItemId() == R.id.action_help) {
String text = getString(R.string.transports_help_text);
showOnboardingDialog(this, text);
return true;
}
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.help_action, menu);
return super.onCreateOptionsMenu(menu);
}
private void initializeCards() {
transportsAdapter = new BaseAdapter() {
@Override
public int getCount() {
return transports.size();
}
@Override
public Transport getItem(int position) {
return transports.get(position);
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
View view;
if (convertView != null) {
view = convertView;
} else {
LayoutInflater inflater = getLayoutInflater();
view = inflater.inflate(R.layout.list_item_transport_card,
parent, false);
}
Transport t = getItem(position);
ImageView icon = view.findViewById(R.id.icon);
icon.setImageDrawable(ContextCompat.getDrawable(
TransportsActivity.this, t.iconDrawable));
icon.setColorFilter(ContextCompat.getColor(
TransportsActivity.this, t.iconColor));
TextView title = view.findViewById(R.id.title);
title.setText(getString(t.title));
SwitchCompat switchCompat =
view.findViewById(R.id.switchCompat);
switchCompat.setText(getString(t.switchLabel));
switchCompat.setOnClickListener(v ->
viewModel.enableTransport(t.id,
switchCompat.isChecked()));
switchCompat.setChecked(t.isSwitchChecked);
TextView summary = view.findViewById(R.id.summary);
if (t.summary == 0) {
summary.setVisibility(GONE);
} else {
summary.setText(t.summary);
summary.setVisibility(VISIBLE);
}
TextView deviceStatus = view.findViewById(R.id.deviceStatus);
deviceStatus.setText(getBulletString(t.deviceStatus));
TextView pluginStatus = view.findViewById(R.id.appStatus);
pluginStatus.setText(getBulletString(t.pluginStatus));
pluginStatus.setVisibility(t.showPluginStatus ? VISIBLE : GONE);
return view;
}
};
Transport tor = createTransport(TorConstants.ID,
R.drawable.transport_tor, R.string.transport_tor,
R.string.tor_enable_title, R.string.tor_enable_summary,
R.string.tor_device_status_offline,
R.string.tor_plugin_status_inactive);
transports.add(tor);
Transport wifi = createTransport(LanTcpConstants.ID,
R.drawable.transport_lan, R.string.transport_lan_long,
R.string.wifi_setting, 0, R.string.lan_device_status_off,
R.string.lan_plugin_status_inactive);
transports.add(wifi);
Transport bt = createTransport(BluetoothConstants.ID,
R.drawable.transport_bt, R.string.transport_bt,
R.string.bluetooth_setting, 0, R.string.bt_device_status_off,
R.string.bt_plugin_status_inactive);
transports.add(bt);
viewModel.getNetworkStatus().observe(this, status -> {
updateTorResources(tor, status);
updateWifiResources(wifi, status);
transportsAdapter.notifyDataSetChanged();
});
viewModel.getBluetoothTurnedOn().observe(this, on -> {
updateBtResources(bt, on);
transportsAdapter.notifyDataSetChanged();
});
}
private String getBulletString(@StringRes int resId) {
return "\u2022 " + getString(resId);
}
@ColorRes
private int getIconColor(State state) {
if (state == ACTIVE) return R.color.briar_lime_400;
else if (state == ENABLING) return R.color.briar_orange_500;
else return android.R.color.tertiary_text_light;
}
private void updateTorResources(Transport tor, NetworkStatus status) {
if (status.isConnected()) {
if (status.isWifi()) {
tor.deviceStatus = R.string.tor_device_status_online_wifi;
} else {
tor.deviceStatus = R.string.tor_device_status_online_mobile;
}
tor.showPluginStatus = true;
} else {
tor.deviceStatus = R.string.tor_device_status_offline;
tor.showPluginStatus = false;
}
}
private void updateWifiResources(Transport wifi, NetworkStatus status) {
if (status.isWifi()) {
wifi.deviceStatus = R.string.lan_device_status_on;
wifi.showPluginStatus = true;
} else {
wifi.deviceStatus = R.string.lan_device_status_off;
wifi.showPluginStatus = false;
}
}
private void updateBtResources(Transport bt, boolean on) {
if (on) {
bt.deviceStatus = R.string.bt_device_status_on;
bt.showPluginStatus = true;
} else {
bt.deviceStatus = R.string.bt_device_status_off;
bt.showPluginStatus = false;
}
}
@StringRes
private int getPluginStatus(TransportId id, State state) {
if (id.equals(TorConstants.ID)) {
return getTorPluginStatus(state);
} else if (id.equals(LanTcpConstants.ID)) {
return getWifiPluginStatus(state);
} else if (id.equals(BluetoothConstants.ID)) {
return getBtPluginStatus(state);
} else throw new AssertionError();
}
@StringRes
private int getTorPluginStatus(State state) {
if (state == ENABLING) {
return R.string.tor_plugin_status_enabling;
} else if (state == ACTIVE) {
return R.string.tor_plugin_status_active;
} else if (state == DISABLED) {
int reasons = viewModel.getReasonsTorDisabled();
if ((reasons & REASON_MOBILE_DATA) != 0) {
return R.string.tor_plugin_status_disabled_mobile_data;
} else if ((reasons & REASON_BATTERY) != 0) {
return R.string.tor_plugin_status_disabled_battery;
} else if ((reasons & REASON_COUNTRY_BLOCKED) != 0) {
return R.string.tor_plugin_status_disabled_country_blocked;
} else {
return R.string.tor_plugin_status_disabled;
}
} else {
return R.string.tor_plugin_status_inactive;
}
}
@StringRes
private int getWifiPluginStatus(State state) {
if (state == ENABLING) return R.string.lan_plugin_status_enabling;
else if (state == ACTIVE) return R.string.lan_plugin_status_active;
else if (state == DISABLED) return R.string.lan_plugin_status_disabled;
else return R.string.lan_plugin_status_inactive;
}
@StringRes
private int getBtPluginStatus(State state) {
if (state == ENABLING) return R.string.bt_plugin_status_enabling;
else if (state == ACTIVE) return R.string.bt_plugin_status_active;
else if (state == DISABLED) return R.string.bt_plugin_status_disabled;
else return R.string.bt_plugin_status_inactive;
}
private Transport createTransport(TransportId id,
@DrawableRes int iconDrawable, @StringRes int title,
@StringRes int switchLabel, @StringRes int summary,
@StringRes int deviceStatus, @StringRes int pluginStatus) {
int iconColor = getIconColor(STARTING_STOPPING);
Transport transport = new Transport(id, iconDrawable, iconColor, title,
switchLabel, false, summary, deviceStatus, pluginStatus, false);
viewModel.getPluginState(id).observe(this, state -> {
transport.iconColor = getIconColor(state);
transport.pluginStatus = getPluginStatus(transport.id, state);
transportsAdapter.notifyDataSetChanged();
});
viewModel.getPluginEnabledSetting(id).observe(this, enabled -> {
transport.isSwitchChecked = enabled;
transportsAdapter.notifyDataSetChanged();
});
return transport;
}
private static class Transport {
private final TransportId id;
@DrawableRes
private final int iconDrawable;
@StringRes
private final int title, switchLabel, summary;
@ColorRes
private int iconColor;
@StringRes
private int deviceStatus, pluginStatus;
private boolean isSwitchChecked, showPluginStatus;
private Transport(TransportId id,
@DrawableRes int iconDrawable,
@ColorRes int iconColor,
@StringRes int title,
@StringRes int switchLabel,
boolean isSwitchChecked,
@StringRes int summary,
@StringRes int deviceStatus,
@StringRes int pluginStatus,
boolean showPluginStatus) {
this.id = id;
this.iconDrawable = iconDrawable;
this.iconColor = iconColor;
this.title = title;
this.switchLabel = switchLabel;
this.isSwitchChecked = isSwitchChecked;
this.summary = summary;
this.deviceStatus = deviceStatus;
this.pluginStatus = pluginStatus;
this.showPluginStatus = showPluginStatus;
}
}
}

View File

@@ -7,8 +7,6 @@ import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -17,8 +15,6 @@ import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.autodelete.AutoDeleteManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.privategroup.GroupMessage;
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
import org.briarproject.briar.api.privategroup.PrivateGroup;
@@ -39,8 +35,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -49,12 +43,9 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
implements CreateGroupController {
private static final Logger LOG =
getLogger(CreateGroupControllerImpl.class.getName());
Logger.getLogger(CreateGroupControllerImpl.class.getName());
private final Executor cryptoExecutor;
private final TransactionManager db;
private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final PrivateGroupFactory groupFactory;
@@ -65,26 +56,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private final Clock clock;
@Inject
CreateGroupControllerImpl(
@DatabaseExecutor Executor dbExecutor,
CreateGroupControllerImpl(@DatabaseExecutor Executor dbExecutor,
@CryptoExecutor Executor cryptoExecutor,
TransactionManager db,
AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager,
LifecycleManager lifecycleManager,
ContactManager contactManager,
IdentityManager identityManager,
PrivateGroupFactory groupFactory,
LifecycleManager lifecycleManager, ContactManager contactManager,
IdentityManager identityManager, PrivateGroupFactory groupFactory,
GroupMessageFactory groupMessageFactory,
PrivateGroupManager groupManager,
GroupInvitationFactory groupInvitationFactory,
GroupInvitationManager groupInvitationManager,
Clock clock) {
GroupInvitationManager groupInvitationManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.cryptoExecutor = cryptoExecutor;
this.db = db;
this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager;
this.contactManager = contactManager;
this.identityManager = identityManager;
this.groupFactory = groupFactory;
@@ -148,14 +129,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> {
try {
db.transaction(true, txn -> {
LocalAuthor localAuthor =
identityManager.getLocalAuthor(txn);
List<InvitationContext> contexts =
createInvitationContexts(txn, contactIds);
txn.attach(() -> signInvitations(g, localAuthor, contexts,
text, handler));
});
LocalAuthor localAuthor = identityManager.getLocalAuthor();
List<Contact> contacts = new ArrayList<>();
for (ContactId c : contactIds) {
try {
contacts.add(contactManager.getContact(c));
} catch (NoSuchContactException e) {
// Continue
}
}
signInvitations(g, localAuthor, contacts, text, handler);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
@@ -163,32 +146,17 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
});
}
private List<InvitationContext> createInvitationContexts(Transaction txn,
Collection<ContactId> contactIds) throws DbException {
List<InvitationContext> contexts = new ArrayList<>();
for (ContactId c : contactIds) {
try {
Contact contact = contactManager.getContact(txn, c);
long timestamp = conversationManager
.getTimestampForOutgoingMessage(txn, c);
long timer = autoDeleteManager.getAutoDeleteTimer(txn, c,
timestamp);
contexts.add(new InvitationContext(contact, timestamp, timer));
} catch (NoSuchContactException e) {
// Continue
}
}
return contexts;
}
private void signInvitations(GroupId g, LocalAuthor localAuthor,
List<InvitationContext> contexts, @Nullable String text,
Collection<Contact> contacts, @Nullable String text,
ResultExceptionHandler<Void, DbException> handler) {
cryptoExecutor.execute(() -> {
for (InvitationContext ctx : contexts) {
ctx.signature = groupInvitationFactory.signInvitation(
ctx.contact, g, ctx.timestamp,
localAuthor.getPrivateKey());
long timestamp = clock.currentTimeMillis();
List<InvitationContext> contexts = new ArrayList<>();
for (Contact c : contacts) {
byte[] signature = groupInvitationFactory.signInvitation(c, g,
timestamp, localAuthor.getPrivateKey());
contexts.add(new InvitationContext(c.getId(), timestamp,
signature));
}
sendInvitations(g, contexts, text, handler);
});
@@ -199,16 +167,16 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
ResultExceptionHandler<Void, DbException> handler) {
runOnDbThread(() -> {
try {
for (InvitationContext ctx : contexts) {
for (InvitationContext context : contexts) {
try {
groupInvitationManager.sendInvitation(g,
ctx.contact.getId(), text, ctx.timestamp,
requireNonNull(ctx.signature),
ctx.autoDeleteTimer);
context.contactId, text, context.timestamp,
context.signature);
} catch (NoSuchContactException e) {
// Continue
}
}
//noinspection ConstantConditions
handler.onResult(null);
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -219,16 +187,15 @@ class CreateGroupControllerImpl extends ContactSelectorControllerImpl
private static class InvitationContext {
private final Contact contact;
private final long timestamp, autoDeleteTimer;
@Nullable
private byte[] signature = null;
private final ContactId contactId;
private final long timestamp;
private final byte[] signature;
private InvitationContext(Contact contact, long timestamp,
long autoDeleteTimer) {
this.contact = contact;
private InvitationContext(ContactId contactId, long timestamp,
byte[] signature) {
this.contactId = contactId;
this.timestamp = timestamp;
this.autoDeleteTimer = autoDeleteTimer;
this.signature = signature;
}
}
}

View File

@@ -15,6 +15,7 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@MethodsNotNullByDefault
@@ -42,7 +43,7 @@ public class GroupInviteFragment extends ContactSelectorFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requireActivity().setTitle(R.string.groups_invite_members);
requireNonNull(getActivity()).setTitle(R.string.groups_invite_members);
}
@Override

View File

@@ -39,6 +39,7 @@ import androidx.annotation.UiThread;
import androidx.recyclerview.widget.LinearLayoutManager;
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -71,7 +72,7 @@ public class GroupListFragment extends BaseFragment implements
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(R.string.groups_button);
requireNonNull(getActivity()).setTitle(R.string.groups_button);
View v = inflater.inflate(R.layout.list, container, false);

View File

@@ -12,6 +12,8 @@ import org.briarproject.briar.R;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CrashFragment extends Fragment {
@@ -33,7 +35,7 @@ public class CrashFragment extends Fragment {
}
private DevReportActivity getDevReportActivity() {
return (DevReportActivity) requireActivity();
return (DevReportActivity) requireNonNull(getActivity());
}
}

View File

@@ -113,7 +113,7 @@ public class ReportFormFragment extends Fragment
report = v.findViewById(R.id.report_content);
progress = v.findViewById(R.id.progress_wheel);
Bundle args = requireArguments();
Bundle args = requireNonNull(getArguments());
isFeedback = args.getBoolean(IS_FEEDBACK);
reportFile =
(File) requireNonNull(args.getSerializable(EXTRA_REPORT_FILE));
@@ -285,7 +285,7 @@ public class ReportFormFragment extends Fragment
}
private DevReportActivity getDevReportActivity() {
return (DevReportActivity) requireActivity();
return (DevReportActivity) requireNonNull(getActivity());
}
}

View File

@@ -32,6 +32,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.account.AccountUtils;
import org.briarproject.briar.android.util.UiUtils;
import java.util.ArrayList;
@@ -251,9 +252,23 @@ public class SettingsFragment extends PreferenceFragmentCompat
throw new RuntimeException("Boom!");
}
);
findPreference("pref_key_export").setOnPreferenceClickListener(
preference -> {
AccountUtils.exportAccount(requireContext());
return true;
}
);
findPreference("pref_key_import").setOnPreferenceClickListener(
preference -> {
AccountUtils.importAccount(requireContext());
return true;
}
);
} else {
findPreference("pref_key_explode").setVisible(false);
findPreference("pref_key_test_data").setVisible(false);
findPreference("pref_key_export").setVisible(false);
findPreference("pref_key_import").setVisible(false);
PreferenceGroup testing =
findPreference("pref_key_explode").getParent();
if (testing == null) throw new AssertionError();

View File

@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.blog.BlogSharingManager;
import org.briarproject.briar.api.conversation.ConversationManager;
import java.util.Collection;
import java.util.concurrent.Executor;
@@ -23,7 +25,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -32,16 +33,21 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
implements ShareBlogController {
private final static Logger LOG =
getLogger(ShareBlogControllerImpl.class.getName());
Logger.getLogger(ShareBlogControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final BlogSharingManager blogSharingManager;
private final Clock clock;
@Inject
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
BlogSharingManager blogSharingManager) {
ConversationManager conversationManager,
BlogSharingManager blogSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.blogSharingManager = blogSharingManager;
this.clock = clock;
}
@Override
@@ -56,7 +62,10 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
try {
blogSharingManager.sendInvitation(g, c, text);
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
blogSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e);
}

View File

@@ -10,8 +10,10 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.forum.ForumSharingManager;
import java.util.Collection;
@@ -23,7 +25,6 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@Immutable
@@ -32,16 +33,21 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
implements ShareForumController {
private final static Logger LOG =
getLogger(ShareForumControllerImpl.class.getName());
Logger.getLogger(ShareForumControllerImpl.class.getName());
private final ConversationManager conversationManager;
private final ForumSharingManager forumSharingManager;
private final Clock clock;
@Inject
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, ContactManager contactManager,
ForumSharingManager forumSharingManager) {
ConversationManager conversationManager,
ForumSharingManager forumSharingManager, Clock clock) {
super(dbExecutor, lifecycleManager, contactManager);
this.conversationManager = conversationManager;
this.forumSharingManager = forumSharingManager;
this.clock = clock;
}
@Override
@@ -56,7 +62,10 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
try {
for (ContactId c : contacts) {
try {
forumSharingManager.sendInvitation(g, c, text);
long time = Math.max(clock.currentTimeMillis(),
conversationManager.getGroupCount(c)
.getLatestMsgTime() + 1);
forumSharingManager.sendInvitation(g, c, text, time);
} catch (NoSuchContactException | NoSuchGroupException e) {
logException(LOG, WARNING, e);
}

View File

@@ -11,13 +11,11 @@ import androidx.core.content.ContextCompat;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.NotificationCompat.VISIBILITY_PRIVATE;
public class BriarNotificationBuilder extends NotificationCompat.Builder {
private final Context context;
public class BriarNotificationBuilder extends NotificationCompat.Builder {
public BriarNotificationBuilder(Context context, String channelId) {
super(context, channelId);
this.context = context;
// Auto-cancel does not fire the delete intent, see
// https://issuetracker.google.com/issues/36961721
setAutoCancel(true);
@@ -28,7 +26,7 @@ public class BriarNotificationBuilder extends NotificationCompat.Builder {
}
public BriarNotificationBuilder setColorRes(@ColorRes int res) {
setColor(ContextCompat.getColor(context, res));
setColor(ContextCompat.getColor(mContext, res));
return this;
}

View File

@@ -79,7 +79,7 @@ public class ImagePreview extends ConstraintLayout {
((ImagePreviewAdapter) imageList.getAdapter());
int pos = requireNonNull(adapter).loadItemPreview(result);
if (pos != NO_POSITION) {
imageList.scrollToPosition(pos);
imageList.smoothScrollToPosition(pos);
}
}

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