mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
67 Commits
release-1.
...
deliver-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0928089ec | ||
|
|
8d735b3023 | ||
|
|
b24a0e4bc3 | ||
|
|
07da91a6f5 | ||
|
|
e4e0e712dc | ||
|
|
9294794448 | ||
|
|
5a9958793d | ||
|
|
651d2ca377 | ||
|
|
ecd64f08cd | ||
|
|
f3bffb6aa6 | ||
|
|
33331dee3e | ||
|
|
641525fa74 | ||
|
|
4b82079e33 | ||
|
|
caa55ffa14 | ||
|
|
47ae594921 | ||
|
|
a17b154024 | ||
|
|
64e1975cf1 | ||
|
|
993502add0 | ||
|
|
54893d2716 | ||
|
|
84657127b8 | ||
|
|
01a146ba71 | ||
|
|
a30e5b672e | ||
|
|
edb584dc3b | ||
|
|
12a8907c8b | ||
|
|
e0f381a973 | ||
|
|
61d3d133e8 | ||
|
|
0caa522f07 | ||
|
|
948212103c | ||
|
|
ce1a57c2b4 | ||
|
|
922a52bf83 | ||
|
|
8cbb38ee68 | ||
|
|
1c4cf7d771 | ||
|
|
090a1bd84e | ||
|
|
44f6f5d416 | ||
|
|
b88f012880 | ||
|
|
93f434e54b | ||
|
|
92f4a3a404 | ||
|
|
c017a813b0 | ||
|
|
6c6dbfd357 | ||
|
|
1f246637e2 | ||
|
|
1ac17cf859 | ||
|
|
0a3ff41feb | ||
|
|
9738dd2838 | ||
|
|
be0e21d39b | ||
|
|
6a2c2bed0f | ||
|
|
de9c6d4447 | ||
|
|
37a2d9f990 | ||
|
|
0e1fb406b5 | ||
|
|
b72e8fa490 | ||
|
|
f3157e5276 | ||
|
|
e2124ff3c9 | ||
|
|
66cc9d25e7 | ||
|
|
e9cdec95e0 | ||
|
|
63d3a78dda | ||
|
|
ccbe6d4bb8 | ||
|
|
cf8241e79c | ||
|
|
61d3fe9055 | ||
|
|
bded1edb2b | ||
|
|
4d27828712 | ||
|
|
0f6f52c37a | ||
|
|
c1cf6f61b9 | ||
|
|
7c22016b81 | ||
|
|
31f42d44af | ||
|
|
a1cf485ecc | ||
|
|
b7d3cd7990 | ||
|
|
4122e0852a | ||
|
|
41411b0e2e |
14
.idea/codeStyles/Project.xml
generated
14
.idea/codeStyles/Project.xml
generated
@@ -28,6 +28,20 @@
|
||||
<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>
|
||||
|
||||
2
.idea/runConfigurations/All_tests.xml
generated
2
.idea/runConfigurations/All_tests.xml
generated
@@ -1,6 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar-android" />
|
||||
<module name="briar.briar-android" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-android" />
|
||||
<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" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
14
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
14
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-api" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-api" />
|
||||
<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" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-core" />
|
||||
<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" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,8 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-java" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-java" />
|
||||
<option name="PACKAGE_NAME" value="" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
@@ -10,11 +8,8 @@
|
||||
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar-android" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.briar-android" />
|
||||
<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" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
14
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
14
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.briar-core" />
|
||||
<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" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,6 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-headless" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar-headless" />
|
||||
<module name="briar.briar-headless" />
|
||||
<option name="PACKAGE_NAME" value="org.briarproject.briar.headless" />
|
||||
<option name="MAIN_CLASS_NAME" value="" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
|
||||
14
.idea/runConfigurations/H2_Performance_Test.xml
generated
14
.idea/runConfigurations/H2_Performance_Test.xml
generated
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-core" />
|
||||
<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="" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,20 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="bramble-core" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
<option name="ALTERNATIVE_JRE_PATH" />
|
||||
<module name="briar.bramble-core" />
|
||||
<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="" />
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="TEST_SEARCH_SCOPE">
|
||||
<value defaultName="singleModule" />
|
||||
</option>
|
||||
<patterns />
|
||||
<method />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
9
.idea/runConfigurations/briar_headless.xml
generated
9
.idea/runConfigurations/briar_headless.xml
generated
@@ -1,6 +1,6 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="briar-headless" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<configuration default="false" name="briar-headless" type="JetRunConfigurationType" singleton="true">
|
||||
<module name="briar.briar-headless" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="-v" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
|
||||
@@ -8,9 +8,8 @@
|
||||
<option name="PASS_PARENT_ENVS" value="true" />
|
||||
<option name="MAIN_CLASS_NAME" value="org.briarproject.briar.headless.MainKt" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<module name="briar-headless" />
|
||||
<envs />
|
||||
<method>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
<option name="Gradle.BeforeRunTask" enabled="true" tasks="jar" externalProjectPath="$PROJECT_DIR$/briar-headless" vmOptions="" scriptParameters="" />
|
||||
</method>
|
||||
</configuration>
|
||||
|
||||
1
bramble-android/.gitignore
vendored
1
bramble-android/.gitignore
vendored
@@ -3,3 +3,4 @@ gen
|
||||
build
|
||||
.settings
|
||||
src/main/res/raw/*.zip
|
||||
src/main/jniLibs
|
||||
@@ -5,14 +5,14 @@ apply plugin: 'witness'
|
||||
apply from: 'witness.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.2'
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 10210
|
||||
versionName "1.2.10"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
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.10@zip'
|
||||
tor 'org.briarproject:tor-android:0.3.5.12@zip'
|
||||
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
||||
@@ -53,10 +53,12 @@ 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' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,8 +69,36 @@ task unpackTorBinaries {
|
||||
copy {
|
||||
from configurations.tor.collect { zipTree(it) }
|
||||
into torBinariesDir
|
||||
// TODO: Remove after next Tor upgrade, which won't include non-PIE binaries
|
||||
include 'geoip.zip', '*_pie.zip'
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dependsOn cleanTorBinaries
|
||||
@@ -76,5 +106,6 @@ task unpackTorBinaries {
|
||||
|
||||
tasks.withType(MergeResources) {
|
||||
inputs.dir torBinariesDir
|
||||
inputs.dir torLibsDir
|
||||
dependsOn unpackTorBinaries
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ class AndroidBluetoothPlugin
|
||||
@Override
|
||||
@Nullable
|
||||
String getBluetoothAddress() {
|
||||
if (adapter == null) return null;
|
||||
String address = AndroidUtils.getBluetoothAddress(app, adapter);
|
||||
return address.isEmpty() ? null : address;
|
||||
}
|
||||
|
||||
@@ -16,19 +16,42 @@ 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,
|
||||
@@ -55,6 +78,9 @@ 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
|
||||
@@ -85,4 +111,112 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
|
||||
'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.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.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',
|
||||
@@ -35,27 +38,30 @@ 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.4.0:protobuf-java-3.4.0.jar:dce7e66b32456a1b1198da0caff3a8acb71548658391e798c79369241e6490a4',
|
||||
'com.google.protobuf:protobuf-java:3.10.0:protobuf-java-3.10.0.jar:161d7d61a8cb3970891c299578702fd079646e032329d6c2cabf998d191437c9',
|
||||
'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:2.21:istack-commons-runtime-2.21.jar:c33e67a0807095f02a0e2da139412dd7c4f9cc1a4c054b3e434f96831ba950f4',
|
||||
'com.sun.xml.fastinfoset:FastInfoset:1.2.13:FastInfoset-1.2.13.jar:27a77db909f3c2833c0b1a37c55af1db06045118ad2eed96ce567b6632bce038',
|
||||
'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',
|
||||
'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.2.12-b140109.1041:jaxb-api-2.2.12-b140109.1041.jar:b5e60cd8b7b5ff01ce4a74c5dd008f4fbd14ced3495d0b47b85cfedc182211f2',
|
||||
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
|
||||
'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',
|
||||
@@ -70,34 +76,35 @@ 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.10:tor-android-0.3.5.10.zip:edd83bf557fcff2105eaa0bdb3f607a6852ebe7360920929ae3039dd5f4774c5',
|
||||
'org.briarproject:tor-android:0.3.5.12:tor-android-0.3.5.12.zip:db71fb3290acff79d572af0752570eaf6aad7c4d88c9b9aa0b4d5afe2b9ead9c',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-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.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.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.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.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.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.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.7.7:stax-ex-1.7.7.jar:a31ff7d77163c0deb09e7fee59ad35ae44c2cee2cc8552a116ccd1583d813fb4',
|
||||
'org.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
|
||||
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
|
||||
'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-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:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
|
||||
'org.ow2.asm:asm:6.0:asm-6.0.jar:dd8971c74a4e697899a8e95caae4ea8760ea6c486dc6b97b1795e75760420461',
|
||||
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -8,6 +9,8 @@ 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;
|
||||
@@ -19,7 +22,9 @@ 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;
|
||||
@@ -28,8 +33,10 @@ 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;
|
||||
|
||||
@@ -41,7 +48,10 @@ class KeyAgreementConnector {
|
||||
}
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(KeyAgreementConnector.class.getName());
|
||||
getLogger(KeyAgreementConnector.class.getName());
|
||||
|
||||
private static final List<TransportId> PREFERRED_TRANSPORTS =
|
||||
asList(BluetoothConstants.ID, LanTcpConstants.ID);
|
||||
|
||||
private final Callbacks callbacks;
|
||||
private final KeyAgreementCrypto keyAgreementCrypto;
|
||||
@@ -105,24 +115,35 @@ class KeyAgreementConnector {
|
||||
this.alice = alice;
|
||||
aliceLatch.countDown();
|
||||
|
||||
// Start connecting over supported transports
|
||||
// Start connecting over supported transports in order of preference
|
||||
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()) {
|
||||
Plugin p = pluginManager.getPlugin(d.getId());
|
||||
if (p instanceof DuplexPlugin) {
|
||||
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) {
|
||||
if (LOG.isLoggable(INFO))
|
||||
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)));
|
||||
LOG.info("Connecting via " + id);
|
||||
transports.add(new Pair<>((DuplexPlugin) p, d.getDescriptor()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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 =
|
||||
@@ -148,15 +169,13 @@ 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(DuplexPlugin plugin, byte[] commitment,
|
||||
BdfList descriptor) {
|
||||
this.plugin = plugin;
|
||||
private ConnectorTask(List<Pair<DuplexPlugin, BdfList>> transports,
|
||||
byte[] commitment) {
|
||||
this.transports = transports;
|
||||
this.commitment = commitment;
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -164,13 +183,18 @@ class KeyAgreementConnector {
|
||||
public KeyAgreementConnection call() throws Exception {
|
||||
// Repeat attempts until we connect, get stopped, or get interrupted
|
||||
while (!stopped) {
|
||||
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());
|
||||
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());
|
||||
}
|
||||
}
|
||||
// Wait 2s before retry (to circumvent transient failures)
|
||||
Thread.sleep(2000);
|
||||
|
||||
@@ -436,8 +436,10 @@ 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;
|
||||
|
||||
@@ -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, torFile, geoIpFile, obfs4File, configFile;
|
||||
private final File torDirectory, geoIpFile, configFile;
|
||||
private final File doneFile, cookieFile;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
@@ -181,9 +181,7 @@ 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");
|
||||
@@ -192,6 +190,14 @@ 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;
|
||||
@@ -224,6 +230,7 @@ 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());
|
||||
@@ -322,44 +329,43 @@ 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();
|
||||
// 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);
|
||||
installTorExecutable();
|
||||
installObfs4Executable();
|
||||
extract(getGeoIpInputStream(), geoIpFile);
|
||||
extract(getConfigInputStream(), configFile);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream getTorInputStream() throws IOException {
|
||||
protected void extract(InputStream in, File dest) throws IOException {
|
||||
OutputStream out = new FileOutputStream(dest);
|
||||
copyAndClose(in, out);
|
||||
}
|
||||
|
||||
protected void installTorExecutable() 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);
|
||||
@@ -376,8 +382,6 @@ 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);
|
||||
@@ -569,6 +573,7 @@ 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());
|
||||
|
||||
@@ -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.10@zip'
|
||||
tor 'org.briarproject:tor:0.3.5.12@zip'
|
||||
tor 'org.briarproject:obfs4proxy:0.0.7@zip'
|
||||
|
||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.24'
|
||||
|
||||
@@ -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.10:tor-0.3.5.10.zip:7b387d3523ae8af289c23be59dc4c64ec5d3721385d7825a09705095e3318d5c',
|
||||
'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
|
||||
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
|
||||
'org.checkerframework:checker-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',
|
||||
|
||||
@@ -16,14 +16,14 @@ def getStdout = { command, defaultValue ->
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.2'
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
versionCode 10210
|
||||
versionName "1.2.10"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
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.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 '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 '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'
|
||||
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
|
||||
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.2.0'
|
||||
def espressoVersion = '3.3.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.2.0'
|
||||
testImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
testImplementation 'androidx.fragment:fragment-testing:1.1.0'
|
||||
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.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.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
|
||||
|
||||
@@ -37,3 +37,6 @@
|
||||
|
||||
# Glide
|
||||
-dontwarn com.bumptech.glide.load.engine.cache.DiskLruCacheWrapper
|
||||
|
||||
# Dependency injection annotations, needed for UI tests on older API levels
|
||||
-keep class javax.inject.**
|
||||
|
||||
@@ -13,8 +13,9 @@ 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)
|
||||
@@ -27,7 +28,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
|
||||
private final ImageHelper imageHelper = new ImageHelperImpl();
|
||||
private final AttachmentRetriever retriever =
|
||||
new AttachmentRetrieverImpl(null, dimensions, imageHelper,
|
||||
new AttachmentRetrieverImpl(null, null, dimensions, imageHelper,
|
||||
new ImageSizeCalculator(imageHelper));
|
||||
|
||||
@Test
|
||||
@@ -35,7 +36,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(160, item.getWidth());
|
||||
assertEquals(240, item.getHeight());
|
||||
@@ -43,7 +44,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
assertEquals(240, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertJpgOrJpeg(item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,7 +52,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(1728, item.getWidth());
|
||||
assertEquals(2592, item.getHeight());
|
||||
@@ -59,7 +60,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
assertEquals(dimensions.maxHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/jpeg", item.getMimeType());
|
||||
assertJpgOrJpeg(item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -67,7 +68,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(msgId, item.getMessageId());
|
||||
assertEquals(737, item.getWidth());
|
||||
assertEquals(510, item.getHeight());
|
||||
@@ -75,7 +76,7 @@ public class AttachmentRetrieverIntegrationTest {
|
||||
assertEquals(138, item.getThumbnailHeight());
|
||||
assertEquals("image/png", item.getMimeType());
|
||||
assertEquals("png", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,14 +84,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,14 +99,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(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());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,14 +114,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(1184, item.getWidth());
|
||||
assertEquals(448, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/png", item.getMimeType());
|
||||
assertEquals("png", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -128,14 +129,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(1, item.getWidth());
|
||||
assertEquals(1, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,14 +144,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(32, item.getWidth());
|
||||
assertEquals(32, item.getHeight());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.minHeight, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -158,8 +159,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.getAttachmentItem(a, true);
|
||||
assertTrue(item.hasError());
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(ERROR, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -167,14 +168,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(65535, item.getWidth());
|
||||
assertEquals(65535, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -182,14 +183,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(10000, item.getWidth());
|
||||
assertEquals(10000, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -197,14 +198,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, true);
|
||||
assertEquals(16384, item.getWidth());
|
||||
assertEquals(16384, item.getHeight());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailWidth());
|
||||
assertEquals(dimensions.maxWidth, item.getThumbnailHeight());
|
||||
assertEquals("image/gif", item.getMimeType());
|
||||
assertEquals("gif", item.getExtension());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -212,14 +213,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(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());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -227,14 +228,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.getAttachmentItem(a, true);
|
||||
AttachmentItem item = retriever.createAttachmentItem(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());
|
||||
assertFalse(item.hasError());
|
||||
assertEquals(AVAILABLE, item.getState());
|
||||
}
|
||||
|
||||
private InputStream getAssetInputStream(String name) throws Exception {
|
||||
|
||||
BIN
briar-android/src/main/ic_launcher-playstore.png
Normal file
BIN
briar-android/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
@@ -17,7 +17,6 @@ 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;
|
||||
@@ -45,7 +44,7 @@ public class AuthorNameFragment extends SetupFragment {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_title));
|
||||
requireActivity().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);
|
||||
|
||||
@@ -20,7 +20,6 @@ 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;
|
||||
|
||||
@@ -50,10 +49,10 @@ public class DozeFragment extends SetupFragment
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_doze_title));
|
||||
requireActivity().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);
|
||||
@@ -78,7 +77,8 @@ public class DozeFragment extends SetupFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
public void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
if (request == REQUEST_DOZE_WHITELISTING) {
|
||||
if (!dozeView.needsToBeShown() || secondAttempt) {
|
||||
@@ -92,11 +92,7 @@ public class DozeFragment extends SetupFragment
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged() {
|
||||
if (dozeView.isChecked() && huaweiView.isChecked()) {
|
||||
next.setEnabled(true);
|
||||
} else {
|
||||
next.setEnabled(false);
|
||||
}
|
||||
next.setEnabled(dozeView.isChecked() && huaweiView.isChecked());
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
|
||||
@@ -24,7 +24,6 @@ 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;
|
||||
|
||||
@@ -55,7 +54,7 @@ public class SetPasswordFragment extends SetupFragment {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro));
|
||||
requireActivity().setTitle(getString(R.string.setup_password_intro));
|
||||
View v = inflater.inflate(R.layout.fragment_setup_password, container,
|
||||
false);
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
|
||||
@@ -75,8 +76,12 @@ 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 || 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);
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
MutableLiveData<AttachmentResult> result = new MutableLiveData<>();
|
||||
this.result = result;
|
||||
uris.addAll(newUris);
|
||||
@@ -95,8 +100,12 @@ class AttachmentCreatorImpl implements AttachmentCreator {
|
||||
@UiThread
|
||||
public LiveData<AttachmentResult> getLiveAttachments() {
|
||||
MutableLiveData<AttachmentResult> result = this.result;
|
||||
if (task == null || result == null || uris.isEmpty())
|
||||
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!");
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
// A task is already running. It will update the result LiveData.
|
||||
// So nothing more to do here.
|
||||
return result;
|
||||
@@ -109,8 +118,8 @@ class AttachmentCreatorImpl implements AttachmentCreator {
|
||||
// get and cache AttachmentItem for ImagePreview
|
||||
try {
|
||||
Attachment a = retriever.getMessageAttachment(h);
|
||||
AttachmentItem item = retriever.getAttachmentItem(a, needsSize);
|
||||
if (item.hasError()) throw new IOException();
|
||||
AttachmentItem item = retriever.createAttachmentItem(a, needsSize);
|
||||
if (item.getState() == ERROR) throw new IOException();
|
||||
AttachmentItemResult itemResult =
|
||||
new AttachmentItemResult(uri, item);
|
||||
itemResults.add(itemResult);
|
||||
@@ -167,21 +176,13 @@ 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) throw new AssertionError();
|
||||
task.cancel();
|
||||
if (task != null) task.cancel();
|
||||
deleteUnsentAttachments();
|
||||
resetState();
|
||||
}
|
||||
|
||||
@@ -7,24 +7,33 @@ 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 boolean hasError;
|
||||
private final long instanceId;
|
||||
private final State state;
|
||||
|
||||
public static final Creator<AttachmentItem> CREATOR =
|
||||
new Creator<AttachmentItem>() {
|
||||
@@ -39,19 +48,33 @@ 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,
|
||||
boolean hasError) {
|
||||
State state) {
|
||||
this.header = header;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.extension = extension;
|
||||
this.thumbnailWidth = thumbnailWidth;
|
||||
this.thumbnailHeight = thumbnailHeight;
|
||||
this.hasError = hasError;
|
||||
instanceId = NEXT_INSTANCE_ID.getAndIncrement();
|
||||
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);
|
||||
}
|
||||
|
||||
protected AttachmentItem(Parcel in) {
|
||||
@@ -64,8 +87,7 @@ public class AttachmentItem implements Parcelable {
|
||||
extension = requireNonNull(in.readString());
|
||||
thumbnailWidth = in.readInt();
|
||||
thumbnailHeight = in.readInt();
|
||||
hasError = in.readByte() != 0;
|
||||
instanceId = in.readLong();
|
||||
state = State.valueOf(requireNonNull(in.readString()));
|
||||
header = new AttachmentHeader(messageId, mimeType);
|
||||
}
|
||||
|
||||
@@ -101,12 +123,16 @@ public class AttachmentItem implements Parcelable {
|
||||
return thumbnailHeight;
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return hasError;
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public String getTransitionName() {
|
||||
return String.valueOf(instanceId);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -123,14 +149,23 @@ public class AttachmentItem implements Parcelable {
|
||||
dest.writeString(extension);
|
||||
dest.writeInt(thumbnailWidth);
|
||||
dest.writeInt(thumbnailHeight);
|
||||
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||
dest.writeLong(instanceId);
|
||||
dest.writeString(state.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 &&
|
||||
instanceId == ((AttachmentItem) o).instanceId;
|
||||
header.getMessageId().equals(
|
||||
((AttachmentItem) o).header.getMessageId()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return header.getMessageId().hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,63 @@
|
||||
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.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
|
||||
@NotNullByDefault
|
||||
public interface AttachmentRetriever {
|
||||
|
||||
void cachePut(MessageId messageId, List<AttachmentItem> attachments);
|
||||
|
||||
@Nullable
|
||||
List<AttachmentItem> cacheGet(MessageId messageId);
|
||||
|
||||
@DatabaseExecutor
|
||||
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 getAttachmentItem(Attachment a, boolean needsSize);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
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.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
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 {
|
||||
@@ -27,6 +41,8 @@ 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;
|
||||
@@ -34,13 +50,17 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
private final int minWidth, maxWidth;
|
||||
private final int minHeight, maxHeight;
|
||||
|
||||
private final Map<MessageId, List<AttachmentItem>> attachmentCache =
|
||||
new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<MessageId, MutableLiveData<AttachmentItem>>
|
||||
itemsWithSize = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<MessageId, MutableLiveData<AttachmentItem>>
|
||||
itemsWithoutSize = new ConcurrentHashMap<>();
|
||||
|
||||
@Inject
|
||||
AttachmentRetrieverImpl(MessagingManager messagingManager,
|
||||
AttachmentRetrieverImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
MessagingManager messagingManager,
|
||||
AttachmentDimensions dimensions, ImageHelper imageHelper,
|
||||
ImageSizeCalculator imageSizeCalculator) {
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.messagingManager = messagingManager;
|
||||
this.imageHelper = imageHelper;
|
||||
this.imageSizeCalculator = imageSizeCalculator;
|
||||
@@ -52,40 +72,143 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
@DatabaseExecutor
|
||||
public Attachment getMessageAttachment(AttachmentHeader h)
|
||||
throws DbException {
|
||||
return messagingManager.getAttachment(h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttachmentItem getAttachmentItem(Attachment a, boolean needsSize) {
|
||||
AttachmentHeader h = a.getHeader();
|
||||
if (!needsSize) {
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||
boolean hasError = false;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
hasError = true;
|
||||
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());
|
||||
}
|
||||
return new AttachmentItem(h, 0, 0, extension, 0, 0, hasError);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
InputStream is = new BufferedInputStream(a.getStream());
|
||||
Size size = imageSizeCalculator.getSize(is, h.getContentType());
|
||||
// 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;
|
||||
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 {
|
||||
String extension =
|
||||
imageHelper.getExtensionFromMimeType(h.getContentType());
|
||||
State state = AVAILABLE;
|
||||
if (extension == null) {
|
||||
extension = "";
|
||||
state = ERROR;
|
||||
}
|
||||
item = new AttachmentItem(h, extension, state);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
private AttachmentItem createAttachmentItem(AttachmentHeader h, Size size) {
|
||||
// calculate thumbnail size
|
||||
Size thumbnailSize = new Size(defaultSize, defaultSize, size.mimeType);
|
||||
if (!size.error) {
|
||||
@@ -104,8 +227,9 @@ class AttachmentRetrieverImpl implements AttachmentRetriever {
|
||||
hasError = true;
|
||||
}
|
||||
if (extension == null) extension = "";
|
||||
return new AttachmentItem(h, size.width, size.height, extension,
|
||||
thumbnailSize.width, thumbnailSize.height, hasError);
|
||||
State state = hasError ? ERROR : AVAILABLE;
|
||||
return new AttachmentItem(h, size.width, size.height,
|
||||
extension, thumbnailSize.width, thumbnailSize.height, state);
|
||||
}
|
||||
|
||||
private Size getThumbnailSize(int width, int height, String mimeType) {
|
||||
|
||||
@@ -25,7 +25,6 @@ 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;
|
||||
@@ -55,7 +54,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
// retrieve MessageId of blog post from arguments
|
||||
byte[] p = requireNonNull(getArguments()).getByteArray(POST_ID);
|
||||
byte[] p = requireArguments().getByteArray(POST_ID);
|
||||
if (p == null) throw new IllegalStateException("No post ID in args");
|
||||
postId = new MessageId(p);
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ 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;
|
||||
@@ -97,14 +96,14 @@ public class BlogFragment extends BaseFragment
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||
groupId = new GroupId(b);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
adapter = new BlogPostAdapter(requireNonNull(getActivity()), this,
|
||||
adapter = new BlogPostAdapter(requireActivity(), this,
|
||||
getFragmentManager());
|
||||
list = v.findViewById(R.id.postList);
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
@@ -196,7 +195,8 @@ public class BlogFragment extends BaseFragment
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
public void onActivityResult(int request, int result,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
|
||||
|
||||
@@ -35,7 +35,6 @@ 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;
|
||||
|
||||
@@ -79,7 +78,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireNonNull(getActivity()).setTitle(R.string.blogs_button);
|
||||
requireActivity().setTitle(R.string.blogs_button);
|
||||
|
||||
View v = inflater.inflate(R.layout.fragment_blog, container, false);
|
||||
|
||||
@@ -103,7 +102,8 @@ public class FeedFragment extends BaseFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
public void onActivityResult(int requestCode, int resultCode,
|
||||
@Nullable Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// The BlogPostAddedEvent arrives when the controller is not listening
|
||||
|
||||
@@ -18,7 +18,6 @@ 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
|
||||
@@ -54,7 +53,7 @@ public class FeedPostFragment extends BasePostFragment {
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No group ID in args");
|
||||
blogId = new GroupId(b);
|
||||
|
||||
@@ -74,7 +74,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
GroupId blogId =
|
||||
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
|
||||
MessageId postId =
|
||||
|
||||
@@ -62,7 +62,6 @@ 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;
|
||||
@@ -122,7 +121,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
|
||||
requireActivity().setTitle(R.string.contact_list_button);
|
||||
|
||||
View contentView = inflater.inflate(R.layout.fragment_contact_list,
|
||||
container, false);
|
||||
@@ -274,8 +273,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 ||
|
||||
@@ -317,7 +316,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
|
||||
@UiThread
|
||||
private void showSnackBar() {
|
||||
if (snackbar != null) return;
|
||||
View v = requireNonNull(getView());
|
||||
View v = requireView();
|
||||
int stringRes = R.string.pending_contact_requests_snackbar;
|
||||
snackbar = new BriarSnackbarBuilder()
|
||||
.setAction(R.string.show, view -> showPendingContactList())
|
||||
|
||||
@@ -33,7 +33,6 @@ 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;
|
||||
|
||||
@@ -83,8 +82,8 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
linkInput.setText(viewModel.getRemoteHandshakeLink());
|
||||
}
|
||||
|
||||
clipboard = (ClipboardManager) requireNonNull(
|
||||
getContext().getSystemService(CLIPBOARD_SERVICE));
|
||||
clipboard = (ClipboardManager)
|
||||
requireContext().getSystemService(CLIPBOARD_SERVICE);
|
||||
|
||||
Button pasteButton = v.findViewById(R.id.pasteButton);
|
||||
pasteButton.setOnClickListener(view -> {
|
||||
@@ -107,7 +106,7 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
ScrollView scrollView = (ScrollView) requireNonNull(getView());
|
||||
ScrollView scrollView = (ScrollView) requireView();
|
||||
View layout = scrollView.getChildAt(0);
|
||||
int scrollBy = layout.getHeight() - scrollView.getHeight();
|
||||
if (scrollBy > 0) {
|
||||
@@ -121,7 +120,7 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
}
|
||||
|
||||
private void onHandshakeLinkLoaded(String link) {
|
||||
View v = requireNonNull(getView());
|
||||
View v = requireView();
|
||||
|
||||
TextView linkView = v.findViewById(R.id.linkView);
|
||||
linkView.setText(link);
|
||||
|
||||
@@ -31,6 +31,7 @@ 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;
|
||||
|
||||
@@ -117,7 +118,8 @@ public class NicknameFragment extends BaseFragment {
|
||||
addButton.setVisibility(INVISIBLE);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
|
||||
viewModel.getAddContactResult().observe(this, result -> {
|
||||
LifecycleOwner owner = getViewLifecycleOwner();
|
||||
viewModel.getAddContactResult().observe(owner, result -> {
|
||||
if (result == null) return;
|
||||
if (result.hasError())
|
||||
handleException(name, requireNonNull(result.getException()));
|
||||
|
||||
@@ -26,7 +26,6 @@ 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;
|
||||
@@ -55,7 +54,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
byte[] b = args.getByteArray(GROUP_ID);
|
||||
if (b == null) throw new IllegalStateException("No GroupId");
|
||||
groupId = new GroupId(b);
|
||||
@@ -74,7 +73,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
||||
list.setEmptyImage(R.drawable.ic_empty_state_contact_list);
|
||||
list.setEmptyText(getString(R.string.no_contacts_selector));
|
||||
list.setEmptyAction(getString(R.string.no_contacts_selector_action));
|
||||
adapter = getAdapter(requireNonNull(getContext()), this);
|
||||
adapter = getAdapter(requireContext(), this);
|
||||
list.setAdapter(adapter);
|
||||
|
||||
// restore selected contacts if available
|
||||
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -65,17 +64,16 @@ 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;
|
||||
@@ -97,6 +95,7 @@ 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;
|
||||
@@ -118,8 +117,6 @@ 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;
|
||||
@@ -136,6 +133,7 @@ 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;
|
||||
@@ -185,8 +183,6 @@ 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();
|
||||
@@ -540,6 +536,7 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
|
||||
try {
|
||||
MessageId id = h.getId();
|
||||
@@ -556,21 +553,11 @@ public class ConversationActivity extends BriarActivity
|
||||
// images we use a grid so the size is fixed
|
||||
List<AttachmentHeader> headers = h.getAttachmentHeaders();
|
||||
if (headers.size() == 1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
@@ -651,59 +638,18 @@ public class ConversationActivity extends BriarActivity
|
||||
&& adapter.isScrolledToBottom(layoutManager);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@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)) {
|
||||
@@ -763,15 +709,6 @@ 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 ||
|
||||
@@ -780,7 +717,7 @@ public class ConversationActivity extends BriarActivity
|
||||
observeOnce(viewModel.getContactDisplayName(), this,
|
||||
name -> addConversationItem(h.accept(visitor)));
|
||||
} else {
|
||||
// visitor also loads message text (if existing)
|
||||
// visitor also loads message text and attachments (if existing)
|
||||
addConversationItem(h.accept(visitor));
|
||||
}
|
||||
}
|
||||
@@ -1107,8 +1044,9 @@ 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();
|
||||
String transitionName = item.getTransitionName(messageItem.getId());
|
||||
ActivityOptionsCompat options =
|
||||
makeSceneTransitionAnimation(this, view, transitionName);
|
||||
ActivityCompat.startActivity(this, i, options.toBundle());
|
||||
@@ -1147,15 +1085,41 @@ public class ConversationActivity extends BriarActivity
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link PrivateMessageHeader#accept(ConversationMessageVisitor)}
|
||||
*/
|
||||
@Override
|
||||
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
|
||||
List<AttachmentItem> attachments =
|
||||
attachmentRetriever.cacheGet(h.getId());
|
||||
if (attachments == null) {
|
||||
loadMessageAttachments(h);
|
||||
return emptyList();
|
||||
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);
|
||||
}
|
||||
return attachments;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ import java.util.List;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class ConversationMessageItem extends ConversationItem {
|
||||
|
||||
private List<AttachmentItem> attachments;
|
||||
private final List<AttachmentItem> attachments;
|
||||
|
||||
ConversationMessageItem(@LayoutRes int layoutRes, PrivateMessageHeader h,
|
||||
List<AttachmentItem> attachments) {
|
||||
@@ -26,8 +27,14 @@ class ConversationMessageItem extends ConversationItem {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
void setAttachments(List<AttachmentItem> attachments) {
|
||||
this.attachments = 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ 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.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;
|
||||
@@ -30,6 +33,7 @@ import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
|
||||
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -56,7 +60,7 @@ import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
|
||||
|
||||
@NotNullByDefault
|
||||
public class ConversationViewModel extends AndroidViewModel
|
||||
implements AttachmentManager {
|
||||
implements EventListener, AttachmentManager {
|
||||
|
||||
private static Logger LOG =
|
||||
getLogger(ConversationViewModel.class.getName());
|
||||
@@ -69,6 +73,7 @@ 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;
|
||||
@@ -101,6 +106,7 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
ConversationViewModel(Application application,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
TransactionManager db,
|
||||
EventBus eventBus,
|
||||
MessagingManager messagingManager,
|
||||
ContactManager contactManager,
|
||||
SettingsManager settingsManager,
|
||||
@@ -110,6 +116,7 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
super(application);
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.db = db;
|
||||
this.eventBus = eventBus;
|
||||
this.messagingManager = messagingManager;
|
||||
this.contactManager = contactManager;
|
||||
this.settingsManager = settingsManager;
|
||||
@@ -119,12 +126,27 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
messagingGroupId = Transformations
|
||||
.map(contact, c -> messagingManager.getContactGroup(c).getId());
|
||||
contactDeleted.setValue(false);
|
||||
|
||||
eventBus.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
attachmentCreator.deleteUnsentAttachments();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,6 +274,7 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void createMessage(GroupId groupId, @Nullable String text,
|
||||
List<AttachmentHeader> headers, long timestamp,
|
||||
boolean hasImageSupport) {
|
||||
@@ -270,6 +293,7 @@ public class ConversationViewModel extends AndroidViewModel
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void storeMessage(PrivateMessage m) {
|
||||
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
|
||||
dbExecutor.execute(() -> {
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
@@ -67,6 +68,7 @@ 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 =
|
||||
@@ -80,6 +82,7 @@ public class ImageActivity extends BriarActivity
|
||||
private AppBarLayout appBarLayout;
|
||||
private ViewPager viewPager;
|
||||
private List<AttachmentItem> attachments;
|
||||
private MessageId conversationMessageId;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
@@ -98,9 +101,20 @@ 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);
|
||||
|
||||
@@ -124,16 +138,11 @@ public class ImageActivity extends BriarActivity
|
||||
TextView contactName = toolbar.findViewById(R.id.contactName);
|
||||
TextView dateView = toolbar.findViewById(R.id.dateView);
|
||||
|
||||
// Intent Extras
|
||||
Intent i = getIntent();
|
||||
attachments = i.getParcelableArrayListExtra(ATTACHMENTS);
|
||||
int position = i.getIntExtra(ATTACHMENT_POSITION, -1);
|
||||
if (position == -1) throw new IllegalStateException();
|
||||
String name = i.getStringExtra(NAME);
|
||||
long time = i.getLongExtra(DATE, 0);
|
||||
// Set contact name and message time
|
||||
String date = formatDateAbsolute(this, time);
|
||||
contactName.setText(name);
|
||||
dateView.setText(date);
|
||||
conversationMessageId = new MessageId(messageIdBytes);
|
||||
|
||||
// Set up image ViewPager
|
||||
viewPager = findViewById(R.id.viewPager);
|
||||
@@ -320,8 +329,8 @@ public class ImageActivity extends BriarActivity
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
Fragment f = ImageFragment
|
||||
.newInstance(attachments.get(position), isFirst);
|
||||
Fragment f = ImageFragment.newInstance(
|
||||
attachments.get(position), conversationMessageId, isFirst);
|
||||
isFirst = false;
|
||||
return f;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ 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);
|
||||
return new ImageViewHolder(v, imageSize);
|
||||
requireNonNull(conversationItem);
|
||||
return new ImageViewHolder(v, imageSize, conversationItem.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,7 @@ 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;
|
||||
@@ -23,8 +24,10 @@ 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;
|
||||
|
||||
@@ -32,27 +35,36 @@ 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 {
|
||||
public class ImageFragment extends Fragment
|
||||
implements RequestListener<Drawable> {
|
||||
|
||||
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, boolean isFirst) {
|
||||
static ImageFragment newInstance(AttachmentItem a,
|
||||
MessageId conversationMessageId, 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;
|
||||
}
|
||||
@@ -67,9 +79,11 @@ public class ImageFragment extends Fragment {
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
|
||||
isFirst = args.getBoolean(IS_FIRST);
|
||||
conversationItemId =
|
||||
new MessageId(requireNonNull(args.getByteArray(ITEM_ID)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -80,57 +94,76 @@ public class ImageFragment extends Fragment {
|
||||
View v = inflater.inflate(R.layout.fragment_image, container,
|
||||
false);
|
||||
|
||||
viewModel = ViewModelProviders.of(requireNonNull(getActivity()),
|
||||
viewModel = ViewModelProviders.of(requireActivity(),
|
||||
viewModelFactory).get(ImageViewModel.class);
|
||||
|
||||
photoView = v.findViewById(R.id.photoView);
|
||||
photoView.setScaleLevels(1, 2, 4);
|
||||
photoView.setOnClickListener(view -> viewModel.clickImage());
|
||||
|
||||
// Request Listener
|
||||
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
|
||||
|
||||
@Override
|
||||
public boolean onLoadFailed(@Nullable GlideException e,
|
||||
Object model, Target<Drawable> target,
|
||||
boolean isFirstResource) {
|
||||
if (getActivity() != null && isFirst)
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onResourceReady(Drawable resource, Object model,
|
||||
Target<Drawable> target, DataSource dataSource,
|
||||
boolean isFirstResource) {
|
||||
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
|
||||
// set transition name only when not animatable,
|
||||
// because the animation won't start otherwise
|
||||
photoView.setTransitionName(
|
||||
attachment.getTransitionName());
|
||||
}
|
||||
// Move image to the top if overlapping toolbar
|
||||
if (viewModel.isOverlappingToolbar(photoView, resource)) {
|
||||
photoView.setScaleType(FIT_START);
|
||||
}
|
||||
if (getActivity() != null && isFirst) {
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Load Image
|
||||
GlideApp.with(this)
|
||||
.load(attachment)
|
||||
// TODO allow if size < maxTextureSize ?
|
||||
// .override(SIZE_ORIGINAL)
|
||||
.diskCacheStrategy(NONE)
|
||||
.error(R.drawable.ic_image_broken)
|
||||
.addListener(listener)
|
||||
.into(photoView);
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
@@ -18,8 +19,12 @@ 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 {
|
||||
@@ -29,25 +34,33 @@ class ImageViewHolder extends ViewHolder {
|
||||
|
||||
protected final ImageView imageView;
|
||||
private final int imageSize;
|
||||
private final MessageId conversationItemId;
|
||||
|
||||
ImageViewHolder(View v, int imageSize) {
|
||||
ImageViewHolder(View v, int imageSize, MessageId conversationItemId) {
|
||||
super(v);
|
||||
imageView = v.findViewById(R.id.imageView);
|
||||
this.imageSize = imageSize;
|
||||
this.conversationItemId = conversationItemId;
|
||||
}
|
||||
|
||||
void bind(AttachmentItem attachment, Radii r, boolean single,
|
||||
boolean needsStretch) {
|
||||
if (attachment.hasError()) {
|
||||
GlideApp.with(imageView)
|
||||
.clear(imageView);
|
||||
imageView.setImageResource(ERROR_RES);
|
||||
} else {
|
||||
setImageViewDimensions(attachment, single, needsStretch);
|
||||
loadImage(attachment, r);
|
||||
if (SDK_INT >= 21) {
|
||||
imageView.setTransitionName(attachment.getTransitionName());
|
||||
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);
|
||||
} else {
|
||||
loadImage(attachment, r);
|
||||
imageView.setScaleType(CENTER_CROP);
|
||||
}
|
||||
if (SDK_INT >= 21) {
|
||||
imageView.setTransitionName(
|
||||
attachment.getTransitionName(conversationItemId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@ 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;
|
||||
@@ -22,6 +27,8 @@ 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;
|
||||
@@ -35,22 +42,28 @@ 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 {
|
||||
public class ImageViewModel extends AndroidViewModel implements EventListener {
|
||||
|
||||
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.
|
||||
*/
|
||||
@@ -62,13 +75,60 @@ public class ImageViewModel extends AndroidViewModel {
|
||||
|
||||
@Inject
|
||||
ImageViewModel(Application application,
|
||||
MessagingManager messagingManager,
|
||||
MessagingManager messagingManager, EventBus eventBus,
|
||||
@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() {
|
||||
|
||||
@@ -47,7 +47,6 @@ 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;
|
||||
@@ -95,7 +94,7 @@ public class ForumListFragment extends BaseEventFragment implements
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
requireNonNull(getActivity()).setTitle(R.string.forums_button);
|
||||
requireActivity().setTitle(R.string.forums_button);
|
||||
|
||||
View contentView =
|
||||
inflater.inflate(R.layout.fragment_forum_list, container,
|
||||
|
||||
@@ -32,7 +32,6 @@ 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;
|
||||
@@ -84,7 +83,7 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
Contact c2 = item.getContact();
|
||||
showMessageScreen(c1, c2);
|
||||
};
|
||||
adapter = new ContactListAdapter(requireNonNull(getActivity()),
|
||||
adapter = new ContactListAdapter(requireActivity(),
|
||||
onContactClickListener);
|
||||
|
||||
list = contentView.findViewById(R.id.list);
|
||||
@@ -92,8 +91,7 @@ public class ContactChooserFragment extends BaseFragment {
|
||||
list.setAdapter(adapter);
|
||||
list.setEmptyText(R.string.no_contacts);
|
||||
|
||||
contactId = new ContactId(
|
||||
requireNonNull(getArguments()).getInt(CONTACT_ID));
|
||||
contactId = new ContactId(requireArguments().getInt(CONTACT_ID));
|
||||
|
||||
return contentView;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ 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;
|
||||
@@ -102,7 +101,7 @@ public class IntroductionMessageFragment extends BaseFragment
|
||||
}
|
||||
|
||||
// get contact IDs from fragment arguments
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
int contactId1 = args.getInt(CONTACT_ID_1, -1);
|
||||
int contactId2 = args.getInt(CONTACT_ID_2, -1);
|
||||
if (contactId1 == -1 || contactId2 == -1) {
|
||||
|
||||
@@ -9,7 +9,6 @@ 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;
|
||||
@@ -114,9 +113,7 @@ public class ContactExchangeActivity extends KeyAgreementActivity {
|
||||
return getString(R.string.exchanging_contact_details);
|
||||
}
|
||||
|
||||
protected void showErrorFragment() {
|
||||
String errorMsg = getString(R.string.connection_error_explanation);
|
||||
BaseFragment f = ContactExchangeErrorFragment.newInstance(errorMsg);
|
||||
showNextFragment(f);
|
||||
private void showErrorFragment() {
|
||||
showNextFragment(new ContactExchangeErrorFragment());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android.keyagreement;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -18,7 +19,10 @@ 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
|
||||
@@ -58,13 +62,12 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
||||
View v = inflater.inflate(R.layout.fragment_error_contact_exchange,
|
||||
container, false);
|
||||
|
||||
// set humanized error message
|
||||
// set optional error message
|
||||
TextView explanation = v.findViewById(R.id.errorMessage);
|
||||
Bundle args = getArguments();
|
||||
if (args == null) {
|
||||
throw new IllegalArgumentException("Use newInstance()");
|
||||
}
|
||||
explanation.setText(args.getString(ERROR_MSG));
|
||||
String errorMessage = args == null ? null : args.getString(ERROR_MSG);
|
||||
if (errorMessage == null) explanation.setVisibility(GONE);
|
||||
else explanation.setText(args.getString(ERROR_MSG));
|
||||
|
||||
// make feedback link clickable
|
||||
TextView sendFeedback = v.findViewById(R.id.sendFeedback);
|
||||
@@ -73,7 +76,11 @@ public class ContactExchangeErrorFragment extends BaseFragment {
|
||||
// buttons
|
||||
Button tryAgain = v.findViewById(R.id.tryAgainButton);
|
||||
tryAgain.setOnClickListener(view -> {
|
||||
if (getActivity() != null) getActivity().onBackPressed();
|
||||
// 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);
|
||||
});
|
||||
Button cancel = v.findViewById(R.id.cancelButton);
|
||||
cancel.setOnClickListener(view -> finish());
|
||||
|
||||
@@ -26,7 +26,6 @@ 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;
|
||||
|
||||
@@ -46,6 +45,7 @@ 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,6 +55,7 @@ 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
|
||||
@@ -133,6 +134,8 @@ 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) {
|
||||
@@ -152,6 +155,9 @@ 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
|
||||
@@ -187,6 +193,7 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
showQrCodeFragmentIfAllowed();
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
private void showQrCodeFragmentIfAllowed() {
|
||||
if (isResumed && continueClicked && areEssentialPermissionsGranted()) {
|
||||
if (isWifiReady() && isBluetoothReady()) {
|
||||
@@ -200,6 +207,8 @@ 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;
|
||||
@@ -210,55 +219,50 @@ 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 &&
|
||||
(locationPermission == Permission.GRANTED ||
|
||||
locationPermission == Permission.PERMANENTLY_DENIED);
|
||||
(SDK_INT < 23 || locationPermission == Permission.GRANTED ||
|
||||
!isBluetoothSupported());
|
||||
}
|
||||
|
||||
private boolean isBluetoothSupported() {
|
||||
return bt != null && bluetoothPlugin != null;
|
||||
}
|
||||
|
||||
private boolean isWifiReady() {
|
||||
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
|
||||
if (p == null) return true; // Continue without wifi
|
||||
State state = p.getState();
|
||||
if (wifiPlugin == null) return true; // Continue without wifi
|
||||
State state = wifiPlugin.getState();
|
||||
// Wait for plugin to become enabled
|
||||
return state == ACTIVE || state == INACTIVE;
|
||||
}
|
||||
|
||||
private boolean isBluetoothReady() {
|
||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
|
||||
bluetoothDecision == BluetoothDecision.WAITING) {
|
||||
// Wait for decision
|
||||
return false;
|
||||
}
|
||||
if (bluetoothDecision == BluetoothDecision.NO_ADAPTER
|
||||
|| bluetoothDecision == BluetoothDecision.REFUSED) {
|
||||
if (!isBluetoothSupported()) {
|
||||
// Continue without Bluetooth
|
||||
return true;
|
||||
}
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt == null) return true; // Continue without Bluetooth
|
||||
if (bluetoothDecision == BluetoothDecision.UNKNOWN ||
|
||||
bluetoothDecision == BluetoothDecision.WAITING ||
|
||||
bluetoothDecision == BluetoothDecision.REFUSED) {
|
||||
// Wait for user to accept
|
||||
return false;
|
||||
}
|
||||
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 p.getState() == ACTIVE;
|
||||
return bluetoothPlugin.getState() == ACTIVE;
|
||||
}
|
||||
|
||||
private boolean shouldEnableWifi() {
|
||||
if (hasEnabledWifi) return false;
|
||||
Plugin p = pluginManager.getPlugin(LanTcpConstants.ID);
|
||||
if (p == null) return false;
|
||||
State state = p.getState();
|
||||
if (wifiPlugin == null) return false;
|
||||
State state = wifiPlugin.getState();
|
||||
return state == STARTING_STOPPING || state == DISABLED;
|
||||
}
|
||||
|
||||
private void requestBluetoothDiscoverable() {
|
||||
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
if (bt == null) {
|
||||
if (!isBluetoothSupported()) {
|
||||
bluetoothDecision = BluetoothDecision.NO_ADAPTER;
|
||||
showQrCodeFragmentIfAllowed();
|
||||
} else {
|
||||
@@ -277,9 +281,8 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
private boolean shouldEnableBluetooth() {
|
||||
if (bluetoothDecision != BluetoothDecision.ACCEPTED) return false;
|
||||
if (hasEnabledBluetooth) return false;
|
||||
Plugin p = pluginManager.getPlugin(BluetoothConstants.ID);
|
||||
if (p == null) return false;
|
||||
State state = p.getState();
|
||||
if (!isBluetoothSupported()) return false;
|
||||
State state = bluetoothPlugin.getState();
|
||||
return state == STARTING_STOPPING || state == DISABLED;
|
||||
}
|
||||
|
||||
@@ -298,6 +301,9 @@ 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();
|
||||
}
|
||||
|
||||
@@ -341,17 +347,17 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
|
||||
private boolean checkPermissions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
// If the camera permission has been permanently denied, ask the
|
||||
// If an essential permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (cameraPermission == Permission.PERMANENTLY_DENIED) {
|
||||
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();
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
// Should we show the rationale for one or both permissions?
|
||||
@@ -371,6 +377,16 @@ 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);
|
||||
@@ -381,8 +397,13 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[] {CAMERA, ACCESS_FINE_LOCATION},
|
||||
String[] permissions;
|
||||
if (isBluetoothSupported()) {
|
||||
permissions = new String[] {CAMERA, ACCESS_FINE_LOCATION};
|
||||
} else {
|
||||
permissions = new String[] {CAMERA};
|
||||
}
|
||||
ActivityCompat.requestPermissions(this, permissions,
|
||||
REQUEST_PERMISSION_CAMERA_LOCATION);
|
||||
}
|
||||
|
||||
@@ -399,12 +420,15 @@ public abstract class KeyAgreementActivity extends BriarActivity implements
|
||||
} else {
|
||||
cameraPermission = 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 (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 a permission dialog has been shown, showing the QR code fragment
|
||||
// on this call path would cause a crash due to
|
||||
|
||||
@@ -54,7 +54,6 @@ 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;
|
||||
@@ -67,6 +66,7 @@ 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,8 +138,7 @@ public class KeyAgreementFragment extends BaseEventFragment
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireNonNull(getActivity())
|
||||
.setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
requireActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR);
|
||||
cameraView.setPreviewConsumer(new QrCodeDecoder(this));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ 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;
|
||||
|
||||
@@ -51,7 +52,8 @@ public class OpenDatabaseFragment extends BaseFragment {
|
||||
|
||||
StartupViewModel viewModel = ViewModelProviders.of(requireActivity(),
|
||||
viewModelFactory).get(StartupViewModel.class);
|
||||
viewModel.getState().observe(this, this::onStateChanged);
|
||||
LifecycleOwner owner = getViewLifecycleOwner();
|
||||
viewModel.getState().observe(owner, this::onStateChanged);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
|
||||
@@ -67,7 +68,8 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
|
||||
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
|
||||
.get(StartupViewModel.class);
|
||||
|
||||
viewModel.getPasswordValidated().observeEvent(this, result -> {
|
||||
LifecycleOwner owner = getViewLifecycleOwner();
|
||||
viewModel.getPasswordValidated().observeEvent(owner, result -> {
|
||||
if (result != SUCCESS) onPasswordInvalid(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ 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
|
||||
@@ -43,7 +42,7 @@ public class GroupInviteFragment extends ContactSelectorFragment {
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requireNonNull(getActivity()).setTitle(R.string.groups_invite_members);
|
||||
requireActivity().setTitle(R.string.groups_invite_members);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -39,7 +39,6 @@ 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
|
||||
@@ -72,7 +71,7 @@ public class GroupListFragment extends BaseFragment implements
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
|
||||
requireNonNull(getActivity()).setTitle(R.string.groups_button);
|
||||
requireActivity().setTitle(R.string.groups_button);
|
||||
|
||||
View v = inflater.inflate(R.layout.list, container, false);
|
||||
|
||||
|
||||
@@ -12,8 +12,6 @@ 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 {
|
||||
@@ -35,7 +33,7 @@ public class CrashFragment extends Fragment {
|
||||
}
|
||||
|
||||
private DevReportActivity getDevReportActivity() {
|
||||
return (DevReportActivity) requireNonNull(getActivity());
|
||||
return (DevReportActivity) requireActivity();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
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) requireNonNull(getActivity());
|
||||
return (DevReportActivity) requireActivity();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ 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 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);
|
||||
@@ -26,7 +28,7 @@ public class BriarNotificationBuilder extends NotificationCompat.Builder {
|
||||
}
|
||||
|
||||
public BriarNotificationBuilder setColorRes(@ColorRes int res) {
|
||||
setColor(ContextCompat.getColor(mContext, res));
|
||||
setColor(ContextCompat.getColor(context, res));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ public class ImagePreview extends ConstraintLayout {
|
||||
((ImagePreviewAdapter) imageList.getAdapter());
|
||||
int pos = requireNonNull(adapter).loadItemPreview(result);
|
||||
if (pos != NO_POSITION) {
|
||||
imageList.smoothScrollToPosition(pos);
|
||||
imageList.scrollToPosition(pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,22 @@ import androidx.lifecycle.Observer;
|
||||
@NotNullByDefault
|
||||
public class LiveEvent<T> extends LiveData<LiveEvent.ConsumableEvent<T>> {
|
||||
|
||||
/**
|
||||
* Creates a LiveEvent initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public LiveEvent(T value) {
|
||||
super(new ConsumableEvent<>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LiveEvent with no value assigned to it.
|
||||
*/
|
||||
public LiveEvent() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void observeEvent(LifecycleOwner owner,
|
||||
LiveEventHandler<T> handler) {
|
||||
LiveEventObserver<T> observer = new LiveEventObserver<>(handler);
|
||||
|
||||
@@ -5,6 +5,22 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@NotNullByDefault
|
||||
public class MutableLiveEvent<T> extends LiveEvent<T> {
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveEvent initialized with the given {@code value}.
|
||||
*
|
||||
* @param value initial value
|
||||
*/
|
||||
public MutableLiveEvent(T value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MutableLiveEvent with no value assigned to it.
|
||||
*/
|
||||
public MutableLiveEvent() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void postEvent(T value) {
|
||||
super.postValue(new ConsumableEvent<>(value));
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public class LinkDialogFragment extends DialogFragment {
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle args = requireNonNull(getArguments());
|
||||
Bundle args = requireArguments();
|
||||
url = requireNonNull(args.getString("url"));
|
||||
|
||||
setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme);
|
||||
@@ -70,7 +70,7 @@ public class LinkDialogFragment extends DialogFragment {
|
||||
Context ctx = requireContext();
|
||||
Intent i = new Intent(ACTION_VIEW, Uri.parse(url));
|
||||
PackageManager packageManager = ctx.getPackageManager();
|
||||
List activities =
|
||||
List<?> activities =
|
||||
packageManager.queryIntentActivities(i, MATCH_DEFAULT_ONLY);
|
||||
boolean choice = activities.size() > 1;
|
||||
Intent intent = choice ? Intent.createChooser(i,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="200dp"
|
||||
android:height="200dp"
|
||||
android:width="115dp"
|
||||
android:height="115dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
|
||||
10
briar-android/src/main/res/drawable/ic_image_missing.xml
Normal file
10
briar-android/src/main/res/drawable/ic_image_missing.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="115dp"
|
||||
android:height="115dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,43 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.20807062"
|
||||
android:scaleY="0.20807062"
|
||||
android:translateX="19.73077"
|
||||
android:translateY="19.73077">
|
||||
<path
|
||||
android:pathData="M94,144.5V264c0,9.7 7.9,17.7 17.7,17.7h8.3c9.7,0 17.7,-8 17.7,-17.7V144.5Z"
|
||||
android:fillColor="#87c214"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M137.7,86.8V64.3c0,-9.7 -8,-17.7 -17.7,-17.7h-8.3C102,46.6 94,54.6 94,64.3v22.5z"
|
||||
android:fillColor="#87c214"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M234.7,183.8V64.3c0,-9.7 -7.9,-17.7 -17.7,-17.7h-8.3c-9.7,0 -17.7,8 -17.7,17.7v119.5z"
|
||||
android:fillColor="#87c214"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M191,241.5V264c0,9.7 8,17.7 17.7,17.7h8.3c9.7,0 17.7,-8 17.7,-17.7v-22.5z"
|
||||
android:fillColor="#87c214"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M87,190.8H64.5c-9.7,0 -17.7,7.9 -17.7,17.7v8.3c0,9.7 7.9,17.7 17.7,17.7H87Z"
|
||||
android:fillColor="#95d220"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M264.2,190.8H144.7v43.7h119.5c9.7,0 17.7,-8 17.7,-17.7v-8.3c-0.1,-9.7 -8,-17.7 -17.7,-17.7z"
|
||||
android:fillColor="#95d220"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="M184,93.8H64.5c-9.7,0 -17.7,7.9 -17.7,17.7v8.3c0,9.7 7.9,17.7 17.7,17.7H184Z"
|
||||
android:fillColor="#95d220"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
android:pathData="m264.2,93.8h-22.5v43.7h22.5c9.7,0 17.7,-7.9 17.7,-17.7v-8.3c-0.1,-9.7 -8,-17.7 -17.7,-17.7z"
|
||||
android:fillColor="#95d220"
|
||||
android:strokeColor="#00000000"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
@@ -22,12 +21,12 @@
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_intro"
|
||||
android:tint="@color/color_primary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toStartOf="@id/explanationText"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"/>
|
||||
app:tint="@color/color_primary"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/explanationImage"
|
||||
@@ -35,16 +34,16 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:adjustViewBounds="true"
|
||||
android:paddingEnd="@dimen/margin_large"
|
||||
android:paddingStart="@dimen/margin_large"
|
||||
android:paddingTop="@dimen/margin_large"
|
||||
android:paddingEnd="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_explanation"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription"/>
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/explanationText"
|
||||
@@ -55,7 +54,7 @@
|
||||
android:text="@string/face_to_face"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/diagram"
|
||||
app:layout_constraintTop_toBottomOf="@id/explanationImage"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/explanationImage" />
|
||||
|
||||
<View
|
||||
android:id="@+id/explanationBorder"
|
||||
@@ -65,14 +64,14 @@
|
||||
app:layout_constraintBottom_toBottomOf="@id/explanationText"
|
||||
app:layout_constraintEnd_toEndOf="@id/explanationImage"
|
||||
app:layout_constraintStart_toStartOf="@id/explanationImage"
|
||||
app:layout_constraintTop_toTopOf="@id/explanationImage"/>
|
||||
app:layout_constraintTop_toTopOf="@id/explanationImage" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="diagram,explanationBorder"/>
|
||||
app:constraint_referenced_ids="diagram,explanationBorder" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/continueButton"
|
||||
@@ -83,7 +82,7 @@
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:text="@string/continue_button"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier"/>
|
||||
app:layout_constraintTop_toBottomOf="@id/barrier" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -53,10 +53,10 @@
|
||||
android:contentDescription="@string/close"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_close"
|
||||
android:tint="@color/briar_text_tertiary_inverse"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/briar_text_tertiary_inverse" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
android:paddingBottom="@dimen/margin_large"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/qr_code_intro"
|
||||
android:tint="@color/color_primary"
|
||||
app:layout_constraintBottom_toTopOf="@id/explanationImage"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/color_primary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/explanationImage"
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
android:layout_height="128dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/startup_lock"
|
||||
android:tint="@color/briar_accent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:tint="@color/briar_accent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
android:layout_height="128dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/startup_lock"
|
||||
android:tint="@color/briar_primary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.5"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:tint="@color/briar_primary"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ProgressBar
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textIsSelectable="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/include_in_report"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
briar-android/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
briar-android/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
briar-android/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
briar-android/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
briar-android/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
briar-android/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
briar-android/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
briar-android/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
BIN
briar-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
briar-android/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
@@ -27,6 +27,7 @@
|
||||
<string name="enter_password">كلمة السّر</string>
|
||||
<string name="try_again">كلمة السرّ خاطئة, الرجاء المحاولة مجدّدا</string>
|
||||
<string name="dialog_title_cannot_check_password">لا يمكن التحقق من كلمة السر</string>
|
||||
<string name="dialog_message_cannot_check_password">Briar لم يتمكن من التحقق من كلمة المرور. الرجاء إعادة تشغيل جهازك من أجل جل المشكلة</string>
|
||||
<string name="sign_in_button">تسجيل الدخول</string>
|
||||
<string name="forgotten_password">نسيتُ كلمة السر</string>
|
||||
<string name="dialog_title_lost_password">فقدت كلمة السر</string>
|
||||
@@ -64,12 +65,36 @@
|
||||
<string name="lock_button">قفل التطبيق</string>
|
||||
<string name="settings_button">الإعدادات</string>
|
||||
<string name="sign_out_button">تسجيل الخروج</string>
|
||||
<string name="transports_onboarding_text">إلمس هنا من أجل التحكم بطريقةالربط مع جهات الاتصال.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">إنترنت</string>
|
||||
<string name="tor_device_status_online_wifi">جهازك لديه ولوج لشبكة الانترنت عبر ال Wi-Fi </string>
|
||||
<string name="tor_device_status_online_mobile">جهازك لديه ولوج لشبكة الانترنت عبر بيانات الهاتف</string>
|
||||
<string name="tor_device_status_offline">جهازك ليس متصل بالانترنت</string>
|
||||
<string name="tor_plugin_status_enabling">جاري اتصال Briar بالانترنت</string>
|
||||
<string name="tor_plugin_status_active">Briar متصل بالانترنت</string>
|
||||
<string name="tor_plugin_status_inactive">Briar لم يتمكن من الاتصال بالانترنت </string>
|
||||
<string name="tor_plugin_status_disabled">إعدادات Briar لاتسمح بالاتصال بالانترنت</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">إعدادات Briar لاتسمح بالاتصال عن طريق بيانات الهاتف</string>
|
||||
<string name="tor_plugin_status_disabled_battery">إعدادات Briar لاتسمح بالاتصال بالانترنت عند استخدام بطارية الهاتف</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">لايمكن استخدام Briar في هذا البلد</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">واي-فاي</string>
|
||||
<string name="transport_lan_long">نفس شبكة الWi-Fi </string>
|
||||
<string name="lan_device_status_on">جهازك متصل بشبكة الWi-Fi</string>
|
||||
<string name="lan_device_status_off">جهازك ليس متصل بشبكة الWi-Fi</string>
|
||||
<string name="lan_plugin_status_enabling">جاري ايصال Briar بشبكة الWi-Fi</string>
|
||||
<string name="lan_plugin_status_active"> Briar متصل بشبكة الWi-Fi</string>
|
||||
<string name="lan_plugin_status_inactive">لم يتمكن Briar من الاتصال بشبكة ال Wi-Fi</string>
|
||||
<string name="lan_plugin_status_disabled">إعدادات Briar لاتسمح بالاتصال بشبكة ال Wi-Fi</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">بلوتوث</string>
|
||||
<string name="bt_device_status_on"> البلوتوث مفعّل </string>
|
||||
<string name="bt_device_status_off">البلوتوث مفعّل</string>
|
||||
<string name="bt_plugin_status_enabling">جاري اتصال Briar بالبلوتوث</string>
|
||||
<string name="bt_plugin_status_active">Briar متصل بالبلوتوث</string>
|
||||
<string name="bt_plugin_status_inactive">لم يتمكن Briar من الاتصال بالانترنت</string>
|
||||
<string name="bt_plugin_status_disabled">إعدادات Briar لاتسمح بالاتصال بالبلوتوث</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">تم تسجيل الخروج من Briar (براير)</string>
|
||||
<string name="reminder_notification_text">الرجاء اللمس لإعادة الدخول</string>
|
||||
@@ -190,7 +215,6 @@
|
||||
<string name="connecting_to_device">يتم الإتصال بالجهاز\u2026</string>
|
||||
<string name="authenticating_with_device">يتم التوثيق مع الجهاز\u2026</string>
|
||||
<string name="connection_error_title">لم يمكن الإتصال بجهة إتصالك</string>
|
||||
<string name="connection_error_explanation">رجاءًا التأكد أن كليكما متصل بنفس شبكة الواي فاي.</string>
|
||||
<string name="connection_error_feedback">إذا إستمرت المشكلة، رجاءًا <a href="feedback">أرسل تقرير </a> لمساعدتنا على تحسين التطبيق.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">إضافة جهة اتصال عن بعد </string>
|
||||
@@ -459,6 +483,8 @@
|
||||
<string name="tor_enable_summary">كل جهات الاتصال تمر عبر شبكة تور من أجل الخصوصية</string>
|
||||
<string name="tor_network_setting">وسيلة الاتصال لشبكة تور</string>
|
||||
<string name="tor_network_setting_automatic">تلقائيًا حسب الموقع</string>
|
||||
<string name="tor_network_setting_without_bridges">استخدام شبكة تور من دون جسور</string>
|
||||
<string name="tor_network_setting_with_bridges">استخدام شبكة تور مع جسور</string>
|
||||
<string name="tor_network_setting_never">لا يمكن الاتصال بالإنترنت</string>
|
||||
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
|
||||
<string name="tor_network_setting_summary">تلقائيا: %1$s (في %2$s)</string>
|
||||
@@ -574,6 +600,7 @@
|
||||
<string name="lock_is_locked">Briar (براير) مقفل</string>
|
||||
<string name="lock_tap_to_unlock">الرجاء اللمس لفك القفل</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">يمكن ل Briar التواصل مع جهات الاتصال عن طريق الانترنت, شكبة ال Wi-Fi أو البلوتوث.n\n\كل وسائل الاتصال عن طريق الانترنت تمر عبر شبكة تور من أجل الخصوصية.n\n\إذا كان من الممكن الوصول إلى شبكة إتصال بعدة طرق فإن Briar سوف يستعملهم جميعاً بالتوازي.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">آليس</string>
|
||||
|
||||
@@ -162,7 +162,6 @@
|
||||
<string name="connecting_to_device">Cihaza qoşulma /2026</string>
|
||||
<string name="authenticating_with_device">Cihazla təsdiqlənir \u2026</string>
|
||||
<string name="connection_error_title">Kontaktınıza qoşula bilmədi</string>
|
||||
<string name="connection_error_explanation">Həmin Wi-Fi şəbəkəsinə qoşulduğunuzu yoxlayın.</string>
|
||||
<string name="connection_error_feedback"><a href="feedback">Bu problem davam edərsə, tətbiqin təkmilləşdirilməsinə kömək etmək üçün rəy göndərin.</a></string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Məsafədə kontakt əlavə etmək </string>
|
||||
|
||||
@@ -134,7 +134,6 @@
|
||||
<string name="connecting_to_device">Povezujem se sa uređajem\u2026</string>
|
||||
<string name="authenticating_with_device">Autentikacija sa uređajem\u2026</string>
|
||||
<string name="connection_error_title">Nije moguće povezivanje sa vašim kontaktom</string>
|
||||
<string name="connection_error_explanation">Provjerite jeste li oboje povezani na ist Wi-Fi mrežu.</string>
|
||||
<string name="connection_error_feedback">Ako se problem ponavlja, Molimo vas <a href="feedback">pošaljite povratne informacije</a> kako bi pomogli poboljšanju aplikacije.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Dodaj udaljeni konktakt</string>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="enter_password">Contrasenya</string>
|
||||
<string name="try_again">La contrasenya és incorrecta, torneu a escriure-la</string>
|
||||
<string name="dialog_title_cannot_check_password">No es pot verificar la contrasenya</string>
|
||||
<string name="dialog_message_cannot_check_password">El Briar no pot verificar la contrasenya. Proveu de reiniciar el vostre aparell per a solucionar aquest problema.</string>
|
||||
<string name="dialog_message_cannot_check_password">Briar no pot verificar la contrasenya. Proveu de reiniciar el vostre aparell per a solucionar aquest problema.</string>
|
||||
<string name="sign_in_button">Inicia la sessió</string>
|
||||
<string name="forgotten_password">No recordo la contrasenya</string>
|
||||
<string name="dialog_title_lost_password">Contrasenya perduda</string>
|
||||
@@ -44,9 +44,9 @@
|
||||
<item quantity="other">Aquesta és una versió de prova de Briar. El vostre compte caducarà en %d dies i no es podrà renovar.</item>
|
||||
</plurals>
|
||||
<string name="expiry_date_reached">Aquesta versió de Briar ha caducat.\nGràcies per haver-lo provat!</string>
|
||||
<string name="download_briar">Per continuar utilitzant el Briar, baixeu la darrera versió.</string>
|
||||
<string name="download_briar">Per continuar utilitzant Briar, baixeu la darrera versió.</string>
|
||||
<string name="create_new_account">Haureu de crear un compte nou, però podeu utilitzar el mateix sobrenom.</string>
|
||||
<string name="download_briar_button">Baixa l\'última versió</string>
|
||||
<string name="download_briar_button">Descarrega la darrera versió</string>
|
||||
<string name="startup_open_database">S\'està desxifrant la base de dades...</string>
|
||||
<string name="startup_migrate_database">S\'està actualitzant la base de dades...</string>
|
||||
<string name="startup_compact_database">S\'està compactant la base de dades...</string>
|
||||
@@ -61,12 +61,36 @@
|
||||
<string name="lock_button">Bloqueja l\'aplicació</string>
|
||||
<string name="settings_button">Configuració</string>
|
||||
<string name="sign_out_button">Tanca la sessió </string>
|
||||
<string name="transports_onboarding_text">Toqueu per decidir com es connecta Briar als vostres contactes.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="tor_device_status_online_wifi">El mòbil té accés a Internet via WiFi</string>
|
||||
<string name="tor_device_status_online_mobile">El mòbil té accés a Internet via dades</string>
|
||||
<string name="tor_device_status_offline">El mòbil no té accés a Internet</string>
|
||||
<string name="tor_plugin_status_enabling">Briar s\'està connectant a Internet</string>
|
||||
<string name="tor_plugin_status_active">Briar està connectat a Internet</string>
|
||||
<string name="tor_plugin_status_inactive">Briar no pot connectar-se a Internet</string>
|
||||
<string name="tor_plugin_status_disabled">Briar està configurat per a no emprar Internet</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">Briar està configurat per a no emprar dades mòbils</string>
|
||||
<string name="tor_plugin_status_disabled_battery">Briar està configurat per a no usar Internet mentre funciona amb bateria</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">Briar està configurat per a no emprar Internet en aquest país</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="transport_lan_long">La mateixa xarxa WiFi</string>
|
||||
<string name="lan_device_status_on">El mòbil està connectat a WiFi</string>
|
||||
<string name="lan_device_status_off">El mòbil no està connectat a WiFi</string>
|
||||
<string name="lan_plugin_status_enabling">Briar s\'està connectant a la xarxa WiFi</string>
|
||||
<string name="lan_plugin_status_active">Briar està connectat a la xarxa WiFi</string>
|
||||
<string name="lan_plugin_status_inactive">Briar no pot connectar-se a la xarxa WiFi</string>
|
||||
<string name="lan_plugin_status_disabled">Briar està configurat per a no emprar la xarxa WiFi</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">El Bluetooth del mòbil està apagat</string>
|
||||
<string name="bt_device_status_off">El Bluetooth del mòbil està engegat</string>
|
||||
<string name="bt_plugin_status_enabling">Briar s\'està connectant a Bluetooth</string>
|
||||
<string name="bt_plugin_status_active">Briar està connectat a Bluetooth</string>
|
||||
<string name="bt_plugin_status_inactive">Briar no pot connectar-se a Bluetooth</string>
|
||||
<string name="bt_plugin_status_disabled">Briar està configurat per a no emprar Bluetooth</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Heu sortit de Briar</string>
|
||||
<string name="reminder_notification_text">Toqueu per a reiniciar la sessió.</string>
|
||||
@@ -112,7 +136,7 @@
|
||||
<string name="fix">Corregeix</string>
|
||||
<string name="help">Ajuda</string>
|
||||
<string name="sorry">Ens sap greu</string>
|
||||
<string name="error_start_activity">No disponible al vostre sistema</string>
|
||||
<string name="error_start_activity">No està disponible en el vostre sistema</string>
|
||||
<string name="status_heading">Estat</string>
|
||||
<!--Contacts and Private Conversations-->
|
||||
<string name="no_contacts">No hi ha cap contacte per mostrar</string>
|
||||
@@ -128,16 +152,16 @@
|
||||
<string name="set_contact_alias">Canvia el nom del contacte</string>
|
||||
<string name="set_contact_alias_hint">Nom del contacte</string>
|
||||
<string name="set_alias_button">Canvia</string>
|
||||
<string name="delete_all_messages">Suprimeix tots els missatges</string>
|
||||
<string name="dialog_title_delete_all_messages">Confirmeu la supressió dels missatges</string>
|
||||
<string name="dialog_message_delete_all_messages">Esteu segur que voleu suprimir tots els missatges?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">No s\'ha pogut suprimir tots els missatges</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_both">Els missatges relacionats amb introduccions i invitacions pendents no es poden suprimir fins que es finalitzin.</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_introductions">Els missatges relacionats amb introduccions pendents no es poden suprimir fins que es finalitzin.</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_invitations">Els missatges relacionats amb invitacions pendents no es poden suprimir fins que es finalitzin.</string>
|
||||
<string name="delete_all_messages">Esborra tots els missatges</string>
|
||||
<string name="dialog_title_delete_all_messages">Confirmeu l\'esborrat dels missatges</string>
|
||||
<string name="dialog_message_delete_all_messages">Segur que voleu esborrar tots els missatges?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">No s\'han pogut esborrar tots els missatges</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_both">Els missatges relacionats amb presentacions i invitacions pendents no es poden suprimir fins que finalitzen.</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_introductions">Els missatges relacionats amb presentacions pendents no es poden suprimir fins que finalitzen.</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_invitations">Els missatges relacionats amb invitacions pendents no es poden suprimir fins que finalitzen.</string>
|
||||
<string name="dialog_message_not_deleted_partly_downloaded">Els missatges baixats parcialment no es poden suprimir fins que s\'acabin de baixar.</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_both">Per a suprimir una invitació o una introducció, cal que seleccioneu la sol·licitdu i la resposta.</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_introductions">Per a suprimir una introducció, cal que seleccioneu la sol·licitud i la resposta.</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_both">Per a suprimir una invitació o una presentació, cal que seleccioneu la sol·licitud i la resposta.</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_introductions">Per a suprimir una presentació, cal que seleccioneu la sol·licitud i la resposta.</string>
|
||||
<string name="dialog_message_not_deleted_not_all_selected_invitations">Per a suprimir una invitació, cal que seleccioneu la sol·licitud i la resposta.</string>
|
||||
<string name="delete_contact">Suprimeix aquest contacte</string>
|
||||
<string name="dialog_title_delete_contact">Confirmeu la supressió del contacte</string>
|
||||
@@ -172,7 +196,6 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="connecting_to_device">Connectant-se al dispositiu\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticant-se amb el dispositiu\u2026</string>
|
||||
<string name="connection_error_title">No ha pogut connectar-se al vostre contacte</string>
|
||||
<string name="connection_error_explanation">Comproveu que esteu connectats a la mateixa xarxa Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si aquest problema persisteix, <a href="feedback">envieu-nos un comentari</a> per ajudar-nos a millorar l\'aplicació.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Afegeix un contacte llunyà</string>
|
||||
@@ -413,10 +436,20 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="pref_theme_auto">Automàtic (segons l\'hora)</string>
|
||||
<string name="pref_theme_system">Valor per defecte del sistema</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">Connexions</string>
|
||||
<string name="bluetooth_setting">Connectat als contactes via Bluetooth</string>
|
||||
<string name="wifi_setting">Connectat als contactes en la mateixa xarxa WiFi</string>
|
||||
<string name="tor_enable_title">Connectat als contactes via Internet</string>
|
||||
<string name="tor_enable_summary">Totes les connexions van via la xarxa Tor per augmentar privacitat</string>
|
||||
<string name="tor_network_setting">Mètode de connexió per a la xarxa Tor</string>
|
||||
<string name="tor_network_setting_automatic">Automàtic, basat en la posició</string>
|
||||
<string name="tor_network_setting_without_bridges">Usa la xarxa Tor sense ponts</string>
|
||||
<string name="tor_network_setting_with_bridges">Usa la xarxa Tor amb ponts</string>
|
||||
<string name="tor_network_setting_never">No connectis a Internet</string>
|
||||
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
|
||||
<string name="tor_network_setting_summary">Automàtic: %1$s (a %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Usa dades mòbils</string>
|
||||
<string name="tor_only_when_charging_title">Només connecta\'t a Internet mentre s\'està carregant </string>
|
||||
<string name="tor_only_when_charging_summary">Desactiva la connexió a Internet quan el dispositiu estigui funcionant amb la bateria</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Seguretat</string>
|
||||
@@ -527,6 +560,7 @@ Així que l\'actualitzi li veureu una icona diferent .</string>
|
||||
<string name="lock_is_locked">Briar està bloquejat</string>
|
||||
<string name="lock_tap_to_unlock">Toqueu per desbloquejar-lo</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">Briar pot contactar els vostres contactes via Internet, WiFi o Bluetooth.\n\nTotes les connexions d\'Internet van via la xarxa Tor per privacitat.\n\nSi es pot arribar a un contacte per diversos mètodes, Briar els usa tots simultàniament.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alba</string>
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
<string name="connecting_to_device">Verbinde mit Gerät\u2026</string>
|
||||
<string name="authenticating_with_device">Authentifiziere Gerät\u2026</string>
|
||||
<string name="connection_error_title">Keine Verbindung zum Kontakt</string>
|
||||
<string name="connection_error_explanation">Überprüfe, ob ihr beide mit demselben WLAN-Netzwerk verbunden seid.</string>
|
||||
<string name="connection_error_feedback">Wenn das Problem weiterbesteht, hilf uns die App zu verbessern und <a href="feedback">schicke Feedback</a>.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Kontakt aus der Ferne hinzufügen</string>
|
||||
@@ -550,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Kamera und Standort</string>
|
||||
<string name="permission_camera_location_request_body">Um den QR-Code zu scannen, braucht Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
|
||||
<string name="permission_camera_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Hinzufügen von Kontakten erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
|
||||
<string name="permission_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um Bluetooth-Geräte zu erkennen.\n\nBitte gewähre den Zugriff.</string>
|
||||
<string name="qr_code">QR-Code</string>
|
||||
<string name="show_qr_code_fullscreen">QR-Code im Vollbildmodus anzeigen</string>
|
||||
<!--App Locking-->
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
<string name="connecting_to_device">Conectando al dispositivo\u2026</string>
|
||||
<string name="authenticating_with_device">Autentificándose con el dispositivo\u2026</string>
|
||||
<string name="connection_error_title">No se pudo conectar a tu contacto</string>
|
||||
<string name="connection_error_explanation">Por favor comprobar que ambos estén conectados a la misma red Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si este problema persiste, por favor <a href="feedback">envía tus comentarios</a> para ayudarnos a mejorar la aplicación.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Añadir un Contacto a Distancia</string>
|
||||
@@ -550,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Cámara y ubicación</string>
|
||||
<string name="permission_camera_location_request_body">Para escanear el código QR, Briar necesita acceso a la cámara.\n\nPara descubrir dispositivos Bluetooth, Briar necesita permiso para acceder tu ubicación.\n\nBriar no la almacena o la comparte con nadie.</string>
|
||||
<string name="permission_camera_denied_body">Has denegado el acceso a la cámara, pero para añadir contactos se requiere el uso de la cámara.\n\nPor favor considera la posibilidad de conceder el acceso.</string>
|
||||
<string name="permission_location_denied_body">Has denegado el acceso a tu ubicación, pero Briar necesita este permiso para descubrir dispositivos Bluetooth.\n\nPor favor considera la posibilidad de conceder el acceso.</string>
|
||||
<string name="qr_code">Código QR</string>
|
||||
<string name="show_qr_code_fullscreen">Mostrar código QR a pantalla completa</string>
|
||||
<!--App Locking-->
|
||||
|
||||
@@ -171,7 +171,6 @@
|
||||
<string name="connecting_to_device">Gailura konektatzen\u2026</string>
|
||||
<string name="authenticating_with_device">Gailuarekin autentifikatzen\u2026</string>
|
||||
<string name="connection_error_title">Ezin izan da zure kontaktuarekin konektatu</string>
|
||||
<string name="connection_error_explanation">Egiaztatu biak Wi-Fi sare berera konektatuta zaudetela.</string>
|
||||
<string name="connection_error_feedback">Arazoa mantentzen bada, mesedez <a href="feedback">bidali iruzkin bat</a> aplikazioa hobetzen laguntzeko.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Gehitu urruneko kontaktua</string>
|
||||
|
||||
@@ -69,10 +69,21 @@
|
||||
<string name="sign_out_button">خروج</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">اینترنت</string>
|
||||
<string name="tor_plugin_status_enabling">Briar در حال اتصال به اینترنت می باشد</string>
|
||||
<string name="tor_plugin_status_active">Briar به اینترنت متصل شد</string>
|
||||
<string name="tor_plugin_status_inactive">Briar نمی تواند به اینترنت متصل شود</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">وای فای</string>
|
||||
<string name="transport_lan_long">همان شبکه وای-فای</string>
|
||||
<string name="lan_device_status_on">موبایل شما به وای-فای وصل می باشد</string>
|
||||
<string name="lan_device_status_off">موبایل شما به وای-فای وصل نیست</string>
|
||||
<string name="lan_plugin_status_enabling">Briar در حال اتصال به شبکه وای-فای می باشد</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">بلوتوث</string>
|
||||
<string name="bt_device_status_on">بلوتوث موبایل شما روشن می باشد</string>
|
||||
<string name="bt_device_status_off">بلوتوث موبایل شما خاموش می باشد</string>
|
||||
<string name="bt_plugin_status_inactive">Briar نمی تواند به بلوتوث وصل شود</string>
|
||||
<string name="bt_plugin_status_disabled">Briar طوری پیکربندی شده که از بلوتوث استفاده نکند</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">از Briar (برایر) خارج شد</string>
|
||||
<string name="reminder_notification_text">برای وارد شدن دوباره ضربه بزنید.</string>
|
||||
@@ -185,7 +196,6 @@
|
||||
<string name="connecting_to_device">اتصال به دستگاهu2026\</string>
|
||||
<string name="authenticating_with_device">تصدیق سازی با دستگاه u2026\</string>
|
||||
<string name="connection_error_title">اتصال به مخاطب شما برقرار نشد</string>
|
||||
<string name="connection_error_explanation">لطفا مطمئن شوید که هر دو شما به شبکه وای فای یکسان متصل هستید.</string>
|
||||
<string name="connection_error_feedback">اگر این مشکل ادامه پیدا کرد، لطفا <a href="feedback">بازخورد ارسال کنید</a> تا به ما در بهبود برنامه کمک کنید.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">افزودن مخاطب از دور</string>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<string name="sign_in_button">Connexion</string>
|
||||
<string name="forgotten_password">J’ai oublié mon mot de passe</string>
|
||||
<string name="dialog_title_lost_password">Mot de passe oublié</string>
|
||||
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil, pas dans le nuage, et nous ne pouvons donc pas réinitialiser votre mot de passe. Souhaitez-vous supprimer votre compte et recommencer ?\n\nAttention : vos identités, contacts et messages seront perdus irrémédiablement.</string>
|
||||
<string name="dialog_message_lost_password">Votre compte Briar est enregistré chiffré sur votre appareil, pas dans le nuage, et nous ne pouvons donc pas réinitialiser votre mot de passe. Voulez-vous supprimer votre compte et recommencer ?\n\nAttention : vos identités, contacts et messages seront perdus irrémédiablement.</string>
|
||||
<string name="startup_failed_notification_title">Impossible de démarrer Briar</string>
|
||||
<string name="startup_failed_notification_text">Toucher pour plus d’informations.</string>
|
||||
<string name="startup_failed_activity_title">Échec de démarrage de Briar</string>
|
||||
@@ -143,7 +143,7 @@
|
||||
<string name="no_contacts_action">Touchez l’icône + pour ajouter un contact</string>
|
||||
<string name="date_no_private_messages">Aucun message.</string>
|
||||
<string name="no_private_messages">Aucun message à afficher</string>
|
||||
<string name="message_hint">Rédiger le message</string>
|
||||
<string name="message_hint">Rédigez un message</string>
|
||||
<string name="image_caption_hint">Ajouter une légende (facultatif)</string>
|
||||
<string name="image_attach">Joindre une image</string>
|
||||
<string name="image_attach_error">Impossible de joindre une ou des images</string>
|
||||
@@ -154,7 +154,7 @@
|
||||
<string name="set_alias_button">Modifier</string>
|
||||
<string name="delete_all_messages">Supprimer tous les messages</string>
|
||||
<string name="dialog_title_delete_all_messages">Confirmer la suppression des messages</string>
|
||||
<string name="dialog_message_delete_all_messages">Souhaitez-vous vraiment supprimer tous les messages ?</string>
|
||||
<string name="dialog_message_delete_all_messages">Voulez-vous vraiment supprimer tous les messages ?</string>
|
||||
<string name="dialog_title_not_all_messages_deleted">Impossible de supprimer tous les messages</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_both">Les messages relatifs aux invitations et présentations en cours ne peuvent pas être supprimés jusqu’à leur conclusion.</string>
|
||||
<string name="dialog_message_not_deleted_ongoing_introductions">Les messages relatifs aux présentations en cours ne peuvent pas être supprimés jusqu’à leur conclusion.</string>
|
||||
@@ -165,13 +165,13 @@
|
||||
<string name="dialog_message_not_deleted_not_all_selected_invitations">Pour supprimer une invitation, vous devez sélectionner la demande et la réponse.</string>
|
||||
<string name="delete_contact">Supprimer le contact</string>
|
||||
<string name="dialog_title_delete_contact">Confirmer la suppression du contact</string>
|
||||
<string name="dialog_message_delete_contact">Souhaitez-vous vraiment supprimer ce contact et tous les messages associés ?</string>
|
||||
<string name="dialog_message_delete_contact">Voulez-vous vraiment supprimer ce contact et tous les messages associés ?</string>
|
||||
<string name="contact_deleted_toast">Le contact a été supprimé</string>
|
||||
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
|
||||
<string name="you">Vous</string>
|
||||
<string name="save_image">Enregistrer l’image</string>
|
||||
<string name="dialog_title_save_image">Enregistrer l’image ?</string>
|
||||
<string name="dialog_message_save_image">L’enregistrement de cette image permettra aux autres applis d’y accéder.\n\n Souhaitez-vous vraiment l’enregistrer ?</string>
|
||||
<string name="dialog_message_save_image">L’enregistrement de cette image permettra aux autres applis d’y accéder.\n\n Voulez-vous vraiment l’enregistrer ?</string>
|
||||
<string name="save_image_success">L’image a été enregistrée</string>
|
||||
<string name="save_image_error">Impossible d’enregistrer l’image</string>
|
||||
<string name="dialog_title_no_image_support">Les images ne sont pas disponibles</string>
|
||||
@@ -195,12 +195,11 @@
|
||||
<string name="connecting_to_device">Connexion à l’appareil\u2026</string>
|
||||
<string name="authenticating_with_device">Autentification avec l’appareil\u2026</string>
|
||||
<string name="connection_error_title">Impossible de se connecter à votre contact</string>
|
||||
<string name="connection_error_explanation">Veuillez vérifier que vous êtes connecté au même réseau Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si le problème persiste, veuillez nous <a href="feedback">envoyer une rétroaction</a> pour nous aider à améliorer l\'appli.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Ajouter un contact à distance</string>
|
||||
<string name="add_contact_remotely_title_case">Ajouter un contact éloigné</string>
|
||||
<string name="add_contact_nearby_title">Ajouter un contact à proximité</string>
|
||||
<string name="add_contact_remotely_title">Ajouter un contact à distance</string>
|
||||
<string name="add_contact_remotely_title">Ajouter un contact éloigné</string>
|
||||
<string name="contact_link_intro">Saisissez ici le lien de votre contact</string>
|
||||
<string name="contact_link_hint">Lien de votre contact</string>
|
||||
<string name="paste_button">Coller</string>
|
||||
@@ -266,7 +265,7 @@
|
||||
<string name="introduction_sent">Votre présentation a été envoyée.</string>
|
||||
<string name="introduction_error">Une erreur est survenue lors de la présentation.</string>
|
||||
<string name="introduction_request_sent">Vous avez demandé de présenter %1$s à %2$s.</string>
|
||||
<string name="introduction_request_received">%1$s a demandé de vous présenter à %2$s. Souhaitez-vous ajouter %2$s à votre liste de contacts ?</string>
|
||||
<string name="introduction_request_received">%1$s a demandé de vous présenter à %2$s. Voulez-vous ajouter %2$s à votre liste de contacts ?</string>
|
||||
<string name="introduction_request_exists_received">%1$s a demandé de vous présenter à %2$s, mais %2$s est déjà dans votre liste de contacts. Puisque %1$s pourrait ne pas le savoir, vous pouvez tout de même répondre :</string>
|
||||
<string name="introduction_request_answered_received">%1$s a demandé de vous présenter à %2$s.</string>
|
||||
<string name="introduction_response_accepted_sent">Vous avez accepté d’être présenté à %1$s.</string>
|
||||
@@ -299,10 +298,10 @@
|
||||
<string name="groups_member_joined">%s s’est joint au groupe</string>
|
||||
<string name="groups_leave">Quitter le groupe</string>
|
||||
<string name="groups_leave_dialog_title">Confirmer la sortie du groupe</string>
|
||||
<string name="groups_leave_dialog_message">Souhaitez-vous vraiment quitter ce groupe ?</string>
|
||||
<string name="groups_leave_dialog_message">Voulez-vous vraiment quitter ce groupe ?</string>
|
||||
<string name="groups_dissolve">Dissoudre le groupe</string>
|
||||
<string name="groups_dissolve_dialog_title">Confirmer la dissolution du groupe</string>
|
||||
<string name="groups_dissolve_dialog_message">Souhaitez-vous vraiment dissoudre ce groupe ?\n\nLes autres participants ne pourront pas poursuivre leur conversation et ne recevront peut-être pas les derniers messages.</string>
|
||||
<string name="groups_dissolve_dialog_message">Voulez-vous vraiment dissoudre ce groupe ?\n\nLes autres participants ne pourront pas poursuivre leur conversation et ne recevront peut-être pas les derniers messages.</string>
|
||||
<string name="groups_dissolve_button">Dissoudre</string>
|
||||
<string name="groups_dissolved_dialog_title">Le groupe a été dissous</string>
|
||||
<string name="groups_dissolved_dialog_message">Le créateur de ce groupe l’a dissous.\n\nVous ne pouvez plus écrire de messages au groupe et ne recevrez peut-être pas tous ceux qui y ont été publiés.</string>
|
||||
@@ -346,7 +345,7 @@
|
||||
<string name="btn_reply">Répondre</string>
|
||||
<string name="forum_leave">Quitter le forum</string>
|
||||
<string name="dialog_title_leave_forum">Confirmer la sortie du forum</string>
|
||||
<string name="dialog_message_leave_forum">Souhaitez-vous vraiment quitter ce forum ?\n\nLes contacts avec qui vous l’avez partagé pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="dialog_message_leave_forum">Voulez-vous vraiment quitter ce forum ?\n\nLes contacts avec qui vous l’avez partagé pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="dialog_button_leave">Quitter</string>
|
||||
<string name="forum_left_toast">À quitté le forum</string>
|
||||
<!--Forum Sharing-->
|
||||
@@ -390,7 +389,7 @@
|
||||
<string name="blogs_feed_empty_state">Aucun billet à afficher</string>
|
||||
<string name="blogs_feed_empty_state_action">Les billets de vos contacts et les blogues auxquels vous vous abonnez apparaîtront ici.\n\nTouchez l’icône de crayon pour rédiger un billet</string>
|
||||
<string name="blogs_remove_blog">Supprimer le blogue</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Souhaitez-vous vraiment supprimer ce blogue ?\nLes billets seront supprimés de votre appareil mais pas des appareils d’autrui.\n\nLes contacts avec qui vous avez partagé ce blogue pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="blogs_remove_blog_dialog_message">Voulez-vous vraiment supprimer ce blogue ?\nLes billets seront supprimés de votre appareil mais pas des appareils d’autrui.\n\nLes contacts avec qui vous avez partagé ce blogue pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="blogs_remove_blog_ok">Supprimer</string>
|
||||
<string name="blogs_blog_removed">Le blogue a été supprimé</string>
|
||||
<string name="blogs_reblog_comment_hint">Ajouter un commentaire (facultatif)</string>
|
||||
@@ -420,7 +419,7 @@
|
||||
<string name="blogs_rss_feeds_manage_author">Auteur :</string>
|
||||
<string name="blogs_rss_feeds_manage_updated">Dernière mise à jour :</string>
|
||||
<string name="blogs_rss_remove_feed">Supprimer le fil</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Souhaitez-vous vraiment supprimer ce fil ?\nLes billets seront supprimés de votre appareil mais pas des appareils d’autrui.\n\nLes contacts avec qui vous avez partagé ce fil pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="blogs_rss_remove_feed_dialog_message">Voulez-vous vraiment supprimer ce fil ?\nLes billets seront supprimés de votre appareil mais pas des appareils d’autrui.\n\nLes contacts avec qui vous avez partagé ce fil pourraient ne plus en recevoir les mises à jour.</string>
|
||||
<string name="blogs_rss_remove_feed_ok">Supprimer</string>
|
||||
<string name="blogs_rss_feeds_manage_delete_error">Impossible de supprimer le fil !</string>
|
||||
<string name="blogs_rss_feeds_manage_empty_state">Aucun fil RSS à afficher\n\nTouchez l’icône + pour importer un fil</string>
|
||||
@@ -484,7 +483,7 @@
|
||||
<string name="panic_app_setting_summary">Aucune appli n’a été définie</string>
|
||||
<string name="panic_app_setting_none">Aucune</string>
|
||||
<string name="dialog_title_connect_panic_app">Confirmer l’application d’urgence</string>
|
||||
<string name="dialog_message_connect_panic_app">Souhaitez-vous vraiment autoriser %1$s à déclencher les actions destructrices du bouton d’urgence ?</string>
|
||||
<string name="dialog_message_connect_panic_app">Voulez-vous vraiment autoriser %1$s à déclencher les actions destructrices du bouton d’urgence ?</string>
|
||||
<string name="panic_setting_destructive_action">Actions destructrices</string>
|
||||
<string name="panic_setting_signout_title">Déconnexion</string>
|
||||
<string name="panic_setting_signout_summary">Se déconnecter de Briar si l’on appuie sur un bouton d’urgence</string>
|
||||
@@ -550,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Appareil photo et position</string>
|
||||
<string name="permission_camera_location_request_body">Afin de balayer le code QR, Briar doit accéder à l’appareil photo.\n\nAfin de découvrir des périphériques Bluetooth, Briar doit accéder à votre position.\n\nBriar n’enregistre pas votre position et ne la partage avec personne.</string>
|
||||
<string name="permission_camera_denied_body">Vous avez refusé l’accès à la caméra, mais l’ajout de contacts exige l’utilisation de celle-ci.\n\nVeuillez envisager d’y accorder l’accès.</string>
|
||||
<string name="permission_location_denied_body">Vous avez refusé l’accès à votre position géographique, mais Briar en a besoin pour découvrir les appareils Bluetooth.\n\nVeuillez envisager d’y accorder l’accès.</string>
|
||||
<string name="qr_code">Code QR</string>
|
||||
<string name="show_qr_code_fullscreen">Afficher le code QR en plein écran</string>
|
||||
<!--App Locking-->
|
||||
|
||||
@@ -61,12 +61,36 @@
|
||||
<string name="lock_button">Bloquear App</string>
|
||||
<string name="settings_button">Axustes</string>
|
||||
<string name="sign_out_button">Finalizar sesión</string>
|
||||
<string name="transports_onboarding_text">Toca aquí para controlar o xeito en que Briar conecta cos teus contactos.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="tor_device_status_online_wifi">O teu móbil ten acceso a internet por Wi-Fi</string>
|
||||
<string name="tor_device_status_online_mobile">O teu móbil ten acceso a internet por datos móbiles</string>
|
||||
<string name="tor_device_status_offline">O teu móbil non ten acceso a internet</string>
|
||||
<string name="tor_plugin_status_enabling">Briar estase conectando a internet</string>
|
||||
<string name="tor_plugin_status_active">Briar está conectada a internet</string>
|
||||
<string name="tor_plugin_status_inactive">Briar non pode conectarse a internet</string>
|
||||
<string name="tor_plugin_status_disabled">Briar está configurada para non usar internet</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">Briar está configurada para non usar datos móbilies</string>
|
||||
<string name="tor_plugin_status_disabled_battery">Briar está configurada para non usar internet se está usando batería</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">Briar está configurada para non usar internet neste país</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="transport_lan_long">Mesma rede Wi-Fi</string>
|
||||
<string name="lan_device_status_on">O móbil está conectado á Wi-Fi</string>
|
||||
<string name="lan_device_status_off">O móbil non está conectado á Wi-Fi</string>
|
||||
<string name="lan_plugin_status_enabling">Briar está conectando á rede Wi-Fi</string>
|
||||
<string name="lan_plugin_status_active">Briar está conectada á rede Wi-Fi</string>
|
||||
<string name="lan_plugin_status_inactive">Briar non pode conectar coa rede Wi-Fi</string>
|
||||
<string name="lan_plugin_status_disabled">Briar está configurada para non usar a rede Wi-Fi</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">O móbil ten o Bluetooth activado</string>
|
||||
<string name="bt_device_status_off">O móbil ten o Bluetooth desactivado</string>
|
||||
<string name="bt_plugin_status_enabling">Briar está conectando por Bluetooth</string>
|
||||
<string name="bt_plugin_status_active">Briar está conectada ó Bluetooth</string>
|
||||
<string name="bt_plugin_status_inactive">Briar non pode conectar por Bluetooth</string>
|
||||
<string name="bt_plugin_status_disabled">Briar está configurada para non usar Bluetooth</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Desconectou de Briar</string>
|
||||
<string name="reminder_notification_text">Toque para voltar a conectar</string>
|
||||
@@ -171,7 +195,6 @@
|
||||
<string name="connecting_to_device">Conectando co dispositivo\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticándose co dispositivo\u2026</string>
|
||||
<string name="connection_error_title">Non se puido conectar co contacto</string>
|
||||
<string name="connection_error_explanation">Por favor, comprobe que ambas están conectadas a mesma rede Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Si persiste o problema,<a href="feedback">envíe un informe</a> para axudarnos a mellorar a app.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Engadir contacto a distancia</string>
|
||||
@@ -526,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Cámara e localización</string>
|
||||
<string name="permission_camera_location_request_body">Para escanear o código QR, Briar precisa acceso a cámara.\n\nPara descubrir dispositivos Bluetooth, Briar precisa permiso a súa localización.\n\nBriar non garda a súa localización nin a comparte con ninguén.</string>
|
||||
<string name="permission_camera_denied_body">Denegou o permiso de acceso a cámara, pero é necesario para engadir contactos.\n\nPor favor, considere conceder o permiso.</string>
|
||||
<string name="permission_location_denied_body">Denegache o acceso á localización, mais Briar precisa este permiso para descubrir dispositivos Bluetooth.\n\nConsidera conceder o permiso.</string>
|
||||
<string name="qr_code">Código QR</string>
|
||||
<string name="show_qr_code_fullscreen">Amosar o código QR a pantalla completa</string>
|
||||
<!--App Locking-->
|
||||
@@ -536,6 +560,7 @@
|
||||
<string name="lock_is_locked">Briar está bloqueada</string>
|
||||
<string name="lock_tap_to_unlock">Toque para desbloquear</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">Briar pode conectar cos teus contactos a través de Internet, Wi-Fi ou Bluetooth.\n\nTodas as conexións a internet pasan a través da rede Tor para máis privacidade.\n\nSe un contacto é accesible de múltiples xeitos, Briar usaráos en paralelo.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alice</string>
|
||||
|
||||
@@ -63,12 +63,36 @@
|
||||
<string name="lock_button">נעל יישום</string>
|
||||
<string name="settings_button">הגדרות</string>
|
||||
<string name="sign_out_button">התנתק</string>
|
||||
<string name="transports_onboarding_text">הקש כאן כדי לשלוט איך Briar מתחבר אל אנשי הקשר שלך.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">אינטרנט</string>
|
||||
<string name="tor_device_status_online_wifi">לטלפון שלך יש גישת אינטרנט באמצעות Wi-Fi</string>
|
||||
<string name="tor_device_status_online_mobile">לטלפון שלך יש חיבור אינטרנט באמצעות נתונים סלולריים</string>
|
||||
<string name="tor_device_status_offline">לטלפון שלך אין גישת אינטרנט</string>
|
||||
<string name="tor_plugin_status_enabling">Briar מתחבר אל האינטרנט</string>
|
||||
<string name="tor_plugin_status_active">Briar מחובר אל האינטרנט</string>
|
||||
<string name="tor_plugin_status_inactive">Briar אינו יכול להתחבר אל האינטרנט</string>
|
||||
<string name="tor_plugin_status_disabled">Briar מתוצר לא להשתמש באינטרנט</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">Briar מתוצר לא להשתמש בנתונים סלולריים</string>
|
||||
<string name="tor_plugin_status_disabled_battery">Briar מתוצר לא להשתמש באינטרנט בעת הרצה על סוללה</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">Briar מתוצר לא להשתמש באינטרנט במדינה זו</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="transport_lan_long">אותה רשת Wi-Fi</string>
|
||||
<string name="lan_device_status_on">הטלפון שלך מחובר אל Wi-Fi</string>
|
||||
<string name="lan_device_status_off">הטלפון שלך אינו מחובר אל Wi-Fi</string>
|
||||
<string name="lan_plugin_status_enabling">Briar מתחבר אל רשת ה־Wi-Fi</string>
|
||||
<string name="lan_plugin_status_active">Briar מחובר אל רשת ה־Wi-Fi</string>
|
||||
<string name="lan_plugin_status_inactive">Briar אינו יכול להתחבר אל רשת ה־Wi-Fi</string>
|
||||
<string name="lan_plugin_status_disabled">Briar מתוצר לא להשתמש ברשת ה־Wi-Fi</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">שן כחולה</string>
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">Bluetooth של הטלפון שלך מופעל</string>
|
||||
<string name="bt_device_status_off">Bluetooth של הטלפון שלך מכובה</string>
|
||||
<string name="bt_plugin_status_enabling">Briar מתחבר אל Bluetooth</string>
|
||||
<string name="bt_plugin_status_active">Briar מחובר אל Bluetooth</string>
|
||||
<string name="bt_plugin_status_inactive">Briar אינו יכול להתחבר אל Bluetooth</string>
|
||||
<string name="bt_plugin_status_disabled">Briar מתוצר לא להשתמש ב־Bluetooth</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">נותקת מן Briar</string>
|
||||
<string name="reminder_notification_text">הקש כדי להתחבר חזרה.</string>
|
||||
@@ -181,7 +205,6 @@
|
||||
<string name="connecting_to_device">מתחבר אל מכשיר\u2026</string>
|
||||
<string name="authenticating_with_device">מזדהה מול המכשיר\u2026</string>
|
||||
<string name="connection_error_title">לא היה ניתן להתחבר אל איש הקשר שלך</string>
|
||||
<string name="connection_error_explanation">אנא בדוק ששניכם מחוברים אל אותה רשת Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">אם בעיה נמשכת, אנא <a href="feedback">שלח משוב</a> כדי לעזור לנו לשפר את היישום.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">הוסף איש קשר בקרבה</string>
|
||||
@@ -436,10 +459,20 @@
|
||||
<string name="pref_theme_auto">אוטומטי (שעות יום)</string>
|
||||
<string name="pref_theme_system">ברירת מחדל</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">חיבורים</string>
|
||||
<string name="bluetooth_setting">התחבר אל אנשי קשר באמצעות Bluetooth</string>
|
||||
<string name="wifi_setting">התחבר אל אנשי קשר באותה רשת Wi-Fi</string>
|
||||
<string name="tor_enable_title">התחבר אל אנשי קשר באמצעות האינטרנט</string>
|
||||
<string name="tor_enable_summary">כל החיבורים עוברים דרך רשת Tor למען פרטיות</string>
|
||||
<string name="tor_network_setting">שיטת חיבור עבור רשת Tor</string>
|
||||
<string name="tor_network_setting_automatic">אוטומטי על סמך מיקום</string>
|
||||
<string name="tor_network_setting_without_bridges">השתמש ברשת Tor בלי גשרים</string>
|
||||
<string name="tor_network_setting_with_bridges">השתמש ברשת Tor עם גשרים</string>
|
||||
<string name="tor_network_setting_never">אל תתחבר אל האינטרנט</string>
|
||||
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
|
||||
<string name="tor_network_setting_summary">אוטומטי: %1$s (תוך %2$s)</string>
|
||||
<string name="tor_mobile_data_title">השתמש בנתונים סלולריים</string>
|
||||
<string name="tor_only_when_charging_title">התחבר אל האינטרנט רק בעת טעינה</string>
|
||||
<string name="tor_only_when_charging_summary">משבית חיבור אינטרנט כשהמכשיר עובד על סוללה</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">אבטחה</string>
|
||||
@@ -536,7 +569,7 @@
|
||||
<string name="permission_camera_title">הרשאת מצלמה</string>
|
||||
<string name="permission_camera_request_body">כדי לסרוק את קוד ה־QR, היישום Briar צריך גישה אל המצלמה.</string>
|
||||
<string name="permission_location_title">הרשאת מיקום</string>
|
||||
<string name="permission_location_request_body">כדי לגלות מכשירי שן־כחולה, Briar צריך הרשאה להשיג גישה אל מיקומך.\n\nBriar אינו מאחסן את מיקומך או משתף אותו עם אף אחד.</string>
|
||||
<string name="permission_location_request_body">כדי לגלות מכשירי Bluetooth, היישום Briar צריך הרשאה להשיג גישה אל מיקומך.\n\nBriar אינו מאחסן את מיקומך או משתף אותו עם אף אחד.</string>
|
||||
<string name="permission_camera_location_title">מצלמה ומיקום</string>
|
||||
<string name="permission_camera_location_request_body">כדי לסרוק את קוד ה־QR, היישום Briar צריך הרשאה אל המצלמה.\n\nכדי לגלות מכשירי שן־כחולה, Briar צריך הרשאה להשיג גישה אל מיקומך.\n\nBriar אינו מאחסן את מיקומך או משתף אותו עם אף אחד.</string>
|
||||
<string name="permission_camera_denied_body">דחית גישה אל המצלמה, אבל הוספת אנשי קשר דורשת שימוש במצלמה.\n\nאנא שקול הענקת גישה.</string>
|
||||
@@ -550,6 +583,7 @@
|
||||
<string name="lock_is_locked">Briar נעול</string>
|
||||
<string name="lock_tap_to_unlock">הקש כדי לבטל נעילה</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">Briar יכול להתחבר אל אנשי הקשר שלך באמצעות האינטרנט, Wi-Fi או Bluetooth.\n\nכל חיבורי האינטרנט עוברים דרך רשת Tor למען פרטיות.\n\nאם איש קשר ניתן להשגה באמצעות שיטות רבות, Briar משתמש בהן במקביל.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">נועה</string>
|
||||
|
||||
@@ -154,7 +154,6 @@
|
||||
<string name="connecting_to_device">उपकरण \ u2026 से कनेक्ट हो रहा है</string>
|
||||
<string name="authenticating_with_device">डिवाइस के साथ प्रमाणीकरण \ u2026</string>
|
||||
<string name="connection_error_title">आपके संपर्क से कनेक्ट नहीं हो सका</string>
|
||||
<string name="connection_error_explanation">कृपया जांचें कि आप दोनों एक ही वाई-फाई नेटवर्क से जुड़े हुए हैं।</string>
|
||||
<string name="connection_error_feedback">यदि यह समस्या बनी रहती है, तो कृपया ऐप को बेहतर बनाने में हमारी सहायता के लिए <a href="feedback">फ़ीडबैक भेजें</a>।</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">दूरी वाले संपर्क जोड़ें</string>
|
||||
|
||||
@@ -61,12 +61,36 @@
|
||||
<string name="lock_button">App zárolása</string>
|
||||
<string name="settings_button">Beállítások</string>
|
||||
<string name="sign_out_button">Kijelentkezés</string>
|
||||
<string name="transports_onboarding_text">Érintse meg itt, hogy beállíthassa, hogyan csatlakozzon a Tor kapcsolataihoz.</string>
|
||||
<!--Transports: Tor-->
|
||||
<string name="transport_tor">Internet</string>
|
||||
<string name="tor_device_status_online_wifi">A telefonja Internet hozzáféréssel rendelkezik Wi-Fi-n keresztül</string>
|
||||
<string name="tor_device_status_online_mobile">A telefonja Internet hozzáféréssel rendelkezik mobil hálózaton keresztül</string>
|
||||
<string name="tor_device_status_offline">A telefonja nem rendelkezik Internet hozzáféréssel</string>
|
||||
<string name="tor_plugin_status_enabling">A Briar csatlakozik az Internethez</string>
|
||||
<string name="tor_plugin_status_active">A Briar csatlakoztatva az Internethez</string>
|
||||
<string name="tor_plugin_status_inactive">A Briar nem tud csatlakozni az Internethez</string>
|
||||
<string name="tor_plugin_status_disabled">A Briar Internet nélküli használatra van beállítva</string>
|
||||
<string name="tor_plugin_status_disabled_mobile_data">A Briar mobil hálózat nélküli használatra van beállítva</string>
|
||||
<string name="tor_plugin_status_disabled_battery">A Briar úgy van beállítva, hogy ne használjon Internetet, ha akkumulátorról megy a telefon</string>
|
||||
<string name="tor_plugin_status_disabled_country_blocked">A Briar úgy van beállítva, hogy ne használjon Internetet ebben az országban</string>
|
||||
<!--Transports: Wi-Fi-->
|
||||
<string name="transport_lan">Wi-Fi</string>
|
||||
<string name="transport_lan_long">Azonos Wi-Fi hálózat</string>
|
||||
<string name="lan_device_status_on">A telefonja Wi-Fi-hez csatlakoztatott.</string>
|
||||
<string name="lan_device_status_off">A telefonja Wi-Fi-hez nem csatlakoztatott.</string>
|
||||
<string name="lan_plugin_status_enabling">A Briar csatlakozik a Wi-Fi hálózathoz</string>
|
||||
<string name="lan_plugin_status_active">A Briar csatlakoztatva a Wi-Fi hálózathoz</string>
|
||||
<string name="lan_plugin_status_inactive">A Briar nem tud csatlakozni a Wi-Fi hálózathoz</string>
|
||||
<string name="lan_plugin_status_disabled">A Briar Wi-Fi nélküli használatra van beállítva</string>
|
||||
<!--Transports: Bluetooth-->
|
||||
<string name="transport_bt">Bluetooth</string>
|
||||
<string name="bt_device_status_on">A telefonja Bluetooth-ja bekapcsolva</string>
|
||||
<string name="bt_device_status_off">A telefonja Bluetooth-ja kikapcsolva</string>
|
||||
<string name="bt_plugin_status_enabling">A Briar csatlakozik a Bluetooth-hoz</string>
|
||||
<string name="bt_plugin_status_active">A Briar csatlakoztatva a Bluetooth-hoz</string>
|
||||
<string name="bt_plugin_status_inactive">A Briar nem tud csatlakozni a Bluetooth-hoz</string>
|
||||
<string name="bt_plugin_status_disabled">A Briar Bluetooth nélküli használatra van beállítva</string>
|
||||
<!--Notifications-->
|
||||
<string name="reminder_notification_title">Kilépve a Briar-ból</string>
|
||||
<string name="reminder_notification_text">Érintse meg az újra belépéshez.</string>
|
||||
@@ -173,7 +197,6 @@ Biztosan szeretné menteni?</string>
|
||||
<string name="connecting_to_device">Csatlakozás az eszközhöz\u2026</string>
|
||||
<string name="authenticating_with_device">Azonosítás az eszközzel\u2026</string>
|
||||
<string name="connection_error_title">Nem sikerült csatlakozni a kapcsolatához</string>
|
||||
<string name="connection_error_explanation">Ellenőrizzék, hogy mindketten ugyanahhoz a Wi-Fi hálózathoz vannak-e csatlakozva.</string>
|
||||
<string name="connection_error_feedback">Ha ez a probléma tartósan fennáll, kérjük <a href="feedback">küldjön visszajelzést</a> nekünk, hogy segítsen fejleszteni az appot.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Távoli kapcsolat hozzá adása</string>
|
||||
@@ -421,10 +444,19 @@ Kapcsolatai, akivel megosztotta ezt a blogot, lehet nem kapnak többé frissít
|
||||
<string name="pref_theme_system">Rendszer alapértelmezett</string>
|
||||
<!--Settings Connections-->
|
||||
<string name="network_settings_title">Kapcsolatok</string>
|
||||
<string name="bluetooth_setting">Csatlakozás a kapcsolatokhoz Bluetooth-on</string>
|
||||
<string name="wifi_setting">Csatlakozás az egy Wi-Fi hálózaton lévő kapcsolatokhoz</string>
|
||||
<string name="tor_enable_title">Csatlakozás a kapcsolatokhoz Interneten</string>
|
||||
<string name="tor_enable_summary">Minden kapcsolat átmegy a Tor hálózaton az adatvédelem érdekében</string>
|
||||
<string name="tor_network_setting">Kapcsolódási mód a Tor hálózathoz</string>
|
||||
<string name="tor_network_setting_automatic">Automatikusan hely alapján</string>
|
||||
<string name="tor_network_setting_without_bridges">Tor hálózat használata hidak nélkül</string>
|
||||
<string name="tor_network_setting_with_bridges">Tor hálózat használata hidakkal</string>
|
||||
<string name="tor_network_setting_never">Ne csatlakozzon az internethez</string>
|
||||
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
|
||||
<string name="tor_network_setting_summary">Automatikus: %1$s ( %2$s)</string>
|
||||
<string name="tor_mobile_data_title">Mobil adat használata</string>
|
||||
<string name="tor_only_when_charging_title">Csatlakozás az internethez csak töltés alatt</string>
|
||||
<string name="tor_only_when_charging_summary">Letiltja az internet kapcsolatot, amikor elemről fut az eszköz</string>
|
||||
<!--Settings Security and Panic-->
|
||||
<string name="security_settings_title">Biztonság</string>
|
||||
@@ -536,6 +568,7 @@ Vigyázat: Ez végleg törli az identitásait, kapcsolatait és üzeneteit</stri
|
||||
<string name="lock_is_locked">A Briar zárolt</string>
|
||||
<string name="lock_tap_to_unlock">Érintse meg a zárolás feloldásához</string>
|
||||
<!--Connections Screen-->
|
||||
<string name="transports_help_text">A Briar Interneten, Wi-Fi-n vagy Bluetooth-on keresztül csatlakozhat kapcsolataihoz.\n\nAz összes internetkapcsolat a Tor hálózaton megy keresztül megy az adatvédelem érdekében.\n\nHa egy kapcsolatot több módszerrel is el lehet érni, Briar párhuzamosan használja azokat.</string>
|
||||
<!--Screenshots-->
|
||||
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
|
||||
<string name="screenshot_alice">Alice</string>
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
<string name="connecting_to_device">Tengist við tæki\u2026</string>
|
||||
<string name="authenticating_with_device">Auðkenni við tæki\u2026</string>
|
||||
<string name="connection_error_title">Gat ekki tengst við tengiliðinn þinn</string>
|
||||
<string name="connection_error_explanation">Athugaðu hvort þið séuð ekki báðir tengdir við sama þráðlausa Wi-Fi netið.</string>
|
||||
<string name="connection_error_feedback">Ef þetta vandamál er viðvarandi, ættirðu að <a href="feedback">senda umsögn</a> til að hjálpa okkur að bæta forritið.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Bæta við fjarlægum tengilið</string>
|
||||
@@ -456,9 +455,9 @@
|
||||
<string name="pref_lock_title">Forritslæsing</string>
|
||||
<string name="pref_lock_summary">Notaðu skjálæsingu tækisins til að vernda Briar á meðan þú ert skráð/ur inn</string>
|
||||
<string name="pref_lock_disabled_summary">Til að nota þetta þarftu að setja upp skjálæsingu fyrir tækið þitt</string>
|
||||
<string name="pref_lock_timeout_title">Tímamörk forritslæsingar við aðgerðaleysi</string>
|
||||
<string name="pref_lock_timeout_title">Tímamörk læsingar forrits við aðgerðaleysi</string>
|
||||
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
|
||||
<string name="pref_lock_timeout_summary">Þehar ekki er verið að nota Briar, læsa því sjálfvirkt eftir %s</string>
|
||||
<string name="pref_lock_timeout_summary">Þegar ekki er verið að nota Briar, læsa því sjálfvirkt eftir %s</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
<string name="pref_lock_timeout_1">1 mínúta</string>
|
||||
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
|
||||
@@ -550,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Myndavél og staðsetning</string>
|
||||
<string name="permission_camera_location_request_body">Til að skanna QR-kóðann, þarf Briar aðgang að myndavélinni.\n\nTil að finna Bluetooth-tæki, þarf Briar heimild til að nota staðsetningu þína.\n\nBriar geymir ekki staðsetningar eða deilir þeim með neinum.</string>
|
||||
<string name="permission_camera_denied_body">Þú hefur hafnað aðgangi að myndavélinni, en það að bæta við tengiliðum krefst notkun hennar.\n\nÍhugaðu að veita þennan aðgang.</string>
|
||||
<string name="permission_location_denied_body">Þú hefur hafnað aðgangi að staðsetningu, en Briar þarf þessa heimild til að geta fundið Bluetooth-tæki.\n\nÍhugaðu að veita þennan aðgang.</string>
|
||||
<string name="qr_code">QR-kóði</string>
|
||||
<string name="show_qr_code_fullscreen">Birta QR-kóða á öllum skjánum</string>
|
||||
<!--App Locking-->
|
||||
|
||||
@@ -195,7 +195,6 @@
|
||||
<string name="connecting_to_device">Connessione al dispositivo\u2026</string>
|
||||
<string name="authenticating_with_device">Autenticazione con il dispositivo\u2026</string>
|
||||
<string name="connection_error_title">Impossibile connettersi al tuo contatto</string>
|
||||
<string name="connection_error_explanation">Verifica di essere entrambi connessi alla stessa rete Wi-Fi.</string>
|
||||
<string name="connection_error_feedback">Se il problema persiste, <a href="feedback">invia un feedback</a> per aiutarci a migliorare l\'app.</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">Aggiungi contatto distante</string>
|
||||
@@ -550,6 +549,7 @@
|
||||
<string name="permission_camera_location_title">Fotocamera e geolocalizzazione</string>
|
||||
<string name="permission_camera_location_request_body">Per scansionare il codice QR, Briar ha bisogno di accedere alla fotocamera.\n\nPer trovare dispositivi Bluetooth, Briar ha bisogno di accedere alla tua posizione.\n\nBriar non memorizza la tua posizione, nè la condivide con terzi.</string>
|
||||
<string name="permission_camera_denied_body">Hai negato l\'accesso alla fotocamera, ma questa serve per aggiungere i contatti.\n\nConsidera la possibilità di concedere l\'accesso.</string>
|
||||
<string name="permission_location_denied_body">Hai negato l\'accesso alla tua posizione, ma Briar ha bisogno di questa autorizzazione per trovare i dispositivi Bluetooth.\n\nConsidera la possibilità di concedere l\'accesso.</string>
|
||||
<string name="qr_code">Codice QR</string>
|
||||
<string name="show_qr_code_fullscreen">Mostra codice QR a tutto schermo</string>
|
||||
<!--App Locking-->
|
||||
|
||||
@@ -149,7 +149,6 @@
|
||||
<string name="connecting_to_device">デバイスに接続中\u2026</string>
|
||||
<string name="authenticating_with_device">デバイス同士での認証中\u2026</string>
|
||||
<string name="connection_error_title">連絡先に接続できませんでした</string>
|
||||
<string name="connection_error_explanation">あなたと連絡相手、両方が同じWi-Fiネットワークに接続していることを確認してください。</string>
|
||||
<string name="connection_error_feedback">この問題が解決しない場合、アプリを改善するために<a href="feedback">フィードバック</a>を送信してください。</string>
|
||||
<!--Adding Contacts Remotely-->
|
||||
<string name="add_contact_remotely_title_case">離れた場所にいる相手を連絡先に追加</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user