mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 02:39:05 +01:00
Compare commits
147 Commits
release-1.
...
offline-te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a438050e68 | ||
|
|
4d0fe24722 | ||
|
|
559b29e8b5 | ||
|
|
1b7b285862 | ||
|
|
178810241f | ||
|
|
61c601cb6d | ||
|
|
2002ad08ca | ||
|
|
d134a67ee9 | ||
|
|
04cf8e16a9 | ||
|
|
227d345858 | ||
|
|
07ef73ab56 | ||
|
|
ea2b1ff4d8 | ||
|
|
69fac86a0c | ||
|
|
bd6b6c1cd6 | ||
|
|
be6c868135 | ||
|
|
041a296666 | ||
|
|
276eeb1c20 | ||
|
|
d46cfb757e | ||
|
|
d81c4e7982 | ||
|
|
8c4d6ed5e4 | ||
|
|
b21b319cb7 | ||
|
|
780f6e97b9 | ||
|
|
1756215183 | ||
|
|
7e3db6c6df | ||
|
|
1adf408ade | ||
|
|
d662ae49ee | ||
|
|
7939c8b213 | ||
|
|
1899873da3 | ||
|
|
5beffb21f1 | ||
|
|
0f9afda329 | ||
|
|
c16663b530 | ||
|
|
77767b45c9 | ||
|
|
79ae8fea8d | ||
|
|
7e3eb1201a | ||
|
|
eb283d81c5 | ||
|
|
be3700d364 | ||
|
|
ccec17f28a | ||
|
|
e8428925ae | ||
|
|
195123e669 | ||
|
|
abe570e905 | ||
|
|
a93b1f18ac | ||
|
|
e4bd6fdf95 | ||
|
|
793d81bd93 | ||
|
|
be637cef65 | ||
|
|
9370062e41 | ||
|
|
8c1f721015 | ||
|
|
22ea4ced0d | ||
|
|
312d31b40e | ||
|
|
b15d42b0cd | ||
|
|
9274b8ef4a | ||
|
|
7ef4ea51b3 | ||
|
|
e285f21d1c | ||
|
|
160cef25af | ||
|
|
c0293a1327 | ||
|
|
035c639aa0 | ||
|
|
29d31e79c3 | ||
|
|
7f7210becd | ||
|
|
ce74fcaab5 | ||
|
|
d4c1e132f7 | ||
|
|
6b976df6a8 | ||
|
|
3e4db3b9da | ||
|
|
0bf59eec20 | ||
|
|
9f828a2222 | ||
|
|
7be77b8c60 | ||
|
|
d5853e8403 | ||
|
|
32e9bf01ec | ||
|
|
a5ce400341 | ||
|
|
a960bfb2c1 | ||
|
|
847650f280 | ||
|
|
77a3199aac | ||
|
|
9a58b37ce2 | ||
|
|
608e1eac6b | ||
|
|
09de768e7e | ||
|
|
faab80f0ea | ||
|
|
07162cad8b | ||
|
|
a5a1cdfabb | ||
|
|
bfcb469d49 | ||
|
|
f8b645d2b1 | ||
|
|
052eb03c9e | ||
|
|
83bf3f4ca7 | ||
|
|
f9181fa021 | ||
|
|
79dae27c24 | ||
|
|
ac0fc21e6e | ||
|
|
dab736ce0e | ||
|
|
8ef21637a9 | ||
|
|
e3a1fca22e | ||
|
|
ea9a2789ab | ||
|
|
cbdbd10cb3 | ||
|
|
d6f985174a | ||
|
|
d184fbd3fe | ||
|
|
ef623370b6 | ||
|
|
5ac636d52d | ||
|
|
f1c71ec5a7 | ||
|
|
5cc280be61 | ||
|
|
a5d8faef3c | ||
|
|
e22e9dcade | ||
|
|
7474ad8606 | ||
|
|
1c3d90f7fc | ||
|
|
6f8d7167db | ||
|
|
99da50d37c | ||
|
|
15f5c8deee | ||
|
|
7913cd322e | ||
|
|
de8ad8f6f9 | ||
|
|
d0bc17e634 | ||
|
|
85433611a5 | ||
|
|
ebd5879761 | ||
|
|
b255ab07ae | ||
|
|
a86ba50dec | ||
|
|
bcbc96dc2d | ||
|
|
a72e92de24 | ||
|
|
1ddcd6cfff | ||
|
|
5dfd9e3546 | ||
|
|
e05575b956 | ||
|
|
fd810f5c16 | ||
|
|
3f5e131250 | ||
|
|
3ee516599d | ||
|
|
c703d90636 | ||
|
|
e228b9fcbf | ||
|
|
6e6cadd3ad | ||
|
|
9cc8d44778 | ||
|
|
ee6f571c31 | ||
|
|
2ac3bdd3ae | ||
|
|
e35ffe0cf0 | ||
|
|
8a04d8edc4 | ||
|
|
a5fb3bb4a4 | ||
|
|
eae329cdfa | ||
|
|
0ce0551f0d | ||
|
|
a198e7d08e | ||
|
|
bca6f1506e | ||
|
|
e420201b00 | ||
|
|
03248d04e5 | ||
|
|
2c39b02644 | ||
|
|
c9c6f3682c | ||
|
|
8f4a0ef030 | ||
|
|
5fe22bcd57 | ||
|
|
b4880af7e2 | ||
|
|
51d21bd669 | ||
|
|
b8f3728a0d | ||
|
|
bbfd4f137d | ||
|
|
7e3ca76dd1 | ||
|
|
524c8d26f8 | ||
|
|
7eccf7dac1 | ||
|
|
0bc06248ed | ||
|
|
c999f05cc7 | ||
|
|
428269b312 | ||
|
|
588e05ce83 | ||
|
|
f7875c99b6 |
@@ -33,7 +33,7 @@ test:
|
||||
stage: test
|
||||
script:
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
|
||||
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: always
|
||||
|
||||
15
.idea/codeStyles/Project.xml
generated
15
.idea/codeStyles/Project.xml
generated
@@ -31,6 +31,15 @@
|
||||
<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" />
|
||||
@@ -188,9 +197,9 @@
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
|
||||
<option name="VARIABLE_ANNOTATION_WRAP" value="1" />
|
||||
<option name="ENUM_CONSTANTS_WRAP" value="1" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
43
.idea/runConfigurations/All_tests.xml
generated
43
.idea/runConfigurations/All_tests.xml
generated
@@ -1,32 +1,21 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":briar-android:testOfficialDebugUnitTest" />
|
||||
<option value=":briar-android:testScreenshotDebugUnitTest" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-android" />
|
||||
<method v="2">
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="GradleRunConfiguration" />
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" />
|
||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,23 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-android" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":bramble-android:testDebugUnitTest" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-android" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
33
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
33
.idea/runConfigurations/All_tests_in_bramble_api.xml
generated
@@ -1,25 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-api" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":bramble-api:animalSnifferMain" />
|
||||
<option value=":bramble-api:animalSnifferTest" />
|
||||
<option value=":bramble-api:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,25 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-core" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":bramble-core:animalSnifferMain" />
|
||||
<option value=":bramble-core:animalSnifferTest" />
|
||||
<option value=":bramble-core:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,23 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in bramble-java" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":bramble-java:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar.bramble-java" />
|
||||
<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 -Djava.library.path=libs" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,24 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-android" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":briar-android:testOfficialDebugUnitTest" />
|
||||
<option value=":briar-android:testScreenshotDebugUnitTest" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
33
.idea/runConfigurations/All_tests_in_briar_api.xml
generated
33
.idea/runConfigurations/All_tests_in_briar_api.xml
generated
@@ -1,25 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-api" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":briar-api:animalSnifferMain" />
|
||||
<option value=":briar-api:animalSnifferTest" />
|
||||
<option value=":briar-api:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<module name="briar.briar-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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
33
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
33
.idea/runConfigurations/All_tests_in_briar_core.xml
generated
@@ -1,25 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-core" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":briar-core:animalSnifferMain" />
|
||||
<option value=":briar-core:animalSnifferTest" />
|
||||
<option value=":briar-core:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
@@ -1,23 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="All tests in briar-headless" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":briar-headless:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="All tests in briar-headless" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="" />
|
||||
<option name="TEST_OBJECT" value="package" />
|
||||
<option name="VM_PARAMETERS" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-headless" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
46
.idea/runConfigurations/BridgeTest.xml
generated
46
.idea/runConfigurations/BridgeTest.xml
generated
@@ -1,28 +1,24 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="BridgeTest" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="--tests "org.briarproject.bramble.plugin.tor.BridgeTest"" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":bramble-java:test" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true">
|
||||
<module name="briar.bramble-java" />
|
||||
<useClassPathOnly />
|
||||
<extension name="coverage">
|
||||
<pattern>
|
||||
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" />
|
||||
<option name="ENABLED" value="true" />
|
||||
</pattern>
|
||||
</extension>
|
||||
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" />
|
||||
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
|
||||
<option name="METHOD_NAME" value="" />
|
||||
<option name="TEST_OBJECT" value="class" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" />
|
||||
<envs>
|
||||
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
|
||||
</envs>
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
14
.idea/runConfigurations/H2_Performance_Test.xml
generated
Normal file
14
.idea/runConfigurations/H2_Performance_Test.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="H2 Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
14
.idea/runConfigurations/HyperSQL_Performance_Test.xml
generated
Normal file
14
.idea/runConfigurations/HyperSQL_Performance_Test.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="HyperSQL Performance Test" type="AndroidJUnit" factoryName="Android JUnit">
|
||||
<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="PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="" />
|
||||
<method v="2">
|
||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
||||
29
README.md
29
README.md
@@ -1,28 +1 @@
|
||||
# Briar
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
|
||||
|
||||
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
|
||||
|
||||
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
## Download Briar
|
||||
|
||||
[<img src="https://briarproject.org//img/fdroid_badge.png" width="240">](https://briarproject.org/fdroid)
|
||||
[<img src="https://briarproject.org/img/google_play_badge_web_generic.png" width="240">](https://play.google.com/store/apps/details?id=org.briarproject.briar.android)
|
||||
|
||||
You can also [download the APK file](https://briarproject.org/apk) directly from
|
||||
our site.
|
||||
|
||||
## Useful links
|
||||
[briarproject.org](https://briarproject.org/)
|
||||
|
||||
[Source code](https://code.briarproject.org/briar/briar/tree/master)
|
||||
|
||||
[Manual](https://briarproject.org/manual/)
|
||||
|
||||
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
|
||||
|
||||
## Donate
|
||||
|
||||
[](https://liberapay.com/Briar/donate) [](https://flattr.com/t/592836/)
|
||||
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K
|
||||
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10401
|
||||
versionName "1.4.1"
|
||||
versionCode 10305
|
||||
versionName "1.3.5"
|
||||
consumerProguardFiles 'proguard-rules.txt'
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -20,7 +20,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I
|
||||
public class AndroidRemovableDrivePluginFactory implements
|
||||
SimplexPluginFactory {
|
||||
|
||||
private static final long MAX_LATENCY = DAYS.toMillis(28);
|
||||
private static final int MAX_LATENCY = (int) DAYS.toMillis(14);
|
||||
|
||||
private final Application app;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.SocketFactory;
|
||||
@@ -49,7 +48,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
|
||||
import static org.briarproject.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@NotNullByDefault
|
||||
class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
@@ -57,13 +55,6 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
private static final Logger LOG =
|
||||
getLogger(AndroidLanTcpPlugin.class.getName());
|
||||
|
||||
/**
|
||||
* The interface name is used as a heuristic for deciding whether the
|
||||
* device is providing a wifi access point.
|
||||
*/
|
||||
private static final Pattern AP_INTERFACE_NAME =
|
||||
Pattern.compile("^(wlan|ap|p2p)[-0-9]");
|
||||
|
||||
private final Executor connectionStatusExecutor;
|
||||
private final ConnectivityManager connectivityManager;
|
||||
@Nullable
|
||||
@@ -139,14 +130,17 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
if (info != null && info.getIpAddress() != 0) {
|
||||
return new Pair<>(intToInetAddress(info.getIpAddress()), false);
|
||||
}
|
||||
// If we're providing an access point, return its address
|
||||
for (NetworkInterface iface : getNetworkInterfaces()) {
|
||||
if (AP_INTERFACE_NAME.matcher(iface.getName()).find()) {
|
||||
for (InterfaceAddress ifAddr : iface.getInterfaceAddresses()) {
|
||||
if (isPossibleWifiApInterface(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses();
|
||||
// If we're providing a normal access point, return its address
|
||||
for (InterfaceAddress ifAddr : ifAddrs) {
|
||||
if (isAndroidWifiApAddress(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
// If we're providing a wifi direct access point, return its address
|
||||
for (InterfaceAddress ifAddr : ifAddrs) {
|
||||
if (isAndroidWifiDirectApAddress(ifAddr)) {
|
||||
return new Pair<>(ifAddr.getAddress(), true);
|
||||
}
|
||||
}
|
||||
// Not connected to wifi
|
||||
@@ -154,18 +148,33 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given address may belong to an interface providing
|
||||
* a wifi access point (including wifi direct legacy mode access points).
|
||||
* Returns true if the given address belongs to a network provided by an
|
||||
* Android access point (including the access point's own address).
|
||||
* <p>
|
||||
* This method may return true for wifi client interfaces as well, but
|
||||
* we've already checked for a wifi client connection above.
|
||||
* The access point's address is usually 192.168.43.1, but at least one
|
||||
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24.
|
||||
*/
|
||||
private boolean isPossibleWifiApInterface(InterfaceAddress ifAddr) {
|
||||
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) {
|
||||
if (ifAddr.getNetworkPrefixLength() != 24) return false;
|
||||
byte[] ip = ifAddr.getAddress().getAddress();
|
||||
return ip.length == 4
|
||||
&& ip[0] == (byte) 192
|
||||
&& ip[1] == (byte) 168;
|
||||
&& ip[1] == (byte) 168
|
||||
&& ip[2] == (byte) 43;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given address belongs to a network provided by an
|
||||
* Android wifi direct legacy mode access point (including the access
|
||||
* point's own address).
|
||||
*/
|
||||
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
|
||||
if (ifAddr.getNetworkPrefixLength() != 24) return false;
|
||||
byte[] ip = ifAddr.getAddress().getAddress();
|
||||
return ip.length == 4
|
||||
&& ip[0] == (byte) 192
|
||||
&& ip[1] == (byte) 168
|
||||
&& ip[2] == (byte) 49;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,4 +10,10 @@ public interface FeatureFlags {
|
||||
boolean shouldEnableProfilePictures();
|
||||
|
||||
boolean shouldEnableDisappearingMessages();
|
||||
|
||||
boolean shouldEnableConnectViaBluetooth();
|
||||
|
||||
boolean shouldEnableShareAppViaOfflineHotspot();
|
||||
|
||||
boolean shouldEnableTransferData();
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ public interface RemovableDriveTask extends Runnable {
|
||||
TransportProperties getTransportProperties();
|
||||
|
||||
/**
|
||||
* Adds an observer to the task. The observer will be notified on the
|
||||
* event thread of the current state of the task and any subsequent state
|
||||
* changes.
|
||||
* Adds an observer to the task. The observer will be notified of state
|
||||
* changes on the event thread. If the task has already finished, the
|
||||
* observer will be notified of its final state.
|
||||
*/
|
||||
void addObserver(Consumer<State> observer);
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package org.briarproject.bramble.util;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.list;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@NotNullByDefault
|
||||
public class NetworkUtils {
|
||||
|
||||
private static final Logger LOG = getLogger(NetworkUtils.class.getName());
|
||||
|
||||
public static List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
// Despite what the docs say, the return value can be null
|
||||
//noinspection ConstantConditions
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ dependencyVerification {
|
||||
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
@@ -21,7 +21,7 @@ dependencyVerification {
|
||||
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ apply from: '../dagger.gradle'
|
||||
|
||||
dependencies {
|
||||
implementation project(path: ':bramble-api', configuration: 'default')
|
||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
|
||||
implementation 'org.bitlet:weupnp:0.1.4'
|
||||
implementation 'net.i2p.crypto:eddsa:0.2.0'
|
||||
@@ -32,14 +32,6 @@ dependencies {
|
||||
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
|
||||
}
|
||||
|
||||
animalsniffer {
|
||||
// Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use),
|
||||
// and it gets used when passing method references instead of lambdas with Java 11.
|
||||
// Note that this line allows *all* methods from java.util.Objects.
|
||||
// That's the best that we can do with the configuration options that Animal Sniffer offers.
|
||||
ignore 'java.util.Objects'
|
||||
}
|
||||
|
||||
// needed to make test output available to bramble-java
|
||||
configurations {
|
||||
testOutput.extendsFrom(testCompile)
|
||||
|
||||
@@ -4,9 +4,6 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
@@ -23,6 +20,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.SecureRandomProvider;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.whispersystems.curve25519.Curve25519;
|
||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.DerivationFunction;
|
||||
import org.bouncycastle.crypto.KeyEncoder;
|
||||
import org.bouncycastle.crypto.Mac;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.engines.AESLightEngine;
|
||||
import org.bouncycastle.crypto.engines.IESEngine;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.bouncycastle.crypto.macs.HMac;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BasicAgreement;
|
||||
import org.spongycastle.crypto.BlockCipher;
|
||||
import org.spongycastle.crypto.CipherParameters;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
import org.spongycastle.crypto.DerivationFunction;
|
||||
import org.spongycastle.crypto.KeyEncoder;
|
||||
import org.spongycastle.crypto.Mac;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.digests.SHA256Digest;
|
||||
import org.spongycastle.crypto.engines.AESLightEngine;
|
||||
import org.spongycastle.crypto.engines.IESEngine;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
|
||||
import org.spongycastle.crypto.generators.KDF2BytesGenerator;
|
||||
import org.spongycastle.crypto.macs.HMac;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.spongycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.crypto.params.IESWithCipherParameters;
|
||||
import org.spongycastle.crypto.parsers.ECIESPublicKeyParser;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.generators.SCrypt;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.spongycastle.crypto.generators.SCrypt;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.math.ec.ECCurve;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
@@ -11,6 +9,8 @@ import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.XSalsa20Engine;
|
||||
import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
|
||||
import org.bouncycastle.crypto.macs.Poly1305;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.DataLengthException;
|
||||
import org.spongycastle.crypto.engines.XSalsa20Engine;
|
||||
import org.spongycastle.crypto.generators.Poly1305KeyGenerator;
|
||||
import org.spongycastle.crypto.macs.Poly1305;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
|
||||
@@ -2331,7 +2331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setInt(2, DELIVERED.getValue());
|
||||
rs = ps.executeQuery();
|
||||
rs.next();
|
||||
long total = rs.getLong(1);
|
||||
long total = rs.getInt(1);
|
||||
rs.close();
|
||||
ps.close();
|
||||
return total;
|
||||
|
||||
@@ -28,9 +28,11 @@ import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -51,7 +53,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
|
||||
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.bramble.util.IoUtils.tryToClose;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@@ -96,6 +98,7 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
/**
|
||||
* Returns true if connections to the given address can be attempted.
|
||||
*/
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
protected abstract boolean isConnectable(InterfaceAddress local,
|
||||
InetSocketAddress remote);
|
||||
|
||||
@@ -395,6 +398,17 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
|
||||
return addrs;
|
||||
}
|
||||
|
||||
private List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof SettingsUpdatedEvent) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.briarproject.bramble.util.Base32;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.SHA3Digest;
|
||||
import org.spongycastle.util.encoders.Base64;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.rendezvous;
|
||||
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.spongycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
|
||||
|
||||
@Test
|
||||
public void testMultiplierProducesSameResultsAsDefault() throws Exception {
|
||||
// Instantiate the default implementation of the curve
|
||||
X9ECParameters defaultX9Parameters =
|
||||
TeleTrusTNamedCurves.getByName("brainpoolp256r1");
|
||||
ECCurve defaultCurve = defaultX9Parameters.getCurve();
|
||||
ECPoint defaultG = defaultX9Parameters.getG();
|
||||
BigInteger defaultN = defaultX9Parameters.getN();
|
||||
BigInteger defaultH = defaultX9Parameters.getH();
|
||||
ECDomainParameters defaultParameters = new ECDomainParameters(
|
||||
defaultCurve, defaultG, defaultN, defaultH);
|
||||
// Instantiate an implementation using the Montgomery ladder multiplier
|
||||
ECDomainParameters montgomeryParameters =
|
||||
constantTime(defaultParameters);
|
||||
// Generate two key pairs with each set of parameters, using the same
|
||||
// deterministic PRNG for both sets of parameters
|
||||
byte[] seed = new byte[32];
|
||||
new SecureRandom().nextBytes(seed);
|
||||
// Montgomery ladder multiplier
|
||||
SecureRandom random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters montgomeryGeneratorParams =
|
||||
new ECKeyGenerationParameters(montgomeryParameters, random);
|
||||
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
|
||||
montgomeryGenerator.init(montgomeryGeneratorParams);
|
||||
AsymmetricCipherKeyPair montgomeryKeyPair1 =
|
||||
montgomeryGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters montgomeryPrivate1 =
|
||||
(ECPrivateKeyParameters) montgomeryKeyPair1.getPrivate();
|
||||
ECPublicKeyParameters montgomeryPublic1 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair1.getPublic();
|
||||
AsymmetricCipherKeyPair montgomeryKeyPair2 =
|
||||
montgomeryGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters montgomeryPrivate2 =
|
||||
(ECPrivateKeyParameters) montgomeryKeyPair2.getPrivate();
|
||||
ECPublicKeyParameters montgomeryPublic2 =
|
||||
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
|
||||
// Default multiplier
|
||||
random = new PseudoSecureRandom(seed);
|
||||
ECKeyGenerationParameters defaultGeneratorParams =
|
||||
new ECKeyGenerationParameters(defaultParameters, random);
|
||||
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
|
||||
defaultGenerator.init(defaultGeneratorParams);
|
||||
AsymmetricCipherKeyPair defaultKeyPair1 =
|
||||
defaultGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters defaultPrivate1 =
|
||||
(ECPrivateKeyParameters) defaultKeyPair1.getPrivate();
|
||||
ECPublicKeyParameters defaultPublic1 =
|
||||
(ECPublicKeyParameters) defaultKeyPair1.getPublic();
|
||||
AsymmetricCipherKeyPair defaultKeyPair2 =
|
||||
defaultGenerator.generateKeyPair();
|
||||
ECPrivateKeyParameters defaultPrivate2 =
|
||||
(ECPrivateKeyParameters) defaultKeyPair2.getPrivate();
|
||||
ECPublicKeyParameters defaultPublic2 =
|
||||
(ECPublicKeyParameters) defaultKeyPair2.getPublic();
|
||||
// The key pairs generated with both sets of parameters should be equal
|
||||
assertEquals(montgomeryPrivate1.getD(), defaultPrivate1.getD());
|
||||
assertEquals(montgomeryPublic1.getQ(), defaultPublic1.getQ());
|
||||
assertEquals(montgomeryPrivate2.getD(), defaultPrivate2.getD());
|
||||
assertEquals(montgomeryPublic2.getQ(), defaultPublic2.getQ());
|
||||
// OK, all of the above was just sanity checks - now for the test!
|
||||
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
|
||||
agreement.init(montgomeryPrivate1);
|
||||
BigInteger sharedSecretMontgomeryMontgomery =
|
||||
agreement.calculateAgreement(montgomeryPublic2);
|
||||
agreement.init(montgomeryPrivate1);
|
||||
BigInteger sharedSecretMontgomeryDefault =
|
||||
agreement.calculateAgreement(defaultPublic2);
|
||||
agreement.init(defaultPrivate1);
|
||||
BigInteger sharedSecretDefaultMontgomery =
|
||||
agreement.calculateAgreement(montgomeryPublic2);
|
||||
agreement.init(defaultPrivate1);
|
||||
BigInteger sharedSecretDefaultDefault =
|
||||
agreement.calculateAgreement(defaultPublic2);
|
||||
// Shared secrets calculated with different multipliers should be equal
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretMontgomeryDefault);
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretDefaultMontgomery);
|
||||
assertEquals(sharedSecretMontgomeryMontgomery,
|
||||
sharedSecretDefaultDefault);
|
||||
}
|
||||
|
||||
private static ECDomainParameters constantTime(ECDomainParameters in) {
|
||||
ECCurve curve = in.getCurve().configure().setMultiplier(
|
||||
new MontgomeryLadderMultiplier()).create();
|
||||
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
|
||||
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
|
||||
ECPoint g = curve.createPoint(x, y);
|
||||
return new ECDomainParameters(curve, g, in.getN(), in.getH());
|
||||
}
|
||||
}
|
||||
@@ -3,26 +3,30 @@ package org.briarproject.bramble.crypto;
|
||||
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
|
||||
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||
|
||||
import org.bouncycastle.asn1.sec.SECNamedCurves;
|
||||
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.BasicAgreement;
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
|
||||
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.params.ParametersWithRandom;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.DSAKCalculator;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
|
||||
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.spongycastle.crypto.BasicAgreement;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
|
||||
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
|
||||
import org.spongycastle.crypto.params.ParametersWithRandom;
|
||||
import org.spongycastle.crypto.signers.DSADigestSigner;
|
||||
import org.spongycastle.crypto.signers.DSAKCalculator;
|
||||
import org.spongycastle.crypto.signers.ECDSASigner;
|
||||
import org.spongycastle.crypto.signers.HMacDSAKCalculator;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
|
||||
import org.whispersystems.curve25519.Curve25519;
|
||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Provider;
|
||||
@@ -51,12 +55,14 @@ public class EllipticCurvePerformanceTest {
|
||||
for (String name : SEC_NAMES) {
|
||||
ECDomainParameters params =
|
||||
convertParams(SECNamedCurves.getByName(name));
|
||||
runTest(name, params);
|
||||
runTest(name + " default", params);
|
||||
runTest(name + " constant", constantTime(params));
|
||||
}
|
||||
for (String name : BRAINPOOL_NAMES) {
|
||||
ECDomainParameters params =
|
||||
convertParams(TeleTrusTNamedCurves.getByName(name));
|
||||
runTest(name, params);
|
||||
runTest(name + " default", params);
|
||||
runTest(name + " constant", constantTime(params));
|
||||
}
|
||||
runCurve25519Test();
|
||||
runEd25519Test();
|
||||
@@ -187,4 +193,13 @@ public class EllipticCurvePerformanceTest {
|
||||
return new ECDomainParameters(in.getCurve(), in.getG(), in.getN(),
|
||||
in.getH());
|
||||
}
|
||||
|
||||
private static ECDomainParameters constantTime(ECDomainParameters in) {
|
||||
ECCurve curve = in.getCurve().configure().setMultiplier(
|
||||
new MontgomeryLadderMultiplier()).create();
|
||||
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
|
||||
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
|
||||
ECPoint g = curve.createPoint(x, y);
|
||||
return new ECDomainParameters(curve, g, in.getN(), in.getH());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.CryptoException;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.briarproject.bramble.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.Digest;
|
||||
import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.spongycastle.crypto.Digest;
|
||||
import org.spongycastle.crypto.digests.Blake2bDigest;
|
||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
|
||||
@@ -24,6 +24,21 @@ public class TestFeatureFlagModule {
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableConnectViaBluetooth() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableTransferData() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ dependencyVerification {
|
||||
'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.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
|
||||
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
|
||||
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
|
||||
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
|
||||
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
@@ -26,14 +27,13 @@ dependencyVerification {
|
||||
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
|
||||
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
|
||||
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
|
||||
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
|
||||
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
|
||||
'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.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
|
||||
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
|
||||
'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
|
||||
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
|
||||
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
|
||||
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
|
||||
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
|
||||
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
|
||||
@@ -48,8 +48,8 @@ dependencyVerification {
|
||||
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
|
||||
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
|
||||
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
|
||||
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
|
||||
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
|
||||
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
|
||||
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,15 +9,16 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.net.NetworkInterface.getNetworkInterfaces;
|
||||
import static java.util.Collections.list;
|
||||
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.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -34,14 +35,19 @@ class JavaNetworkManager implements NetworkManager {
|
||||
public NetworkStatus getNetworkStatus() {
|
||||
boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
|
||||
try {
|
||||
for (NetworkInterface i : getNetworkInterfaces()) {
|
||||
if (i.isLoopback() || !i.isUp()) continue;
|
||||
for (InetAddress addr : list(i.getInetAddresses())) {
|
||||
connected = true;
|
||||
if (addr instanceof Inet4Address) {
|
||||
hasIpv4 = true;
|
||||
} else if (!addr.isMulticastAddress()) {
|
||||
hasIpv6Unicast = true;
|
||||
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
|
||||
if (interfaces == null) {
|
||||
LOG.info("No network interfaces");
|
||||
} else {
|
||||
for (NetworkInterface i : list(interfaces)) {
|
||||
if (i.isLoopback() || !i.isUp()) continue;
|
||||
for (InetAddress addr : list(i.getInetAddresses())) {
|
||||
connected = true;
|
||||
if (addr instanceof Inet4Address) {
|
||||
hasIpv4 = true;
|
||||
} else if (!addr.isMulticastAddress()) {
|
||||
hasIpv6Unicast = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="500" height="175" viewBox="0 0 500 175"
|
||||
id="svg2" version="1.1" sodipodi:docname="bluetooth.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="892"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.93337848"
|
||||
inkscape:cx="-49.861215"
|
||||
inkscape:cy="137.66042"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="144"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="499.24374"
|
||||
height="175.49413"
|
||||
viewBox="0 0 499.24373 175.49413"
|
||||
id="svg2"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<metadata
|
||||
@@ -39,23 +26,22 @@
|
||||
</metadata>
|
||||
<path
|
||||
id="path4201"
|
||||
d="m 459.80937,167.15983 -4.32657,-4.3343 -7.06956,-2.42737 c -3.88825,-1.33505 -8.72285,-2.99587 -10.74354,-3.6907 l -3.674,-1.26333 3.524,-0.1726 c 8.24183,-0.40367 12.98778,-4.00671 14.33634,-10.88389 0.79628,-4.06078 1.12887,-17.29805 0.83016,-33.04122 -0.15338,-8.08375 -0.16617,-15.41641 -0.0284,-16.29481 0.13775,-0.8784 0.53527,-2.42011 0.88339,-3.42602 1.22247,-3.53243 0.33996,-11.90828 -1.8577,-17.63146 -0.34848,-0.9075 -1.93434,-4.215 -3.52415,-7.35 -4.15849,-8.2003 -4.50458,-8.94188 -4.89292,-10.4841 -0.45865,-1.82145 -0.21161,-5.43531 0.4625,-6.7659 0.66625,-1.31505 2.15695,-2.91616 3.24622,-3.48662 1.29885,-0.68024 2.61568,0.12202 4.6864,2.85512 3.42153,4.51599 14.00135,19.44095 15.73191,22.19301 3.74551,5.95636 5.95558,11.16496 7.9722,18.78849 0.6547,2.475 1.88525,6.9975 2.73456,10.05 0.84931,3.0525 2.71404,10.15792 4.14385,15.78983 l 2.59964,10.23983 4.65,5.19319 c 2.5575,2.85626 5.7975,6.46371 7.2,8.01657 1.4025,1.55286 2.55,2.97543 2.55,3.16127 0,0.33546 -34.49955,29.29931 -34.89913,29.29931 -0.11475,0 -2.15559,-1.95044 -4.5352,-4.3343 z M 365.9443,150.77206 c -1.25762,-0.62844 -2.20557,-1.3788 -2.91402,-2.30663 -2.08931,-2.73629 -1.95034,2.36868 -1.86433,-68.48249 l 0.0777,-64.03881 0.66066,-1.23494 c 1.0152,-1.89767 1.99201,-2.91087 3.73952,-3.87887 l 1.59982,-0.88619 37.78387,-0.0796 c 42.45592,-0.0894 39.40239,-0.2483 42.11646,2.19188 0.87544,0.78709 1.75715,1.95946 2.18393,2.90385 0.71264,1.57698 0.71613,1.63839 0.80561,14.20405 l 0.0899,12.62022 -1.79817,-0.13007 c -1.42577,-0.10313 -2.08143,0.007 -3.16601,0.5321 -2.01294,0.97445 -3.93993,2.89871 -5.11476,5.10753 l -1.03717,1.95 -0.007,-12.825 -0.007,-12.825 h -33.6 -33.6 v 51.3 51.3 h 33.59873 33.59874 l 0.0763,-34.425 c 0.073,-32.96021 0.0982,-34.36117 0.59098,-32.925 0.28309,0.825 1.80562,3.9975 3.3834,7.05 5.49252,10.62624 5.40494,9.86009 5.39597,47.20335 -0.007,27.62122 -0.12358,29.95084 -1.66204,33.10906 -1.07144,2.19949 -2.71143,3.71042 -5.05823,4.66019 l -1.67381,0.6774 -36.1677,0.0797 -36.16769,0.0797 -1.864,-0.93145 z m 42.39939,-5.03813 c 2.87119,-1.30885 4.45771,-3.6784 4.43003,-6.61652 -0.0388,-4.11587 -3.1088,-7.22328 -7.1364,-7.22328 -2.11956,0 -3.56727,0.60889 -5.16364,2.17177 -2.24518,2.19807 -2.75398,5.43897 -1.30101,8.28704 0.71312,1.39782 2.52137,3.00905 3.96214,3.53045 1.49707,0.54176 3.84003,0.47454 5.20888,-0.14946 z"
|
||||
style="fill:#000000;stroke:none" />
|
||||
d="m 459.80937,171.15983 -4.32657,-4.3343 -7.06956,-2.42737 c -3.88825,-1.33505 -8.72285,-2.99587 -10.74354,-3.6907 l -3.674,-1.26333 3.524,-0.1726 c 8.24183,-0.40367 12.98778,-4.00671 14.33634,-10.88389 0.79628,-4.06078 1.12887,-17.29805 0.83016,-33.04122 -0.15338,-8.08375 -0.16617,-15.41641 -0.0284,-16.29481 0.13775,-0.8784 0.53527,-2.42011 0.88339,-3.42602 1.22247,-3.53243 0.33996,-11.90828 -1.8577,-17.63146 -0.34848,-0.9075 -1.93434,-4.215 -3.52415,-7.35 -4.15849,-8.2003 -4.50458,-8.94188 -4.89292,-10.4841 -0.45865,-1.82145 -0.21161,-5.43531 0.4625,-6.7659 0.66625,-1.31505 2.15695,-2.91616 3.24622,-3.48662 1.29885,-0.68024 2.61568,0.12202 4.6864,2.85512 3.42153,4.51599 14.00135,19.44095 15.73191,22.19301 3.74551,5.95636 5.95558,11.16496 7.9722,18.78849 0.6547,2.475 1.88525,6.9975 2.73456,10.05 0.84931,3.0525 2.71404,10.15792 4.14385,15.78983 l 2.59964,10.23983 4.65,5.19319 c 2.5575,2.85626 5.7975,6.46371 7.2,8.01657 1.4025,1.55286 2.55,2.97543 2.55,3.16127 0,0.33546 -34.49955,29.29931 -34.89913,29.29931 -0.11475,0 -2.15559,-1.95044 -4.5352,-4.3343 z M 365.9443,154.77206 c -1.25762,-0.62844 -2.20557,-1.3788 -2.91402,-2.30663 -2.08931,-2.73629 -1.95034,2.36868 -1.86433,-68.48249 l 0.0777,-64.03881 0.66066,-1.23494 c 1.0152,-1.89767 1.99201,-2.91087 3.73952,-3.87887 l 1.59982,-0.88619 37.78387,-0.0796 c 42.45592,-0.0894 39.40239,-0.2483 42.11646,2.19188 0.87544,0.78709 1.75715,1.95946 2.18393,2.90385 0.71264,1.57698 0.71613,1.63839 0.80561,14.20405 l 0.0899,12.62022 -1.79817,-0.13007 c -1.42577,-0.10313 -2.08143,0.007 -3.16601,0.5321 -2.01294,0.97445 -3.93993,2.89871 -5.11476,5.10753 l -1.03717,1.95 -0.007,-12.825 -0.007,-12.825 -33.6,0 -33.6,0 0,51.3 0,51.3 33.59873,0 33.59874,0 0.0763,-34.425 c 0.073,-32.96021 0.0982,-34.36117 0.59098,-32.925 0.28309,0.825 1.80562,3.9975 3.3834,7.05 5.49252,10.62624 5.40494,9.86009 5.39597,47.20335 -0.007,27.62122 -0.12358,29.95084 -1.66204,33.10906 -1.07144,2.19949 -2.71143,3.71042 -5.05823,4.66019 l -1.67381,0.6774 -36.1677,0.0797 -36.16769,0.0797 -1.864,-0.93145 z m 42.39939,-5.03813 c 2.87119,-1.30885 4.45771,-3.6784 4.43003,-6.61652 -0.0388,-4.11587 -3.1088,-7.22328 -7.1364,-7.22328 -2.11956,0 -3.56727,0.60889 -5.16364,2.17177 -2.24518,2.19807 -2.75398,5.43897 -1.30101,8.28704 0.71312,1.39782 2.52137,3.00905 3.96214,3.53045 1.49707,0.54176 3.84003,0.47454 5.20888,-0.14946 z"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
id="path4201-1"
|
||||
d="m 39.434334,167.15983 4.32657,-4.3343 7.06956,-2.42737 c 3.88825,-1.33505 8.72285,-2.99587 10.74354,-3.6907 l 3.674,-1.26333 -3.524,-0.1726 c -8.24183,-0.40367 -12.98778,-4.00671 -14.33634,-10.88389 -0.79628,-4.06078 -1.12887,-17.29805 -0.83016,-33.04122 0.15338,-8.08375 0.16617,-15.41641 0.0284,-16.29481 -0.13775,-0.8784 -0.53527,-2.42011 -0.88339,-3.42602 -1.22247,-3.53243 -0.33996,-11.90828 1.8577,-17.63146 0.34848,-0.9075 1.93434,-4.215 3.52415,-7.35 4.15849,-8.2003 4.50458,-8.94188 4.89292,-10.4841 0.45865,-1.82145 0.21161,-5.43531 -0.4625,-6.7659 -0.66625,-1.31505 -2.15695,-2.91616 -3.24622,-3.48662 -1.29885,-0.68024 -2.61568,0.12202 -4.6864,2.85512 -3.42153,4.51599 -14.00135,19.44095 -15.73191,22.19301 -3.74551,5.95636 -5.955584,11.16496 -7.972204,18.78849 -0.6547,2.475 -1.88525,6.9975 -2.73456,10.05 -0.84931,3.0525 -2.71404,10.15792 -4.14385,15.78983 L 14.4,125.82379 9.75,131.01698 c -2.5575,2.85626 -5.7975,6.46371 -7.2,8.01657 -1.4025,1.55286 -2.55,2.97543 -2.55,3.16127 0,0.33546 34.499554,29.29931 34.899134,29.29931 0.11475,0 2.15559,-1.95044 4.53519,-4.3343 z m 93.865056,-16.38777 c 1.25762,-0.62844 2.20557,-1.3788 2.91402,-2.30663 2.08931,-2.73629 1.95034,2.36868 1.86433,-68.48249 l -0.0777,-64.03881 -0.66066,-1.23494 c -1.0152,-1.89767 -1.99201,-2.91087 -3.73952,-3.87887 L 132.00004,9.94413 94.216184,9.86453 c -42.45592,-0.0894 -39.40239,-0.2483 -42.11646,2.19188 -0.87544,0.78709 -1.75715,1.95946 -2.18393,2.90385 -0.71264,1.57698 -0.71613,1.63839 -0.80561,14.20405 l -0.0899,12.62022 1.79817,-0.13007 c 1.42577,-0.10313 2.08143,0.007 3.16601,0.5321 2.01294,0.97445 3.93993,2.89871 5.11476,5.10753 l 1.03717,1.95 0.007,-12.825 0.007,-12.825 h 33.6 33.599986 v 51.3 51.3 H 93.751664 60.152924 l -0.0763,-34.425 c -0.073,-32.96021 -0.0982,-34.36117 -0.59098,-32.925 -0.28309,0.825 -1.80562,3.9975 -3.3834,7.05 -5.49252,10.62624 -5.40494,9.86009 -5.39597,47.20335 0.007,27.62122 0.12358,29.95084 1.66204,33.10906 1.07144,2.19949 2.71143,3.71042 5.05823,4.66019 l 1.67381,0.6774 36.1677,0.0797 36.167676,0.0797 1.864,-0.93145 z m -42.399376,-5.03813 c -2.87119,-1.30885 -4.45771,-3.6784 -4.43003,-6.61652 0.0388,-4.11587 3.1088,-7.22328 7.1364,-7.22328 2.11956,0 3.56727,0.60889 5.16364,2.17177 2.245176,2.19807 2.753976,5.43897 1.301006,8.28704 -0.713116,1.39782 -2.521366,3.00905 -3.962136,3.53045 -1.49707,0.54176 -3.84003,0.47454 -5.20888,-0.14946 z"
|
||||
style="fill:#000000;stroke:none" />
|
||||
d="m 39.434334,171.15983 4.32657,-4.3343 7.06956,-2.42737 c 3.88825,-1.33505 8.72285,-2.99587 10.74354,-3.6907 l 3.674,-1.26333 -3.524,-0.1726 c -8.24183,-0.40367 -12.98778,-4.00671 -14.33634,-10.88389 -0.79628,-4.06078 -1.12887,-17.29805 -0.83016,-33.04122 0.15338,-8.08375 0.16617,-15.41641 0.0284,-16.29481 -0.13775,-0.8784 -0.53527,-2.42011 -0.88339,-3.42602 -1.22247,-3.53243 -0.33996,-11.90828 1.8577,-17.63146 0.34848,-0.9075 1.93434,-4.215 3.52415,-7.35 4.15849,-8.2003 4.50458,-8.94188 4.89292,-10.4841 0.45865,-1.82145 0.21161,-5.43531 -0.4625,-6.7659 -0.66625,-1.31505 -2.15695,-2.91616 -3.24622,-3.48662 -1.29885,-0.68024 -2.61568,0.12202 -4.6864,2.85512 -3.42153,4.51599 -14.00135,19.44095 -15.73191,22.19301 -3.74551,5.95636 -5.955584,11.16496 -7.972204,18.78849 -0.6547,2.475 -1.88525,6.9975 -2.73456,10.05 -0.84931,3.0525 -2.71404,10.15792 -4.14385,15.78983 L 14.4,129.82379 9.75,135.01698 c -2.5575,2.85626 -5.7975,6.46371 -7.2,8.01657 -1.4025,1.55286 -2.55,2.97543 -2.55,3.16127 0,0.33546 34.499554,29.29931 34.899134,29.29931 0.11475,0 2.15559,-1.95044 4.53519,-4.3343 z m 93.865056,-16.38777 c 1.25762,-0.62844 2.20557,-1.3788 2.91402,-2.30663 2.08931,-2.73629 1.95034,2.36868 1.86433,-68.48249 l -0.0777,-64.03881 -0.66066,-1.23494 c -1.0152,-1.89767 -1.99201,-2.91087 -3.73952,-3.87887 l -1.59982,-0.88619 -37.783856,-0.0796 c -42.45592,-0.0894 -39.40239,-0.2483 -42.11646,2.19188 -0.87544,0.78709 -1.75715,1.95946 -2.18393,2.90385 -0.71264,1.57698 -0.71613,1.63839 -0.80561,14.20405 l -0.0899,12.62022 1.79817,-0.13007 c 1.42577,-0.10313 2.08143,0.007 3.16601,0.5321 2.01294,0.97445 3.93993,2.89871 5.11476,5.10753 l 1.03717,1.95 0.007,-12.825 0.007,-12.825 33.6,0 33.599986,0 0,51.3 0,51.3 -33.598716,0 -33.59874,0 -0.0763,-34.425 c -0.073,-32.96021 -0.0982,-34.36117 -0.59098,-32.925 -0.28309,0.825 -1.80562,3.9975 -3.3834,7.05 -5.49252,10.62624 -5.40494,9.86009 -5.39597,47.20335 0.007,27.62122 0.12358,29.95084 1.66204,33.10906 1.07144,2.19949 2.71143,3.71042 5.05823,4.66019 l 1.67381,0.6774 36.1677,0.0797 36.167676,0.0797 1.864,-0.93145 z m -42.399376,-5.03813 c -2.87119,-1.30885 -4.45771,-3.6784 -4.43003,-6.61652 0.0388,-4.11587 3.1088,-7.22328 7.1364,-7.22328 2.11956,0 3.56727,0.60889 5.16364,2.17177 2.245176,2.19807 2.753976,5.43897 1.301006,8.28704 -0.713116,1.39782 -2.521366,3.00905 -3.962136,3.53045 -1.49707,0.54176 -3.84003,0.47454 -5.20888,-0.14946 z"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
id="rect4270"
|
||||
d="m 247.25369,71.97921 h 4.73637 c 13.16497,0 23.76348,10.598514 23.76348,23.763485 v 32.174615 c 0,13.16497 -10.59851,23.76348 -23.76348,23.76348 h -4.73637 c -13.16497,0 -23.76349,-10.59851 -23.76349,-23.76348 V 95.742695 c 0,-13.164971 10.59852,-23.763485 23.76349,-23.763485 z"
|
||||
style="fill:#0a3d91;stroke:none" />
|
||||
d="m 247.25369,75.97921 4.73637,0 c 13.16497,0 23.76348,10.598514 23.76348,23.763485 l 0,32.174615 c 0,13.16497 -10.59851,23.76348 -23.76348,23.76348 l -4.73637,0 c -13.16497,0 -23.76349,-10.59851 -23.76349,-23.76348 l 0,-32.174615 c 0,-13.164971 10.59852,-23.763485 23.76349,-23.763485 z"
|
||||
style="fill:#0a3d91" />
|
||||
<path
|
||||
id="path4272"
|
||||
d="m 236.31105,102.92749 24.90674,25.07007 -12.00423,14.53574 0,-51.936691 12.00423,13.882451 -24.90674,24.41678"
|
||||
style="fill:none;stroke:#ffffff;stroke-width:4.32805729" />
|
||||
<path
|
||||
id="path4844"
|
||||
d="m 143.67921,23.5571 c -1.59043,2.623831 0.18153,5.574737 2.78461,6.642714 2.69504,1.572445 6.1706,4.987176 9.14793,2.232151 2.23313,-2.039515 0.60129,-5.727894 -2.04109,-6.67035 -3.06433,-1.676733 -6.55172,-5.514807 -9.89145,-2.204515 z M 351.7046,23.12937 c -2.76779,1.960623 -7.06819,2.694997 -8.37782,6.042717 -0.64195,2.73095 1.93572,4.99255 4.58419,4.426453 3.2021,-1.637914 7.05658,-2.8424 9.17849,-5.94769 0.56931,-2.60435 -1.49324,-5.35066 -4.26769,-4.745697 -0.41558,-0.107679 -0.76003,0.03505 -1.11717,0.224217 z m -26.76172,13.17187 c -2.93431,1.708015 -7.32039,1.922759 -8.93076,5.153004 -0.90775,2.654328 1.43202,5.159236 4.12365,4.861044 3.35587,-1.304271 7.31954,-2.112696 9.73662,-5.002268 0.81368,-2.537666 -0.97658,-5.469583 -3.79644,-5.128908 -0.4035,-0.14574 -0.75969,-0.03824 -1.13307,0.117128 z m -155.23633,2.46289 c -1.21948,2.711869 0.75683,5.460507 3.4437,6.14538 2.85847,1.080772 6.66937,4.11785 9.31224,1.23392 2.3151,-2.03652 0.33929,-5.835383 -2.40983,-6.41823 -3.32833,-1.175814 -7.38479,-4.713633 -10.34611,-0.96107 z m 127.27344,7.61719 c -3.12289,1.309174 -7.49226,0.970678 -9.49244,3.970097 -1.23184,2.518748 0.7727,5.298264 3.48037,5.339749 3.48706,-0.907308 7.56742,-1.1128 10.30314,-3.739116 1.11397,-2.422222 -0.31199,-5.546068 -3.1516,-5.551016 -0.3827,-0.195662 -0.75023,-0.126964 -1.13947,-0.01971 z m -97.49023,1.54492 c -2.22858,1.461838 -2.68005,4.865948 -0.27553,6.376909 2.5869,1.470583 5.91459,1.954941 8.83971,2.495346 2.78485,0.332188 4.74603,-2.479173 3.89118,-5.048668 -2.04264,-3.279676 -6.47206,-2.73364 -9.71704,-4.089227 -0.91277,0.08855 -1.82555,0.177093 -2.73832,0.26564 z m 68.51367,4.50782 c -3.27623,0.788856 -7.46081,-0.187875 -9.93635,2.381992 -1.64972,2.268076 -0.15709,5.353662 2.50432,5.86185 3.61172,-0.292365 7.71764,0.09072 10.84009,-2.093962 1.46245,-2.227649 0.52336,-5.531725 -2.28478,-5.959415 -0.34949,-0.249225 -0.72211,-0.239347 -1.12328,-0.190465 z m -39.44922,1.41796 c -1.93309,1.83595 -1.76537,5.266611 0.87081,6.321491 2.80857,0.988807 6.17065,0.846733 9.1444,0.861366 2.79909,-0.172766 4.22635,-3.289835 2.92376,-5.664738 -2.60365,-2.851635 -6.84699,-1.541752 -10.29251,-2.269899 -0.88215,0.250593 -1.76431,0.501187 -2.64646,0.75178 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0a3d91;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:7.55;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:7.55, 22.65;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
d="m 252.50083,112.08988 11.6563,-11.45156 -17.12592,-19.790888 0.0193,25.792048 -9.14185,-9.24454 -3.12531,3.09064 11.5214,11.58063 -11.49773,11.30756 c 3.03061,3.05593 0,0 3.03061,3.05593 l 9.18921,-8.97308 c 0.0443,4.73517 0.004,27.16515 0.004,27.16515 l 17.10224,-20.77257 z m -1.07721,-19.570428 6.79869,7.863838 -6.79869,6.63278 z m -0.0237,40.027828 0.0237,-15.39623 6.89338,6.9879 z"
|
||||
id="path1536"
|
||||
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.254891"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccc" />
|
||||
d="m 143.67921,27.5571 c -1.59043,2.623831 0.18153,5.574737 2.78461,6.642714 2.69504,1.572445 6.1706,4.987176 9.14793,2.232151 2.23313,-2.039515 0.60129,-5.727894 -2.04109,-6.67035 -3.06433,-1.676733 -6.55172,-5.514807 -9.89145,-2.204515 z M 351.7046,27.12937 c -2.76779,1.960623 -7.06819,2.694997 -8.37782,6.042717 -0.64195,2.73095 1.93572,4.99255 4.58419,4.426453 3.2021,-1.637914 7.05658,-2.8424 9.17849,-5.94769 0.56931,-2.60435 -1.49324,-5.35066 -4.26769,-4.745697 -0.41558,-0.107679 -0.76003,0.03505 -1.11717,0.224217 z m -26.76172,13.17187 c -2.93431,1.708015 -7.32039,1.922759 -8.93076,5.153004 -0.90775,2.654328 1.43202,5.159236 4.12365,4.861044 3.35587,-1.304271 7.31954,-2.112696 9.73662,-5.002268 0.81368,-2.537666 -0.97658,-5.469583 -3.79644,-5.128908 -0.4035,-0.14574 -0.75969,-0.03824 -1.13307,0.117128 z m -155.23633,2.46289 c -1.21948,2.711869 0.75683,5.460507 3.4437,6.14538 2.85847,1.080772 6.66937,4.11785 9.31224,1.23392 2.3151,-2.03652 0.33929,-5.835383 -2.40983,-6.41823 -3.32833,-1.175814 -7.38479,-4.713633 -10.34611,-0.96107 z m 127.27344,7.61719 c -3.12289,1.309174 -7.49226,0.970678 -9.49244,3.970097 -1.23184,2.518748 0.7727,5.298264 3.48037,5.339749 3.48706,-0.907308 7.56742,-1.1128 10.30314,-3.739116 1.11397,-2.422222 -0.31199,-5.546068 -3.1516,-5.551016 -0.3827,-0.195662 -0.75023,-0.126964 -1.13947,-0.01971 z m -97.49023,1.54492 c -2.22858,1.461838 -2.68005,4.865948 -0.27553,6.376909 2.5869,1.470583 5.91459,1.954941 8.83971,2.495346 2.78485,0.332188 4.74603,-2.479173 3.89118,-5.048668 -2.04264,-3.279676 -6.47206,-2.73364 -9.71704,-4.089227 -0.91277,0.08855 -1.82555,0.177093 -2.73832,0.26564 z m 68.51367,4.50782 c -3.27623,0.788856 -7.46081,-0.187875 -9.93635,2.381992 -1.64972,2.268076 -0.15709,5.353662 2.50432,5.86185 3.61172,-0.292365 7.71764,0.09072 10.84009,-2.093962 1.46245,-2.227649 0.52336,-5.531725 -2.28478,-5.959415 -0.34949,-0.249225 -0.72211,-0.239347 -1.12328,-0.190465 z m -39.44922,1.41796 c -1.93309,1.83595 -1.76537,5.266611 0.87081,6.321491 2.80857,0.988807 6.17065,0.846733 9.1444,0.861366 2.79909,-0.172766 4.22635,-3.289835 2.92376,-5.664738 -2.60365,-2.851635 -6.84699,-1.541752 -10.29251,-2.269899 -0.88215,0.250593 -1.76431,0.501187 -2.64646,0.75178 z"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0a3d91;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:7.55000019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:7.55, 22.65;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.2 KiB |
@@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="256" height="204"
|
||||
viewBox="0 0 256 204" fill="none" version="1.1" id="svg42" sodipodi:docname="share_app.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<metadata id="metadata48">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs id="defs46" />
|
||||
<sodipodi:namedview pagecolor="#000000" bordercolor="#666666" borderopacity="1"
|
||||
objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="964"
|
||||
id="namedview44" showgrid="false" inkscape:zoom="2.4705882" inkscape:cx="123.34752"
|
||||
inkscape:cy="83.759737" inkscape:window-x="1920" inkscape:window-y="72"
|
||||
inkscape:window-maximized="0" inkscape:current-layer="svg42" />
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M41.583 100.841H22.1807V102.841H52.7503V136.663H54.7503V102.841H68.0341C70.9241 102.841 73.267 105.184 73.267 108.074C73.267 112.068 76.5053 115.307 80.4999 115.307H92.727V113.307H80.4999C77.6098 113.307 75.267 110.964 75.267 108.074C75.267 104.079 72.0287 100.841 68.0341 100.841H43.583V63.6593H41.583V100.841Z"
|
||||
fill="#657D99" id="path2" />
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M122.611 86.0596V46.2531H124.611V86.0596H122.611Z" fill="#657D99" id="path4" />
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M119.754 179.928L119.754 142.682L121.754 142.682L121.754 179.928L119.754 179.928Z"
|
||||
fill="#657D99" id="path6" />
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M195.251 100.841V86.5962H193.251V113.826H148.362V115.826H194.251C198.125 115.826 201.301 118.969 201.301 122.826V127.044H203.301V122.826C203.301 118.186 199.75 114.378 195.251 113.881V102.841H233.052V100.841H195.251Z"
|
||||
fill="#657D99" id="path8" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M32.1213 57.4112L32.8875 55.5637C33.5961 55.8576 34.3747 56.0208 35.1966 56.0208H37.0795V58.0208H35.1966C34.1072 58.0208 33.0686 57.804 32.1213 57.4112ZM48.3769 58.0208V56.0208H50.2598C51.0816 56.0208 51.8602 55.8576 52.5689 55.5637L53.335 57.4112C52.3877 57.804 51.3491 58.0208 50.2598 58.0208H48.3769ZM58.2935 36.8067H56.2935V34.9238C56.2935 34.102 56.1303 33.3234 55.8364 32.6147L57.6839 31.8486C58.0767 32.7959 58.2935 33.8345 58.2935 34.9238V36.8067ZM37.0795 26.8901H35.1966C34.1072 26.8901 33.0686 27.1069 32.1213 27.4997L32.8875 29.3472C33.5961 29.0533 34.3747 28.8901 35.1966 28.8901H37.0795V26.8901ZM27.1628 48.1042H29.1628V49.9871C29.1628 50.8089 29.326 51.5875 29.6199 52.2962L27.7724 53.0623C27.3796 52.115 27.1628 51.0764 27.1628 49.9871V48.1042ZM27.1628 44.3384H29.1628V40.5725H27.1628V44.3384ZM27.1628 36.8067H29.1628V34.9238C29.1628 34.102 29.326 33.3234 29.6199 32.6147L27.7724 31.8486C27.3796 32.7959 27.1628 33.8345 27.1628 34.9238V36.8067ZM40.8453 26.8901V28.8901H44.6111V26.8901H40.8453ZM48.3769 26.8901V28.8901H50.2598C51.0816 28.8901 51.8602 29.0533 52.5689 29.3472L53.335 27.4997C52.3877 27.1069 51.3491 26.8901 50.2598 26.8901H48.3769ZM58.2935 40.5725H56.2935V44.3383H58.2935V40.5725ZM58.2935 48.1042H56.2935V49.9871C56.2935 50.8089 56.1303 51.5875 55.8364 52.2962L57.6839 53.0623C58.0767 52.115 58.2935 51.0764 58.2935 49.9871V48.1042ZM44.6111 58.0208V56.0208H40.8453V58.0208H44.6111Z"
|
||||
fill="#657D99" id="path10" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M112.487 40.4256L112.875 38.4636C113.41 38.5694 113.964 38.6251 114.533 38.6251H116.99V40.6251H114.533C113.833 40.6251 113.149 40.5565 112.487 40.4256ZM131.733 40.6251V38.6251H134.191C134.76 38.6251 135.314 38.5694 135.848 38.4636L136.237 40.4256C135.575 40.5565 134.891 40.6251 134.191 40.6251H131.733ZM140.016 38.8593L138.903 37.1973C139.829 36.5774 140.627 35.7797 141.247 34.8536L142.909 35.9663C142.143 37.1095 141.159 38.0939 140.016 38.8593ZM144.674 12.9411H142.674V10.4839C142.674 9.91475 142.619 9.36059 142.513 8.82599L144.475 8.43785C144.606 9.0996 144.674 9.78374 144.674 10.4839V12.9411ZM142.909 4.65889L141.247 5.77152C140.627 4.84548 139.829 4.04776 138.903 3.42779L140.016 1.76585C141.159 2.53126 142.143 3.5156 142.909 4.65889ZM116.99 0H114.533C113.833 0 113.149 0.0686375 112.487 0.199551L112.875 2.16153C113.41 2.05577 113.964 2 114.533 2H116.99V0ZM108.708 1.76585L109.821 3.42779C108.895 4.04776 108.097 4.84548 107.477 5.77152L105.815 4.65888C106.581 3.5156 107.565 2.53126 108.708 1.76585ZM104.049 27.6841H106.049V30.1412C106.049 30.7104 106.105 31.2645 106.211 31.7992L104.249 32.1873C104.118 31.5255 104.049 30.8414 104.049 30.1412V27.6841ZM105.815 35.9663L107.477 34.8536C108.097 35.7797 108.895 36.5774 109.821 37.1973L108.708 38.8593C107.565 38.0939 106.581 37.1095 105.815 35.9663ZM104.049 22.7697H106.049V17.8554H104.049V22.7697ZM104.049 12.9411H106.049V10.4839C106.049 9.91475 106.105 9.36059 106.211 8.82598L104.249 8.43785C104.118 9.09959 104.049 9.78374 104.049 10.4839V12.9411ZM121.905 0V2H126.819V0H121.905ZM131.733 0V2H134.191C134.76 2 135.314 2.05577 135.848 2.16153L136.237 0.199552C135.575 0.0686377 134.891 0 134.191 0H131.733ZM144.674 17.8554H142.674V22.7697H144.674V17.8554ZM144.674 27.6841H142.674V30.1412C142.674 30.7104 142.619 31.2645 142.513 31.7992L144.475 32.1873C144.606 31.5255 144.674 30.8414 144.674 30.1412V27.6841ZM126.819 40.6251V38.6251H121.905V40.6251H126.819Z"
|
||||
fill="#657D99" id="path12" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M183.397 81.9904L184.163 80.1429C184.902 80.4493 185.714 80.6194 186.57 80.6194H188.513V82.6194H186.57C185.446 82.6194 184.375 82.3957 183.397 81.9904ZM200.171 82.6194V80.6194H202.114C202.97 80.6194 203.782 80.4493 204.521 80.1429L205.287 81.9904C204.309 82.3957 203.238 82.6194 202.114 82.6194H200.171ZM210.403 60.7295H208.403V58.7866C208.403 57.9301 208.233 57.1184 207.927 56.3795L209.774 55.6134C210.18 56.5908 210.403 57.6626 210.403 58.7866V60.7295ZM188.513 50.4969H186.57C185.446 50.4969 184.375 50.7207 183.397 51.126L184.163 52.9734C184.902 52.667 185.714 52.4969 186.57 52.4969H188.513V50.4969ZM178.281 72.3869H180.281V74.3297C180.281 75.1863 180.451 75.998 180.757 76.7368L178.91 77.5029C178.504 76.5255 178.281 75.4538 178.281 74.3297V72.3869ZM178.281 68.5011H180.281V64.6153H178.281V68.5011ZM178.281 60.7295H180.281V58.7866C180.281 57.9301 180.451 57.1184 180.757 56.3795L178.91 55.6134C178.504 56.5908 178.281 57.6626 178.281 58.7866V60.7295ZM192.399 50.4969V52.4969H196.285V50.4969H192.399ZM200.171 50.4969V52.4969H202.114C202.97 52.4969 203.782 52.667 204.521 52.9734L205.287 51.126C204.309 50.7207 203.238 50.4969 202.114 50.4969H200.171ZM210.403 64.6153H208.403V68.5011H210.403V64.6153ZM210.403 72.3869H208.403V74.3297C208.403 75.1863 208.233 75.998 207.927 76.7368L209.774 77.5029C210.18 76.5255 210.403 75.4538 210.403 74.3297V72.3869ZM196.285 82.6194V80.6194H192.399V82.6194H196.285Z"
|
||||
fill="#657D99" id="path14" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M188.271 180.562L188.659 178.6C189.316 178.73 189.996 178.798 190.694 178.798H192.633V180.798H190.694C189.865 180.798 189.055 180.717 188.271 180.562ZM212.029 180.798V178.798H213.968C214.666 178.798 215.346 178.73 216.003 178.6L216.391 180.562C215.607 180.717 214.797 180.798 213.968 180.798H212.029ZM220.865 178.708L219.753 177.046C220.889 176.285 221.868 175.306 222.629 174.17L224.291 175.282C223.384 176.636 222.219 177.801 220.865 178.708ZM226.381 147.05H224.381V145.111C224.381 144.413 224.313 143.733 224.183 143.077L226.145 142.688C226.3 143.472 226.381 144.282 226.381 145.111V147.05ZM224.291 138.214L222.629 139.327C221.868 138.19 220.889 137.211 219.753 136.451L220.865 134.789C222.219 135.695 223.384 136.86 224.291 138.214ZM192.633 132.698H190.694C189.865 132.698 189.055 132.779 188.271 132.934L188.659 134.896C189.316 134.766 189.996 134.698 190.694 134.698H192.633V132.698ZM183.797 134.789L184.91 136.451C183.773 137.211 182.794 138.19 182.033 139.327L180.372 138.214C181.278 136.86 182.443 135.695 183.797 134.789ZM178.281 166.446H180.281V168.385C180.281 169.083 180.349 169.763 180.479 170.42L178.517 170.808C178.362 170.024 178.281 169.214 178.281 168.385V166.446ZM180.372 175.282L182.033 174.17C182.794 175.306 183.773 176.285 184.91 177.046L183.797 178.708C182.443 177.801 181.278 176.636 180.372 175.282ZM178.281 162.567H180.281V158.688H178.281V162.567ZM178.281 154.809H180.281V150.93H178.281V154.809ZM178.281 147.05H180.281V145.111C180.281 144.413 180.349 143.733 180.479 143.077L178.517 142.688C178.362 143.472 178.281 144.282 178.281 145.111V147.05ZM196.512 132.698V134.698H200.392V132.698H196.512ZM204.271 132.698V134.698H208.15V132.698H204.271ZM212.029 132.698V134.698H213.968C214.666 134.698 215.346 134.766 216.003 134.896L216.391 132.934C215.607 132.779 214.797 132.698 213.968 132.698H212.029ZM226.381 150.93H224.381V154.809H226.381V150.93ZM226.381 158.688H224.381V162.567H226.381V158.688ZM226.381 166.446H224.381V168.385C224.381 169.083 224.313 169.763 224.183 170.42L226.145 170.808C226.3 170.024 226.381 169.214 226.381 168.385V166.446ZM208.15 180.798V178.798H204.271V180.798H208.15ZM200.392 180.798V178.798H196.512V180.798H200.392Z"
|
||||
fill="#657D99" id="path16" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M40.3891 175.457L41.1552 173.609C41.9338 173.932 42.7892 174.111 43.6915 174.111H45.7134V176.111H43.6915C42.5217 176.111 41.4063 175.878 40.3891 175.457ZM57.8451 176.111V174.111H59.8671C60.7693 174.111 61.6247 173.932 62.4033 173.609L63.1694 175.457C62.1522 175.878 61.0368 176.111 59.8671 176.111H57.8451ZM68.4941 153.33H66.4941V151.309C66.4941 150.406 66.3149 149.551 65.992 148.772L67.8395 148.006C68.2613 149.023 68.4941 150.139 68.4941 151.309V153.33ZM45.7134 142.682H43.6915C42.5217 142.682 41.4063 142.914 40.3891 143.336L41.1552 145.184C41.9338 144.861 42.7892 144.682 43.6915 144.682H45.7134V142.682ZM35.0645 165.462H37.0645V167.484C37.0645 168.386 37.2437 169.242 37.5665 170.02L35.7191 170.786C35.2973 169.769 35.0645 168.654 35.0645 167.484V165.462ZM35.0645 161.418H37.0645V157.374H35.0645V161.418ZM35.0645 153.33H37.0645V151.309C37.0645 150.406 37.2437 149.551 37.5665 148.772L35.7191 148.006C35.2973 149.023 35.0645 150.139 35.0645 151.309V153.33ZM49.7573 142.682V144.682H53.8012V142.682H49.7573ZM57.8451 142.682V144.682H59.8671C60.7693 144.682 61.6247 144.861 62.4033 145.184L63.1694 143.336C62.1522 142.914 61.0368 142.682 59.8671 142.682H57.8451ZM68.4941 157.374H66.4941V161.418H68.4941V157.374ZM68.4941 165.462H66.4941V167.484C66.4941 168.386 66.3149 169.242 65.992 170.02L67.8395 170.786C68.2613 169.769 68.4941 168.654 68.4941 167.484V165.462ZM53.8012 176.111V174.111H49.7573V176.111H53.8012Z"
|
||||
fill="#657D99" id="path18" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M2.84 110.802L3.60611 108.954C3.91005 109.081 4.24493 109.151 4.60133 109.151H6.75821V111.151H4.60133C3.97743 111.151 3.38253 111.027 2.84 110.802ZM11.072 111.151V109.151H13.2288C13.5852 109.151 13.9201 109.081 14.2241 108.954L14.9902 110.802C14.4476 111.027 13.8527 111.151 13.2288 111.151H11.072ZM17.8302 100.079H15.8302V97.9223C15.8302 97.5659 15.7596 97.231 15.6336 96.927L17.481 96.1609C17.706 96.7035 17.8302 97.2984 17.8302 97.9223V100.079ZM6.75821 93.3209H4.60133C3.97743 93.3209 3.38253 93.4451 2.84 93.6701L3.60611 95.5175C3.91005 95.3915 4.24493 95.3209 4.60133 95.3209H6.75821V93.3209ZM0 104.393H2V106.55C2 106.906 2.07056 107.241 2.1966 107.545L0.349152 108.311C0.124172 107.769 0 107.174 0 106.55V104.393ZM0 100.079H2V97.9223C2 97.5659 2.07056 97.231 2.1966 96.927L0.349152 96.1609C0.124172 96.7035 0 97.2984 0 97.9223V100.079ZM11.072 93.3209V95.3209H13.2288C13.5852 95.3209 13.9201 95.3915 14.2241 95.5175L14.9902 93.6701C14.4476 93.4451 13.8527 93.3209 13.2288 93.3209H11.072ZM17.8302 104.393H15.8302V106.55C15.8302 106.906 15.7596 107.241 15.6336 107.545L17.481 108.311C17.706 107.769 17.8302 107.174 17.8302 106.55V104.393Z"
|
||||
fill="#657D99" id="path20" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M114.745 202.969L115.511 201.122C115.815 201.248 116.149 201.318 116.506 201.318H118.663V203.318H116.506C115.882 203.318 115.287 203.194 114.745 202.969ZM122.977 203.318V201.318H125.133C125.49 201.318 125.825 201.248 126.129 201.122L126.895 202.969C126.352 203.194 125.757 203.318 125.133 203.318H122.977ZM129.735 192.246H127.735V190.09C127.735 189.733 127.664 189.398 127.538 189.094L129.386 188.328C129.611 188.871 129.735 189.466 129.735 190.09V192.246ZM118.663 185.488H116.506C115.882 185.488 115.287 185.612 114.745 185.837L115.511 187.685C115.815 187.559 116.149 187.488 116.506 187.488H118.663V185.488ZM111.905 196.56H113.905V198.717C113.905 199.074 113.975 199.408 114.101 199.712L112.254 200.478C112.029 199.936 111.905 199.341 111.905 198.717V196.56ZM111.905 192.246H113.905V190.09C113.905 189.733 113.975 189.398 114.101 189.094L112.254 188.328C112.029 188.871 111.905 189.466 111.905 190.09V192.246ZM122.977 185.488V187.488H125.133C125.49 187.488 125.825 187.559 126.129 187.685L126.895 185.837C126.352 185.612 125.757 185.488 125.133 185.488H122.977ZM129.735 196.56H127.735V198.717C127.735 199.074 127.664 199.408 127.538 199.712L129.386 200.478C129.611 199.936 129.735 199.341 129.735 198.717V196.56Z"
|
||||
fill="#657D99" id="path22" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M240.444 110.802L241.21 108.954C241.514 109.081 241.849 109.151 242.205 109.151H244.362V111.151H242.205C241.581 111.151 240.987 111.027 240.444 110.802ZM248.676 111.151V109.151H250.833C251.189 109.151 251.524 109.081 251.828 108.954L252.594 110.802C252.052 111.027 251.457 111.151 250.833 111.151H248.676ZM255.434 100.079H253.434V97.9223C253.434 97.5659 253.364 97.231 253.238 96.927L255.085 96.1609C255.31 96.7035 255.434 97.2984 255.434 97.9223V100.079ZM244.362 93.3209H242.205C241.581 93.3209 240.987 93.4451 240.444 93.6701L241.21 95.5175C241.514 95.3915 241.849 95.3209 242.205 95.3209H244.362V93.3209ZM237.604 104.393H239.604V106.55C239.604 106.906 239.675 107.241 239.801 107.545L237.953 108.311C237.728 107.769 237.604 107.174 237.604 106.55V104.393ZM237.604 100.079H239.604V97.9223C239.604 97.5659 239.675 97.231 239.801 96.927L237.953 96.1609C237.728 96.7035 237.604 97.2984 237.604 97.9223V100.079ZM248.676 93.3209V95.3209H250.833C251.189 95.3209 251.524 95.3915 251.828 95.5175L252.594 93.6701C252.052 93.4451 251.457 93.3209 250.833 93.3209H248.676ZM255.434 104.393H253.434V106.55C253.434 106.906 253.364 107.241 253.238 107.545L255.085 108.311C255.31 107.769 255.434 107.174 255.434 106.55V104.393Z"
|
||||
fill="#657D99" id="path24" />
|
||||
<path
|
||||
d="M108.75 109.808V129.511C108.75 131.11 110.052 132.429 111.668 132.429H113.036C114.636 132.429 115.955 131.11 115.955 129.511V109.808H108.75V109.808Z"
|
||||
fill="#87C214" id="path26" />
|
||||
<path
|
||||
d="M115.938 100.312V96.6023C115.938 95.003 114.636 93.684 113.036 93.684H111.668C110.069 93.684 108.75 95.003 108.75 96.6023V100.312H115.938V100.312Z"
|
||||
fill="#87C214" id="path28" />
|
||||
<path
|
||||
d="M131.931 116.305V96.6023C131.931 95.003 130.628 93.684 129.029 93.684H127.66C126.061 93.684 124.742 95.003 124.742 96.6023V116.305H131.931V116.305Z"
|
||||
fill="#87C214" id="path30" />
|
||||
<path
|
||||
d="M124.742 125.801V129.511C124.742 131.11 126.061 132.429 127.66 132.429H129.029C130.628 132.429 131.947 131.11 131.947 129.511V125.801H124.742Z"
|
||||
fill="#87C214" id="path32" />
|
||||
<path
|
||||
d="M107.595 117.459H103.886C102.286 117.459 100.967 118.761 100.967 120.377V121.746C100.967 123.345 102.27 124.664 103.886 124.664H107.595V117.459Z"
|
||||
fill="#95D220" id="path34" />
|
||||
<path
|
||||
d="M136.795 117.459H117.092V124.664H136.795C138.394 124.664 139.713 123.345 139.713 121.746V120.377C139.713 118.761 138.394 117.459 136.795 117.459Z"
|
||||
fill="#95D220" id="path36" />
|
||||
<path
|
||||
d="M123.588 101.466H103.886C102.286 101.466 100.967 102.769 100.967 104.385V105.753C100.967 107.352 102.27 108.671 103.886 108.671H123.588V101.466Z"
|
||||
fill="#95D220" id="path38" />
|
||||
<path
|
||||
d="M136.795 101.466H133.085V108.671H136.795C138.394 108.671 139.713 107.369 139.713 105.753V104.384C139.713 102.769 138.394 101.466 136.795 101.466Z"
|
||||
fill="#95D220" id="path40" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -26,8 +26,8 @@ android {
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 30
|
||||
versionCode 10401
|
||||
versionName "1.4.1"
|
||||
versionCode 10305
|
||||
versionName "1.3.5"
|
||||
applicationId "org.briarproject.briar.android"
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
@@ -110,7 +110,7 @@ dependencies {
|
||||
|
||||
implementation 'info.guardianproject.panic:panic:1.0'
|
||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||
implementation 'de.hdodenhof:circleimageview:3.1.0'
|
||||
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.3.0'
|
||||
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.account.BriarAccountModule;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.account.SignInTestCreateAccount;
|
||||
@@ -22,7 +21,6 @@ import dagger.Component;
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
|
||||
import org.briarproject.bramble.BrambleAndroidModule;
|
||||
import org.briarproject.bramble.BrambleCoreModule;
|
||||
import org.briarproject.bramble.account.BriarAccountModule;
|
||||
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
|
||||
import org.briarproject.bramble.system.ClockModule;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
@@ -21,7 +20,6 @@ import dagger.Component;
|
||||
AttachmentModule.class,
|
||||
ClockModule.class,
|
||||
MediaModule.class,
|
||||
RemovableDriveModule.class,
|
||||
BriarCoreModule.class,
|
||||
BrambleAndroidModule.class,
|
||||
BriarAccountModule.class,
|
||||
|
||||
@@ -344,13 +344,7 @@
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.StartupFailureActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="false"
|
||||
android:finishOnTaskLaunch="true"
|
||||
android:label="@string/startup_failed_activity_title"
|
||||
android:launchMode="singleInstance"
|
||||
android:process=":briar_startup_failure"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||
android:label="@string/startup_failed_activity_title" />
|
||||
|
||||
<activity
|
||||
android:name="org.briarproject.briar.android.settings.SettingsActivity"
|
||||
@@ -462,17 +456,6 @@
|
||||
android:label="@string/hotspot_title"
|
||||
android:theme="@style/BriarTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".android.contact.connect.ConnectViaBluetoothActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/connect_via_bluetooth_title"
|
||||
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||
android:theme="@style/BriarTheme">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
<li id="troubleshooting_1">If you can't download the app, try it with a different web
|
||||
browser app.
|
||||
</li>
|
||||
<li id="troubleshooting_2">To install the downloaded app,
|
||||
you might need to allow your browser to install unknown apps.
|
||||
We recommend to undo that after successful installation.
|
||||
<li id="troubleshooting_2">Ensure that your browser is allowed to download apps directly by
|
||||
giving it the permission or enabling the installation of apps from "Unknown Sources" in
|
||||
system settings.
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.briarproject.briar.BriarCoreEagerSingletons;
|
||||
import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.attachment.AttachmentModule;
|
||||
import org.briarproject.briar.android.attachment.media.MediaModule;
|
||||
import org.briarproject.briar.android.contact.connect.BluetoothIntroFragment;
|
||||
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
|
||||
import org.briarproject.briar.android.hotspot.AbstractTabsFragment;
|
||||
import org.briarproject.briar.android.hotspot.FallbackFragment;
|
||||
@@ -237,6 +236,4 @@ public interface AndroidComponent
|
||||
void inject(SendFragment sendFragment);
|
||||
|
||||
void inject(ReceiveFragment receiveFragment);
|
||||
|
||||
void inject(BluetoothIntroFragment bluetoothIntroFragment);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.briarproject.briar.android.account.SetupModule;
|
||||
import org.briarproject.briar.android.blog.BlogModule;
|
||||
import org.briarproject.briar.android.contact.ContactListModule;
|
||||
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
|
||||
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothModule;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.hotspot.HotspotModule;
|
||||
import org.briarproject.briar.android.introduction.IntroductionModule;
|
||||
@@ -76,6 +75,7 @@ import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
|
||||
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
|
||||
@Module(includes = {
|
||||
SetupModule.class,
|
||||
@@ -89,7 +89,6 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBL
|
||||
DevReportModule.class,
|
||||
ContactListModule.class,
|
||||
IntroductionModule.class,
|
||||
ConnectViaBluetoothModule.class,
|
||||
// below need to be within same scope as ViewModelProvider.Factory
|
||||
BlogModule.class,
|
||||
ForumModule.class,
|
||||
@@ -158,8 +157,7 @@ public class AppModule {
|
||||
@Singleton
|
||||
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
|
||||
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
|
||||
AndroidRemovableDrivePluginFactory drive,
|
||||
FeatureFlags featureFlags) {
|
||||
AndroidRemovableDrivePluginFactory drive) {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@@ -170,8 +168,7 @@ public class AppModule {
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
if (SDK_INT >= 19) return singletonList(drive);
|
||||
else return emptyList();
|
||||
return SDK_INT >= 19 ? singletonList(drive) : emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -308,6 +305,21 @@ public class AppModule {
|
||||
public boolean shouldEnableDisappearingMessages() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableConnectViaBluetooth() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableTransferData() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldEnableShareAppViaOfflineHotspot() {
|
||||
return IS_DEBUG_BUILD;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
@@ -33,7 +34,11 @@ import java.util.logging.Logger;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
||||
import static android.app.NotificationManager.IMPORTANCE_LOW;
|
||||
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
|
||||
import static android.content.Intent.ACTION_SHUTDOWN;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
@@ -50,6 +55,7 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
|
||||
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
|
||||
@@ -60,6 +66,8 @@ public class BriarService extends Service {
|
||||
|
||||
public static String EXTRA_START_RESULT =
|
||||
"org.briarproject.briar.START_RESULT";
|
||||
public static String EXTRA_NOTIFICATION_ID =
|
||||
"org.briarproject.briar.FAILURE_NOTIFICATION_ID";
|
||||
public static String EXTRA_STARTUP_FAILED =
|
||||
"org.briarproject.briar.STARTUP_FAILED";
|
||||
|
||||
@@ -127,11 +135,12 @@ public class BriarService extends Service {
|
||||
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
ongoingChannel.setShowBadge(false);
|
||||
nm.createNotificationChannel(ongoingChannel);
|
||||
// Delete the unused channel previously used for startup
|
||||
// failure notifications
|
||||
// TODO: Remove this ID after a reasonable upgrade period
|
||||
// (added 2021-07-12)
|
||||
nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
|
||||
NotificationChannel failureChannel = new NotificationChannel(
|
||||
FAILURE_CHANNEL_ID,
|
||||
getString(R.string.startup_failed_notification_title),
|
||||
IMPORTANCE_DEFAULT);
|
||||
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET);
|
||||
nm.createNotificationChannel(failureChannel);
|
||||
}
|
||||
Notification foregroundNotification =
|
||||
notificationManager.getForegroundNotification();
|
||||
@@ -147,7 +156,7 @@ public class BriarService extends Service {
|
||||
} else {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.warning("Startup failed: " + result);
|
||||
showStartupFailure(result);
|
||||
showStartupFailureNotification(result);
|
||||
stopSelf();
|
||||
}
|
||||
}, "LifecycleStartup");
|
||||
@@ -173,13 +182,29 @@ public class BriarService extends Service {
|
||||
Localizer.getInstance().setLocale(this);
|
||||
}
|
||||
|
||||
private void showStartupFailure(StartResult result) {
|
||||
private void showStartupFailureNotification(StartResult result) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
// Bring the entry activity to the front to clear the back stack
|
||||
Intent i = new Intent(BriarService.this, ENTRY_ACTIVITY);
|
||||
NotificationCompat.Builder b = new NotificationCompat.Builder(
|
||||
BriarService.this, FAILURE_CHANNEL_ID);
|
||||
b.setSmallIcon(android.R.drawable.stat_notify_error);
|
||||
b.setContentTitle(getText(
|
||||
R.string.startup_failed_notification_title));
|
||||
b.setContentText(getText(
|
||||
R.string.startup_failed_notification_text));
|
||||
Intent i = new Intent(BriarService.this,
|
||||
StartupFailureActivity.class);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
|
||||
i.putExtra(EXTRA_START_RESULT, result);
|
||||
i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID);
|
||||
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
|
||||
0, i, FLAG_UPDATE_CURRENT));
|
||||
NotificationManager nm = (NotificationManager)
|
||||
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
|
||||
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
|
||||
// Bring the dashboard to the front to clear the back stack
|
||||
i = new Intent(BriarService.this, ENTRY_ACTIVITY);
|
||||
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
|
||||
i.putExtra(EXTRA_STARTUP_FAILED, true);
|
||||
i.putExtra(EXTRA_START_RESULT, result);
|
||||
startActivity(i);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
@@ -14,7 +15,9 @@ import org.briarproject.briar.android.fragment.ErrorFragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -38,6 +41,14 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
private void handleIntent(Intent i) {
|
||||
StartResult result =
|
||||
(StartResult) i.getSerializableExtra(EXTRA_START_RESULT);
|
||||
int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
|
||||
|
||||
// cancel notification
|
||||
if (notificationId > -1) {
|
||||
Object o = getSystemService(NOTIFICATION_SERVICE);
|
||||
NotificationManager nm = (NotificationManager) requireNonNull(o);
|
||||
nm.cancel(notificationId);
|
||||
}
|
||||
|
||||
// show proper error message
|
||||
int errorRes;
|
||||
@@ -67,4 +78,5 @@ public class StartupFailureActivity extends BaseActivity implements
|
||||
public void runOnDbThread(@NonNull Runnable runnable) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
|
||||
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
|
||||
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
|
||||
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
|
||||
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothActivity;
|
||||
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
||||
import org.briarproject.briar.android.conversation.BluetoothConnecterDialogFragment;
|
||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||
import org.briarproject.briar.android.conversation.ConversationSettingsDialog;
|
||||
import org.briarproject.briar.android.conversation.ImageActivity;
|
||||
@@ -238,6 +238,9 @@ public interface ActivityComponent {
|
||||
|
||||
void inject(ConversationSettingsDialog dialog);
|
||||
|
||||
void inject(
|
||||
BluetoothConnecterDialogFragment bluetoothConnecterDialogFragment);
|
||||
|
||||
void inject(RssFeedImportFragment fragment);
|
||||
|
||||
void inject(RssFeedManageFragment fragment);
|
||||
@@ -245,6 +248,4 @@ public interface ActivityComponent {
|
||||
void inject(RssFeedImportFailedDialogFragment fragment);
|
||||
|
||||
void inject(RssFeedDeleteFeedDialogFragment fragment);
|
||||
|
||||
void inject(ConnectViaBluetoothActivity connectViaBluetoothActivity);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
import org.briarproject.briar.api.identity.AuthorInfo;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
@@ -50,7 +50,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
|
||||
return text;
|
||||
}
|
||||
|
||||
boolean isRssFeed() {
|
||||
public boolean isRssFeed() {
|
||||
return header.isRssFeed();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ import androidx.annotation.UiThread;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
@@ -45,7 +44,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView text;
|
||||
private final ViewGroup commentContainer;
|
||||
private final boolean fullText, authorClickable;
|
||||
private final int padding;
|
||||
|
||||
private final OnBlogPostClickListener listener;
|
||||
|
||||
@@ -63,8 +61,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
reblogButton = v.findViewById(R.id.commentView);
|
||||
text = v.findViewById(R.id.textView);
|
||||
commentContainer = v.findViewById(R.id.commentContainer);
|
||||
padding = ctx.getResources()
|
||||
.getDimensionPixelSize(R.dimen.listitem_vertical_margin);
|
||||
}
|
||||
|
||||
void hideReblogButton() {
|
||||
@@ -133,12 +129,6 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
|
||||
} else {
|
||||
reblogger.setVisibility(GONE);
|
||||
}
|
||||
|
||||
// Apply Android 4 padding fix after setting up author/reblogger views
|
||||
if (SDK_INT < 21) {
|
||||
reblogger.setPadding(padding, padding, padding, padding);
|
||||
author.setPadding(padding, padding, padding, padding);
|
||||
}
|
||||
}
|
||||
|
||||
private void onBindComment(BlogCommentItem item, boolean authorClickable) {
|
||||
|
||||
@@ -57,12 +57,8 @@ public class RssFeedActivity extends BriarActivity
|
||||
onBackPressed();
|
||||
}
|
||||
} else if (result == FAILED) {
|
||||
String url = viewModel.getUrlFailedImport();
|
||||
if (url == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
RssFeedImportFailedDialogFragment dialog =
|
||||
RssFeedImportFailedDialogFragment.newInstance(url);
|
||||
RssFeedImportFailedDialogFragment.newInstance();
|
||||
dialog.show(getSupportFragmentManager(),
|
||||
RssFeedImportFailedDialogFragment.TAG);
|
||||
} else if (result == EXISTS) {
|
||||
|
||||
@@ -25,15 +25,8 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
private RssFeedViewModel viewModel;
|
||||
|
||||
private static final String ARG_URL = "url";
|
||||
|
||||
static RssFeedImportFailedDialogFragment newInstance(String retryUrl) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_URL, retryUrl);
|
||||
RssFeedImportFailedDialogFragment f =
|
||||
new RssFeedImportFailedDialogFragment();
|
||||
f.setArguments(args);
|
||||
return f;
|
||||
static RssFeedImportFailedDialogFragment newInstance() {
|
||||
return new RssFeedImportFailedDialogFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,8 +45,8 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
|
||||
R.style.BriarDialogTheme);
|
||||
builder.setMessage(R.string.blogs_rss_feeds_import_error);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setPositiveButton(R.string.try_again_button, (dialog, which) ->
|
||||
viewModel.importFeed(requireArguments().getString(ARG_URL)));
|
||||
builder.setPositiveButton(R.string.try_again_button,
|
||||
(dialog, which) -> viewModel.retryImportFeed());
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.briarproject.briar.api.feed.Feed;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
|
||||
@@ -159,9 +159,11 @@ class RssFeedViewModel extends DbViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getUrlFailedImport() {
|
||||
return urlFailedImport;
|
||||
void retryImportFeed() {
|
||||
if (urlFailedImport == null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
importFeed(urlFailedImport);
|
||||
}
|
||||
|
||||
private boolean exists(String url) {
|
||||
|
||||
@@ -59,7 +59,7 @@ public class LinkExchangeFragment extends BaseFragment
|
||||
@Override
|
||||
public void injectFragment(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
viewModel = new ViewModelProvider(getActivity(), viewModelFactory)
|
||||
.get(AddContactViewModel.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
|
||||
|
||||
class BluetoothConditionManager {
|
||||
|
||||
private enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
|
||||
/**
|
||||
* Call this when the using activity or fragment starts,
|
||||
* because permissions might have changed while it was stopped.
|
||||
*/
|
||||
void reset() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onLocationPermissionResult(Activity activity,
|
||||
@Nullable Boolean result) {
|
||||
if (result != null && result) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(activity,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
boolean areRequirementsFulfilled(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest,
|
||||
Runnable onLocationDenied) {
|
||||
boolean permissionGranted =
|
||||
SDK_INT < 23 || locationPermission == Permission.GRANTED;
|
||||
boolean locationEnabled = isLocationEnabled(ctx);
|
||||
if (permissionGranted && locationEnabled) return true;
|
||||
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, onLocationDenied);
|
||||
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(ctx, permissionRequest);
|
||||
} else if (!locationEnabled) {
|
||||
showLocationDialog(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void showDenialDialog(Context ctx, Runnable onLocationDenied) {
|
||||
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.permission_location_title)
|
||||
.setMessage(R.string.permission_location_denied_body)
|
||||
.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
|
||||
.setNegativeButton(R.string.cancel, (v, d) ->
|
||||
onLocationDenied.run())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showRationale(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest) {
|
||||
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.permission_location_title)
|
||||
.setMessage(R.string.permission_location_request_body)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) ->
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION))
|
||||
.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothIntroFragment extends Fragment {
|
||||
|
||||
final static String TAG = BluetoothIntroFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private final BluetoothConditionManager conditionManager =
|
||||
new BluetoothConditionManager();
|
||||
private ConnectViaBluetoothViewModel viewModel;
|
||||
|
||||
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
|
||||
registerForActivityResult(new RequestBluetoothDiscoverable(),
|
||||
this::onBluetoothDiscoverable);
|
||||
private final ActivityResultLauncher<String> permissionRequest =
|
||||
registerForActivityResult(new RequestPermission(),
|
||||
this::onPermissionRequestResult);
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
getAndroidComponent(requireContext()).inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(ConnectViaBluetoothViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater
|
||||
.inflate(R.layout.fragment_bluetooth_intro, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
Button startButton = view.findViewById(R.id.startButton);
|
||||
startButton.setOnClickListener(this::onStartClicked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
conditionManager.reset();
|
||||
}
|
||||
|
||||
private void onStartClicked(View v) {
|
||||
if (viewModel.shouldStartFlow()) {
|
||||
// The dialog starts a permission request which comes back as true
|
||||
// if the permission is already granted.
|
||||
// So we can use the request as a generic entry point
|
||||
// to the whole flow.
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPermissionRequestResult(@Nullable Boolean result) {
|
||||
Activity a = requireActivity();
|
||||
// update permission result in BluetoothConnecter
|
||||
conditionManager.onLocationPermissionResult(a, result);
|
||||
// what to do when the user denies granting the location permission
|
||||
Runnable onLocationPermissionDenied = () -> Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.connect_via_bluetooth_no_location_permission,
|
||||
LENGTH_LONG).show();
|
||||
// if requirements are fulfilled, request Bluetooth discoverability
|
||||
if (conditionManager.areRequirementsFulfilled(a, permissionRequest,
|
||||
onLocationPermissionDenied)) {
|
||||
bluetoothDiscoverableRequest.launch(120); // for 2min
|
||||
}
|
||||
}
|
||||
|
||||
private void onBluetoothDiscoverable(@Nullable Boolean result) {
|
||||
if (result != null && result) {
|
||||
viewModel.onBluetoothDiscoverable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothProgressFragment extends Fragment {
|
||||
|
||||
final static String TAG = BluetoothProgressFragment.class.getName();
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater
|
||||
.inflate(R.layout.fragment_bluetooth_progress, container, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showFragment;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ConnectViaBluetoothActivity extends BriarActivity {
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private ConnectViaBluetoothViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void injectActivity(ActivityComponent component) {
|
||||
component.inject(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory)
|
||||
.get(ConnectViaBluetoothViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = requireNonNull(getIntent());
|
||||
int contactId = intent.getIntExtra(CONTACT_ID, -1);
|
||||
if (contactId == -1) throw new IllegalArgumentException("ContactId");
|
||||
viewModel.setContactId(new ContactId(contactId));
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
|
||||
viewModel.getState().observeEvent(this, this::onStateChanged);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
Fragment f = new BluetoothIntroFragment();
|
||||
String tag = BluetoothIntroFragment.TAG;
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragmentContainer, f, tag)
|
||||
.commitNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
viewModel.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onStateChanged(ConnectViaBluetoothState state) {
|
||||
if (state instanceof ConnectViaBluetoothState.Connecting) {
|
||||
Fragment f = new BluetoothProgressFragment();
|
||||
String tag = BluetoothProgressFragment.TAG;
|
||||
showFragment(getSupportFragmentManager(), f, tag, false);
|
||||
} else if (state instanceof ConnectViaBluetoothState.Success) {
|
||||
Toast.makeText(this, R.string.connect_via_bluetooth_success,
|
||||
LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
} else if (state instanceof ConnectViaBluetoothState.Error) {
|
||||
Toast.makeText(this,
|
||||
((ConnectViaBluetoothState.Error) state).errorRes,
|
||||
LENGTH_LONG).show();
|
||||
supportFinishAfterTransition();
|
||||
} else throw new AssertionError();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import org.briarproject.briar.android.viewmodel.ViewModelKey;
|
||||
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.multibindings.IntoMap;
|
||||
|
||||
@Module
|
||||
public abstract class ConnectViaBluetoothModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ConnectViaBluetoothViewModel.class)
|
||||
abstract ViewModel bindContactListViewModel(
|
||||
ConnectViaBluetoothViewModel connectViaBluetoothViewModel);
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
abstract class ConnectViaBluetoothState {
|
||||
|
||||
static class Connecting extends ConnectViaBluetoothState {
|
||||
}
|
||||
|
||||
static class Success extends ConnectViaBluetoothState {
|
||||
}
|
||||
|
||||
static class Error extends ConnectViaBluetoothState {
|
||||
@StringRes
|
||||
final int errorRes;
|
||||
|
||||
Error(@StringRes int errorRes) {
|
||||
this.errorRes = errorRes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
package org.briarproject.briar.android.contact.connect;
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.content.Context;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.connection.ConnectionManager;
|
||||
import org.briarproject.bramble.api.connection.ConnectionRegistry;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.TransactionManager;
|
||||
import org.briarproject.bramble.api.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.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginManager;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
|
||||
@@ -22,22 +21,23 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothState.Connecting;
|
||||
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothState.Success;
|
||||
import org.briarproject.briar.android.viewmodel.DbViewModel;
|
||||
import org.briarproject.briar.android.viewmodel.LiveEvent;
|
||||
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
|
||||
import org.briarproject.briar.android.contact.ContactItem;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
@@ -46,50 +46,48 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
|
||||
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
EventListener {
|
||||
class BluetoothConnecter implements EventListener {
|
||||
|
||||
private final Logger LOG =
|
||||
getLogger(ConnectViaBluetoothViewModel.class.getName());
|
||||
private final Logger LOG = getLogger(BluetoothConnecter.class.getName());
|
||||
|
||||
private final long BT_ACTIVE_TIMEOUT = SECONDS.toMillis(5);
|
||||
|
||||
private enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
private final Application app;
|
||||
private final PluginManager pluginManager;
|
||||
private final Executor ioExecutor;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final ConnectionRegistry connectionRegistry;
|
||||
@Nullable
|
||||
private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
|
||||
private final EventBus eventBus;
|
||||
private final TransportPropertyManager transportPropertyManager;
|
||||
private final ConnectionManager connectionManager;
|
||||
|
||||
@Nullable
|
||||
private volatile BluetoothPlugin bluetoothPlugin;
|
||||
@Nullable
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private ContactId contactId = null;
|
||||
|
||||
private final MutableLiveEvent<ConnectViaBluetoothState> state =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@Inject
|
||||
ConnectViaBluetoothViewModel(
|
||||
Application app,
|
||||
@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager,
|
||||
TransactionManager db,
|
||||
AndroidExecutor androidExecutor,
|
||||
BluetoothConnecter(Application app,
|
||||
PluginManager pluginManager,
|
||||
@IoExecutor Executor ioExecutor,
|
||||
AndroidExecutor androidExecutor,
|
||||
ConnectionRegistry connectionRegistry,
|
||||
EventBus eventBus,
|
||||
TransportPropertyManager transportPropertyManager,
|
||||
ConnectionManager connectionManager) {
|
||||
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
this.app = app;
|
||||
this.pluginManager = pluginManager;
|
||||
this.ioExecutor = ioExecutor;
|
||||
this.androidExecutor = androidExecutor;
|
||||
this.bluetoothPlugin = (BluetoothPlugin) pluginManager.getPlugin(ID);
|
||||
this.connectionRegistry = connectionRegistry;
|
||||
this.eventBus = eventBus;
|
||||
@@ -97,22 +95,20 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
this.connectionManager = connectionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
stopConnecting();
|
||||
boolean isConnectedViaBluetooth(ContactId contactId) {
|
||||
return connectionRegistry.isConnected(contactId, ID);
|
||||
}
|
||||
|
||||
boolean isDiscovering() {
|
||||
return bluetoothPlugin.isDiscovering();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this as soon as it becomes available.
|
||||
*/
|
||||
void setContactId(ContactId contactId) {
|
||||
this.contactId = contactId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this when the using activity or fragment starts.
|
||||
* Call this when the using activity or fragment starts,
|
||||
* because permissions might have changed while it was stopped.
|
||||
*/
|
||||
void reset() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
// When this class is instantiated before we are logged in
|
||||
// (like when returning to a killed activity), bluetoothPlugin would be
|
||||
// null and we consider bluetooth not supported. So reset here.
|
||||
@@ -120,52 +116,94 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
}
|
||||
|
||||
@UiThread
|
||||
boolean shouldStartFlow() {
|
||||
if (isBluetoothNotSupported()) {
|
||||
state.setEvent(new ConnectViaBluetoothState.Error(
|
||||
R.string.bt_plugin_status_inactive));
|
||||
return false;
|
||||
} else if (isConnectedViaBluetooth()) {
|
||||
state.setEvent(new Success());
|
||||
return false;
|
||||
} else if (isDiscovering()) {
|
||||
state.setEvent(new ConnectViaBluetoothState.Error(
|
||||
R.string.connect_via_bluetooth_already_discovering));
|
||||
return false;
|
||||
void onLocationPermissionResult(Activity activity,
|
||||
@Nullable Boolean result) {
|
||||
if (result != null && result) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(activity,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isBluetoothNotSupported() {
|
||||
boolean isBluetoothNotSupported() {
|
||||
return bt == null || bluetoothPlugin == null;
|
||||
}
|
||||
|
||||
private boolean isDiscovering() {
|
||||
// we should not be calling this if isBluetoothNotSupported() is true
|
||||
return requireNonNull(bluetoothPlugin).isDiscovering();
|
||||
boolean areRequirementsFulfilled(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest,
|
||||
Runnable onLocationDenied) {
|
||||
boolean permissionGranted =
|
||||
SDK_INT < 23 || locationPermission == Permission.GRANTED;
|
||||
boolean locationEnabled = isLocationEnabled(ctx);
|
||||
if (permissionGranted && locationEnabled) return true;
|
||||
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, onLocationDenied);
|
||||
} else if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(ctx, permissionRequest);
|
||||
} else if (!locationEnabled) {
|
||||
showLocationDialog(ctx);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isConnectedViaBluetooth() {
|
||||
return connectionRegistry.isConnected(requireNonNull(contactId), ID);
|
||||
private void showDenialDialog(Context ctx, Runnable onLocationDenied) {
|
||||
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.permission_location_title)
|
||||
.setMessage(R.string.permission_location_denied_body)
|
||||
.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
|
||||
.setNegativeButton(R.string.cancel, (v, d) ->
|
||||
onLocationDenied.run())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showRationale(Context ctx,
|
||||
ActivityResultLauncher<String> permissionRequest) {
|
||||
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.permission_location_title)
|
||||
.setMessage(R.string.permission_location_request_body)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) ->
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION))
|
||||
.show();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void onBluetoothDiscoverable() {
|
||||
ContactId contactId = requireNonNull(this.contactId);
|
||||
BluetoothPlugin bluetoothPlugin = requireNonNull(this.bluetoothPlugin);
|
||||
void onBluetoothDiscoverable(ContactItem contact) {
|
||||
contactId = contact.getContact().getId();
|
||||
connect();
|
||||
}
|
||||
|
||||
state.setEvent(new Connecting());
|
||||
@UiThread
|
||||
@Override
|
||||
public void eventOccurred(@NonNull Event e) {
|
||||
if (e instanceof ConnectionOpenedEvent) {
|
||||
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
|
||||
if (c.getContactId().equals(contactId) && c.isIncoming() &&
|
||||
c.getTransportId() == ID) {
|
||||
if (bluetoothPlugin != null) {
|
||||
bluetoothPlugin.stopDiscoverAndConnect();
|
||||
}
|
||||
LOG.info("Contact connected to us");
|
||||
showToast(R.string.toast_connect_via_bluetooth_success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
bluetoothPlugin.disablePolling();
|
||||
pluginManager.setPluginEnabled(ID, true);
|
||||
|
||||
ioExecutor.execute(() -> {
|
||||
try {
|
||||
if (!waitForBluetoothActive()) {
|
||||
state.postEvent(new ConnectViaBluetoothState.Error(
|
||||
R.string.bt_plugin_status_inactive));
|
||||
showToast(R.string.bt_plugin_status_inactive);
|
||||
LOG.warning("Bluetooth plugin didn't become active");
|
||||
return;
|
||||
}
|
||||
showToast(R.string.toast_connect_via_bluetooth_start);
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
String uuid = null;
|
||||
@@ -188,7 +226,7 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
LOG.info("Could connect, handling connection");
|
||||
connectionManager
|
||||
.manageOutgoingConnection(contactId, ID, conn);
|
||||
state.postEvent(new Success());
|
||||
showToast(R.string.toast_connect_via_bluetooth_success);
|
||||
}
|
||||
} finally {
|
||||
eventBus.removeListener(this);
|
||||
@@ -199,23 +237,8 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void eventOccurred(@NonNull Event e) {
|
||||
if (e instanceof ConnectionOpenedEvent) {
|
||||
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
|
||||
if (c.getContactId().equals(contactId) && c.isIncoming() &&
|
||||
c.getTransportId() == ID) {
|
||||
stopConnecting();
|
||||
LOG.info("Contact connected to us");
|
||||
state.postEvent(new Success());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
private boolean waitForBluetoothActive() {
|
||||
BluetoothPlugin bluetoothPlugin = requireNonNull(this.bluetoothPlugin);
|
||||
long left = BT_ACTIVE_TIMEOUT;
|
||||
final long sleep = 250;
|
||||
try {
|
||||
@@ -241,9 +264,9 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
final long sleep = 250;
|
||||
try {
|
||||
while (left > 0) {
|
||||
if (isConnectedViaBluetooth()) {
|
||||
if (isConnectedViaBluetooth(contactId)) {
|
||||
LOG.info("Failed to connect, but contact connected");
|
||||
// no success state needed here, as it gets shown when
|
||||
// no Toast needed here, as it gets shown when
|
||||
// ConnectionOpenedEvent is received
|
||||
return;
|
||||
}
|
||||
@@ -254,19 +277,13 @@ class ConnectViaBluetoothViewModel extends DbViewModel implements
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
LOG.warning("Failed to connect");
|
||||
state.postEvent(new ConnectViaBluetoothState.Error(
|
||||
R.string.connect_via_bluetooth_error));
|
||||
showToast(R.string.toast_connect_via_bluetooth_error);
|
||||
}
|
||||
|
||||
private void stopConnecting() {
|
||||
BluetoothPlugin bluetoothPlugin = this.bluetoothPlugin;
|
||||
if (bluetoothPlugin != null) {
|
||||
bluetoothPlugin.stopDiscoverAndConnect();
|
||||
}
|
||||
}
|
||||
|
||||
LiveEvent<ConnectViaBluetoothState> getState() {
|
||||
return state;
|
||||
private void showToast(@StringRes int res) {
|
||||
androidExecutor.runOnUiThread(() ->
|
||||
Toast.makeText(app, res, Toast.LENGTH_LONG).show()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package org.briarproject.briar.android.conversation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
import org.briarproject.briar.android.contact.ContactItem;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.DialogInterface.BUTTON_POSITIVE;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class BluetoothConnecterDialogFragment extends DialogFragment {
|
||||
|
||||
final static String TAG = BluetoothConnecterDialogFragment.class.getName();
|
||||
|
||||
@Inject
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private ConversationViewModel viewModel;
|
||||
private BluetoothConnecter bluetoothConnecter;
|
||||
|
||||
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
|
||||
registerForActivityResult(new RequestBluetoothDiscoverable(),
|
||||
this::onBluetoothDiscoverable);
|
||||
private final ActivityResultLauncher<String> permissionRequest =
|
||||
registerForActivityResult(new RequestPermission(),
|
||||
this::onPermissionRequestResult);
|
||||
|
||||
@Override
|
||||
public void onAttach(Context ctx) {
|
||||
super.onAttach(ctx);
|
||||
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
|
||||
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
|
||||
.get(ConversationViewModel.class);
|
||||
bluetoothConnecter = viewModel.getBluetoothConnecter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
Context ctx = requireContext();
|
||||
return new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
|
||||
.setTitle(R.string.dialog_title_connect_via_bluetooth)
|
||||
.setMessage(R.string.dialog_message_connect_via_bluetooth)
|
||||
// actual listener gets set in onResume()
|
||||
.setPositiveButton(R.string.start, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setCancelable(false) // keep it open until dismissed
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
bluetoothConnecter.reset();
|
||||
if (bluetoothConnecter.isBluetoothNotSupported()) {
|
||||
showToast(R.string.toast_connect_via_bluetooth_error);
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
// MenuItem only gets enabled after contactItem has loaded
|
||||
ContactItem contact =
|
||||
requireNonNull(viewModel.getContactItem().getValue());
|
||||
ContactId contactId = contact.getContact().getId();
|
||||
if (bluetoothConnecter.isConnectedViaBluetooth(contactId)) {
|
||||
showToast(R.string.toast_connect_via_bluetooth_success);
|
||||
dismiss();
|
||||
return;
|
||||
}
|
||||
if (bluetoothConnecter.isDiscovering()) {
|
||||
showToast(R.string.toast_connect_via_bluetooth_already_discovering);
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Set the click listener for the START button here
|
||||
// to prevent it from automatically dismissing the dialog.
|
||||
// The dialog is shown in onStart(), so we set the listener here later.
|
||||
AlertDialog dialog = (AlertDialog) getDialog();
|
||||
Button positiveButton = dialog.getButton(BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(this::onStartClicked);
|
||||
}
|
||||
|
||||
private void onStartClicked(View v) {
|
||||
// The dialog starts a permission request which comes back as true
|
||||
// if the permission is already granted.
|
||||
// So we can use the request as a generic entry point to the whole flow.
|
||||
permissionRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void onPermissionRequestResult(@Nullable Boolean result) {
|
||||
Activity a = requireActivity();
|
||||
// update permission result in BluetoothConnecter
|
||||
bluetoothConnecter.onLocationPermissionResult(a, result);
|
||||
// what to do when the user denies granting the location permission
|
||||
Runnable onLocationPermissionDenied = () -> {
|
||||
Toast.makeText(requireContext(),
|
||||
R.string.toast_connect_via_bluetooth_no_location_permission,
|
||||
LENGTH_LONG).show();
|
||||
dismiss();
|
||||
};
|
||||
// if requirements are fulfilled, request Bluetooth discoverability
|
||||
if (bluetoothConnecter.areRequirementsFulfilled(a, permissionRequest,
|
||||
onLocationPermissionDenied)) {
|
||||
bluetoothDiscoverableRequest.launch(120); // for 2min
|
||||
}
|
||||
}
|
||||
|
||||
private void onBluetoothDiscoverable(@Nullable Boolean result) {
|
||||
if (result != null && result) {
|
||||
// MenuItem only gets enabled after contactItem has loaded
|
||||
ContactItem contact =
|
||||
requireNonNull(viewModel.getContactItem().getValue());
|
||||
bluetoothConnecter.onBluetoothDiscoverable(contact);
|
||||
dismiss();
|
||||
} else {
|
||||
showToast(R.string.toast_connect_via_bluetooth_not_discoverable);
|
||||
}
|
||||
}
|
||||
|
||||
private void showToast(@StringRes int stringRes) {
|
||||
Toast.makeText(requireContext(), stringRes, LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,7 +48,6 @@ import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.attachment.AttachmentItem;
|
||||
import org.briarproject.briar.android.attachment.AttachmentRetriever;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothActivity;
|
||||
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
|
||||
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
@@ -105,6 +104,7 @@ import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -379,8 +379,11 @@ public class ConversationActivity extends BriarActivity
|
||||
this::showIntroductionOnboarding);
|
||||
}
|
||||
});
|
||||
if (!featureFlags.shouldEnableConnectViaBluetooth()) {
|
||||
menu.findItem(R.id.action_connect_via_bluetooth).setVisible(false);
|
||||
}
|
||||
// Transfer Data feature only supported on API 19+
|
||||
if (SDK_INT >= 19) {
|
||||
if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
|
||||
menu.findItem(R.id.action_transfer_data).setVisible(true);
|
||||
}
|
||||
// enable alias and bluetooth action once available
|
||||
@@ -419,9 +422,9 @@ public class ConversationActivity extends BriarActivity
|
||||
onAutoDeleteTimerNoticeClicked();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_connect_via_bluetooth) {
|
||||
Intent intent = new Intent(this, ConnectViaBluetoothActivity.class);
|
||||
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||
startActivity(intent);
|
||||
FragmentManager fm = getSupportFragmentManager();
|
||||
new BluetoothConnecterDialogFragment().show(fm,
|
||||
BluetoothConnecterDialogFragment.TAG);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_transfer_data) {
|
||||
Intent intent = new Intent(this, RemovableDriveActivity.class);
|
||||
|
||||
@@ -101,6 +101,7 @@ public class ConversationViewModel extends DbViewModel
|
||||
private final AttachmentCreator attachmentCreator;
|
||||
private final AutoDeleteManager autoDeleteManager;
|
||||
private final ConversationManager conversationManager;
|
||||
private final BluetoothConnecter bluetoothConnecter;
|
||||
|
||||
@Nullable
|
||||
private ContactId contactId = null;
|
||||
@@ -139,7 +140,8 @@ public class ConversationViewModel extends DbViewModel
|
||||
AttachmentRetriever attachmentRetriever,
|
||||
AttachmentCreator attachmentCreator,
|
||||
AutoDeleteManager autoDeleteManager,
|
||||
ConversationManager conversationManager) {
|
||||
ConversationManager conversationManager,
|
||||
BluetoothConnecter bluetoothConnecter) {
|
||||
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
|
||||
this.db = db;
|
||||
this.eventBus = eventBus;
|
||||
@@ -152,6 +154,7 @@ public class ConversationViewModel extends DbViewModel
|
||||
this.attachmentCreator = attachmentCreator;
|
||||
this.autoDeleteManager = autoDeleteManager;
|
||||
this.conversationManager = conversationManager;
|
||||
this.bluetoothConnecter = bluetoothConnecter;
|
||||
messagingGroupId = map(contactItem, c ->
|
||||
messagingManager.getContactGroup(c.getContact()).getId());
|
||||
eventBus.addListener(this);
|
||||
@@ -411,6 +414,10 @@ public class ConversationViewModel extends DbViewModel
|
||||
return attachmentRetriever;
|
||||
}
|
||||
|
||||
BluetoothConnecter getBluetoothConnecter() {
|
||||
return bluetoothConnecter;
|
||||
}
|
||||
|
||||
LiveData<ContactItem> getContactItem() {
|
||||
return contactItem;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -21,11 +22,9 @@ import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.widget.ImageViewCompat;
|
||||
import androidx.core.widget.NestedScrollView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import static android.view.View.FOCUS_DOWN;
|
||||
import static android.view.View.GONE;
|
||||
|
||||
/**
|
||||
* A fragment to be used at the end of a user flow
|
||||
@@ -59,7 +58,7 @@ public class FinalFragment extends Fragment {
|
||||
return f;
|
||||
}
|
||||
|
||||
private NestedScrollView scrollView;
|
||||
private ScrollView scrollView;
|
||||
protected Button buttonView;
|
||||
|
||||
@Nullable
|
||||
@@ -70,7 +69,7 @@ public class FinalFragment extends Fragment {
|
||||
View v = inflater
|
||||
.inflate(R.layout.fragment_final, container, false);
|
||||
|
||||
scrollView = (NestedScrollView) v;
|
||||
scrollView = (ScrollView) v;
|
||||
ImageView iconView = v.findViewById(R.id.iconView);
|
||||
TextView titleView = v.findViewById(R.id.titleView);
|
||||
TextView textView = v.findViewById(R.id.textView);
|
||||
@@ -82,12 +81,7 @@ public class FinalFragment extends Fragment {
|
||||
int color = getResources().getColor(args.getInt(ARG_ICON_TINT));
|
||||
ColorStateList tint = ColorStateList.valueOf(color);
|
||||
ImageViewCompat.setImageTintList(iconView, tint);
|
||||
int textRes = args.getInt(ARG_TEXT);
|
||||
if (textRes == 0) {
|
||||
textView.setVisibility(GONE);
|
||||
} else {
|
||||
textView.setText(textRes);
|
||||
}
|
||||
textView.setText(args.getInt(ARG_TEXT));
|
||||
|
||||
buttonView.setOnClickListener(view -> onBackButtonPressed());
|
||||
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.wifi.WifiManager;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
|
||||
/**
|
||||
* Abstract base class for the ConditionManagers that ensure that the conditions
|
||||
* to open a hotspot are fulfilled. There are different extensions of this for
|
||||
* API levels lower than 29 and 29+.
|
||||
*/
|
||||
abstract class AbstractConditionManager {
|
||||
|
||||
enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
protected final Consumer<Boolean> permissionUpdateCallback;
|
||||
protected FragmentActivity ctx;
|
||||
WifiManager wifiManager;
|
||||
|
||||
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
|
||||
this.permissionUpdateCallback = permissionUpdateCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a FragmentActivity context here during `onCreateView()`.
|
||||
*/
|
||||
void init(FragmentActivity ctx) {
|
||||
this.ctx = ctx;
|
||||
this.wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this during onStart() in the fragment where the ConditionManager
|
||||
* is used.
|
||||
*/
|
||||
abstract void onStart();
|
||||
|
||||
/**
|
||||
* Check if all required conditions are met such that the hotspot can be
|
||||
* started. If any precondition is not met yet, bring up relevant dialogs
|
||||
* asking the user to grant relevant permissions or take relevant actions.
|
||||
*
|
||||
* @return true if conditions are fulfilled and flow can continue.
|
||||
*/
|
||||
abstract boolean checkAndRequestConditions();
|
||||
|
||||
void showDenialDialog(FragmentActivity ctx,
|
||||
@StringRes int title, @StringRes int body,
|
||||
DialogInterface.OnClickListener onOkClicked, Runnable onDismiss) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setPositiveButton(R.string.ok, onOkClicked);
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> ctx.supportFinishAfterTransition());
|
||||
builder.setOnDismissListener(dialog -> onDismiss.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
void showRationale(Context ctx, @StringRes int title,
|
||||
@StringRes int body, Runnable onContinueClicked,
|
||||
Runnable onDismiss) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
(dialog, which) -> onContinueClicked.run());
|
||||
builder.setOnDismissListener(dialog -> onDismiss.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
@@ -44,7 +43,6 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
|
||||
protected Button stopButton;
|
||||
protected Button connectedButton;
|
||||
protected TextView connectedView;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -87,9 +85,6 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
finishAfterTransition(requireActivity());
|
||||
});
|
||||
connectedButton = view.findViewById(R.id.connectedButton);
|
||||
connectedView = view.findViewById(R.id.connectedView);
|
||||
viewModel.getPeersConnectedEvent()
|
||||
.observe(getViewLifecycleOwner(), this::onPeerConnected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,13 +126,4 @@ public abstract class AbstractTabsFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onPeerConnected(int peers) {
|
||||
if (peers == 0) {
|
||||
connectedView.setText(R.string.hotspot_no_peers_connected);
|
||||
} else {
|
||||
connectedView.setText(getResources().getQuantityString(
|
||||
R.plurals.hotspot_peers_connected, peers, peers));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,171 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels < 29.
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled.
|
||||
* <p>
|
||||
* Be sure to call {@link #onRequestPermissionResult(Boolean)} and
|
||||
* {@link #onRequestWifiEnabledResult()} when you get the
|
||||
* {@link ActivityResult}.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
class ConditionManager extends AbstractConditionManager {
|
||||
@NotNullByDefault
|
||||
class ConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager.class.getName());
|
||||
private enum Permission {
|
||||
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
|
||||
}
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
private Permission wifiSetting = Permission.SHOW_RATIONALE;
|
||||
|
||||
private final FragmentActivity ctx;
|
||||
private final WifiManager wifiManager;
|
||||
private final ActivityResultLauncher<String> locationRequest;
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled()));
|
||||
ConditionManager(FragmentActivity ctx,
|
||||
ActivityResultLauncher<String> locationRequest,
|
||||
ActivityResultLauncher<Intent> wifiRequest) {
|
||||
this.ctx = ctx;
|
||||
this.wifiManager = (WifiManager) ctx.getApplicationContext()
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
this.locationRequest = locationRequest;
|
||||
this.wifiRequest = wifiRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStart() {
|
||||
// nothing to do here
|
||||
/**
|
||||
* Call this to reset state when UI starts,
|
||||
* because state might have changed.
|
||||
*/
|
||||
void resetPermissions() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
wifiSetting = Permission.SHOW_RATIONALE;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(String.format("areEssentialPermissionsGranted(): " +
|
||||
"wifiManager.isWifiEnabled()? %b",
|
||||
wifiManager.isWifiEnabled()));
|
||||
}
|
||||
return wifiManager.isWifiEnabled();
|
||||
/**
|
||||
* This makes a request for location permission.
|
||||
* If {@link #checkAndRequestConditions()} returns true, you can continue.
|
||||
*/
|
||||
void startConditionChecks() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return true if conditions are fulfilled and flow can continue.
|
||||
*/
|
||||
boolean checkAndRequestConditions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
// Try enabling the Wifi and return true if that seems to have been
|
||||
// successful, i.e. "Wifi is either already in the requested state, or
|
||||
// in progress toward the requested state".
|
||||
if (wifiManager.setWifiEnabled(true)) {
|
||||
LOG.info("Enabled wifi");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wifi is not enabled and we can't seem to enable it, so ask the user
|
||||
// to enable it for us.
|
||||
showRationale(ctx, R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
// If an essential permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_denied_body,
|
||||
getGoToSettingsListener(ctx));
|
||||
return false;
|
||||
}
|
||||
if (wifiSetting == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_denied_body,
|
||||
(d, w) -> requestEnableWiFi());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we show the rationale for location permission or Wi-Fi?
|
||||
if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_request_body,
|
||||
this::requestPermissions);
|
||||
} else if (wifiSetting == Permission.SHOW_RATIONALE) {
|
||||
showRationale(R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(ctx,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
void onRequestWifiEnabledResult() {
|
||||
wifiSetting = wifiManager.isWifiEnabled() ? Permission.GRANTED :
|
||||
Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
if (SDK_INT < 29) {
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
//noinspection deprecation
|
||||
return wifiManager.setWifiEnabled(true);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return locationPermission == Permission.GRANTED
|
||||
&& wifiManager.isWifiEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void showDenialDialog(@StringRes int title, @StringRes int body,
|
||||
OnClickListener onOkClicked) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setPositiveButton(R.string.ok, onOkClicked);
|
||||
builder.setNegativeButton(R.string.cancel,
|
||||
(dialog, which) -> ctx.supportFinishAfterTransition());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void showRationale(@StringRes int title, @StringRes int body,
|
||||
Runnable onContinueClicked) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(body);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
(dialog, which) -> onContinueClicked.run());
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS));
|
||||
Intent i = SDK_INT < 29 ?
|
||||
new Intent(Settings.ACTION_WIFI_SETTINGS) :
|
||||
new Intent(Settings.Panel.ACTION_WIFI);
|
||||
wifiRequest.launch(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.provider.Settings;
|
||||
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import androidx.activity.result.ActivityResultCaller;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
|
||||
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
|
||||
|
||||
/**
|
||||
* This class ensures that the conditions to open a hotspot are fulfilled on
|
||||
* API levels >= 29.
|
||||
* <p>
|
||||
* As soon as {@link #checkAndRequestConditions()} returns true,
|
||||
* all conditions are fulfilled.
|
||||
*/
|
||||
@RequiresApi(29)
|
||||
class ConditionManager29 extends AbstractConditionManager {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(ConditionManager29.class.getName());
|
||||
|
||||
private Permission locationPermission = Permission.UNKNOWN;
|
||||
|
||||
private final ActivityResultLauncher<String> locationRequest;
|
||||
private final ActivityResultLauncher<Intent> wifiRequest;
|
||||
|
||||
ConditionManager29(ActivityResultCaller arc,
|
||||
Consumer<Boolean> permissionUpdateCallback) {
|
||||
super(permissionUpdateCallback);
|
||||
locationRequest = arc.registerForActivityResult(
|
||||
new RequestPermission(), granted -> {
|
||||
onRequestPermissionResult(granted);
|
||||
permissionUpdateCallback.accept(TRUE.equals(granted));
|
||||
});
|
||||
wifiRequest = arc.registerForActivityResult(
|
||||
new StartActivityForResult(),
|
||||
result -> permissionUpdateCallback
|
||||
.accept(wifiManager.isWifiEnabled())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onStart() {
|
||||
locationPermission = Permission.UNKNOWN;
|
||||
}
|
||||
|
||||
private boolean areEssentialPermissionsGranted() {
|
||||
boolean isWifiEnabled = wifiManager.isWifiEnabled();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info(String.format("areEssentialPermissionsGranted(): " +
|
||||
"locationPermission? %s, " +
|
||||
"wifiManager.isWifiEnabled()? %b",
|
||||
locationPermission, isWifiEnabled));
|
||||
}
|
||||
return locationPermission == Permission.GRANTED && isWifiEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean checkAndRequestConditions() {
|
||||
if (areEssentialPermissionsGranted()) return true;
|
||||
|
||||
if (locationPermission == Permission.UNKNOWN) {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the location permission has been permanently denied, ask the
|
||||
// user to change the setting
|
||||
if (locationPermission == Permission.PERMANENTLY_DENIED) {
|
||||
showDenialDialog(ctx, R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_denied_body,
|
||||
getGoToSettingsListener(ctx),
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we show the rationale for location permission?
|
||||
if (locationPermission == Permission.SHOW_RATIONALE) {
|
||||
showRationale(ctx, R.string.permission_location_title,
|
||||
R.string.permission_hotspot_location_request_body,
|
||||
this::requestPermissions,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// If Wifi is not enabled, we show the rationale for enabling Wifi?
|
||||
if (!wifiManager.isWifiEnabled()) {
|
||||
showRationale(ctx, R.string.wifi_settings_title,
|
||||
R.string.wifi_settings_request_enable_body,
|
||||
this::requestEnableWiFi,
|
||||
() -> permissionUpdateCallback.accept(false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// we shouldn't usually reach this point, but if we do, return false
|
||||
// anyway to force a recheck. Maybe some condition changed in the
|
||||
// meantime.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onRequestPermissionResult(@Nullable Boolean granted) {
|
||||
if (granted != null && granted) {
|
||||
locationPermission = Permission.GRANTED;
|
||||
} else if (shouldShowRequestPermissionRationale(ctx,
|
||||
ACCESS_FINE_LOCATION)) {
|
||||
locationPermission = Permission.SHOW_RATIONALE;
|
||||
} else {
|
||||
locationPermission = Permission.PERMANENTLY_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
private void requestPermissions() {
|
||||
locationRequest.launch(ACCESS_FINE_LOCATION);
|
||||
}
|
||||
|
||||
private void requestEnableWiFi() {
|
||||
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,7 +15,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.fragment.BaseFragment;
|
||||
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,6 +22,7 @@ import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -33,10 +33,12 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
|
||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
||||
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
|
||||
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
|
||||
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class FallbackFragment extends BaseFragment {
|
||||
@@ -48,7 +50,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
|
||||
private HotspotViewModel viewModel;
|
||||
private final ActivityResultLauncher<String> launcher =
|
||||
registerForActivityResult(new CreateDocumentAdvanced(),
|
||||
registerForActivityResult(new CreateDocument(),
|
||||
this::onDocumentCreated);
|
||||
private Button fallbackButton;
|
||||
private ProgressBar progressBar;
|
||||
@@ -73,7 +75,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
@Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
return inflater
|
||||
.inflate(R.layout.fragment_hotspot_fallback, container, false);
|
||||
.inflate(R.layout.fragment_hotspot_save_apk, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,7 +92,8 @@ public class FallbackFragment extends BaseFragment {
|
||||
if (SDK_INT >= 19) launcher.launch(getApkFileName());
|
||||
else viewModel.exportApk();
|
||||
});
|
||||
viewModel.getSavedApkToUri().observeEvent(this, this::shareUri);
|
||||
viewModel.getSavedApkToUri()
|
||||
.observeEvent(this, uri -> shareUri(this, uri));
|
||||
}
|
||||
|
||||
private void onDocumentCreated(@Nullable Uri uri) {
|
||||
@@ -104,12 +107,12 @@ public class FallbackFragment extends BaseFragment {
|
||||
progressBar.setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
void shareUri(Uri uri) {
|
||||
static void shareUri(Fragment fragment, Uri uri) {
|
||||
Intent i = new Intent(ACTION_SEND);
|
||||
i.putExtra(EXTRA_STREAM, uri);
|
||||
i.setType("*/*"); // gives us all sharing options
|
||||
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
|
||||
Context ctx = requireContext();
|
||||
Context ctx = fragment.requireContext();
|
||||
if (SDK_INT <= 19) {
|
||||
// Workaround for Android bug:
|
||||
// ctx.grantUriPermission also needed for Android 4
|
||||
@@ -121,7 +124,7 @@ public class FallbackFragment extends BaseFragment {
|
||||
FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(i, null));
|
||||
fragment.startActivity(Intent.createChooser(i, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -59,13 +59,12 @@ public class HotspotActivity extends BriarActivity
|
||||
// check if fragment is already added
|
||||
// to not lose state on configuration changes
|
||||
if (fm.findFragmentByTag(tag) == null) {
|
||||
if (started.wasNotYetConsumed()) {
|
||||
started.consume();
|
||||
if (!started.consume()) {
|
||||
showFragment(fm, new HotspotFragment(), tag);
|
||||
}
|
||||
}
|
||||
} else if (hotspotState instanceof HotspotError) {
|
||||
HotspotError error = (HotspotError) hotspotState;
|
||||
HotspotError error = ((HotspotError) hotspotState);
|
||||
showErrorFragment(error.getError());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,8 +3,12 @@ package org.briarproject.briar.android.hotspot;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -21,6 +25,8 @@ public class HotspotFragment extends AbstractTabsFragment {
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
connectedButton.setOnClickListener(v -> showNextFragment());
|
||||
viewModel.getPeerConnectedEvent().observeEvent(getViewLifecycleOwner(),
|
||||
this::onPeerConnected);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,6 +39,17 @@ public class HotspotFragment extends AbstractTabsFragment {
|
||||
return QrHotspotFragment.newInstance(true);
|
||||
}
|
||||
|
||||
private void onPeerConnected(boolean connected) {
|
||||
if (!connected) return;
|
||||
new BriarSnackbarBuilder()
|
||||
.setAction(R.string.hotspot_peer_connected_action, v ->
|
||||
showNextFragment())
|
||||
.make(connectedButton, R.string.hotspot_peer_connected,
|
||||
Snackbar.LENGTH_LONG)
|
||||
.setAnchorView(connectedButton)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showNextFragment() {
|
||||
Fragment f = new WebsiteFragment();
|
||||
String tag = WebsiteFragment.TAG;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
@@ -9,7 +10,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
@@ -20,14 +20,15 @@ import org.briarproject.briar.R;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.view.View.FOCUS_DOWN;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static androidx.transition.TransitionManager.beginDelayedTransition;
|
||||
@@ -44,15 +45,22 @@ public class HotspotIntroFragment extends Fragment {
|
||||
ViewModelProvider.Factory viewModelFactory;
|
||||
|
||||
private HotspotViewModel viewModel;
|
||||
private ConditionManager conditionManager;
|
||||
|
||||
private Button startButton;
|
||||
private ProgressBar progressBar;
|
||||
private TextView progressTextView;
|
||||
private ScrollView scrollView;
|
||||
|
||||
private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
|
||||
new ConditionManager(this, this::onPermissionUpdate) :
|
||||
new ConditionManager29(this, this::onPermissionUpdate);
|
||||
private final ActivityResultLauncher<String> locationRequest =
|
||||
registerForActivityResult(new RequestPermission(), granted -> {
|
||||
conditionManager.onRequestPermissionResult(granted);
|
||||
startHotspot();
|
||||
});
|
||||
private final ActivityResultLauncher<Intent> wifiRequest =
|
||||
registerForActivityResult(new StartActivityForResult(), result -> {
|
||||
conditionManager.onRequestWifiEnabledResult();
|
||||
startHotspot();
|
||||
});
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
@@ -61,6 +69,8 @@ public class HotspotIntroFragment extends Fragment {
|
||||
getAndroidComponent(activity).inject(this);
|
||||
viewModel = new ViewModelProvider(activity, viewModelFactory)
|
||||
.get(HotspotViewModel.class);
|
||||
conditionManager =
|
||||
new ConditionManager(activity, locationRequest, wifiRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,11 +83,11 @@ public class HotspotIntroFragment extends Fragment {
|
||||
startButton = v.findViewById(R.id.startButton);
|
||||
progressBar = v.findViewById(R.id.progressBar);
|
||||
progressTextView = v.findViewById(R.id.progressTextView);
|
||||
scrollView = v.findViewById(R.id.scrollView);
|
||||
|
||||
startButton.setOnClickListener(this::onButtonClick);
|
||||
|
||||
conditionManager.init(requireActivity());
|
||||
startButton.setOnClickListener(button -> {
|
||||
startButton.setEnabled(false);
|
||||
conditionManager.startConditionChecks();
|
||||
});
|
||||
|
||||
return v;
|
||||
}
|
||||
@@ -85,17 +95,11 @@ public class HotspotIntroFragment extends Fragment {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
conditionManager.onStart();
|
||||
// Scroll down in case the screen is small, so the button is visible
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
conditionManager.resetPermissions();
|
||||
}
|
||||
|
||||
private void onButtonClick(View view) {
|
||||
startButton.setEnabled(false);
|
||||
startHotspotIfConditionsFulfilled();
|
||||
}
|
||||
|
||||
private void startHotspotIfConditionsFulfilled() {
|
||||
private void startHotspot() {
|
||||
startButton.setEnabled(true);
|
||||
if (conditionManager.checkAndRequestConditions()) {
|
||||
showInstallWarningIfNeeded();
|
||||
beginDelayedTransition((ViewGroup) requireView());
|
||||
@@ -106,13 +110,6 @@ public class HotspotIntroFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void onPermissionUpdate(boolean recheckPermissions) {
|
||||
startButton.setEnabled(true);
|
||||
if (recheckPermissions) {
|
||||
startHotspotIfConditionsFulfilled();
|
||||
}
|
||||
}
|
||||
|
||||
private void showInstallWarningIfNeeded() {
|
||||
Context ctx = requireContext();
|
||||
ApplicationInfo applicationInfo;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -11,7 +10,6 @@ import android.net.wifi.p2p.WifiP2pManager;
|
||||
import android.net.wifi.p2p.WifiP2pManager.ActionListener;
|
||||
import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
@@ -36,7 +34,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import static android.content.Context.POWER_SERVICE;
|
||||
import static android.content.Context.WIFI_P2P_SERVICE;
|
||||
import static android.content.Context.WIFI_SERVICE;
|
||||
import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
|
||||
@@ -47,35 +44,30 @@ import static android.net.wifi.p2p.WifiP2pManager.ERROR;
|
||||
import static android.net.wifi.p2p.WifiP2pManager.NO_SERVICE_REQUESTS;
|
||||
import static android.net.wifi.p2p.WifiP2pManager.P2P_UNSUPPORTED;
|
||||
import static android.os.Build.VERSION.SDK_INT;
|
||||
import static android.os.PowerManager.FULL_WAKE_LOCK;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
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.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
||||
import static org.briarproject.briar.android.util.UiUtils.handleException;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
class HotspotManager {
|
||||
class HotspotManager implements ActionListener {
|
||||
|
||||
interface HotspotListener {
|
||||
@UiThread
|
||||
void onStartingHotspot();
|
||||
|
||||
@IoExecutor
|
||||
void onHotspotStarted(NetworkConfig networkConfig);
|
||||
|
||||
@UiThread
|
||||
void onPeersUpdated(int peers);
|
||||
void onDeviceConnected();
|
||||
|
||||
void onHotspotStopped();
|
||||
|
||||
@UiThread
|
||||
void onHotspotError(String error);
|
||||
}
|
||||
|
||||
private static final Logger LOG = getLogger(HotspotManager.class.getName());
|
||||
|
||||
private static final int MAX_FRAMEWORK_ATTEMPTS = 5;
|
||||
private static final int MAX_GROUP_INFO_ATTEMPTS = 5;
|
||||
private static final int RETRY_DELAY_MILLIS = 1000;
|
||||
private static final String HOTSPOT_NAMESPACE = "hotspot";
|
||||
@@ -92,17 +84,14 @@ class HotspotManager {
|
||||
private final SecureRandom random;
|
||||
private final WifiManager wifiManager;
|
||||
private final WifiP2pManager wifiP2pManager;
|
||||
private final PowerManager powerManager;
|
||||
private final Handler handler;
|
||||
private final String lockTag;
|
||||
|
||||
private HotspotListener listener;
|
||||
private WifiManager.WifiLock wifiLock;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private WifiP2pManager.Channel channel;
|
||||
@Nullable
|
||||
@RequiresApi(29)
|
||||
private volatile NetworkConfig savedNetworkConfig = null;
|
||||
private volatile NetworkConfig savedNetworkConfig;
|
||||
|
||||
@Inject
|
||||
HotspotManager(Application ctx,
|
||||
@@ -121,7 +110,6 @@ class HotspotManager {
|
||||
.getSystemService(WIFI_SERVICE);
|
||||
wifiP2pManager =
|
||||
(WifiP2pManager) ctx.getSystemService(WIFI_P2P_SERVICE);
|
||||
powerManager = (PowerManager) ctx.getSystemService(POWER_SERVICE);
|
||||
handler = new Handler(ctx.getMainLooper());
|
||||
lockTag = ctx.getPackageName() + ":app-sharing-hotspot";
|
||||
}
|
||||
@@ -138,106 +126,30 @@ class HotspotManager {
|
||||
return;
|
||||
}
|
||||
listener.onStartingHotspot();
|
||||
acquireLocks();
|
||||
startWifiP2pFramework(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* As soon as Wifi is enabled, we try starting the WifiP2p framework.
|
||||
* If Wifi has just been enabled, it is possible that will fail. If that
|
||||
* happens we try again for MAX_FRAMEWORK_ATTEMPTS times after a delay of
|
||||
* RETRY_DELAY_MILLIS after each attempt.
|
||||
* <p>
|
||||
* Rationale: it can take a few milliseconds for WifiP2p to become available
|
||||
* after enabling Wifi. Depending on the API level it is possible to check this
|
||||
* using {@link WifiP2pManager#requestP2pState} or register a BroadcastReceiver
|
||||
* on the WIFI_P2P_STATE_CHANGED_ACTION to get notified when WifiP2p is really
|
||||
* available. Trying to implement a solution that works reliably using these
|
||||
* checks turned out to be a long rabbit-hole with lots of corner cases and
|
||||
* workarounds for specific situations.
|
||||
* Instead we now rely on this trial-and-error approach of just starting
|
||||
* the framework and retrying if it fails.
|
||||
* <p>
|
||||
* We'll realize that the framework is busy when the ActionListener passed
|
||||
* to {@link WifiP2pManager#createGroup} is called with onFailure(BUSY)
|
||||
*/
|
||||
@UiThread
|
||||
private void startWifiP2pFramework(int attempt) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("startWifiP2pFramework attempt: " + attempt);
|
||||
}
|
||||
/*
|
||||
* It is important that we call WifiP2pManager#initialize again
|
||||
* for every attempt to starting the framework because otherwise,
|
||||
* createGroup() will continue to fail with a BUSY state.
|
||||
*/
|
||||
channel = wifiP2pManager.initialize(ctx, ctx.getMainLooper(), null);
|
||||
if (channel == null) {
|
||||
releaseHotspotWithError(
|
||||
listener.onHotspotError(
|
||||
ctx.getString(R.string.hotspot_error_no_wifi_direct));
|
||||
return;
|
||||
}
|
||||
|
||||
ActionListener listener = new ActionListener() {
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onSuccess() {
|
||||
requestGroupInfo(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onFailure(int reason) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("onFailure: " + reason);
|
||||
}
|
||||
if (reason == BUSY) {
|
||||
// WifiP2p not ready yet or hotspot already running
|
||||
restartWifiP2pFramework(attempt);
|
||||
} else if (reason == P2P_UNSUPPORTED) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p unsupported"));
|
||||
} else if (reason == ERROR) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p error"));
|
||||
} else if (reason == NO_SERVICE_REQUESTS) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"no service requests"));
|
||||
} else {
|
||||
// all cases covered, in doubt set to error
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed_unknown,
|
||||
reason));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (SDK_INT >= 29) {
|
||||
Runnable createGroup = () -> {
|
||||
NetworkConfig c = requireNonNull(savedNetworkConfig);
|
||||
WifiP2pConfig config = new WifiP2pConfig.Builder()
|
||||
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
|
||||
.setNetworkName(c.ssid)
|
||||
.setPassphrase(c.password)
|
||||
.build();
|
||||
wifiP2pManager.createGroup(channel, config, listener);
|
||||
};
|
||||
if (savedNetworkConfig == null) {
|
||||
dbExecutor.execute(() -> {
|
||||
// load savedNetworkConfig before starting hotspot
|
||||
dbExecutor.execute(() -> {
|
||||
loadSavedNetworkConfig();
|
||||
androidExecutor.runOnUiThread(createGroup);
|
||||
loadSavedNetworkConfig();
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
WifiP2pConfig config = new WifiP2pConfig.Builder()
|
||||
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
|
||||
.setNetworkName(savedNetworkConfig.ssid)
|
||||
.setPassphrase(savedNetworkConfig.password)
|
||||
.build();
|
||||
acquireLock();
|
||||
wifiP2pManager.createGroup(channel, config, this);
|
||||
});
|
||||
} else {
|
||||
// savedNetworkConfig was already loaded, create group now
|
||||
createGroup.run();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
wifiP2pManager.createGroup(channel, listener);
|
||||
acquireLock();
|
||||
wifiP2pManager.createGroup(channel, this);
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// this should never happen, because we request permissions before
|
||||
@@ -245,45 +157,54 @@ class HotspotManager {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void restartWifiP2pFramework(int attempt) {
|
||||
LOG.info("retrying to start WifiP2p framework");
|
||||
if (attempt < MAX_FRAMEWORK_ATTEMPTS) {
|
||||
if (SDK_INT >= 27 && channel != null) channel.close();
|
||||
channel = null;
|
||||
handler.postDelayed(() -> startWifiP2pFramework(attempt + 1),
|
||||
RETRY_DELAY_MILLIS);
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onSuccess() {
|
||||
requestGroupInfo(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
|
||||
public void onFailure(int reason) {
|
||||
if (reason == BUSY) {
|
||||
// Hotspot already running
|
||||
requestGroupInfo(1);
|
||||
} else if (reason == P2P_UNSUPPORTED) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"p2p unsupported"));
|
||||
} else if (reason == ERROR) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed, "p2p error"));
|
||||
} else if (reason == NO_SERVICE_REQUESTS) {
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed,
|
||||
"no service requests"));
|
||||
} else {
|
||||
releaseHotspotWithError(
|
||||
ctx.getString(R.string.hotspot_error_framework_busy));
|
||||
// all cases covered, in doubt set to error
|
||||
releaseHotspotWithError(ctx.getString(
|
||||
R.string.hotspot_error_start_callback_failed_unknown,
|
||||
reason));
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void stopWifiP2pHotspot() {
|
||||
if (channel == null) return;
|
||||
wifiP2pManager.removeGroup(channel, new ActionListener() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
closeChannelAndReleaseLocks();
|
||||
releaseHotspot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(int reason) {
|
||||
// not propagating back error
|
||||
if (LOG.isLoggable(WARNING)) {
|
||||
LOG.warning("Error removing Wifi P2P group: " + reason);
|
||||
}
|
||||
closeChannelAndReleaseLocks();
|
||||
releaseHotspot();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("WakelockTimeout")
|
||||
private void acquireLocks() {
|
||||
// FLAG_KEEP_SCREEN_ON is not respected on some Huawei devices.
|
||||
wakeLock = powerManager.newWakeLock(FULL_WAKE_LOCK, lockTag);
|
||||
wakeLock.acquire();
|
||||
private void acquireLock() {
|
||||
// WIFI_MODE_FULL has no effect on API >= 29
|
||||
int lockType =
|
||||
SDK_INT >= 29 ? WIFI_MODE_FULL_HIGH_PERF : WIFI_MODE_FULL;
|
||||
@@ -291,21 +212,22 @@ class HotspotManager {
|
||||
wifiLock.acquire();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void releaseHotspot() {
|
||||
listener.onHotspotStopped();
|
||||
closeChannelAndReleaseLock();
|
||||
}
|
||||
|
||||
private void releaseHotspotWithError(String error) {
|
||||
listener.onHotspotError(error);
|
||||
closeChannelAndReleaseLocks();
|
||||
closeChannelAndReleaseLock();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void closeChannelAndReleaseLocks() {
|
||||
if (SDK_INT >= 27 && channel != null) channel.close();
|
||||
private void closeChannelAndReleaseLock() {
|
||||
if (SDK_INT >= 27) channel.close();
|
||||
channel = null;
|
||||
if (wakeLock.isHeld()) wakeLock.release();
|
||||
if (wifiLock.isHeld()) wifiLock.release();
|
||||
wifiLock.release();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void requestGroupInfo(int attempt) {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("requestGroupInfo attempt: " + attempt);
|
||||
@@ -335,8 +257,7 @@ class HotspotManager {
|
||||
ioExecutor.execute(() -> {
|
||||
String content = createWifiLoginString(group.getNetworkName(),
|
||||
group.getPassphrase());
|
||||
Bitmap qrCode = QrCodeUtils.createQrCode(
|
||||
(int) (dm.heightPixels * HOTSPOT_QRCODE_FACTOR), content);
|
||||
Bitmap qrCode = QrCodeUtils.createQrCode(dm, content);
|
||||
NetworkConfig config = new NetworkConfig(group.getNetworkName(),
|
||||
group.getPassphrase(), qrCode);
|
||||
listener.onHotspotStarted(config);
|
||||
@@ -349,22 +270,27 @@ class HotspotManager {
|
||||
LOG.info("group is null");
|
||||
return false;
|
||||
} else if (!group.getNetworkName().startsWith("DIRECT-")) {
|
||||
LOG.info("received networkName without prefix 'DIRECT-'");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("received networkName without prefix 'DIRECT-': " +
|
||||
group.getNetworkName());
|
||||
}
|
||||
return false;
|
||||
} else if (SDK_INT >= 29) {
|
||||
// if we get here, the savedNetworkConfig must have a value
|
||||
String networkName = requireNonNull(savedNetworkConfig).ssid;
|
||||
String networkName = savedNetworkConfig.ssid;
|
||||
if (!networkName.equals(group.getNetworkName())) {
|
||||
LOG.info("expected networkName does not match received one");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("expected networkName: " + networkName);
|
||||
LOG.info("received networkName: " + group.getNetworkName());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void retryRequestingGroupInfo(int attempt) {
|
||||
LOG.info("retrying to request group info");
|
||||
LOG.info("retrying");
|
||||
// On some devices we need to wait for the group info to become available
|
||||
if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
|
||||
handler.postDelayed(() -> requestGroupInfo(attempt + 1),
|
||||
@@ -377,13 +303,19 @@ class HotspotManager {
|
||||
|
||||
@UiThread
|
||||
private void requestGroupInfoForConnection() {
|
||||
LOG.info("requestGroupInfo for connection");
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("requestGroupInfo for connection");
|
||||
}
|
||||
GroupInfoListener groupListener = group -> {
|
||||
if (group != null) {
|
||||
listener.onPeersUpdated(group.getClientList().size());
|
||||
if (group == null || group.getClientList().isEmpty()) {
|
||||
handler.postDelayed(this::requestGroupInfoForConnection,
|
||||
RETRY_DELAY_MILLIS);
|
||||
} else {
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("client list " + group.getClientList());
|
||||
}
|
||||
listener.onDeviceConnected();
|
||||
}
|
||||
handler.postDelayed(this::requestGroupInfoForConnection,
|
||||
RETRY_DELAY_MILLIS);
|
||||
};
|
||||
try {
|
||||
if (channel == null) return;
|
||||
@@ -439,15 +371,26 @@ class HotspotManager {
|
||||
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
|
||||
}
|
||||
|
||||
// exclude chars that are easy to confuse: 0 (O), 5 (S), 1 l (I)
|
||||
private static final String chars = "2346789abcdefghijkmnopqrstuvwxyz";
|
||||
private static final String digits = "123456789"; // avoid 0
|
||||
private static final String letters = "abcdefghijkmnopqrstuvwxyz"; // no l
|
||||
private static final String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no I, O
|
||||
|
||||
private String getRandomString(int length) {
|
||||
char[] c = new char[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
c[i] = chars.charAt(random.nextInt(chars.length()));
|
||||
if (random.nextBoolean()) {
|
||||
c[i] = random(digits);
|
||||
} else if (random.nextBoolean()) {
|
||||
c[i] = random(letters);
|
||||
} else {
|
||||
c[i] = random(LETTERS);
|
||||
}
|
||||
}
|
||||
return new String(c);
|
||||
}
|
||||
|
||||
private char random(String universe) {
|
||||
return universe.charAt(random.nextInt(universe.length()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,19 +57,16 @@ abstract class HotspotState {
|
||||
return websiteConfig;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
boolean wasNotYetConsumed() {
|
||||
return !consumed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this state as consumed, i.e. the UI has already done something
|
||||
* as a result of the state changing to this. This can be used in order
|
||||
* to not repeat actions such as showing fragments on rotation changes.
|
||||
*/
|
||||
@UiThread
|
||||
void consume() {
|
||||
boolean consume() {
|
||||
boolean old = consumed;
|
||||
consumed = true;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ class HotspotViewModel extends DbViewModel
|
||||
|
||||
private final MutableLiveData<HotspotState> state =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Integer> peersConnected =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveEvent<Boolean> peerConnected =
|
||||
new MutableLiveEvent<>();
|
||||
private final MutableLiveEvent<Uri> savedApkToUri =
|
||||
new MutableLiveEvent<>();
|
||||
|
||||
@@ -137,8 +137,14 @@ class HotspotViewModel extends DbViewModel
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void onPeersUpdated(int peers) {
|
||||
peersConnected.setValue(peers);
|
||||
public void onDeviceConnected() {
|
||||
peerConnected.setEvent(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHotspotStopped() {
|
||||
LOG.info("stopping webserver");
|
||||
ioExecutor.execute(webServerManager::stopWebServer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +170,7 @@ class HotspotViewModel extends DbViewModel
|
||||
public void onWebServerError() {
|
||||
state.postValue(new HotspotError(getApplication()
|
||||
.getString(R.string.hotspot_error_web_server_start)));
|
||||
stopHotspot();
|
||||
hotspotManager.stopWifiP2pHotspot();
|
||||
}
|
||||
|
||||
void exportApk(Uri uri) {
|
||||
@@ -213,8 +219,8 @@ class HotspotViewModel extends DbViewModel
|
||||
return state;
|
||||
}
|
||||
|
||||
LiveData<Integer> getPeersConnectedEvent() {
|
||||
return peersConnected;
|
||||
LiveEvent<Boolean> getPeerConnectedEvent() {
|
||||
return peerConnected;
|
||||
}
|
||||
|
||||
LiveEvent<Uri> getSavedApkToUri() {
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package org.briarproject.briar.android.hotspot;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
@@ -18,6 +16,7 @@ import org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
|
||||
@@ -61,23 +60,19 @@ public class QrHotspotFragment extends Fragment {
|
||||
TextView qrIntroView = v.findViewById(R.id.qrIntroView);
|
||||
ImageView qrCodeView = v.findViewById(R.id.qrCodeView);
|
||||
|
||||
boolean forWifi = requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT);
|
||||
|
||||
qrIntroView.setText(forWifi ? R.string.hotspot_qr_wifi :
|
||||
R.string.hotspot_qr_site);
|
||||
|
||||
Consumer<HotspotStarted> consumer;
|
||||
if (requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT)) {
|
||||
qrIntroView.setText(R.string.hotspot_qr_wifi);
|
||||
consumer = state ->
|
||||
qrCodeView.setImageBitmap(state.getNetworkConfig().qrCode);
|
||||
} else {
|
||||
qrIntroView.setText(R.string.hotspot_qr_site);
|
||||
consumer = state ->
|
||||
qrCodeView.setImageBitmap(state.getWebsiteConfig().qrCode);
|
||||
}
|
||||
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
|
||||
if (state instanceof HotspotStarted) {
|
||||
HotspotStarted s = (HotspotStarted) state;
|
||||
Bitmap qrCode = forWifi ? s.getNetworkConfig().qrCode :
|
||||
s.getWebsiteConfig().qrCode;
|
||||
if (qrCode == null) {
|
||||
Toast.makeText(requireContext(), R.string.error,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
qrCodeView.setImageResource(R.drawable.ic_image_broken);
|
||||
} else {
|
||||
qrCodeView.setImageBitmap(qrCode);
|
||||
}
|
||||
consumer.accept((HotspotStarted) state);
|
||||
}
|
||||
});
|
||||
return v;
|
||||
|
||||
@@ -14,20 +14,22 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InterfaceAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.list;
|
||||
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.util.LogUtils.logException;
|
||||
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
|
||||
import static org.briarproject.briar.android.hotspot.WebServer.PORT;
|
||||
import static org.briarproject.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -47,7 +49,7 @@ class WebServerManager {
|
||||
private final WebServer webServer;
|
||||
private final DisplayMetrics dm;
|
||||
|
||||
private volatile WebServerListener listener;
|
||||
private WebServerListener listener;
|
||||
|
||||
@Inject
|
||||
WebServerManager(Application ctx) {
|
||||
@@ -55,7 +57,6 @@ class WebServerManager {
|
||||
dm = ctx.getResources().getDisplayMetrics();
|
||||
}
|
||||
|
||||
@UiThread
|
||||
void setListener(WebServerListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
@@ -84,8 +85,7 @@ class WebServerManager {
|
||||
}
|
||||
url = "http://" + address.getHostAddress() + ":" + PORT;
|
||||
}
|
||||
Bitmap qrCode = QrCodeUtils.createQrCode(
|
||||
(int) (dm.heightPixels * HOTSPOT_QRCODE_FACTOR), url);
|
||||
Bitmap qrCode = QrCodeUtils.createQrCode(dm, url);
|
||||
listener.onWebServerStarted(new WebsiteConfig(url, qrCode));
|
||||
}
|
||||
|
||||
@@ -110,4 +110,16 @@ class WebServerManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<NetworkInterface> getNetworkInterfaces() {
|
||||
try {
|
||||
Enumeration<NetworkInterface> ifaces =
|
||||
NetworkInterface.getNetworkInterfaces();
|
||||
return ifaces == null ? emptyList() : list(ifaces);
|
||||
} catch (SocketException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
return emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.StartupFailureActivity;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.FeedFragment;
|
||||
@@ -74,7 +73,6 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
|
||||
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
|
||||
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent;
|
||||
@@ -252,11 +250,6 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
|
||||
private void exitIfStartupFailed(Intent intent) {
|
||||
if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) {
|
||||
// Launch StartupFailureActivity in its own process, then exit
|
||||
Intent i = new Intent(this, StartupFailureActivity.class);
|
||||
i.putExtra(EXTRA_START_RESULT,
|
||||
intent.getSerializableExtra(EXTRA_START_RESULT));
|
||||
startActivity(i);
|
||||
finish();
|
||||
LOG.info("Exiting");
|
||||
System.exit(0);
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.widget.ScrollView;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.widget.OnboardingFullDialogFragment;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -52,10 +51,6 @@ public class ChooserFragment extends Fragment {
|
||||
container, false);
|
||||
|
||||
scrollView = (ScrollView) v;
|
||||
|
||||
Button buttonLearnMore = v.findViewById(R.id.buttonLearnMore);
|
||||
buttonLearnMore.setOnClickListener(e -> showLearnMoreDialog());
|
||||
|
||||
Button sendButton = v.findViewById(R.id.sendButton);
|
||||
sendButton.setOnClickListener(i -> viewModel.startSendData());
|
||||
|
||||
@@ -80,10 +75,4 @@ public class ChooserFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void showLearnMoreDialog() {
|
||||
OnboardingFullDialogFragment.newInstance(
|
||||
R.string.removable_drive_menu_title,
|
||||
R.string.removable_drive_explanation
|
||||
).show(getChildFragmentManager(), OnboardingFullDialogFragment.TAG);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,6 @@ public class ReceiveFragment extends Fragment {
|
||||
private Button button;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private boolean checkForStateLoss = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -75,10 +73,6 @@ public class ReceiveFragment extends Fragment {
|
||||
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
|
||||
viewModel.getState()
|
||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||
|
||||
// need to check for lost ViewModel state when creating with prior state
|
||||
if (savedInstanceState != null) checkForStateLoss = true;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -90,23 +84,6 @@ public class ReceiveFragment extends Fragment {
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This code gets called *after* launcher had a chance
|
||||
// to return the activity result.
|
||||
if (checkForStateLoss && viewModel.hasNoState()) {
|
||||
// We were recreated, but have lost the ViewModel state,
|
||||
// because our activity was destroyed.
|
||||
//
|
||||
// Remove the current fragment from the stack
|
||||
// to prevent duplicates on the back stack.
|
||||
getParentFragmentManager().popBackStack();
|
||||
// Start again (picks up existing task or allows to start a new one)
|
||||
viewModel.startReceiveData();
|
||||
}
|
||||
}
|
||||
|
||||
private void onOldTaskResumed(boolean resumed) {
|
||||
if (resumed) {
|
||||
Toast.makeText(requireContext(),
|
||||
@@ -127,8 +104,6 @@ public class ReceiveFragment extends Fragment {
|
||||
|
||||
private void onDocumentChosen(@Nullable Uri uri) {
|
||||
if (uri == null) return;
|
||||
// we just got our document, so don't treat this as a state loss
|
||||
checkForStateLoss = false;
|
||||
viewModel.importData(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,12 +75,6 @@ class RemovableDriveViewModel extends DbViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
boolean hasNoState() {
|
||||
return action.getLastValue() == null && state.getValue() == null &&
|
||||
task == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this as soon as it becomes available.
|
||||
*/
|
||||
|
||||
@@ -51,8 +51,6 @@ public class SendFragment extends Fragment {
|
||||
private Button button;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
private boolean checkForStateLoss = false;
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
@@ -82,9 +80,6 @@ public class SendFragment extends Fragment {
|
||||
viewModel.getState()
|
||||
.observe(getViewLifecycleOwner(), this::onStateChanged);
|
||||
|
||||
// need to check for lost ViewModel state when creating with prior state
|
||||
if (savedInstanceState != null) checkForStateLoss = true;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -96,23 +91,6 @@ public class SendFragment extends Fragment {
|
||||
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// This code gets called *after* launcher had a chance
|
||||
// to return the activity result.
|
||||
if (checkForStateLoss && viewModel.hasNoState()) {
|
||||
// We were recreated, but have lost the ViewModel state,
|
||||
// because our activity was destroyed.
|
||||
//
|
||||
// Remove the current fragment from the stack
|
||||
// to prevent duplicates on the back stack.
|
||||
getParentFragmentManager().popBackStack();
|
||||
// Start again (picks up existing task or allows to start a new one)
|
||||
viewModel.startSendData();
|
||||
}
|
||||
}
|
||||
|
||||
private void onOldTaskResumed(boolean resumed) {
|
||||
if (resumed) {
|
||||
Toast.makeText(requireContext(),
|
||||
@@ -149,8 +127,6 @@ public class SendFragment extends Fragment {
|
||||
|
||||
private void onDocumentCreated(@Nullable Uri uri) {
|
||||
if (uri == null) return;
|
||||
// we just got our document, so don't treat this as a state loss
|
||||
checkForStateLoss = false;
|
||||
viewModel.exportData(uri);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.briarproject.briar.android.reporting;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.android.logging.LogEncrypter;
|
||||
@@ -11,7 +10,6 @@ import java.lang.Thread.UncaughtExceptionHandler;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
|
||||
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
|
||||
|
||||
@NotNullByDefault
|
||||
@@ -30,8 +28,6 @@ class BriarExceptionHandler implements UncaughtExceptionHandler {
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
if (IS_DEBUG_BUILD) Log.w("Uncaught exception", e);
|
||||
|
||||
// encrypt logs to disk before handing over to new process
|
||||
// the intent has limited space, so we can't reliably store them there.
|
||||
byte[] logKey = logEncrypter.encryptLogs();
|
||||
|
||||
@@ -86,6 +86,12 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
PreferenceGroup dev = requireNonNull(findPreference(PREF_KEY_DEV));
|
||||
dev.setVisible(false);
|
||||
}
|
||||
|
||||
if (!viewModel.shouldEnableShareAppViaOfflineHotspot()) {
|
||||
Preference shareApp =
|
||||
requireNonNull(findPreference(PREF_KEY_SHARE_APP));
|
||||
shareApp.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -262,4 +262,8 @@ class SettingsViewModel extends DbViewModel implements EventListener {
|
||||
return screenLockTimeout;
|
||||
}
|
||||
|
||||
boolean shouldEnableShareAppViaOfflineHotspot() {
|
||||
return featureFlags.shouldEnableShareAppViaOfflineHotspot();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,19 +26,13 @@ public class QrCodeUtils {
|
||||
|
||||
private static final Logger LOG = getLogger(QrCodeUtils.class.getName());
|
||||
|
||||
public static final double HOTSPOT_QRCODE_FACTOR = 0.35;
|
||||
|
||||
@Nullable
|
||||
public static Bitmap createQrCode(DisplayMetrics dm, String input) {
|
||||
return createQrCode(Math.min(dm.widthPixels, dm.heightPixels), input);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Bitmap createQrCode(int edgeLen, String input) {
|
||||
int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels);
|
||||
try {
|
||||
// Generate QR code
|
||||
BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE,
|
||||
edgeLen, edgeLen);
|
||||
smallestDimen, smallestDimen);
|
||||
return renderQrCode(encoded);
|
||||
} catch (WriterException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
|
||||
@@ -62,6 +62,7 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
@@ -340,6 +341,11 @@ public class UiUtils {
|
||||
return i;
|
||||
}
|
||||
|
||||
public static void putShowAdvancedExtra(Intent i) {
|
||||
i.putExtra(SDK_INT <= 28 ? "android.content.extra.SHOW_ADVANCED" :
|
||||
"android.provider.extra.SHOW_ADVANCED", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if location is enabled,
|
||||
* or it isn't required due to this being a SDK < 28 device.
|
||||
@@ -561,5 +567,4 @@ public class UiUtils {
|
||||
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
|
||||
SOFT_INPUT_STATE_HIDDEN);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.briarproject.briar.android.view;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.ImageView;
|
||||
@@ -30,7 +29,6 @@ import im.delight.android.identicons.IdenticonDrawable;
|
||||
import static android.content.Context.LAYOUT_INFLATER_SERVICE;
|
||||
import static android.graphics.Typeface.BOLD;
|
||||
import static android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||
import static androidx.appcompat.content.res.AppCompatResources.getDrawable;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
|
||||
import static org.briarproject.briar.api.identity.AuthorInfo.Status.NONE;
|
||||
@@ -179,14 +177,14 @@ public class AuthorView extends ConstraintLayout {
|
||||
case RSS_FEED:
|
||||
avatarIcon.setVisibility(INVISIBLE);
|
||||
date.setVisibility(VISIBLE);
|
||||
setRssVectorAvatar();
|
||||
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||
setAvatarSize(R.dimen.blogs_avatar_normal_size);
|
||||
setTextSize(authorName, R.dimen.text_size_small);
|
||||
break;
|
||||
case RSS_FEED_REBLOGGED:
|
||||
avatarIcon.setVisibility(INVISIBLE);
|
||||
date.setVisibility(VISIBLE);
|
||||
setRssVectorAvatar();
|
||||
avatar.setImageResource(R.drawable.ic_rss_feed);
|
||||
setAvatarSize(R.dimen.blogs_avatar_comment_size);
|
||||
setTextSize(authorName, R.dimen.text_size_tiny);
|
||||
break;
|
||||
@@ -206,16 +204,4 @@ public class AuthorView extends ConstraintLayout {
|
||||
v.setTextSize(COMPLEX_UNIT_PX, textSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies special hack to use AppCompat vector drawable support
|
||||
* when setting the RSS vector drawable to the avatar view.
|
||||
* {@link ImageView#setImageResource(int)} is not working as
|
||||
* {@link CircleImageView} is not using
|
||||
* {@link androidx.appcompat.widget.AppCompatImageView}.
|
||||
*/
|
||||
private void setRssVectorAvatar() {
|
||||
Drawable d = getDrawable(getContext(), R.drawable.ic_rss_feed);
|
||||
avatar.setImageDrawable(d);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -42,13 +42,10 @@ public interface AndroidNotificationManager {
|
||||
// that will sort below the main channels such as contacts
|
||||
String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
|
||||
String ONGOING_CHANNEL_ID = "zForegroundService2";
|
||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
String REMINDER_CHANNEL_ID = "zSignInReminder";
|
||||
String HOTSPOT_CHANNEL_ID = "zHotspot";
|
||||
|
||||
// This channel is no longer used - keep the ID so we can remove the
|
||||
// channel from existing installations
|
||||
String FAILURE_CHANNEL_ID = "zStartupFailure";
|
||||
|
||||
// Actions for pending intents
|
||||
String ACTION_DISMISS_REMINDER = "dismissReminder";
|
||||
String ACTION_STOP_HOTSPOT = "stopHotspot";
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="35dp"
|
||||
android:viewportWidth="500"
|
||||
android:viewportHeight="175">
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="m459.809,167.16 l-4.327,-4.334 -7.07,-2.427c-3.888,-1.335 -8.723,-2.996 -10.744,-3.691l-3.674,-1.263 3.524,-0.173c8.242,-0.404 12.988,-4.007 14.336,-10.884 0.796,-4.061 1.129,-17.298 0.83,-33.041 -0.153,-8.084 -0.166,-15.416 -0.028,-16.295 0.138,-0.878 0.535,-2.42 0.883,-3.426 1.222,-3.532 0.34,-11.908 -1.858,-17.631 -0.348,-0.908 -1.934,-4.215 -3.524,-7.35 -4.158,-8.2 -4.505,-8.942 -4.893,-10.484 -0.459,-1.821 -0.212,-5.435 0.463,-6.766 0.666,-1.315 2.157,-2.916 3.246,-3.487 1.299,-0.68 2.616,0.122 4.686,2.855 3.422,4.516 14.001,19.441 15.732,22.193 3.746,5.956 5.956,11.165 7.972,18.788 0.655,2.475 1.885,6.997 2.735,10.05 0.849,3.053 2.714,10.158 4.144,15.79l2.6,10.24 4.65,5.193c2.557,2.856 5.798,6.464 7.2,8.017 1.403,1.553 2.55,2.975 2.55,3.161 0,0.335 -34.5,29.299 -34.899,29.299 -0.115,0 -2.156,-1.95 -4.535,-4.334zM365.944,150.772c-1.258,-0.628 -2.206,-1.379 -2.914,-2.307 -2.089,-2.736 -1.95,2.369 -1.864,-68.482l0.078,-64.039 0.661,-1.235c1.015,-1.898 1.992,-2.911 3.74,-3.879l1.6,-0.886 37.784,-0.08c42.456,-0.089 39.402,-0.248 42.116,2.192 0.875,0.787 1.757,1.959 2.184,2.904 0.713,1.577 0.716,1.638 0.806,14.204l0.09,12.62 -1.798,-0.13c-1.426,-0.103 -2.081,0.007 -3.166,0.532 -2.013,0.974 -3.94,2.899 -5.115,5.108l-1.037,1.95 -0.007,-12.825 -0.007,-12.825h-33.6,-33.6v51.3,51.3h33.599,33.599l0.076,-34.425c0.073,-32.96 0.098,-34.361 0.591,-32.925 0.283,0.825 1.806,3.997 3.383,7.05 5.493,10.626 5.405,9.86 5.396,47.203 -0.007,27.621 -0.124,29.951 -1.662,33.109 -1.071,2.199 -2.711,3.71 -5.058,4.66l-1.674,0.677 -36.168,0.08 -36.168,0.08 -1.864,-0.931zM408.344,145.734c2.871,-1.309 4.458,-3.678 4.43,-6.617 -0.039,-4.116 -3.109,-7.223 -7.136,-7.223 -2.12,0 -3.567,0.609 -5.164,2.172 -2.245,2.198 -2.754,5.439 -1.301,8.287 0.713,1.398 2.521,3.009 3.962,3.53 1.497,0.542 3.84,0.475 5.209,-0.149z" />
|
||||
<path
|
||||
android:fillColor="?attr/colorControlNormal"
|
||||
android:pathData="m39.434,167.16 l4.327,-4.334 7.07,-2.427c3.888,-1.335 8.723,-2.996 10.744,-3.691l3.674,-1.263 -3.524,-0.173c-8.242,-0.404 -12.988,-4.007 -14.336,-10.884 -0.796,-4.061 -1.129,-17.298 -0.83,-33.041 0.153,-8.084 0.166,-15.416 0.028,-16.295 -0.138,-0.878 -0.535,-2.42 -0.883,-3.426 -1.222,-3.532 -0.34,-11.908 1.858,-17.631 0.348,-0.908 1.934,-4.215 3.524,-7.35 4.158,-8.2 4.505,-8.942 4.893,-10.484 0.459,-1.821 0.212,-5.435 -0.463,-6.766 -0.666,-1.315 -2.157,-2.916 -3.246,-3.487 -1.299,-0.68 -2.616,0.122 -4.686,2.855 -3.422,4.516 -14.001,19.441 -15.732,22.193 -3.746,5.956 -5.956,11.165 -7.972,18.788 -0.655,2.475 -1.885,6.997 -2.735,10.05 -0.849,3.053 -2.714,10.158 -4.144,15.79L14.4,125.824 9.75,131.017c-2.557,2.856 -5.798,6.464 -7.2,8.017 -1.403,1.553 -2.55,2.975 -2.55,3.161 0,0.335 34.5,29.299 34.899,29.299 0.115,0 2.156,-1.95 4.535,-4.334zM133.299,150.772c1.258,-0.628 2.206,-1.379 2.914,-2.307 2.089,-2.736 1.95,2.369 1.864,-68.482l-0.078,-64.039 -0.661,-1.235c-1.015,-1.898 -1.992,-2.911 -3.74,-3.879L132,9.944 94.216,9.865c-42.456,-0.089 -39.402,-0.248 -42.116,2.192 -0.875,0.787 -1.757,1.959 -2.184,2.904 -0.713,1.577 -0.716,1.638 -0.806,14.204l-0.09,12.62 1.798,-0.13c1.426,-0.103 2.081,0.007 3.166,0.532 2.013,0.974 3.94,2.899 5.115,5.108l1.037,1.95 0.007,-12.825 0.007,-12.825h33.6,33.6v51.3,51.3L93.752,126.194 60.153,126.194l-0.076,-34.425c-0.073,-32.96 -0.098,-34.361 -0.591,-32.925 -0.283,0.825 -1.806,3.997 -3.383,7.05 -5.493,10.626 -5.405,9.86 -5.396,47.203 0.007,27.621 0.124,29.951 1.662,33.109 1.071,2.199 2.711,3.71 5.058,4.66l1.674,0.677 36.168,0.08 36.168,0.08 1.864,-0.931zM90.9,145.734c-2.871,-1.309 -4.458,-3.678 -4.43,-6.617 0.039,-4.116 3.109,-7.223 7.136,-7.223 2.12,0 3.567,0.609 5.164,2.172 2.245,2.198 2.754,5.439 1.301,8.287 -0.713,1.398 -2.521,3.009 -3.962,3.53 -1.497,0.542 -3.84,0.475 -5.209,-0.149z" />
|
||||
<path
|
||||
android:fillColor="#0a3d91"
|
||||
android:pathData="m247.254,71.979h4.736c13.165,0 23.763,10.599 23.763,23.763v32.175c0,13.165 -10.599,23.763 -23.763,23.763h-4.736c-13.165,0 -23.763,-10.599 -23.763,-23.763V95.743c0,-13.165 10.599,-23.763 23.763,-23.763z" />
|
||||
<path
|
||||
android:fillColor="#0a3d91"
|
||||
android:pathData="m143.679,23.557c-1.59,2.624 0.182,5.575 2.785,6.643 2.695,1.572 6.171,4.987 9.148,2.232 2.233,-2.04 0.601,-5.728 -2.041,-6.67 -3.064,-1.677 -6.552,-5.515 -9.891,-2.205zM351.705,23.129c-2.768,1.961 -7.068,2.695 -8.378,6.043 -0.642,2.731 1.936,4.993 4.584,4.426 3.202,-1.638 7.057,-2.842 9.178,-5.948 0.569,-2.604 -1.493,-5.351 -4.268,-4.746 -0.416,-0.108 -0.76,0.035 -1.117,0.224zM324.943,36.301c-2.934,1.708 -7.32,1.923 -8.931,5.153 -0.908,2.654 1.432,5.159 4.124,4.861 3.356,-1.304 7.32,-2.113 9.737,-5.002 0.814,-2.538 -0.977,-5.47 -3.796,-5.129 -0.403,-0.146 -0.76,-0.038 -1.133,0.117zM169.707,38.764c-1.219,2.712 0.757,5.461 3.444,6.145 2.858,1.081 6.669,4.118 9.312,1.234 2.315,-2.037 0.339,-5.835 -2.41,-6.418 -3.328,-1.176 -7.385,-4.714 -10.346,-0.961zM296.98,46.381c-3.123,1.309 -7.492,0.971 -9.492,3.97 -1.232,2.519 0.773,5.298 3.48,5.34 3.487,-0.907 7.567,-1.113 10.303,-3.739 1.114,-2.422 -0.312,-5.546 -3.152,-5.551 -0.383,-0.196 -0.75,-0.127 -1.139,-0.02zM199.49,47.926c-2.229,1.462 -2.68,4.866 -0.276,6.377 2.587,1.471 5.915,1.955 8.84,2.495 2.785,0.332 4.746,-2.479 3.891,-5.049 -2.043,-3.28 -6.472,-2.734 -9.717,-4.089 -0.913,0.089 -1.826,0.177 -2.738,0.266zM268.003,52.434c-3.276,0.789 -7.461,-0.188 -9.936,2.382 -1.65,2.268 -0.157,5.354 2.504,5.862 3.612,-0.292 7.718,0.091 10.84,-2.094 1.462,-2.228 0.523,-5.532 -2.285,-5.959 -0.349,-0.249 -0.722,-0.239 -1.123,-0.19zM228.554,53.852c-1.933,1.836 -1.765,5.267 0.871,6.321 2.809,0.989 6.171,0.847 9.144,0.861 2.799,-0.173 4.226,-3.29 2.924,-5.665 -2.604,-2.852 -6.847,-1.542 -10.293,-2.27 -0.882,0.251 -1.764,0.501 -2.646,0.752z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m252.501,112.09 l11.656,-11.452 -17.126,-19.791 0.019,25.792 -9.142,-9.245 -3.125,3.091 11.521,11.581 -11.498,11.308c3.031,3.056 0,0 3.031,3.056l9.189,-8.973c0.044,4.735 0.004,27.165 0.004,27.165l17.102,-20.773zM251.424,92.519 L258.222,100.383 251.424,107.016zM251.4,132.547 L251.424,117.151 258.317,124.139z" />
|
||||
</vector>
|
||||
@@ -1,66 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="204dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="204">
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M41.583,100.841H22.181V102.841H52.75V136.663H54.75V102.841H68.034C70.924,102.841 73.267,105.184 73.267,108.074C73.267,112.068 76.505,115.307 80.5,115.307H92.727V113.307H80.5C77.61,113.307 75.267,110.964 75.267,108.074C75.267,104.079 72.029,100.841 68.034,100.841H43.583V63.659H41.583V100.841Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M122.611,86.06V46.253H124.611V86.06H122.611Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M119.754,179.928L119.754,142.682L121.754,142.682L121.754,179.928L119.754,179.928Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M195.251,100.841V86.596H193.251V113.826H148.362V115.826H194.251C198.125,115.826 201.301,118.969 201.301,122.826V127.044H203.301V122.826C203.301,118.186 199.75,114.378 195.251,113.881V102.841H233.052V100.841H195.251Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M32.121,57.411L32.888,55.564C33.596,55.858 34.375,56.021 35.197,56.021H37.079V58.021H35.197C34.107,58.021 33.069,57.804 32.121,57.411ZM48.377,58.021V56.021H50.26C51.082,56.021 51.86,55.858 52.569,55.564L53.335,57.411C52.388,57.804 51.349,58.021 50.26,58.021H48.377ZM58.293,36.807H56.293V34.924C56.293,34.102 56.13,33.323 55.836,32.615L57.684,31.849C58.077,32.796 58.293,33.834 58.293,34.924V36.807ZM37.079,26.89H35.197C34.107,26.89 33.069,27.107 32.121,27.5L32.888,29.347C33.596,29.053 34.375,28.89 35.197,28.89H37.079V26.89ZM27.163,48.104H29.163V49.987C29.163,50.809 29.326,51.588 29.62,52.296L27.772,53.062C27.38,52.115 27.163,51.076 27.163,49.987V48.104ZM27.163,44.338H29.163V40.572H27.163V44.338ZM27.163,36.807H29.163V34.924C29.163,34.102 29.326,33.323 29.62,32.615L27.772,31.849C27.38,32.796 27.163,33.834 27.163,34.924V36.807ZM40.845,26.89V28.89H44.611V26.89H40.845ZM48.377,26.89V28.89H50.26C51.082,28.89 51.86,29.053 52.569,29.347L53.335,27.5C52.388,27.107 51.349,26.89 50.26,26.89H48.377ZM58.293,40.572H56.293V44.338H58.293V40.572ZM58.293,48.104H56.293V49.987C56.293,50.809 56.13,51.588 55.836,52.296L57.684,53.062C58.077,52.115 58.293,51.076 58.293,49.987V48.104ZM44.611,58.021V56.021H40.845V58.021H44.611Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M112.487,40.426L112.875,38.464C113.41,38.569 113.964,38.625 114.533,38.625H116.99V40.625H114.533C113.833,40.625 113.149,40.556 112.487,40.426ZM131.733,40.625V38.625H134.191C134.76,38.625 135.314,38.569 135.848,38.464L136.237,40.426C135.575,40.556 134.891,40.625 134.191,40.625H131.733ZM140.016,38.859L138.903,37.197C139.829,36.577 140.627,35.78 141.247,34.854L142.909,35.966C142.143,37.11 141.159,38.094 140.016,38.859ZM144.674,12.941H142.674V10.484C142.674,9.915 142.619,9.361 142.513,8.826L144.475,8.438C144.606,9.1 144.674,9.784 144.674,10.484V12.941ZM142.909,4.659L141.247,5.772C140.627,4.845 139.829,4.048 138.903,3.428L140.016,1.766C141.159,2.531 142.143,3.516 142.909,4.659ZM116.99,0H114.533C113.833,0 113.149,0.069 112.487,0.2L112.875,2.162C113.41,2.056 113.964,2 114.533,2H116.99V0ZM108.708,1.766L109.821,3.428C108.895,4.048 108.097,4.845 107.477,5.772L105.815,4.659C106.581,3.516 107.565,2.531 108.708,1.766ZM104.049,27.684H106.049V30.141C106.049,30.71 106.105,31.264 106.211,31.799L104.249,32.187C104.118,31.525 104.049,30.841 104.049,30.141V27.684ZM105.815,35.966L107.477,34.854C108.097,35.78 108.895,36.577 109.821,37.197L108.708,38.859C107.565,38.094 106.581,37.11 105.815,35.966ZM104.049,22.77H106.049V17.855H104.049V22.77ZM104.049,12.941H106.049V10.484C106.049,9.915 106.105,9.361 106.211,8.826L104.249,8.438C104.118,9.1 104.049,9.784 104.049,10.484V12.941ZM121.905,0V2H126.819V0H121.905ZM131.733,0V2H134.191C134.76,2 135.314,2.056 135.848,2.162L136.237,0.2C135.575,0.069 134.891,0 134.191,0H131.733ZM144.674,17.855H142.674V22.77H144.674V17.855ZM144.674,27.684H142.674V30.141C142.674,30.71 142.619,31.264 142.513,31.799L144.475,32.187C144.606,31.525 144.674,30.841 144.674,30.141V27.684ZM126.819,40.625V38.625H121.905V40.625H126.819Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M183.397,81.99L184.163,80.143C184.902,80.449 185.714,80.619 186.57,80.619H188.513V82.619H186.57C185.446,82.619 184.375,82.396 183.397,81.99ZM200.171,82.619V80.619H202.114C202.97,80.619 203.782,80.449 204.521,80.143L205.287,81.99C204.309,82.396 203.238,82.619 202.114,82.619H200.171ZM210.403,60.729H208.403V58.787C208.403,57.93 208.233,57.118 207.927,56.38L209.774,55.613C210.18,56.591 210.403,57.663 210.403,58.787V60.729ZM188.513,50.497H186.57C185.446,50.497 184.375,50.721 183.397,51.126L184.163,52.973C184.902,52.667 185.714,52.497 186.57,52.497H188.513V50.497ZM178.281,72.387H180.281V74.33C180.281,75.186 180.451,75.998 180.757,76.737L178.91,77.503C178.504,76.525 178.281,75.454 178.281,74.33V72.387ZM178.281,68.501H180.281V64.615H178.281V68.501ZM178.281,60.729H180.281V58.787C180.281,57.93 180.451,57.118 180.757,56.38L178.91,55.613C178.504,56.591 178.281,57.663 178.281,58.787V60.729ZM192.399,50.497V52.497H196.285V50.497H192.399ZM200.171,50.497V52.497H202.114C202.97,52.497 203.782,52.667 204.521,52.973L205.287,51.126C204.309,50.721 203.238,50.497 202.114,50.497H200.171ZM210.403,64.615H208.403V68.501H210.403V64.615ZM210.403,72.387H208.403V74.33C208.403,75.186 208.233,75.998 207.927,76.737L209.774,77.503C210.18,76.525 210.403,75.454 210.403,74.33V72.387ZM196.285,82.619V80.619H192.399V82.619H196.285Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M188.271,180.562L188.659,178.6C189.316,178.73 189.996,178.798 190.694,178.798H192.633V180.798H190.694C189.865,180.798 189.055,180.717 188.271,180.562ZM212.029,180.798V178.798H213.968C214.666,178.798 215.346,178.73 216.003,178.6L216.391,180.562C215.607,180.717 214.797,180.798 213.968,180.798H212.029ZM220.865,178.708L219.753,177.046C220.889,176.285 221.868,175.306 222.629,174.17L224.291,175.282C223.384,176.636 222.219,177.801 220.865,178.708ZM226.381,147.05H224.381V145.111C224.381,144.413 224.313,143.733 224.183,143.077L226.145,142.688C226.3,143.472 226.381,144.282 226.381,145.111V147.05ZM224.291,138.214L222.629,139.327C221.868,138.19 220.889,137.211 219.753,136.451L220.865,134.789C222.219,135.695 223.384,136.86 224.291,138.214ZM192.633,132.698H190.694C189.865,132.698 189.055,132.779 188.271,132.934L188.659,134.896C189.316,134.766 189.996,134.698 190.694,134.698H192.633V132.698ZM183.797,134.789L184.91,136.451C183.773,137.211 182.794,138.19 182.033,139.327L180.372,138.214C181.278,136.86 182.443,135.695 183.797,134.789ZM178.281,166.446H180.281V168.385C180.281,169.083 180.349,169.763 180.479,170.42L178.517,170.808C178.362,170.024 178.281,169.214 178.281,168.385V166.446ZM180.372,175.282L182.033,174.17C182.794,175.306 183.773,176.285 184.91,177.046L183.797,178.708C182.443,177.801 181.278,176.636 180.372,175.282ZM178.281,162.567H180.281V158.688H178.281V162.567ZM178.281,154.809H180.281V150.93H178.281V154.809ZM178.281,147.05H180.281V145.111C180.281,144.413 180.349,143.733 180.479,143.077L178.517,142.688C178.362,143.472 178.281,144.282 178.281,145.111V147.05ZM196.512,132.698V134.698H200.392V132.698H196.512ZM204.271,132.698V134.698H208.15V132.698H204.271ZM212.029,132.698V134.698H213.968C214.666,134.698 215.346,134.766 216.003,134.896L216.391,132.934C215.607,132.779 214.797,132.698 213.968,132.698H212.029ZM226.381,150.93H224.381V154.809H226.381V150.93ZM226.381,158.688H224.381V162.567H226.381V158.688ZM226.381,166.446H224.381V168.385C224.381,169.083 224.313,169.763 224.183,170.42L226.145,170.808C226.3,170.024 226.381,169.214 226.381,168.385V166.446ZM208.15,180.798V178.798H204.271V180.798H208.15ZM200.392,180.798V178.798H196.512V180.798H200.392Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M40.389,175.457L41.155,173.609C41.934,173.932 42.789,174.111 43.692,174.111H45.713V176.111H43.692C42.522,176.111 41.406,175.878 40.389,175.457ZM57.845,176.111V174.111H59.867C60.769,174.111 61.625,173.932 62.403,173.609L63.169,175.457C62.152,175.878 61.037,176.111 59.867,176.111H57.845ZM68.494,153.33H66.494V151.309C66.494,150.406 66.315,149.551 65.992,148.772L67.84,148.006C68.261,149.023 68.494,150.139 68.494,151.309V153.33ZM45.713,142.682H43.692C42.522,142.682 41.406,142.914 40.389,143.336L41.155,145.184C41.934,144.861 42.789,144.682 43.692,144.682H45.713V142.682ZM35.064,165.462H37.064V167.484C37.064,168.386 37.244,169.242 37.567,170.02L35.719,170.786C35.297,169.769 35.064,168.654 35.064,167.484V165.462ZM35.064,161.418H37.064V157.374H35.064V161.418ZM35.064,153.33H37.064V151.309C37.064,150.406 37.244,149.551 37.567,148.772L35.719,148.006C35.297,149.023 35.064,150.139 35.064,151.309V153.33ZM49.757,142.682V144.682H53.801V142.682H49.757ZM57.845,142.682V144.682H59.867C60.769,144.682 61.625,144.861 62.403,145.184L63.169,143.336C62.152,142.914 61.037,142.682 59.867,142.682H57.845ZM68.494,157.374H66.494V161.418H68.494V157.374ZM68.494,165.462H66.494V167.484C66.494,168.386 66.315,169.242 65.992,170.02L67.84,170.786C68.261,169.769 68.494,168.654 68.494,167.484V165.462ZM53.801,176.111V174.111H49.757V176.111H53.801Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M2.84,110.802L3.606,108.954C3.91,109.081 4.245,109.151 4.601,109.151H6.758V111.151H4.601C3.977,111.151 3.383,111.027 2.84,110.802ZM11.072,111.151V109.151H13.229C13.585,109.151 13.92,109.081 14.224,108.954L14.99,110.802C14.448,111.027 13.853,111.151 13.229,111.151H11.072ZM17.83,100.079H15.83V97.922C15.83,97.566 15.76,97.231 15.634,96.927L17.481,96.161C17.706,96.703 17.83,97.298 17.83,97.922V100.079ZM6.758,93.321H4.601C3.977,93.321 3.383,93.445 2.84,93.67L3.606,95.518C3.91,95.392 4.245,95.321 4.601,95.321H6.758V93.321ZM0,104.393H2V106.55C2,106.906 2.071,107.241 2.197,107.545L0.349,108.311C0.124,107.769 0,107.174 0,106.55V104.393ZM0,100.079H2V97.922C2,97.566 2.071,97.231 2.197,96.927L0.349,96.161C0.124,96.703 0,97.298 0,97.922V100.079ZM11.072,93.321V95.321H13.229C13.585,95.321 13.92,95.392 14.224,95.518L14.99,93.67C14.448,93.445 13.853,93.321 13.229,93.321H11.072ZM17.83,104.393H15.83V106.55C15.83,106.906 15.76,107.241 15.634,107.545L17.481,108.311C17.706,107.769 17.83,107.174 17.83,106.55V104.393Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M114.745,202.969L115.511,201.122C115.815,201.248 116.149,201.318 116.506,201.318H118.663V203.318H116.506C115.882,203.318 115.287,203.194 114.745,202.969ZM122.977,203.318V201.318H125.133C125.49,201.318 125.825,201.248 126.129,201.122L126.895,202.969C126.352,203.194 125.757,203.318 125.133,203.318H122.977ZM129.735,192.246H127.735V190.09C127.735,189.733 127.664,189.398 127.538,189.094L129.386,188.328C129.611,188.871 129.735,189.466 129.735,190.09V192.246ZM118.663,185.488H116.506C115.882,185.488 115.287,185.612 114.745,185.837L115.511,187.685C115.815,187.559 116.149,187.488 116.506,187.488H118.663V185.488ZM111.905,196.56H113.905V198.717C113.905,199.074 113.975,199.408 114.101,199.712L112.254,200.478C112.029,199.936 111.905,199.341 111.905,198.717V196.56ZM111.905,192.246H113.905V190.09C113.905,189.733 113.975,189.398 114.101,189.094L112.254,188.328C112.029,188.871 111.905,189.466 111.905,190.09V192.246ZM122.977,185.488V187.488H125.133C125.49,187.488 125.825,187.559 126.129,187.685L126.895,185.837C126.352,185.612 125.757,185.488 125.133,185.488H122.977ZM129.735,196.56H127.735V198.717C127.735,199.074 127.664,199.408 127.538,199.712L129.386,200.478C129.611,199.936 129.735,199.341 129.735,198.717V196.56Z" />
|
||||
<path
|
||||
android:fillColor="#657D99"
|
||||
android:pathData="M240.444,110.802L241.21,108.954C241.514,109.081 241.849,109.151 242.205,109.151H244.362V111.151H242.205C241.581,111.151 240.987,111.027 240.444,110.802ZM248.676,111.151V109.151H250.833C251.189,109.151 251.524,109.081 251.828,108.954L252.594,110.802C252.052,111.027 251.457,111.151 250.833,111.151H248.676ZM255.434,100.079H253.434V97.922C253.434,97.566 253.364,97.231 253.238,96.927L255.085,96.161C255.31,96.703 255.434,97.298 255.434,97.922V100.079ZM244.362,93.321H242.205C241.581,93.321 240.987,93.445 240.444,93.67L241.21,95.518C241.514,95.392 241.849,95.321 242.205,95.321H244.362V93.321ZM237.604,104.393H239.604V106.55C239.604,106.906 239.675,107.241 239.801,107.545L237.953,108.311C237.728,107.769 237.604,107.174 237.604,106.55V104.393ZM237.604,100.079H239.604V97.922C239.604,97.566 239.675,97.231 239.801,96.927L237.953,96.161C237.728,96.703 237.604,97.298 237.604,97.922V100.079ZM248.676,93.321V95.321H250.833C251.189,95.321 251.524,95.392 251.828,95.518L252.594,93.67C252.052,93.445 251.457,93.321 250.833,93.321H248.676ZM255.434,104.393H253.434V106.55C253.434,106.906 253.364,107.241 253.238,107.545L255.085,108.311C255.31,107.769 255.434,107.174 255.434,106.55V104.393Z" />
|
||||
<path
|
||||
android:fillColor="#87C214"
|
||||
android:pathData="M108.75,109.808V129.511C108.75,131.11 110.052,132.429 111.668,132.429H113.036C114.636,132.429 115.955,131.11 115.955,129.511V109.808H108.75V109.808Z" />
|
||||
<path
|
||||
android:fillColor="#87C214"
|
||||
android:pathData="M115.938,100.312V96.602C115.938,95.003 114.636,93.684 113.036,93.684H111.668C110.069,93.684 108.75,95.003 108.75,96.602V100.312H115.938V100.312Z" />
|
||||
<path
|
||||
android:fillColor="#87C214"
|
||||
android:pathData="M131.931,116.305V96.602C131.931,95.003 130.628,93.684 129.029,93.684H127.66C126.061,93.684 124.742,95.003 124.742,96.602V116.305H131.931V116.305Z" />
|
||||
<path
|
||||
android:fillColor="#87C214"
|
||||
android:pathData="M124.742,125.801V129.511C124.742,131.11 126.061,132.429 127.66,132.429H129.029C130.628,132.429 131.947,131.11 131.947,129.511V125.801H124.742Z" />
|
||||
<path
|
||||
android:fillColor="#95D220"
|
||||
android:pathData="M107.595,117.459H103.886C102.286,117.459 100.967,118.761 100.967,120.377V121.746C100.967,123.345 102.27,124.664 103.886,124.664H107.595V117.459Z" />
|
||||
<path
|
||||
android:fillColor="#95D220"
|
||||
android:pathData="M136.795,117.459H117.092V124.664H136.795C138.394,124.664 139.713,123.345 139.713,121.746V120.377C139.713,118.761 138.394,117.459 136.795,117.459Z" />
|
||||
<path
|
||||
android:fillColor="#95D220"
|
||||
android:pathData="M123.588,101.466H103.886C102.286,101.466 100.967,102.769 100.967,104.385V105.753C100.967,107.352 102.27,108.671 103.886,108.671H123.588V101.466Z" />
|
||||
<path
|
||||
android:fillColor="#95D220"
|
||||
android:pathData="M136.795,101.466H133.085V108.671H136.795C138.394,108.671 139.713,107.369 139.713,105.753V104.384C139.713,102.769 138.394,101.466 136.795,101.466Z" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user