Compare commits

...

56 Commits

Author SHA1 Message Date
akwizgran
5fdc7e7cc4 Bump version numbers for 1.2.13 release. 2021-01-07 16:23:11 +00:00
akwizgran
7569d5ffb3 Update translations. 2021-01-07 16:21:59 +00:00
akwizgran
deca5d56cc Merge branch '1885-malformed-links' into 'master'
Do not produce malformed links for adding contacts when on other locales such as Turkish

Closes #1885

See merge request briar/briar!1335
2021-01-07 15:07:55 +00:00
Torsten Grote
3d6b48bb34 Do not produce malformed links for adding contacts when on other locales
such as Turkish
2021-01-07 11:52:27 -03:00
akwizgran
0dc631b7a8 Merge branch '1869-forum-list-view-model' into 'master'
Introduce ViewModel for ForumListFragment

Closes #1869

See merge request briar/briar!1331
2021-01-07 14:45:47 +00:00
Torsten Grote
921e952b05 Rename ForumItem to ForumPostItem 2021-01-07 08:58:14 -03:00
Torsten Grote
3b02797639 Block forum post notifications while viewing forum list 2021-01-07 08:58:14 -03:00
Torsten Grote
e2e67edbbe Introduce ForumListViewModel 2021-01-07 08:58:13 -03:00
Torsten Grote
a9cd40faeb Add transactions to methods in ForumManager 2021-01-07 08:58:13 -03:00
akwizgran
04517e942e Merge branch '1753-query-filter' into 'master'
Define manifest <queries> allowing us to make intent queries on API 30+

See merge request briar/briar!1323
2021-01-05 14:47:00 +00:00
akwizgran
9a25ad892d Merge branch '1753-screen-filter' into 'master'
Prepare screen overlay warning for targeting API 30

See merge request briar/briar!1322
2021-01-05 14:31:10 +00:00
akwizgran
3457d8f9ab Merge branch '1861-no-wifi-networks' into 'master'
Remove calls to WifiManager#getConfiguredNetworks()

Closes #1861

See merge request briar/briar!1330
2021-01-05 14:03:40 +00:00
Torsten Grote
5fb2624ffa Remove calls to WifiManager#getConfiguredNetworks()
as these require fine location permission now and don't work when
location services are disabled.
2021-01-05 10:22:32 -03:00
akwizgran
ed9a7bec2c Merge branch '1800-group-list-view-model' into 'master'
Using ListAdapter for PrivateGroupList

See merge request briar/briar!1327
2021-01-05 11:25:33 +00:00
Torsten Grote
ff70315d5c Address small things found in code review
of group list view model migration.
2021-01-04 16:19:29 -03:00
Torsten Grote
f197243273 Block all group message notifications while viewing list of private groups 2021-01-04 15:56:37 -03:00
Torsten Grote
6409a3b179 Refactor handleDbException to handleException 2021-01-04 15:39:02 -03:00
Torsten Grote
f882e46b33 Make GroupItem immutable and introduce copy constructors 2021-01-04 15:22:31 -03:00
akwizgran
efa63c306a Merge branch '1800-db-view-model' into 'master'
Introduce DbViewModel as replacement of DbController

See merge request briar/briar!1326
2021-01-04 14:00:42 +00:00
Torsten Grote
205b4f77b2 Add beginning of a ViewModel test
mostly to demonstrate how those could look like
2020-12-18 14:42:33 -03:00
Torsten Grote
015ecb1d99 Migrate GroupListController to a ViewModel
Use ListAdapter to calculate list diffs on a background thread
2020-12-17 17:40:24 -03:00
Torsten Grote
fd86b73626 Load list of private groups in a single DB transaction 2020-12-17 17:40:24 -03:00
Torsten Grote
9048392d4e Add methods to DbViewModel for loading and updating lists of items 2020-12-17 17:40:23 -03:00
Torsten Grote
480aaaa35e Introduce DbViewModel as replacement of DbController 2020-12-16 15:23:05 -03:00
Torsten Grote
002feb8e29 Merge branch '1720-add-up-button-to-feedback-activity' into 'master'
Add "up navigation" button to FeedbackActivity

See merge request briar/briar!1325
2020-12-16 16:52:34 +00:00
akwizgran
c6ba2b037a Add "up navigation" button to FeedbackActivity. 2020-12-16 16:04:08 +00:00
Torsten Grote
98788c7c80 Define manifest <queries> allowing us to make intent queries on API 30+ 2020-12-14 12:01:56 -03:00
Torsten Grote
e6f66ebc95 Screen overlay warning: remove ability to query and remember allowed apps for API 30+
as we can't query all installed apps anymore when targeting API 30
2020-12-14 10:53:12 -03:00
akwizgran
04485e58da Merge branch '1720-no-acra' into 'master'
Remove ACRA and implement the few bits we need ourselves

Closes #1114, #1720, and #1793

See merge request briar/briar!1319
2020-12-14 13:34:11 +00:00
Torsten Grote
97118fd92b Kill crash reporter process only with some delay 2020-12-14 09:14:42 -03:00
akwizgran
ac4fbf202f Fix duplicate DeviceInfo key. 2020-12-11 16:40:55 +00:00
akwizgran
b81495eac1 Use JSON bools and numbers, use fixed format for dates, normalise JSON keys. 2020-12-11 16:30:29 +00:00
akwizgran
db90f75d2e Remove unused string, remove periods from single-sentence toasts. 2020-12-11 16:29:29 +00:00
Torsten Grote
bed3abfd40 Address review feedback for ACRA replacement 2020-12-11 10:50:39 -03:00
Torsten Grote
0967f6c48e Merge branch '1794-tell-tor-about-ipv6-only-networks' into 'master'
Tell Tor when we're on an IPv6-only network

Closes #1794

See merge request briar/briar!1320
2020-12-10 18:24:45 +00:00
Torsten Grote
f9a8fcb207 Move Android version from basic info to device info
because the basic info is always sent and we say there won't be data of the device in what we send.
2020-12-10 14:40:33 -03:00
Torsten Grote
eb3c2a3566 Remove ACRA and implement the few bits we need ourselves 2020-12-10 14:29:25 -03:00
Torsten Grote
8d735b3023 Merge branch 'tor-0.3.5.12' into 'master'
Upgrade Tor to 0.3.5.12

Closes #1849

See merge request briar/briar!1298
2020-11-16 14:42:30 +00:00
akwizgran
b24a0e4bc3 Upgrade Tor to 0.3.5.12. 2020-11-16 13:29:24 +00:00
akwizgran
07da91a6f5 Merge branch 'gradle-plugin-4.1' into 'master'
Upgrade Gradle plugin to 4.1.1

See merge request briar/briar!1296
2020-11-11 17:45:47 +00:00
akwizgran
e4e0e712dc Update translations. 2020-11-11 16:59:08 +00:00
Torsten Grote
9294794448 Merge branch '1841-keep-dependency-injection-annotations' into 'master'
Keep dependency injection annotations at runtime

Closes #1841

See merge request briar/briar!1297
2020-11-11 16:54:44 +00:00
akwizgran
5a9958793d Keep dependency injection annotations at runtime. 2020-11-11 16:43:16 +00:00
akwizgran
651d2ca377 Add comment to explain suppressed warning. 2020-11-11 14:22:20 +00:00
akwizgran
ecd64f08cd Upgrade Gradle plugin to 4.1.1. 2020-11-11 12:33:36 +00:00
akwizgran
f3bffb6aa6 Fix some more lint errors. 2020-11-10 17:48:48 +00:00
akwizgran
33331dee3e Fix some lint errors and warnings. 2020-11-10 17:30:53 +00:00
akwizgran
641525fa74 Upgrade Android and Kotlin dependencies, Gradle Witness. 2020-11-10 16:57:51 +00:00
akwizgran
4b82079e33 Upgrade Gradle plugin to 4.1. 2020-11-10 15:18:50 +00:00
akwizgran
caa55ffa14 Merge branch 'android-studio-4.1-update-run-configurations' into 'master'
Update run configurations for Android Studio 4.1

See merge request briar/briar!1295
2020-11-09 13:49:28 +00:00
akwizgran
47ae594921 Update run configurations for Android Studio 4.1. 2020-11-09 12:43:29 +00:00
akwizgran
a17b154024 Update translations. 2020-11-09 12:42:13 +00:00
akwizgran
02ee678bab If using bridges, use meek if the network is IPv6-only. 2020-11-03 13:52:12 +00:00
akwizgran
f6bdbb1b80 Let Tor know if we're on an IPv6-only network. 2020-11-03 13:44:57 +00:00
Torsten Grote
64e1975cf1 Merge branch 'adaptive-icon' into 'master'
Add adaptive icon for API 26+ and Play Store icon

Closes #1456

See merge request briar/briar!1293
2020-11-03 11:55:12 +00:00
akwizgran
993502add0 Add adaptive icon for API 26+ and Play Store icon. 2020-11-03 11:35:53 +00:00
185 changed files with 3417 additions and 2219 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ configurations {
dependencies { dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
tor 'org.briarproject:tor-android:0.3.5.10@zip' tor 'org.briarproject:tor-android:0.3.5.12@zip'
tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip' tor 'org.briarproject:obfs4proxy-android:0.0.11-2@zip'
annotationProcessor 'com.google.dagger:dagger-compiler:2.24' annotationProcessor 'com.google.dagger:dagger-compiler:2.24'

View File

@@ -1,11 +1,15 @@
package org.briarproject.bramble.network; package org.briarproject.bramble.network;
import android.annotation.TargetApi;
import android.app.Application; import android.app.Application;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
@@ -19,6 +23,11 @@ import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler; import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.api.system.TaskScheduler.Cancellable; import org.briarproject.bramble.api.system.TaskScheduler.Cancellable;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -36,16 +45,22 @@ import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION; import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class AndroidNetworkManager implements NetworkManager, Service { class AndroidNetworkManager implements NetworkManager, Service {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(AndroidNetworkManager.class.getName()); getLogger(AndroidNetworkManager.class.getName());
// See android.net.wifi.WifiManager // See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION = private static final String WIFI_AP_STATE_CHANGED_ACTION =
@@ -54,7 +69,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
private final TaskScheduler scheduler; private final TaskScheduler scheduler;
private final EventBus eventBus; private final EventBus eventBus;
private final Executor eventExecutor; private final Executor eventExecutor;
private final Context appContext; private final Application app;
private final ConnectivityManager connectivityManager;
private final AtomicReference<Cancellable> connectivityCheck = private final AtomicReference<Cancellable> connectivityCheck =
new AtomicReference<>(); new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false); private final AtomicBoolean used = new AtomicBoolean(false);
@@ -67,7 +83,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
this.scheduler = scheduler; this.scheduler = scheduler;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventExecutor = eventExecutor; this.eventExecutor = eventExecutor;
this.appContext = app.getApplicationContext(); this.app = app;
connectivityManager = (ConnectivityManager)
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
} }
@Override @Override
@@ -82,24 +100,82 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter); app.registerReceiver(networkStateReceiver, filter);
} }
@Override @Override
public void stopService() { public void stopService() {
if (networkStateReceiver != null) if (networkStateReceiver != null)
appContext.unregisterReceiver(networkStateReceiver); app.unregisterReceiver(networkStateReceiver);
} }
@Override @Override
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
ConnectivityManager cm = (ConnectivityManager) NetworkInfo net = connectivityManager.getActiveNetworkInfo();
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) throw new AssertionError();
NetworkInfo net = cm.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected(); boolean connected = net != null && net.isConnected();
boolean wifi = connected && net.getType() == TYPE_WIFI; boolean wifi = false, ipv6Only = false;
return new NetworkStatus(connected, wifi); if (connected) {
wifi = net.getType() == TYPE_WIFI;
if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only();
else ipv6Only = areAllAvailableNetworksIpv6Only();
}
return new NetworkStatus(connected, wifi, ipv6Only);
}
/**
* Returns true if the
* {@link ConnectivityManager#getActiveNetwork() active network} has an
* IPv6 unicast address and no IPv4 addresses. The active network is
* assumed not to be a loopback interface.
*/
@TargetApi(23)
private boolean isActiveNetworkIpv6Only() {
Network net = connectivityManager.getActiveNetwork();
if (net == null) {
LOG.info("No active network");
return false;
}
LinkProperties props = connectivityManager.getLinkProperties(net);
if (props == null) {
LOG.info("No link properties for active network");
return false;
}
boolean hasIpv6Unicast = false;
for (LinkAddress linkAddress : props.getLinkAddresses()) {
InetAddress addr = linkAddress.getAddress();
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
return hasIpv6Unicast;
}
/**
* Returns true if the device has at least one network interface with an
* IPv6 unicast address and no interfaces with IPv4 addresses, excluding
* loopback interfaces and interfaces that are
* {@link NetworkInterface#isUp() down}. If this method returns true and
* the device has internet access then it's via IPv6 only.
*/
private boolean areAllAvailableNetworksIpv6Only() {
try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces == null) {
LOG.info("No network interfaces");
return false;
}
boolean hasIpv6Unicast = false;
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback() || !i.isUp()) continue;
for (InetAddress addr : list(i.getInetAddresses())) {
if (addr instanceof Inet4Address) return false;
if (!addr.isMulticastAddress()) hasIpv6Unicast = true;
}
}
return hasIpv6Unicast;
} catch (SocketException e) {
logException(LOG, WARNING, e);
return false;
}
} }
private void updateConnectionStatus() { private void updateConnectionStatus() {

View File

@@ -6,8 +6,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build; import android.os.Build;
import android.os.Parcel; import android.os.Parcel;
import android.os.StrictMode; import android.os.StrictMode;
@@ -17,12 +15,10 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.content.Context.WIFI_SERVICE;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.Secure.ANDROID_ID; import static android.provider.Settings.Secure.ANDROID_ID;
@@ -52,15 +48,6 @@ class AndroidSecureRandomProvider extends UnixSecureRandomProvider {
String id = Settings.Secure.getString(contentResolver, ANDROID_ID); String id = Settings.Secure.getString(contentResolver, ANDROID_ID);
if (id != null) out.writeUTF(id); if (id != null) out.writeUTF(id);
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
WifiManager wm = (WifiManager) appContext.getApplicationContext()
.getSystemService(WIFI_SERVICE);
if (wm != null) {
List<WifiConfiguration> configs = wm.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs)
parcel.writeParcelable(config, 0);
}
}
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt != null) { if (bt != null) {
for (BluetoothDevice device : bt.getBondedDevices()) for (BluetoothDevice device : bt.getBondedDevices())

View File

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

View File

@@ -8,11 +8,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault @NotNullByDefault
public class NetworkStatus { public class NetworkStatus {
private final boolean connected, wifi; private final boolean connected, wifi, ipv6Only;
public NetworkStatus(boolean connected, boolean wifi) { public NetworkStatus(boolean connected, boolean wifi, boolean ipv6Only) {
this.connected = connected; this.connected = connected;
this.wifi = wifi; this.wifi = wifi;
this.ipv6Only = ipv6Only;
} }
public boolean isConnected() { public boolean isConnected() {
@@ -22,4 +23,8 @@ public class NetworkStatus {
public boolean isWifi() { public boolean isWifi() {
return wifi; return wifi;
} }
public boolean isIpv6Only() {
return ipv6Only;
}
} }

View File

@@ -23,6 +23,8 @@ public interface DevReporter {
/** /**
* Sends any reports previously stored on disk. * Sends any reports previously stored on disk.
*
* @return The number of reports that were sent.
*/ */
void sendReports(); int sendReports();
} }

View File

@@ -11,6 +11,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.Base32; import org.briarproject.bramble.util.Base32;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import javax.inject.Inject; import javax.inject.Inject;
@@ -52,7 +53,7 @@ class PendingContactFactoryImpl implements PendingContactFactory {
byte[] raw = new byte[RAW_LINK_BYTES]; byte[] raw = new byte[RAW_LINK_BYTES];
raw[0] = FORMAT_VERSION; raw[0] = FORMAT_VERSION;
arraycopy(encoded, 0, raw, 1, encoded.length); arraycopy(encoded, 0, raw, 1, encoded.length);
return "briar://" + Base32.encode(raw).toLowerCase(); return "briar://" + Base32.encode(raw).toLowerCase(Locale.US);
} }
private PublicKey parseHandshakeLink(String link) throws FormatException { private PublicKey parseHandshakeLink(String link) throws FormatException {

View File

@@ -863,6 +863,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!state.isTorRunning()) return; if (!state.isTorRunning()) return;
boolean online = status.isConnected(); boolean online = status.isConnected();
boolean wifi = status.isWifi(); boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
String country = locationUtils.getCurrentCountry(); String country = locationUtils.getCurrentCountry();
boolean blocked = boolean blocked =
circumventionProvider.isTorProbablyBlocked(country); circumventionProvider.isTorProbablyBlocked(country);
@@ -879,7 +880,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC; boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("Online: " + online + ", wifi: " + wifi); LOG.info("Online: " + online + ", wifi: " + wifi
+ ", IPv6 only: " + ipv6Only);
if (country.isEmpty()) LOG.info("Country code unknown"); if (country.isEmpty()) LOG.info("Country code unknown");
else LOG.info("Country code: " + country); else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging); LOG.info("Charging: " + charging);
@@ -916,7 +918,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true; enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES || if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) { (automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) { if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges"); LOG.info("Using meek bridges");
enableBridges = true; enableBridges = true;
useMeek = true; useMeek = true;
@@ -942,6 +945,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enableNetwork) { if (enableNetwork) {
enableBridges(enableBridges, useMeek); enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding); enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
} }
enableNetwork(enableNetwork); enableNetwork(enableNetwork);
} catch (IOException e) { } catch (IOException e) {
@@ -954,6 +958,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
controlConnection.setConf("ConnectionPadding", enable ? "1" : "0"); controlConnection.setConf("ConnectionPadding", enable ? "1" : "0");
} }
private void useIpv6(boolean ipv6Only) throws IOException {
controlConnection.setConf("ClientUseIPv4", ipv6Only ? "0" : "1");
controlConnection.setConf("ClientUseIPv6", ipv6Only ? "1" : "0");
}
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
protected class PluginState { protected class PluginState {

View File

@@ -29,6 +29,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
@@ -100,11 +101,12 @@ class DevReporterImpl implements DevReporter, EventListener {
} }
@Override @Override
public void sendReports() { public int sendReports() {
File reportDir = devConfig.getReportDir(); File reportDir = devConfig.getReportDir();
File[] reports = reportDir.listFiles(); File[] reports = reportDir.listFiles();
int reportsSent = 0;
if (reports == null || reports.length == 0) if (reports == null || reports.length == 0)
return; // No reports to send return reportsSent; // No reports to send
LOG.info("Sending reports to developers"); LOG.info("Sending reports to developers");
for (File f : reports) { for (File f : reports) {
@@ -116,13 +118,15 @@ class DevReporterImpl implements DevReporter, EventListener {
in = new FileInputStream(f); in = new FileInputStream(f);
IoUtils.copyAndClose(in, out); IoUtils.copyAndClose(in, out);
f.delete(); f.delete();
reportsSent++;
} catch (IOException e) { } catch (IOException e) {
LOG.log(WARNING, "Failed to send reports", e); LOG.log(WARNING, "Failed to send reports", e);
tryToClose(out, LOG, WARNING); tryToClose(out, LOG, WARNING);
tryToClose(in, LOG, WARNING); tryToClose(in, LOG, WARNING);
return; return reportsSent;
} }
} }
LOG.info("Reports sent"); if (LOG.isLoggable(INFO)) LOG.info(reportsSent + " report(s) sent");
return reportsSent;
} }
} }

View File

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

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.Enumeration; import java.util.Enumeration;
@@ -14,8 +16,8 @@ import javax.inject.Inject;
import static java.net.NetworkInterface.getNetworkInterfaces; import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list; 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.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -23,7 +25,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class JavaNetworkManager implements NetworkManager { class JavaNetworkManager implements NetworkManager {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(JavaNetworkManager.class.getName()); getLogger(JavaNetworkManager.class.getName());
@Inject @Inject
JavaNetworkManager() { JavaNetworkManager() {
@@ -31,26 +33,28 @@ class JavaNetworkManager implements NetworkManager {
@Override @Override
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
boolean connected = false; boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
try { try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces(); Enumeration<NetworkInterface> interfaces = getNetworkInterfaces();
if (interfaces != null) { if (interfaces == null) {
LOG.info("No network interfaces");
} else {
for (NetworkInterface i : list(interfaces)) { for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback()) continue; if (i.isLoopback() || !i.isUp()) continue;
if (i.isUp() && i.getInetAddresses().hasMoreElements()) { for (InetAddress addr : list(i.getInetAddresses())) {
if (LOG.isLoggable(INFO)) {
LOG.info("Interface " + i.getDisplayName() +
" is up with at least one address.");
}
connected = true; connected = true;
break; if (addr instanceof Inet4Address) {
hasIpv4 = true;
} else if (!addr.isMulticastAddress()) {
hasIpv6Unicast = true;
}
} }
} }
} }
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }
return new NetworkStatus(connected, false); return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast);
} }
} }

View File

@@ -24,7 +24,7 @@ dependencyVerification {
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8', 'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8', 'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642', 'org.briarproject:obfs4proxy:0.0.7:obfs4proxy-0.0.7.zip:5b2f693262ce43a7e130f7cc7d5d1617925330640a2eb6d71085e95df8ee0642',
'org.briarproject:tor:0.3.5.10:tor-0.3.5.10.zip:7b387d3523ae8af289c23be59dc4c64ec5d3721385d7825a09705095e3318d5c', 'org.briarproject:tor:0.3.5.12:tor-0.3.5.12.zip:2f542c4befd216f2226bf7c76e3b8b2d99af6f146a8cb28bf727f42014587006',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -93,20 +93,19 @@ dependencies {
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android') implementation project(':bramble-android')
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.0.0' implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.1.0-beta01' implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
implementation 'ch.acra:acra:4.11'
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:3.0.1' implementation 'de.hdodenhof:circleimageview:3.0.1'
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24 implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.0.0' implementation 'uk.co.samuelwall:material-tap-target-prompt:3.0.0'
implementation 'com.vanniktech:emoji-google:0.6.0' implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21
implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1' implementation 'com.github.kobakei:MaterialFabSpeedDial:1.2.1'
implementation 'com.github.chrisbanes:PhotoView:2.3.0' implementation 'com.github.chrisbanes:PhotoView:2.3.0'
def glideVersion = '4.10.0' def glideVersion = '4.10.0'
@@ -120,23 +119,24 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0' compileOnly 'javax.annotation:jsr250-api:1.0'
def espressoVersion = '3.2.0' def espressoVersion = '3.3.0'
def jmockVersion = '2.8.2' def jmockVersion = '2.8.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput') testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput') testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'androidx.test:runner:1.2.0' testImplementation 'androidx.test:runner:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.1' testImplementation 'androidx.test.ext:junit:1.1.2'
testImplementation 'androidx.fragment:fragment-testing:1.1.0' testImplementation 'androidx.fragment:fragment-testing:1.2.5'
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion" testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-core:3.1.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13.1'
testImplementation "org.jmock:jmock:$jmockVersion" testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion" testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion" testImplementation "org.jmock:jmock-legacy:$jmockVersion"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput') androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion" androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"

View File

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

View File

@@ -65,30 +65,43 @@
<service <service
android:name="org.briarproject.briar.android.NotificationCleanupService" android:name="org.briarproject.briar.android.NotificationCleanupService"
android:exported="false"></service> android:exported="false" />
<activity <activity
android:name="org.briarproject.briar.android.reporting.DevReportActivity" android:name="org.briarproject.briar.android.reporting.CrashReportActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:exported="false" android:exported="false"
android:finishOnTaskLaunch="true" android:finishOnTaskLaunch="true"
android:label="@string/crash_report_title" android:label="@string/crash_report_title"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:process=":briar_error_handler"
android:theme="@style/BriarTheme.NoActionBar" android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden"></activity> android:windowSoftInputMode="adjustResize|stateHidden" />
<activity
android:name="org.briarproject.briar.android.reporting.FeedbackActivity"
android:exported="false"
android:label="@string/feedback_title"
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity"
android:theme="@style/BriarTheme.NoActionBar"
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity <activity
android:name="org.briarproject.briar.android.splash.ExpiredActivity" android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name"></activity> android:label="@string/app_name" />
<activity <activity
android:name="org.briarproject.briar.android.login.StartupActivity" android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name"></activity> android:label="@string/app_name" />
<activity <activity
android:name="org.briarproject.briar.android.account.SetupActivity" android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title" android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible"></activity> android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
<activity <activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity" android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
@@ -346,7 +359,7 @@
<activity <activity
android:name="org.briarproject.briar.android.StartupFailureActivity" android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title"></activity> android:label="@string/startup_failed_activity_title" />
<activity <activity
android:name="org.briarproject.briar.android.settings.SettingsActivity" android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -412,11 +425,11 @@
<activity <activity
android:name="org.briarproject.briar.android.logout.ExitActivity" android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay"></activity> android:theme="@android:style/Theme.NoDisplay" />
<activity <activity
android:name=".android.logout.HideUiActivity" android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay"></activity> android:theme="@android:style/Theme.NoDisplay" />
<activity <activity
android:name=".android.account.UnlockActivity" android:name=".android.account.UnlockActivity"
@@ -436,4 +449,27 @@
android:theme="@style/BriarTheme" /> android:theme="@style/BriarTheme" />
</application> </application>
<queries>
<package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" />
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<!-- white-listing the intents below does not seem necessary,
but they are still included in case modified Android versions require it -->
<intent>
<action android:name="android.bluetooth.adapter.action.REQUEST_DISCOVERABLE" />
</intent>
<intent>
<action android:name="android.settings.CHANNEL_NOTIFICATION_SETTINGS" />
</intent>
</queries>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.identity.IdentityManager; import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; import org.briarproject.bramble.api.keyagreement.KeyAgreementTask;
@@ -33,7 +34,6 @@ import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.reporting.BriarReportSender;
import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -86,6 +86,8 @@ public interface AndroidComponent
@DatabaseExecutor @DatabaseExecutor
Executor databaseExecutor(); Executor databaseExecutor();
TransactionManager transactionManager();
MessageTracker messageTracker(); MessageTracker messageTracker();
LifecycleManager lifecycleManager(); LifecycleManager lifecycleManager();
@@ -173,8 +175,6 @@ public interface AndroidComponent
void inject(BriarService briarService); void inject(BriarService briarService);
void inject(BriarReportSender briarReportSender);
void inject(NotificationCleanupService notificationCleanupService); void inject(NotificationCleanupService notificationCleanupService);
void inject(EmojiTextInputView textInputView); void inject(EmojiTextInputView textInputView);

View File

@@ -109,7 +109,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Nullable @Nullable
private GroupId blockedGroup = null; private GroupId blockedGroup = null;
private boolean blockSignInReminder = false; private boolean blockSignInReminder = false;
private boolean blockBlogs = false; private boolean blockForums = false, blockGroups = false,
blockBlogs = false;
private long lastSound = 0; private long lastSound = 0;
private volatile Settings settings = new Settings(); private volatile Settings settings = new Settings();
@@ -223,8 +224,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
if (s.getNamespace().equals(SETTINGS_NAMESPACE)) if (s.getNamespace().equals(SETTINGS_NAMESPACE))
settings = s.getSettings(); settings = s.getSettings();
} else if (e instanceof ConversationMessageReceivedEvent) { } else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent p = ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent) e; (ConversationMessageReceivedEvent<?>) e;
showContactNotification(p.getContactId()); showContactNotification(p.getContactId());
} else if (e instanceof GroupMessageAddedEvent) { } else if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
@@ -385,6 +386,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread @UiThread
private void showGroupMessageNotification(GroupId g) { private void showGroupMessageNotification(GroupId g) {
if (blockGroups) return;
if (g.equals(blockedGroup)) return; if (g.equals(blockedGroup)) return;
groupCounts.add(g); groupCounts.add(g);
updateGroupMessageNotification(true); updateGroupMessageNotification(true);
@@ -452,6 +454,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread @UiThread
private void showForumPostNotification(GroupId g) { private void showForumPostNotification(GroupId g) {
if (blockForums) return;
if (g.equals(blockedGroup)) return; if (g.equals(blockedGroup)) return;
forumCounts.add(g); forumCounts.add(g);
updateForumPostNotification(true); updateForumPostNotification(true);
@@ -681,6 +684,26 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
}); });
} }
@Override
public void blockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = true);
}
@Override
public void unblockAllForumPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockForums = false);
}
@Override
public void blockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = true);
}
@Override
public void unblockAllGroupMessageNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockGroups = false);
}
@Override @Override
public void blockAllBlogPostNotifications() { public void blockAllBlogPostNotifications() {
androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true); androidExecutor.runOnUiThread((Runnable) () -> blockBlogs = true);

View File

@@ -28,9 +28,12 @@ import org.briarproject.bramble.plugin.tor.AndroidTorPluginFactory;
import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.LockManagerImpl; import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.viewmodel.ViewModelModule; import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
@@ -63,7 +66,11 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
ContactExchangeModule.class, ContactExchangeModule.class,
LoginModule.class, LoginModule.class,
NavDrawerModule.class, NavDrawerModule.class,
ViewModelModule.class ViewModelModule.class,
DevReportModule.class,
// below need to be within same scope as ViewModelProvider.Factory
ForumModule.BindsModule.class,
GroupListModule.class,
}) })
public class AppModule { public class AppModule {
@@ -196,7 +203,10 @@ public class AppModule {
ScreenFilterMonitor provideScreenFilterMonitor( ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager, LifecycleManager lifecycleManager,
ScreenFilterMonitorImpl screenFilterMonitor) { ScreenFilterMonitorImpl screenFilterMonitor) {
lifecycleManager.registerService(screenFilterMonitor); if (SDK_INT <= 29) {
// this keeps track of installed apps and does not work on API 30+
lifecycleManager.registerService(screenFilterMonitor);
}
return screenFilterMonitor; return screenFilterMonitor;
} }

View File

@@ -14,19 +14,13 @@ import android.preference.PreferenceManager;
import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.google.GoogleEmojiProvider; import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleAndroidEagerSingletons; import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAppComponent; import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarReportPrimer; import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection; import java.util.Collection;
@@ -34,50 +28,14 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
import androidx.annotation.NonNull;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.BRAND;
import static org.acra.ReportField.BUILD_CONFIG;
import static org.acra.ReportField.CRASH_CONFIGURATION;
import static org.acra.ReportField.CUSTOM_DATA;
import static org.acra.ReportField.DEVICE_FEATURES;
import static org.acra.ReportField.DISPLAY;
import static org.acra.ReportField.INITIAL_CONFIGURATION;
import static org.acra.ReportField.PACKAGE_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.PRODUCT;
import static org.acra.ReportField.REPORT_ID;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_APP_START_DATE;
import static org.acra.ReportField.USER_CRASH_DATE;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@ReportsCrashes(
reportPrimerClass = BriarReportPrimer.class,
logcatArguments = {"-d", "-v", "time", "*:I"},
reportSenderFactoryClasses = {BriarReportSenderFactory.class},
mode = ReportingInteractionMode.DIALOG,
reportDialogClass = DevReportActivity.class,
resDialogOkToast = R.string.dev_report_saved,
deleteOldUnsentReportsOnApplicationStart = false,
buildConfigClass = BuildConfig.class,
customReportContent = {
REPORT_ID,
APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME,
PHONE_MODEL, ANDROID_VERSION, BRAND, PRODUCT,
BUILD_CONFIG,
CUSTOM_DATA,
STACK_TRACE,
INITIAL_CONFIGURATION, CRASH_CONFIGURATION,
DISPLAY, DEVICE_FEATURES,
USER_APP_START_DATE, USER_CRASH_DATE
}
)
public class BriarApplicationImpl extends Application public class BriarApplicationImpl extends Application
implements BriarApplication { implements BriarApplication {
@@ -85,12 +43,15 @@ public class BriarApplicationImpl extends Application
getLogger(BriarApplicationImpl.class.getName()); getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler(); private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent; private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs; private volatile SharedPreferences prefs;
@Override @Override
protected void attachBaseContext(Context base) { protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null) if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base); prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here. // Loading the language needs to be done here.
@@ -98,7 +59,6 @@ public class BriarApplicationImpl extends Application
super.attachBaseContext( super.attachBaseContext(
Localizer.getInstance().setLocale(base)); Localizer.getInstance().setLocale(base));
setTheme(base, prefs); setTheme(base, prefs);
ACRA.init(this);
} }
@Override @Override
@@ -144,7 +104,7 @@ public class BriarApplicationImpl extends Application
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
Localizer.getInstance().setLocale(this); Localizer.getInstance().setLocale(this);
} }

View File

@@ -58,7 +58,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Logger.getLogger(ScreenFilterMonitorImpl.class.getName()); Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
/* /*
* Ignore Play Services if it uses this package name and public key - it's * Ignore Play Services if it uses this package name and public key - it's
* effectively a system app, but not flagged as such on older systems * effectively a system app, but not flagged as such on older systems
*/ */
private static final String PLAY_SERVICES_PACKAGE = private static final String PLAY_SERVICES_PACKAGE =
@@ -108,7 +108,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet()); Collections.emptySet());
List<AppDetails> apps = new ArrayList<>(); List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packageInfos = @SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS); pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) { for (PackageInfo packageInfo : packageInfos) {
if (!allowed.contains(packageInfo.packageName) if (!allowed.contains(packageInfo.packageName)

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.util.UiUtils.setError; import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -55,7 +54,7 @@ public class SetPasswordFragment extends SetupFragment {
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(getString(R.string.setup_password_intro)); requireActivity().setTitle(getString(R.string.setup_password_intro));
View v = inflater.inflate(R.layout.fragment_setup_password, container, View v = inflater.inflate(R.layout.fragment_setup_password, container,
false); false);

View File

@@ -60,12 +60,14 @@ import org.briarproject.briar.android.privategroup.creation.GroupInviteFragment;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationModule;
import org.briarproject.briar.android.privategroup.list.GroupListFragment; import org.briarproject.briar.android.privategroup.list.GroupListFragment;
import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity;
import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule; import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule; import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity; import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment; import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment;
import org.briarproject.briar.android.reporting.CrashFragment;
import org.briarproject.briar.android.reporting.CrashReportActivity;
import org.briarproject.briar.android.reporting.ReportFormFragment;
import org.briarproject.briar.android.settings.SettingsActivity; import org.briarproject.briar.android.settings.SettingsActivity;
import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity; import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -91,7 +93,6 @@ import dagger.Component;
ForumModule.class, ForumModule.class,
GroupInvitationModule.class, GroupInvitationModule.class,
GroupConversationModule.class, GroupConversationModule.class,
GroupListModule.class,
GroupMemberModule.class, GroupMemberModule.class,
GroupRevealModule.class, GroupRevealModule.class,
SharingModule.class SharingModule.class
@@ -184,6 +185,8 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity); void inject(PendingContactListActivity activity);
void inject(CrashReportActivity crashReportActivity);
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
@@ -234,4 +237,8 @@ public interface ActivityComponent {
void inject(ImageFragment imageFragment); void inject(ImageFragment imageFragment);
void inject(ReportFormFragment reportFormFragment);
void inject(CrashFragment crashFragment);
} }

View File

@@ -6,7 +6,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
@@ -18,7 +17,6 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment; import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.widget.TapSafeFrameLayout; import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener; import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
@@ -40,9 +38,11 @@ import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static androidx.lifecycle.Lifecycle.State.STARTED; import static androidx.lifecycle.Lifecycle.State.STARTED;
import static java.util.Collections.emptyList;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS; import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
@@ -50,7 +50,6 @@ import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
/** /**
* Warning: Some activities don't extend {@link BaseActivity}. * Warning: Some activities don't extend {@link BaseActivity}.
* E.g. {@link DevReportActivity}
*/ */
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -123,6 +122,7 @@ public abstract class BaseActivity extends AppCompatActivity
return new ActivityModule(this); return new ActivityModule(this);
} }
// TODO use a test module where this is used in tests
protected ForumModule getForumModule() { protected ForumModule getForumModule() {
return new ForumModule(); return new ForumModule();
} }
@@ -202,9 +202,15 @@ public abstract class BaseActivity extends AppCompatActivity
// If the dialog is already visible, filter the tap // If the dialog is already visible, filter the tap
ScreenFilterDialogFragment f = findDialogFragment(); ScreenFilterDialogFragment f = findDialogFragment();
if (f != null && f.isVisible()) return false; if (f != null && f.isVisible()) return false;
Collection<AppDetails> apps = screenFilterMonitor.getApps(); Collection<AppDetails> apps;
// If all overlay apps have been allowed, allow the tap // querying all apps is only possible at API 29 and below
if (apps.isEmpty()) return true; if (SDK_INT <= 29) {
apps = screenFilterMonitor.getApps();
// If all overlay apps have been allowed, allow the tap
if (apps.isEmpty()) return true;
} else {
apps = emptyList();
}
// Show dialog unless onSaveInstanceState() has been called, see #1112 // Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) { if (!fm.isStateSaved()) {
@@ -241,7 +247,7 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@UiThread @UiThread
public void handleDbException(DbException e) { public void handleException(Exception e) {
supportFinishAfterTransition(); supportFinishAfterTransition();
} }
@@ -266,7 +272,12 @@ public abstract class BaseActivity extends AppCompatActivity
private void protectToolbar() { private void protectToolbar() {
findToolbar(); findToolbar();
if (toolbar != null) { if (toolbar != null) {
boolean filter = !screenFilterMonitor.getApps().isEmpty(); boolean filter;
if (SDK_INT <= 29) {
filter = !screenFilterMonitor.getApps().isEmpty();
} else {
filter = true;
}
UiUtils.setFilterTouchesWhenObscured(toolbar, filter); UiUtils.setFilterTouchesWhenObscured(toolbar, filter);
} }
} }

View File

@@ -129,10 +129,6 @@ public abstract class BriarActivity extends BaseActivity {
lockManager.onActivityStop(); lockManager.onActivityStop();
} }
protected boolean signedIn() {
return briarController.accountSignedIn();
}
/** /**
* Sets the transition animations. * Sets the transition animations.
* *

View File

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

View File

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

View File

@@ -58,7 +58,7 @@ public class BlogPostFragment extends BasePostFragment implements BlogListener {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

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

View File

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

View File

@@ -74,7 +74,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments()); Bundle args = requireArguments();
GroupId blogId = GroupId blogId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID))); new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId = MessageId postId =
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
new UiExceptionHandler<DbException>(this) { new UiExceptionHandler<DbException>(this) {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
finish(); finish();

View File

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

View File

@@ -9,7 +9,11 @@ import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchPendingContactException; import org.briarproject.bramble.api.db.NoSuchPendingContactException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.LiveResult; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
@@ -21,7 +25,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -31,14 +34,12 @@ import static org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_R
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class AddContactViewModel extends AndroidViewModel { public class AddContactViewModel extends DbViewModel {
private final static Logger LOG = private final static Logger LOG =
getLogger(AddContactViewModel.class.getName()); getLogger(AddContactViewModel.class.getName());
private final ContactManager contactManager; private final ContactManager contactManager;
@DatabaseExecutor
private final Executor dbExecutor;
private final MutableLiveData<String> handshakeLink = private final MutableLiveData<String> handshakeLink =
new MutableLiveData<>(); new MutableLiveData<>();
@@ -52,10 +53,12 @@ public class AddContactViewModel extends AndroidViewModel {
@Inject @Inject
AddContactViewModel(Application application, AddContactViewModel(Application application,
ContactManager contactManager, ContactManager contactManager,
@DatabaseExecutor Executor dbExecutor) { @DatabaseExecutor Executor dbExecutor,
super(application); LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.contactManager = contactManager; this.contactManager = contactManager;
this.dbExecutor = dbExecutor;
} }
void onCreate() { void onCreate() {
@@ -63,7 +66,7 @@ public class AddContactViewModel extends AndroidViewModel {
} }
private void loadHandshakeLink() { private void loadHandshakeLink() {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
handshakeLink.postValue(contactManager.getHandshakeLink()); handshakeLink.postValue(contactManager.getHandshakeLink());
} catch (DbException e) { } catch (DbException e) {
@@ -102,7 +105,7 @@ public class AddContactViewModel extends AndroidViewModel {
void addContact(String nickname) { void addContact(String nickname) {
if (remoteHandshakeLink == null) throw new IllegalStateException(); if (remoteHandshakeLink == null) throw new IllegalStateException();
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
contactManager.addPendingContact(remoteHandshakeLink, nickname); contactManager.addPendingContact(remoteHandshakeLink, nickname);
addContactResult.postValue(new LiveResult<>(true)); addContactResult.postValue(new LiveResult<>(true));
@@ -122,11 +125,11 @@ public class AddContactViewModel extends AndroidViewModel {
} }
public void updatePendingContact(String name, PendingContact p) { public void updatePendingContact(String name, PendingContact p) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
contactManager.removePendingContact(p.getId()); contactManager.removePendingContact(p.getId());
addContact(name); addContact(name);
} catch(NoSuchPendingContactException e) { } catch (NoSuchPendingContactException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
// no error in UI as pending contact was converted into contact // no error in UI as pending contact was converted into contact
} catch (DbException e) { } catch (DbException e) {

View File

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

View File

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

View File

@@ -11,12 +11,16 @@ import org.briarproject.bramble.api.contact.event.PendingContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent; import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.rendezvous.RendezvousPoller; import org.briarproject.bramble.api.rendezvous.RendezvousPoller;
import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent; import org.briarproject.bramble.api.rendezvous.event.RendezvousPollEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -26,7 +30,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -36,14 +39,12 @@ import static org.briarproject.bramble.api.contact.PendingContactState.OFFLINE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class PendingContactListViewModel extends AndroidViewModel public class PendingContactListViewModel extends DbViewModel
implements EventListener { implements EventListener {
private final Logger LOG = private final Logger LOG =
getLogger(PendingContactListViewModel.class.getName()); getLogger(PendingContactListViewModel.class.getName());
@DatabaseExecutor
private final Executor dbExecutor;
private final ContactManager contactManager; private final ContactManager contactManager;
private final RendezvousPoller rendezvousPoller; private final RendezvousPoller rendezvousPoller;
private final EventBus eventBus; private final EventBus eventBus;
@@ -56,11 +57,13 @@ public class PendingContactListViewModel extends AndroidViewModel
@Inject @Inject
PendingContactListViewModel(Application application, PendingContactListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
ContactManager contactManager, ContactManager contactManager,
RendezvousPoller rendezvousPoller, RendezvousPoller rendezvousPoller,
EventBus eventBus) { EventBus eventBus) {
super(application); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.dbExecutor = dbExecutor;
this.contactManager = contactManager; this.contactManager = contactManager;
this.rendezvousPoller = rendezvousPoller; this.rendezvousPoller = rendezvousPoller;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -87,7 +90,7 @@ public class PendingContactListViewModel extends AndroidViewModel
} }
private void loadPendingContacts() { private void loadPendingContacts() {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Collection<Pair<PendingContact, PendingContactState>> pairs = Collection<Pair<PendingContact, PendingContactState>> pairs =
contactManager.getPendingContacts(); contactManager.getPendingContacts();
@@ -113,7 +116,7 @@ public class PendingContactListViewModel extends AndroidViewModel
} }
void removePendingContact(PendingContactId id) { void removePendingContact(PendingContactId id) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
contactManager.removePendingContact(id); contactManager.removePendingContact(id);
} catch (DbException e) { } catch (DbException e) {

View File

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

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.controller;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@Deprecated
@NotNullByDefault @NotNullByDefault
public interface DbController { public interface DbController {

View File

@@ -11,6 +11,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject; import javax.inject.Inject;
@Immutable @Immutable
@Deprecated
@NotNullByDefault @NotNullByDefault
public class DbControllerImpl implements DbController { public class DbControllerImpl implements DbController {

View File

@@ -98,7 +98,6 @@ import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.selection.Selection; import androidx.recyclerview.selection.Selection;
import androidx.recyclerview.selection.SelectionPredicates; import androidx.recyclerview.selection.SelectionPredicates;
import androidx.recyclerview.selection.SelectionTracker; import androidx.recyclerview.selection.SelectionTracker;
@@ -224,8 +223,9 @@ public class ConversationActivity extends BriarActivity
if (id == -1) throw new IllegalStateException(); if (id == -1) throw new IllegalStateException();
contactId = new ContactId(id); contactId = new ContactId(id);
viewModel = ViewModelProviders.of(this, viewModelFactory) viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ConversationViewModel.class); .get(ConversationViewModel.class);
viewModel.setContactId(contactId);
attachmentRetriever = viewModel.getAttachmentRetriever(); attachmentRetriever = viewModel.getAttachmentRetriever();
setContentView(R.layout.activity_conversation); setContentView(R.layout.activity_conversation);
@@ -330,16 +330,6 @@ public class ConversationActivity extends BriarActivity
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@Override
public void onResume() {
super.onResume();
// Trigger loading of contact data, noop if data was loaded already.
//
// We can only start loading data *after* we are sure
// the user has signed in. After sign-in, onCreate() isn't run again.
if (signedIn()) viewModel.setContactId(contactId);
}
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();

View File

@@ -15,17 +15,20 @@ import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId; import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message; import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.attachment.AttachmentCreator; import org.briarproject.briar.android.attachment.AttachmentCreator;
import org.briarproject.briar.android.attachment.AttachmentManager; import org.briarproject.briar.android.attachment.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult; import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentRetriever; import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.AttachmentHeader; import org.briarproject.briar.api.messaging.AttachmentHeader;
@@ -44,7 +47,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations; import androidx.lifecycle.Transformations;
@@ -59,10 +61,10 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce; import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
@NotNullByDefault @NotNullByDefault
public class ConversationViewModel extends AndroidViewModel public class ConversationViewModel extends DbViewModel
implements EventListener, AttachmentManager { implements EventListener, AttachmentManager {
private static Logger LOG = private static final Logger LOG =
getLogger(ConversationViewModel.class.getName()); getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE = private static final String SHOW_ONBOARDING_IMAGE =
@@ -70,8 +72,6 @@ public class ConversationViewModel extends AndroidViewModel
private static final String SHOW_ONBOARDING_INTRODUCTION = private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction"; "showOnboardingIntroduction";
@DatabaseExecutor
private final Executor dbExecutor;
private final TransactionManager db; private final TransactionManager db;
private final EventBus eventBus; private final EventBus eventBus;
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
@@ -105,7 +105,9 @@ public class ConversationViewModel extends AndroidViewModel
@Inject @Inject
ConversationViewModel(Application application, ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db, TransactionManager db,
AndroidExecutor androidExecutor,
EventBus eventBus, EventBus eventBus,
MessagingManager messagingManager, MessagingManager messagingManager,
ContactManager contactManager, ContactManager contactManager,
@@ -113,8 +115,7 @@ public class ConversationViewModel extends AndroidViewModel
PrivateMessageFactory privateMessageFactory, PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator) { AttachmentCreator attachmentCreator) {
super(application); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.dbExecutor = dbExecutor;
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
@@ -143,7 +144,7 @@ public class ConversationViewModel extends AndroidViewModel
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e; AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) { if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received"); LOG.info("Attachment received");
dbExecutor.execute(() -> attachmentRetriever runOnDbThread(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId())); .loadAttachmentItem(a.getMessageId()));
} }
} }
@@ -163,7 +164,7 @@ public class ConversationViewModel extends AndroidViewModel
} }
private void loadContact(ContactId contactId) { private void loadContact(ContactId contactId) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
long start = now(); long start = now();
Contact c = contactManager.getContact(contactId); Contact c = contactManager.getContact(contactId);
@@ -181,7 +182,7 @@ public class ConversationViewModel extends AndroidViewModel
} }
void markMessageRead(GroupId g, MessageId m) { void markMessageRead(GroupId g, MessageId m) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
long start = now(); long start = now();
messagingManager.setReadFlag(g, m, true); messagingManager.setReadFlag(g, m, true);
@@ -193,7 +194,7 @@ public class ConversationViewModel extends AndroidViewModel
} }
void setContactAlias(String alias) { void setContactAlias(String alias) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
contactManager.setContactAlias(requireNonNull(contactId), contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias); alias.isEmpty() ? null : alias);
@@ -296,7 +297,7 @@ public class ConversationViewModel extends AndroidViewModel
@UiThread @UiThread
private void storeMessage(PrivateMessage m) { private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId()); attachmentCreator.onAttachmentsSent(m.getMessage().getId());
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
long start = now(); long start = now();
messagingManager.addLocalMessage(m); messagingManager.addLocalMessage(m);
@@ -356,7 +357,7 @@ public class ConversationViewModel extends AndroidViewModel
@UiThread @UiThread
void recheckFeaturesAndOnboarding(ContactId contactId) { void recheckFeaturesAndOnboarding(ContactId contactId) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
checkFeaturesAndOnboarding(contactId); checkFeaturesAndOnboarding(contactId);
} catch (DbException e) { } catch (DbException e) {

View File

@@ -27,6 +27,7 @@ import javax.inject.Inject;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders; import androidx.lifecycle.ViewModelProviders;
@@ -78,7 +79,7 @@ public class ImageFragment extends Fragment
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Bundle args = requireNonNull(getArguments()); Bundle args = requireArguments();
attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION)); attachment = requireNonNull(args.getParcelable(ATTACHMENT_POSITION));
isFirst = args.getBoolean(IS_FIRST); isFirst = args.getBoolean(IS_FIRST);
conversationItemId = conversationItemId =
@@ -93,7 +94,7 @@ public class ImageFragment extends Fragment
View v = inflater.inflate(R.layout.fragment_image, container, View v = inflater.inflate(R.layout.fragment_image, container,
false); false);
viewModel = ViewModelProviders.of(requireNonNull(getActivity()), viewModel = ViewModelProviders.of(requireActivity(),
viewModelFactory).get(ImageViewModel.class); viewModelFactory).get(ImageViewModel.class);
photoView = v.findViewById(R.id.photoView); photoView = v.findViewById(R.id.photoView);
@@ -110,8 +111,9 @@ public class ImageFragment extends Fragment
photoView.setImageResource(R.drawable.ic_image_missing); photoView.setImageResource(R.drawable.ic_image_missing);
startPostponedTransition(); startPostponedTransition();
// state is not final, so observe state changes // state is not final, so observe state changes
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.getOnAttachmentReceived(attachment.getMessageId()) viewModel.getOnAttachmentReceived(attachment.getMessageId())
.observeEvent(this, this::onAttachmentReceived); .observeEvent(owner, this::onAttachmentReceived);
} }
return v; return v;

View File

@@ -7,13 +7,17 @@ import android.view.View;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; 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.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import org.briarproject.briar.api.messaging.Attachment; import org.briarproject.briar.api.messaging.Attachment;
@@ -37,7 +41,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.AndroidViewModel;
import static android.media.MediaScannerConnection.scanFile; import static android.media.MediaScannerConnection.scanFile;
import static android.os.Environment.DIRECTORY_PICTURES; import static android.os.Environment.DIRECTORY_PICTURES;
@@ -49,20 +52,18 @@ import static org.briarproject.bramble.util.IoUtils.copyAndClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
public class ImageViewModel extends AndroidViewModel implements EventListener { public class ImageViewModel extends DbViewModel implements EventListener {
private static Logger LOG = getLogger(ImageViewModel.class.getName()); private static final Logger LOG = getLogger(ImageViewModel.class.getName());
private final MessagingManager messagingManager; private final MessagingManager messagingManager;
private final EventBus eventBus; private final EventBus eventBus;
@DatabaseExecutor
private final Executor dbExecutor;
@IoExecutor @IoExecutor
private final Executor ioExecutor; private final Executor ioExecutor;
private boolean receivedAttachmentsInitialized = false; private boolean receivedAttachmentsInitialized = false;
private HashMap<MessageId, MutableLiveEvent<Boolean>> receivedAttachments = private final HashMap<MessageId, MutableLiveEvent<Boolean>>
new HashMap<>(); receivedAttachments = new HashMap<>();
/** /**
* true means there was an error saving the image, false if image was saved. * true means there was an error saving the image, false if image was saved.
@@ -75,13 +76,16 @@ public class ImageViewModel extends AndroidViewModel implements EventListener {
@Inject @Inject
ImageViewModel(Application application, ImageViewModel(Application application,
MessagingManager messagingManager, EventBus eventBus, MessagingManager messagingManager,
EventBus eventBus,
@DatabaseExecutor Executor dbExecutor, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
@IoExecutor Executor ioExecutor) { @IoExecutor Executor ioExecutor) {
super(application); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.messagingManager = messagingManager; this.messagingManager = messagingManager;
this.eventBus = eventBus; this.eventBus = eventBus;
this.dbExecutor = dbExecutor;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
eventBus.addListener(this); eventBus.addListener(this);
@@ -195,7 +199,7 @@ public class ImageViewModel extends AndroidViewModel implements EventListener {
private void saveImage(AttachmentItem attachment, OutputStreamProvider osp, private void saveImage(AttachmentItem attachment, OutputStreamProvider osp,
@Nullable Runnable afterCopy) { @Nullable Runnable afterCopy) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Attachment a = Attachment a =
messagingManager.getAttachment(attachment.getHeader()); messagingManager.getAttachment(attachment.getHeader());

View File

@@ -38,7 +38,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEX
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ForumActivity extends public class ForumActivity extends
ThreadListActivity<Forum, ForumItem, ThreadItemAdapter<ForumItem>> ThreadListActivity<Forum, ForumPostItem, ThreadItemAdapter<ForumPostItem>>
implements ForumListener { implements ForumListener {
@Inject @Inject
@@ -50,7 +50,7 @@ public class ForumActivity extends
} }
@Override @Override
protected ThreadListController<Forum, ForumItem> getController() { protected ThreadListController<Forum, ForumPostItem> getController() {
return forumController; return forumController;
} }
@@ -82,7 +82,7 @@ public class ForumActivity extends
} }
@Override @Override
protected ThreadItemAdapter<ForumItem> createAdapter( protected ThreadItemAdapter<ForumPostItem> createAdapter(
LinearLayoutManager layoutManager) { LinearLayoutManager layoutManager) {
return new ThreadItemAdapter<>(this, layoutManager); return new ThreadItemAdapter<>(this, layoutManager);
} }
@@ -156,7 +156,7 @@ public class ForumActivity extends
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -8,9 +8,9 @@ import org.briarproject.briar.api.forum.Forum;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
@NotNullByDefault @NotNullByDefault
interface ForumController extends ThreadListController<Forum, ForumItem> { interface ForumController extends ThreadListController<Forum, ForumPostItem> {
interface ForumListener extends ThreadListListener<ForumItem> { interface ForumListener extends ThreadListListener<ForumPostItem> {
@UiThread @UiThread
void onForumLeft(ContactId c); void onForumLeft(ContactId c);
} }

View File

@@ -43,7 +43,7 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault @NotNullByDefault
class ForumControllerImpl extends class ForumControllerImpl extends
ThreadListControllerImpl<Forum, ForumItem, ForumPostHeader, ForumPost, ForumListener> ThreadListControllerImpl<Forum, ForumPostItem, ForumPostHeader, ForumPost, ForumListener>
implements ForumController { implements ForumController {
private static final Logger LOG = private static final Logger LOG =
@@ -138,8 +138,8 @@ class ForumControllerImpl extends
@Override @Override
public void createAndStoreMessage(String text, public void createAndStoreMessage(String text,
@Nullable ForumItem parentItem, @Nullable ForumPostItem parentItem,
ResultExceptionHandler<ForumItem, DbException> handler) { ResultExceptionHandler<ForumPostItem, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
LocalAuthor author = identityManager.getLocalAuthor(); LocalAuthor author = identityManager.getLocalAuthor();
@@ -158,7 +158,7 @@ class ForumControllerImpl extends
private void createMessage(String text, long timestamp, private void createMessage(String text, long timestamp,
@Nullable MessageId parentId, LocalAuthor author, @Nullable MessageId parentId, LocalAuthor author,
ResultExceptionHandler<ForumItem, DbException> handler) { ResultExceptionHandler<ForumPostItem, DbException> handler) {
cryptoExecutor.execute(() -> { cryptoExecutor.execute(() -> {
LOG.info("Creating forum post..."); LOG.info("Creating forum post...");
ForumPost msg = forumManager.createLocalPost(getGroupId(), text, ForumPost msg = forumManager.createLocalPost(getGroupId(), text,
@@ -178,8 +178,8 @@ class ForumControllerImpl extends
} }
@Override @Override
protected ForumItem buildItem(ForumPostHeader header, String text) { protected ForumPostItem buildItem(ForumPostHeader header, String text) {
return new ForumItem(header, text); return new ForumPostItem(header, text);
} }
} }

View File

@@ -1,134 +1,47 @@
package org.briarproject.briar.android.forum; package org.briarproject.briar.android.forum;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.TextAvatarView;
import org.briarproject.briar.api.forum.Forum;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
import static android.view.View.GONE; @NotNullByDefault
import static android.view.View.VISIBLE; class ForumListAdapter extends ListAdapter<ForumListItem, ForumViewHolder> {
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_NAME;
class ForumListAdapter ForumListAdapter() {
extends BriarAdapter<ForumListItem, ForumListAdapter.ForumViewHolder> { super(new ForumListCallback());
ForumListAdapter(Context ctx) {
super(ctx, ForumListItem.class);
} }
@Override @Override
public ForumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ForumViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(ctx).inflate( View v = LayoutInflater.from(parent.getContext()).inflate(
R.layout.list_item_forum, parent, false); R.layout.list_item_forum, parent, false);
return new ForumViewHolder(v); return new ForumViewHolder(v);
} }
@Override @Override
public void onBindViewHolder(ForumViewHolder ui, int position) { public void onBindViewHolder(ForumViewHolder viewHolder, int position) {
ForumListItem item = getItemAt(position); viewHolder.bind(getItem(position));
if (item == null) return; }
// Avatar @NotNullByDefault
ui.avatar.setText(item.getForum().getName().substring(0, 1)); private static class ForumListCallback extends ItemCallback<ForumListItem> {
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes()); @Override
ui.avatar.setUnreadCount(item.getUnreadCount()); public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
return a.equals(b);
// Forum Name
ui.name.setText(item.getForum().getName());
// Post Count
int postCount = item.getPostCount();
if (postCount > 0) {
ui.postCount.setText(ctx.getResources()
.getQuantityString(R.plurals.posts, postCount,
postCount));
} else {
ui.postCount.setText(ctx.getString(R.string.no_posts));
} }
// Date @Override
if (item.isEmpty()) { public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
ui.date.setVisibility(GONE); return a.isEmpty() == b.isEmpty() &&
} else { a.getTimestamp() == b.getTimestamp() &&
long timestamp = item.getTimestamp(); a.getUnreadCount() == b.getUnreadCount();
ui.date.setText(UiUtils.formatDate(ctx, timestamp));
ui.date.setVisibility(VISIBLE);
}
// Open Forum on Click
ui.layout.setOnClickListener(v -> {
Intent i = new Intent(ctx, ForumActivity.class);
Forum f = item.getForum();
i.putExtra(GROUP_ID, f.getId().getBytes());
i.putExtra(GROUP_NAME, f.getName());
ctx.startActivity(i);
});
}
@Override
public int compare(ForumListItem a, ForumListItem b) {
if (a == b) return 0;
// The forum with the newest message comes first
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by forum name
String aName = a.getForum().getName();
String bName = b.getForum().getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
@Override
public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
return a.isEmpty() == b.isEmpty() &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount();
}
@Override
public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
return a.getForum().equals(b.getForum());
}
int findItemPosition(GroupId g) {
int count = getItemCount();
for (int i = 0; i < count; i++) {
ForumListItem item = getItemAt(i);
if (item != null && item.getForum().getGroup().getId().equals(g))
return i;
}
return INVALID_POSITION; // Not found
}
static class ForumViewHolder extends RecyclerView.ViewHolder {
private final ViewGroup layout;
private final TextAvatarView avatar;
private final TextView name;
private final TextView postCount;
private final TextView date;
private ForumViewHolder(View v) {
super(v);
layout = (ViewGroup) v;
avatar = v.findViewById(R.id.avatarView);
name = v.findViewById(R.id.forumNameView);
postCount = v.findViewById(R.id.postCountView);
date = v.findViewById(R.id.dateView);
} }
} }
} }

View File

@@ -12,81 +12,48 @@ import android.view.ViewGroup;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchGroupException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseEventFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.sharing.ForumInvitationActivity; import org.briarproject.briar.android.sharing.ForumInvitationActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumPostHeader;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ForumListFragment extends BaseEventFragment implements public class ForumListFragment extends BaseFragment implements
OnClickListener { OnClickListener {
public final static String TAG = ForumListFragment.class.getName(); public final static String TAG = ForumListFragment.class.getName();
private final static Logger LOG = Logger.getLogger(TAG);
private ForumListViewModel viewModel;
private BriarRecyclerView list; private BriarRecyclerView list;
private ForumListAdapter adapter;
private Snackbar snackbar; private Snackbar snackbar;
private final ForumListAdapter adapter = new ForumListAdapter();
@Inject @Inject
AndroidNotificationManager notificationManager; ViewModelProvider.Factory viewModelFactory;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ForumManager forumManager;
@Inject
volatile ForumSharingManager forumSharingManager;
public static ForumListFragment newInstance() { public static ForumListFragment newInstance() {
return new ForumListFragment();
Bundle args = new Bundle();
ForumListFragment fragment = new ForumListFragment();
fragment.setArguments(args);
return fragment;
} }
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ForumListViewModel.class);
} }
@Nullable @Nullable
@@ -94,24 +61,35 @@ public class ForumListFragment extends BaseEventFragment implements
public View onCreateView(LayoutInflater inflater, public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireActivity().setTitle(R.string.forums_button);
requireNonNull(getActivity()).setTitle(R.string.forums_button); View v = inflater.inflate(R.layout.fragment_forum_list, container,
false);
View contentView = list = v.findViewById(R.id.forumList);
inflater.inflate(R.layout.fragment_forum_list, container,
false);
adapter = new ForumListAdapter(getActivity());
list = contentView.findViewById(R.id.forumList);
list.setLayoutManager(new LinearLayoutManager(getActivity())); list.setLayoutManager(new LinearLayoutManager(getActivity()));
list.setAdapter(adapter); list.setAdapter(adapter);
viewModel.getForumListItems().observe(getViewLifecycleOwner(), result ->
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
})
);
snackbar = new BriarSnackbarBuilder() snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, this) .setAction(R.string.show, this)
.make(list, "", LENGTH_INDEFINITE); .make(list, "", LENGTH_INDEFINITE);
viewModel.getNumInvitations().observe(getViewLifecycleOwner(), num -> {
if (num == 0) {
snackbar.dismiss();
} else {
snackbar.setText(getResources().getQuantityString(
R.plurals.forums_shared, num, num));
if (!snackbar.isShownOrQueued()) snackbar.show();
}
});
return contentView; return v;
} }
@Override @Override
@@ -122,18 +100,23 @@ public class ForumListFragment extends BaseEventFragment implements
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
notificationManager.clearAllForumPostNotifications(); viewModel.blockAllForumPostNotifications();
loadForums(); viewModel.clearAllForumPostNotifications();
loadAvailableForums(); // The attributes and sorting of the forums may have changed while we
// were stopped and we have no way finding out about them, so re-load
// e.g. less unread posts in a forum after viewing it.
viewModel.loadForums();
// The number of invitations might have changed while we were stopped
// e.g. because of accepting an invitation which does not trigger event
viewModel.loadForumInvitations();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
viewModel.unblockAllForumPostNotifications();
} }
@Override @Override
@@ -145,123 +128,12 @@ public class ForumListFragment extends BaseEventFragment implements
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items // Handle presses on the action bar items
switch (item.getItemId()) { if (item.getItemId() == R.id.action_create_forum) {
case R.id.action_create_forum: Intent intent = new Intent(getContext(), CreateForumActivity.class);
Intent intent = startActivity(intent);
new Intent(getContext(), CreateForumActivity.class); return true;
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
} }
} return super.onOptionsItemSelected(item);
private void loadForums() {
int revision = adapter.getRevision();
listener.runOnDbThread(() -> {
try {
long start = now();
Collection<ForumListItem> forums = new ArrayList<>();
for (Forum f : forumManager.getForums()) {
try {
GroupCount count =
forumManager.getGroupCount(f.getId());
forums.add(new ForumListItem(f, count));
} catch (NoSuchGroupException e) {
// Continue
}
}
logDuration(LOG, "Full load", start);
displayForums(revision, forums);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayForums(int revision, Collection<ForumListItem> forums) {
runOnUiThreadUnlessDestroyed(() -> {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (forums.isEmpty()) list.showData();
else adapter.replaceAll(forums);
} else {
LOG.info("Concurrent update, reloading");
loadForums();
}
});
}
private void loadAvailableForums() {
listener.runOnDbThread(() -> {
try {
long start = now();
int available = forumSharingManager.getInvitations().size();
logDuration(LOG, "Loading available", start);
displayAvailableForums(available);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayAvailableForums(int availableCount) {
runOnUiThreadUnlessDestroyed(() -> {
if (availableCount == 0) {
snackbar.dismiss();
} else {
snackbar.setText(getResources().getQuantityString(
R.plurals.forums_shared, availableCount,
availableCount));
if (!snackbar.isShownOrQueued()) snackbar.show();
}
});
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading available forums");
loadAvailableForums();
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Forum added, reloading forums");
loadForums();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Forum removed, removing from list");
removeForum(g.getGroup().getId());
}
} else if (e instanceof ForumPostReceivedEvent) {
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
LOG.info("Forum post added, updating item");
updateItem(f.getGroupId(), f.getHeader());
} else if (e instanceof ForumInvitationRequestReceivedEvent) {
LOG.info("Forum invitation received, reloading available forums");
loadAvailableForums();
}
}
@UiThread
private void updateItem(GroupId g, ForumPostHeader m) {
adapter.incrementRevision();
int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position);
if (item != null) {
item.addHeader(m);
adapter.updateItemAt(position, item);
}
}
@UiThread
private void removeForum(GroupId g) {
adapter.incrementRevision();
int position = adapter.findItemPosition(g);
ForumListItem item = adapter.getItemAt(position);
if (item != null) adapter.remove(item);
} }
@Override @Override

View File

@@ -4,12 +4,16 @@ import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.forum.Forum; import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumPostHeader; import org.briarproject.briar.api.forum.ForumPostHeader;
// This class is NOT thread-safe import javax.annotation.concurrent.Immutable;
class ForumListItem {
import androidx.annotation.Nullable;
@Immutable
class ForumListItem implements Comparable<ForumListItem> {
private final Forum forum; private final Forum forum;
private int postCount, unread; private final int postCount, unread;
private long timestamp; private final long timestamp;
ForumListItem(Forum forum, GroupCount count) { ForumListItem(Forum forum, GroupCount count) {
this.forum = forum; this.forum = forum;
@@ -18,10 +22,11 @@ class ForumListItem {
this.timestamp = count.getLatestMsgTime(); this.timestamp = count.getLatestMsgTime();
} }
void addHeader(ForumPostHeader h) { ForumListItem(ForumListItem item, ForumPostHeader h) {
postCount++; this.forum = item.forum;
if (!h.isRead()) unread++; this.postCount = item.postCount + 1;
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp(); this.unread = item.unread + (h.isRead() ? 0 : 1);
this.timestamp = Math.max(item.timestamp, h.getTimestamp());
} }
Forum getForum() { Forum getForum() {
@@ -43,4 +48,29 @@ class ForumListItem {
int getUnreadCount() { int getUnreadCount() {
return unread; return unread;
} }
@Override
public int hashCode() {
return forum.getId().hashCode();
}
@Override
public boolean equals(@Nullable Object o) {
return o instanceof ForumListItem && getForum().equals(
((ForumListItem) o).getForum());
}
@Override
public int compareTo(ForumListItem o) {
if (this == o) return 0;
// The forum with the newest message comes first
long aTime = getTimestamp(), bTime = o.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by forum name
String aName = getForum().getName();
String bName = o.getForum().getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
} }

View File

@@ -0,0 +1,187 @@
package org.briarproject.briar.android.forum;
import android.app.Application;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.forum.Forum;
import org.briarproject.briar.api.forum.ForumManager;
import org.briarproject.briar.api.forum.ForumPostHeader;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class ForumListViewModel extends DbViewModel implements EventListener {
private static final Logger LOG =
getLogger(ForumListViewModel.class.getName());
private final ForumManager forumManager;
private final ForumSharingManager forumSharingManager;
private final AndroidNotificationManager notificationManager;
private final EventBus eventBus;
private final MutableLiveData<LiveResult<List<ForumListItem>>> forumItems =
new MutableLiveData<>();
private final MutableLiveData<Integer> numInvitations =
new MutableLiveData<>();
@Inject
ForumListViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
ForumManager forumManager,
ForumSharingManager forumSharingManager,
AndroidNotificationManager notificationManager, EventBus eventBus) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.forumManager = forumManager;
this.forumSharingManager = forumSharingManager;
this.notificationManager = notificationManager;
this.eventBus = eventBus;
this.eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
void clearAllForumPostNotifications() {
notificationManager.clearAllForumPostNotifications();
}
void blockAllForumPostNotifications() {
notificationManager.blockAllForumPostNotifications();
}
void unblockAllForumPostNotifications() {
notificationManager.unblockAllForumPostNotifications();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, reloading available forums");
loadForumInvitations();
} else if (e instanceof ForumInvitationRequestReceivedEvent) {
LOG.info("Forum invitation received, reloading available forums");
loadForumInvitations();
} else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Forum added, reloading forums");
loadForums();
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getClientId().equals(CLIENT_ID)) {
LOG.info("Forum removed, removing from list");
onGroupRemoved(g.getGroup().getId());
}
} else if (e instanceof ForumPostReceivedEvent) {
ForumPostReceivedEvent f = (ForumPostReceivedEvent) e;
LOG.info("Forum post added, updating item");
onForumPostReceived(f.getGroupId(), f.getHeader());
}
}
public void loadForums() {
loadList(this::loadForums, forumItems::setValue);
}
@DatabaseExecutor
private List<ForumListItem> loadForums(Transaction txn) throws DbException {
long start = now();
List<ForumListItem> forums = new ArrayList<>();
for (Forum f : forumManager.getForums(txn)) {
GroupCount count = forumManager.getGroupCount(txn, f.getId());
forums.add(new ForumListItem(f, count));
}
Collections.sort(forums);
logDuration(LOG, "Loading forums", start);
return forums;
}
@UiThread
private void onForumPostReceived(GroupId g, ForumPostHeader header) {
List<ForumListItem> list = updateListItems(forumItems,
itemToTest -> itemToTest.getForum().getId().equals(g),
itemToUpdate -> new ForumListItem(itemToUpdate, header));
if (list == null) return;
// re-sort as the order of items may have changed
Collections.sort(list);
forumItems.setValue(new LiveResult<>(list));
}
@UiThread
private void onGroupRemoved(GroupId groupId) {
List<ForumListItem> list = removeListItems(forumItems, i ->
i.getForum().getId().equals(groupId)
);
if (list == null) return;
forumItems.setValue(new LiveResult<>(list));
}
void loadForumInvitations() {
runOnDbThread(() -> {
try {
long start = now();
int available = forumSharingManager.getInvitations().size();
logDuration(LOG, "Loading available", start);
numInvitations.postValue(available);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
LiveData<LiveResult<List<ForumListItem>>> getForumListItems() {
return forumItems;
}
LiveData<Integer> getNumInvitations() {
return numInvitations;
}
}

View File

@@ -2,13 +2,25 @@ package org.briarproject.briar.android.forum;
import org.briarproject.briar.android.activity.ActivityScope; import org.briarproject.briar.android.activity.ActivityScope;
import org.briarproject.briar.android.activity.BaseActivity; import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import dagger.multibindings.IntoMap;
@Module @Module
public class ForumModule { public class ForumModule {
@Module
public interface BindsModule {
@Binds
@IntoMap
@ViewModelKey(ForumListViewModel.class)
ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel);
}
@ActivityScope @ActivityScope
@Provides @Provides
ForumController provideForumController(BaseActivity activity, ForumController provideForumController(BaseActivity activity,

View File

@@ -10,15 +10,15 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe @NotThreadSafe
class ForumItem extends ThreadItem { class ForumPostItem extends ThreadItem {
ForumItem(ForumPostHeader h, String text) { ForumPostItem(ForumPostHeader h, String text) {
super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(), super(h.getId(), h.getParentId(), text, h.getTimestamp(), h.getAuthor(),
h.getAuthorInfo(), h.isRead()); h.getAuthorInfo(), h.isRead());
} }
ForumItem(MessageId messageId, @Nullable MessageId parentId, String text, ForumPostItem(MessageId messageId, @Nullable MessageId parentId,
long timestamp, Author author, AuthorInfo authorInfo) { String text, long timestamp, Author author, AuthorInfo authorInfo) {
super(messageId, parentId, text, timestamp, author, authorInfo, true); super(messageId, parentId, text, timestamp, author, authorInfo, true);
} }

View File

@@ -0,0 +1,77 @@
package org.briarproject.briar.android.forum;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.UiUtils;
import org.briarproject.briar.android.view.TextAvatarView;
import org.briarproject.briar.api.forum.Forum;
import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_NAME;
class ForumViewHolder extends RecyclerView.ViewHolder {
private final Context ctx;
private final ViewGroup layout;
private final TextAvatarView avatar;
private final TextView name;
private final TextView postCount;
private final TextView date;
ForumViewHolder(View v) {
super(v);
ctx = v.getContext();
layout = (ViewGroup) v;
avatar = v.findViewById(R.id.avatarView);
name = v.findViewById(R.id.forumNameView);
postCount = v.findViewById(R.id.postCountView);
date = v.findViewById(R.id.dateView);
}
void bind(ForumListItem item) {
// Avatar
avatar.setText(item.getForum().getName().substring(0, 1));
avatar.setBackgroundBytes(item.getForum().getId().getBytes());
avatar.setUnreadCount(item.getUnreadCount());
// Forum Name
name.setText(item.getForum().getName());
// Post Count
int count = item.getPostCount();
if (count > 0) {
postCount.setText(ctx.getResources()
.getQuantityString(R.plurals.posts, count, count));
} else {
postCount.setText(ctx.getString(R.string.no_posts));
}
// Date
if (item.isEmpty()) {
date.setVisibility(GONE);
} else {
long timestamp = item.getTimestamp();
date.setText(UiUtils.formatDate(ctx, timestamp));
date.setVisibility(VISIBLE);
}
// Open Forum on Click
layout.setOnClickListener(v -> {
Intent i = new Intent(ctx, ForumActivity.class);
Forum f = item.getForum();
i.putExtra(GROUP_ID, f.getId().getBytes());
i.putExtra(GROUP_NAME, f.getName());
ctx.startActivity(i);
});
}
}

View File

@@ -5,7 +5,6 @@ import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.MenuItem; import android.view.MenuItem;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext; import org.briarproject.briar.android.DestroyableContext;
@@ -77,7 +76,7 @@ public abstract class BaseFragment extends Fragment
void showNextFragment(BaseFragment f); void showNextFragment(BaseFragment f);
@UiThread @UiThread
void handleDbException(DbException e); void handleException(Exception e);
} }
@CallSuper @CallSuper
@@ -100,8 +99,8 @@ public abstract class BaseFragment extends Fragment
} }
@UiThread @UiThread
protected void handleDbException(DbException e) { protected void handleException(Exception e) {
listener.handleDbException(e); listener.handleException(e);
} }
} }

View File

@@ -5,6 +5,7 @@ import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -28,6 +29,10 @@ import javax.inject.Inject;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import static android.os.Build.VERSION.SDK_INT;
import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION;
import static android.view.View.GONE;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class ScreenFilterDialogFragment extends DialogFragment { public class ScreenFilterDialogFragment extends DialogFragment {
@@ -37,7 +42,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
@Inject @Inject
ScreenFilterMonitor screenFilterMonitor; ScreenFilterMonitor screenFilterMonitor;
DismissListener dismissListener = null; private DismissListener dismissListener = null;
public static ScreenFilterDialogFragment newInstance( public static ScreenFilterDialogFragment newInstance(
Collection<AppDetails> apps) { Collection<AppDetails> apps) {
@@ -83,10 +88,20 @@ public class ScreenFilterDialogFragment extends DialogFragment {
View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null); View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
builder.setView(dialogView); builder.setView(dialogView);
TextView message = dialogView.findViewById(R.id.screen_filter_message); TextView message = dialogView.findViewById(R.id.screen_filter_message);
message.setText(getString(R.string.screen_filter_body,
TextUtils.join("\n", appNames)));
CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox); CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox);
builder.setNeutralButton(R.string.continue_button, (dialog, which) -> { if (SDK_INT <= 29) {
message.setText(getString(R.string.screen_filter_body,
TextUtils.join("\n", appNames)));
} else {
message.setText(R.string.screen_filter_body_api_30);
allow.setVisibility(GONE);
builder.setNeutralButton(R.string.screen_filter_review_apps,
(dialog, which) -> {
Intent i = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(i);
});
}
builder.setPositiveButton(R.string.continue_button, (dialog, which) -> {
if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames); if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames);
dialog.dismiss(); dialog.dismiss();
}); });

View File

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

View File

@@ -39,7 +39,6 @@ import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_SHORT; import static android.widget.Toast.LENGTH_SHORT;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@@ -102,7 +101,7 @@ public class IntroductionMessageFragment extends BaseFragment
} }
// get contact IDs from fragment arguments // get contact IDs from fragment arguments
Bundle args = requireNonNull(getArguments()); Bundle args = requireArguments();
int contactId1 = args.getInt(CONTACT_ID_1, -1); int contactId1 = args.getInt(CONTACT_ID_1, -1);
int contactId2 = args.getInt(CONTACT_ID_2, -1); int contactId2 = args.getInt(CONTACT_ID_2, -1);
if (contactId1 == -1 || contactId2 == -1) { if (contactId1 == -1 || contactId2 == -1) {

View File

@@ -10,14 +10,11 @@ import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.util.UiUtils; import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
@@ -41,9 +38,6 @@ public class ContactExchangeErrorFragment extends BaseFragment {
return f; return f;
} }
@Inject
AndroidExecutor androidExecutor;
@Override @Override
public String getUniqueTag() { public String getUniqueTag() {
return TAG; return TAG;
@@ -88,8 +82,8 @@ public class ContactExchangeErrorFragment extends BaseFragment {
} }
private void triggerFeedback() { private void triggerFeedback() {
UiUtils.triggerFeedback(requireContext());
finish(); finish();
UiUtils.triggerFeedback(androidExecutor);
} }
} }

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,6 @@ import android.widget.TextView;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener; import com.google.android.material.navigation.NavigationView.OnNavigationItemSelectedListener;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -365,7 +364,7 @@ public class NavDrawerActivity extends BriarActivity implements
} }
@Override @Override
public void handleDbException(DbException e) { public void handleException(Exception e) {
// Do nothing for now // Do nothing for now
} }

View File

@@ -4,9 +4,13 @@ import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -14,7 +18,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -28,7 +31,7 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@NotNullByDefault @NotNullByDefault
public class NavDrawerViewModel extends AndroidViewModel { public class NavDrawerViewModel extends DbViewModel {
private static final Logger LOG = private static final Logger LOG =
getLogger(NavDrawerViewModel.class.getName()); getLogger(NavDrawerViewModel.class.getName());
@@ -37,8 +40,6 @@ public class NavDrawerViewModel extends AndroidViewModel {
private static final String SHOW_TRANSPORTS_ONBOARDING = private static final String SHOW_TRANSPORTS_ONBOARDING =
"showTransportsOnboarding"; "showTransportsOnboarding";
@DatabaseExecutor
private final Executor dbExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final MutableLiveData<Boolean> showExpiryWarning = private final MutableLiveData<Boolean> showExpiryWarning =
@@ -49,10 +50,13 @@ public class NavDrawerViewModel extends AndroidViewModel {
new MutableLiveData<>(); new MutableLiveData<>();
@Inject @Inject
NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor, NavDrawerViewModel(Application app,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
SettingsManager settingsManager) { SettingsManager settingsManager) {
super(app); super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
} }
@@ -62,7 +66,7 @@ public class NavDrawerViewModel extends AndroidViewModel {
@UiThread @UiThread
void checkExpiryWarning() { void checkExpiryWarning() {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Settings settings = Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE); settingsManager.getSettings(SETTINGS_NAMESPACE);
@@ -97,7 +101,7 @@ public class NavDrawerViewModel extends AndroidViewModel {
@UiThread @UiThread
void expiryWarningDismissed() { void expiryWarningDismissed() {
showExpiryWarning.setValue(false); showExpiryWarning.setValue(false);
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Settings settings = new Settings(); Settings settings = new Settings();
int date = (int) (System.currentTimeMillis() / 1000L); int date = (int) (System.currentTimeMillis() / 1000L);
@@ -120,7 +124,7 @@ public class NavDrawerViewModel extends AndroidViewModel {
shouldAskForDozeWhitelisting.setValue(false); shouldAskForDozeWhitelisting.setValue(false);
return; return;
} }
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Settings settings = Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE); settingsManager.getSettings(SETTINGS_NAMESPACE);
@@ -141,7 +145,7 @@ public class NavDrawerViewModel extends AndroidViewModel {
@UiThread @UiThread
void checkTransportsOnboarding() { void checkTransportsOnboarding() {
if (showTransportsOnboarding.getValue() != null) return; if (showTransportsOnboarding.getValue() != null) return;
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Settings settings = Settings settings =
settingsManager.getSettings(SETTINGS_NAMESPACE); settingsManager.getSettings(SETTINGS_NAMESPACE);
@@ -157,7 +161,7 @@ public class NavDrawerViewModel extends AndroidViewModel {
@UiThread @UiThread
void transportsOnboardingShown() { void transportsOnboardingShown() {
showTransportsOnboarding.setValue(false); showTransportsOnboarding.setValue(false);
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
Settings settings = new Settings(); Settings settings = new Settings();
settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false); settings.putBoolean(SHOW_TRANSPORTS_ONBOARDING, false);

View File

@@ -9,9 +9,11 @@ import android.content.IntentFilter;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager; import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.network.NetworkStatus; import org.briarproject.bramble.api.network.NetworkStatus;
import org.briarproject.bramble.api.network.event.NetworkStatusEvent; import org.briarproject.bramble.api.network.event.NetworkStatusEvent;
@@ -27,6 +29,8 @@ import org.briarproject.bramble.api.plugin.event.TransportStateEvent;
import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager; import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -34,7 +38,6 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
@@ -51,13 +54,12 @@ import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@NotNullByDefault @NotNullByDefault
public class PluginViewModel extends AndroidViewModel implements EventListener { public class PluginViewModel extends DbViewModel implements EventListener {
private static final Logger LOG = private static final Logger LOG =
getLogger(PluginViewModel.class.getName()); getLogger(PluginViewModel.class.getName());
private final Application app; private final Application app;
private final Executor dbExecutor;
private final SettingsManager settingsManager; private final SettingsManager settingsManager;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final EventBus eventBus; private final EventBus eventBus;
@@ -85,11 +87,12 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
@Inject @Inject
PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor, PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor,
SettingsManager settingsManager, PluginManager pluginManager, LifecycleManager lifecycleManager, TransactionManager db,
EventBus eventBus, NetworkManager networkManager) { AndroidExecutor androidExecutor, SettingsManager settingsManager,
super(app); PluginManager pluginManager, EventBus eventBus,
NetworkManager networkManager) {
super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.app = app; this.app = app;
this.dbExecutor = dbExecutor;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -182,7 +185,7 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
} }
private void loadSettings() { private void loadSettings() {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
boolean tor = isPluginEnabled(TorConstants.ID, boolean tor = isPluginEnabled(TorConstants.ID,
TorConstants.DEFAULT_PREF_PLUGIN_ENABLE); TorConstants.DEFAULT_PREF_PLUGIN_ENABLE);
@@ -219,7 +222,7 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
} }
private void mergeSettings(Settings s, String namespace) { private void mergeSettings(Settings s, String namespace) {
dbExecutor.execute(() -> { runOnDbThread(() -> {
try { try {
long start = now(); long start = now();
settingsManager.mergeSettings(s, namespace); settingsManager.mergeSettings(s, namespace);
@@ -235,8 +238,7 @@ public class PluginViewModel extends AndroidViewModel implements EventListener {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(EXTRA_STATE, 0); int state = intent.getIntExtra(EXTRA_STATE, 0);
if (state == STATE_ON) bluetoothTurnedOn.postValue(true); bluetoothTurnedOn.postValue(state == STATE_ON);
else bluetoothTurnedOn.postValue(false);
} }
} }
} }

View File

@@ -82,6 +82,7 @@ public class PanicPreferencesFragment extends PreferenceFragmentCompat
entries.add(0, getString(R.string.panic_app_setting_none)); entries.add(0, getString(R.string.panic_app_setting_none));
entryValues.add(0, PACKAGE_NAME_NONE); entryValues.add(0, PACKAGE_NAME_NONE);
// only info.guardianproject.ripple is whitelisted in manifest
for (ResolveInfo resolveInfo : PanicResponder.resolveTriggerApps(pm)) { for (ResolveInfo resolveInfo : PanicResponder.resolveTriggerApps(pm)) {
if (resolveInfo.activityInfo == null) if (resolveInfo.activityInfo == null)
continue; continue;

View File

@@ -106,7 +106,7 @@ public class GroupActivity extends
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -125,7 +125,7 @@ public class GroupActivity extends
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -264,7 +264,7 @@ public class GroupActivity extends
// GroupRemovedEvent being fired // GroupRemovedEvent being fired
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -51,7 +51,7 @@ public class CreateGroupActivity extends BriarActivity
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -69,7 +69,7 @@ public class GroupInviteActivity extends ContactSelectorActivity
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

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

View File

@@ -8,15 +8,19 @@ import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.privategroup.GroupMessageHeader; import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
// This class is not thread-safe import javax.annotation.concurrent.Immutable;
import androidx.annotation.Nullable;
@Immutable
@NotNullByDefault @NotNullByDefault
class GroupItem { class GroupItem implements Comparable<GroupItem> {
private final PrivateGroup privateGroup; private final PrivateGroup privateGroup;
private final AuthorInfo authorInfo; private final AuthorInfo authorInfo;
private int messageCount, unreadCount; private final int messageCount, unreadCount;
private long timestamp; private final long timestamp;
private boolean dissolved; private final boolean dissolved;
GroupItem(PrivateGroup privateGroup, AuthorInfo authorInfo, GroupItem(PrivateGroup privateGroup, AuthorInfo authorInfo,
GroupCount count, boolean dissolved) { GroupCount count, boolean dissolved) {
@@ -28,18 +32,22 @@ class GroupItem {
this.dissolved = dissolved; this.dissolved = dissolved;
} }
void addMessageHeader(GroupMessageHeader header) { GroupItem(GroupItem item, GroupMessageHeader header) {
messageCount++; this.privateGroup = item.privateGroup;
if (header.getTimestamp() > timestamp) { this.authorInfo = item.authorInfo;
timestamp = header.getTimestamp(); this.messageCount = item.messageCount + 1;
} this.unreadCount = item.unreadCount + (header.isRead() ? 0 : 1);
if (!header.isRead()) { this.timestamp = Math.max(header.getTimestamp(), item.timestamp);
unreadCount++; this.dissolved = item.dissolved;
}
} }
PrivateGroup getPrivateGroup() { GroupItem(GroupItem item, boolean isDissolved) {
return privateGroup; this.privateGroup = item.privateGroup;
this.authorInfo = item.authorInfo;
this.messageCount = item.messageCount;
this.unreadCount = item.unreadCount;
this.timestamp = item.timestamp;
this.dissolved = isDissolved;
} }
GroupId getId() { GroupId getId() {
@@ -78,8 +86,27 @@ class GroupItem {
return dissolved; return dissolved;
} }
void setDissolved() { @Override
dissolved = true; public int hashCode() {
return getId().hashCode();
} }
@Override
public boolean equals(@Nullable Object o) {
return o instanceof GroupItem &&
getId().equals(((GroupItem) o).getId());
}
@Override
public int compareTo(GroupItem o) {
if (this == o) return 0;
// The group with the latest message comes first
long aTime = getTimestamp(), bTime = o.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by group name
String aName = getName();
String bName = o.getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
} }

View File

@@ -1,81 +1,52 @@
package org.briarproject.briar.android.privategroup.list; package org.briarproject.briar.android.privategroup.list;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
import org.briarproject.briar.android.util.BriarAdapter;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION; import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.ListAdapter;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class GroupListAdapter extends BriarAdapter<GroupItem, GroupViewHolder> { class GroupListAdapter extends ListAdapter<GroupItem, GroupViewHolder> {
private final OnGroupRemoveClickListener listener; private final OnGroupRemoveClickListener listener;
GroupListAdapter(Context ctx, OnGroupRemoveClickListener listener) { GroupListAdapter(OnGroupRemoveClickListener listener) {
super(ctx, GroupItem.class); super(new GroupItemCallback());
this.listener = listener; this.listener = listener;
} }
@Override @Override
public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(ctx).inflate( View v = LayoutInflater.from(parent.getContext())
R.layout.list_item_group, parent, false); .inflate(R.layout.list_item_group, parent, false);
return new GroupViewHolder(v); return new GroupViewHolder(v);
} }
@Override @Override
public void onBindViewHolder(GroupViewHolder ui, int position) { public void onBindViewHolder(GroupViewHolder ui, int position) {
ui.bindView(ctx, items.get(position), listener); ui.bindView(getItem(position), listener);
} }
@Override private static class GroupItemCallback extends ItemCallback<GroupItem> {
public int compare(GroupItem a, GroupItem b) { @Override
if (a == b) return 0; public boolean areItemsTheSame(GroupItem a, GroupItem b) {
// The group with the latest message comes first return a.equals(b);
long aTime = a.getTimestamp(), bTime = b.getTimestamp();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
// Break ties by group name
String aName = a.getName();
String bName = b.getName();
return String.CASE_INSENSITIVE_ORDER.compare(aName, bName);
}
@Override
public boolean areContentsTheSame(GroupItem a, GroupItem b) {
return a.getMessageCount() == b.getMessageCount() &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount() &&
a.isDissolved() == b.isDissolved();
}
@Override
public boolean areItemsTheSame(GroupItem a, GroupItem b) {
return a.getId().equals(b.getId());
}
int findItemPosition(GroupId g) {
for (int i = 0; i < items.size(); i++) {
GroupItem item = items.get(i);
if (item.getId().equals(g)) {
return i;
}
} }
return INVALID_POSITION;
}
void removeItem(GroupId groupId) { @Override
int pos = findItemPosition(groupId); public boolean areContentsTheSame(GroupItem a, GroupItem b) {
if (pos != INVALID_POSITION) items.removeItemAt(pos); return a.getMessageCount() == b.getMessageCount() &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount() &&
a.isDissolved() == b.isDissolved();
}
} }
} }

View File

@@ -1,60 +0,0 @@
package org.briarproject.briar.android.privategroup.list;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import java.util.Collection;
import androidx.annotation.UiThread;
@NotNullByDefault
interface GroupListController extends DbController {
/**
* The listener must be set right after the controller was injected
*/
@UiThread
void setGroupListListener(GroupListListener listener);
@UiThread
void unsetGroupListListener(GroupListListener listener);
@UiThread
void onStart();
@UiThread
void onStop();
void loadGroups(
ResultExceptionHandler<Collection<GroupItem>, DbException> result);
void removeGroup(GroupId g, ExceptionHandler<DbException> result);
void loadAvailableGroups(
ResultExceptionHandler<Integer, DbException> result);
interface GroupListListener {
@UiThread
void onGroupMessageAdded(GroupMessageHeader header);
@UiThread
void onGroupInvitationReceived();
@UiThread
void onGroupAdded(GroupId groupId);
@UiThread
void onGroupRemoved(GroupId groupId);
@UiThread
void onGroupDissolved(GroupId groupId);
}
}

View File

@@ -12,30 +12,22 @@ import android.view.ViewGroup;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.controller.handler.UiExceptionHandler;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity; import org.briarproject.briar.android.privategroup.creation.CreateGroupActivity;
import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity; import org.briarproject.briar.android.privategroup.invitation.GroupInvitationActivity;
import org.briarproject.briar.android.privategroup.list.GroupListController.GroupListListener;
import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener; import org.briarproject.briar.android.privategroup.list.GroupViewHolder.OnGroupRemoveClickListener;
import org.briarproject.briar.android.util.BriarSnackbarBuilder; import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView; import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import java.util.Collection;
import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE; import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
@@ -44,26 +36,26 @@ import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class GroupListFragment extends BaseFragment implements public class GroupListFragment extends BaseFragment implements
GroupListListener, OnGroupRemoveClickListener, OnClickListener { OnGroupRemoveClickListener, OnClickListener {
public final static String TAG = GroupListFragment.class.getName(); public final static String TAG = GroupListFragment.class.getName();
private static final Logger LOG = Logger.getLogger(TAG);
public static GroupListFragment newInstance() { public static GroupListFragment newInstance() {
return new GroupListFragment(); return new GroupListFragment();
} }
@Inject @Inject
GroupListController controller; ViewModelProvider.Factory viewModelFactory;
private GroupListViewModel viewModel;
private BriarRecyclerView list; private BriarRecyclerView list;
private GroupListAdapter adapter; private GroupListAdapter adapter;
private Snackbar snackbar;
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
controller.setGroupListListener(this); viewModel = new ViewModelProvider(this, viewModelFactory)
.get(GroupListViewModel.class);
} }
@Nullable @Nullable
@@ -72,21 +64,36 @@ public class GroupListFragment extends BaseFragment implements
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.groups_button); requireActivity().setTitle(R.string.groups_button);
View v = inflater.inflate(R.layout.list, container, false); View v = inflater.inflate(R.layout.list, container, false);
adapter = new GroupListAdapter(getActivity(), this); adapter = new GroupListAdapter(this);
list = v.findViewById(R.id.list); list = v.findViewById(R.id.list);
list.setEmptyImage(R.drawable.ic_empty_state_group_list); list.setEmptyImage(R.drawable.ic_empty_state_group_list);
list.setEmptyText(R.string.groups_list_empty); list.setEmptyText(R.string.groups_list_empty);
list.setEmptyAction(R.string.groups_list_empty_action); list.setEmptyAction(R.string.groups_list_empty_action);
list.setLayoutManager(new LinearLayoutManager(getContext())); list.setLayoutManager(new LinearLayoutManager(getContext()));
list.setAdapter(adapter); list.setAdapter(adapter);
viewModel.getGroupItems().observe(getViewLifecycleOwner(), result ->
result.onError(this::handleException).onSuccess(items -> {
adapter.submitList(items);
if (requireNonNull(items).size() == 0) list.showData();
})
);
snackbar = new BriarSnackbarBuilder() Snackbar snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, this) .setAction(R.string.show, this)
.make(list, "", LENGTH_INDEFINITE); .make(list, "", LENGTH_INDEFINITE);
viewModel.getNumInvitations().observe(getViewLifecycleOwner(), num -> {
if (num == 0) {
snackbar.dismiss();
} else {
snackbar.setText(getResources().getQuantityString(
R.plurals.groups_invitations_open, num, num));
if (!snackbar.isShownOrQueued()) snackbar.show();
}
});
return v; return v;
} }
@@ -94,25 +101,23 @@ public class GroupListFragment extends BaseFragment implements
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
controller.onStart(); viewModel.blockAllGroupMessageNotifications();
viewModel.clearAllGroupMessageNotifications();
// The attributes and sorting of the groups may have changed while we
// were stopped and we have no way finding out about them, so re-load
// e.g. less unread messages in a group after viewing it.
viewModel.loadGroups();
// The number of invitations might have changed while we were stopped
// e.g. because of accepting an invitation which does not trigger event
viewModel.loadNumInvitations();
list.startPeriodicUpdate(); list.startPeriodicUpdate();
loadGroups();
loadAvailableGroups();
} }
@Override @Override
public void onStop() { public void onStop() {
super.onStop(); super.onStop();
controller.onStop();
list.stopPeriodicUpdate(); list.stopPeriodicUpdate();
adapter.clear(); viewModel.unblockAllGroupMessageNotifications();
list.showProgressBar();
}
@Override
public void onDestroy() {
super.onDestroy();
controller.unsetGroupListListener(this);
} }
@Override @Override
@@ -123,68 +128,18 @@ public class GroupListFragment extends BaseFragment implements
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { if (item.getItemId() == R.id.action_add_group) {
case R.id.action_add_group: Intent i = new Intent(getContext(), CreateGroupActivity.class);
Intent i = new Intent(getContext(), CreateGroupActivity.class); startActivity(i);
startActivity(i); return true;
return true;
default:
return super.onOptionsItemSelected(item);
} }
return super.onOptionsItemSelected(item);
} }
@UiThread @UiThread
@Override @Override
public void onGroupRemoveClick(GroupItem item) { public void onGroupRemoveClick(GroupItem item) {
controller.removeGroup(item.getId(), viewModel.removeGroup(item.getId());
new UiExceptionHandler<DbException>(this) {
// result handled by GroupRemovedEvent and onGroupRemoved()
@Override
public void onExceptionUi(DbException exception) {
handleDbException(exception);
}
});
}
@UiThread
@Override
public void onGroupMessageAdded(GroupMessageHeader header) {
adapter.incrementRevision();
int position = adapter.findItemPosition(header.getGroupId());
GroupItem item = adapter.getItemAt(position);
if (item != null) {
item.addMessageHeader(header);
adapter.updateItemAt(position, item);
}
}
@Override
public void onGroupInvitationReceived() {
loadAvailableGroups();
}
@UiThread
@Override
public void onGroupAdded(GroupId groupId) {
loadGroups();
}
@UiThread
@Override
public void onGroupRemoved(GroupId groupId) {
adapter.incrementRevision();
adapter.removeItem(groupId);
}
@Override
public void onGroupDissolved(GroupId groupId) {
adapter.incrementRevision();
int position = adapter.findItemPosition(groupId);
GroupItem item = adapter.getItemAt(position);
if (item != null) {
item.setDissolved();
adapter.updateItemAt(position, item);
}
} }
@Override @Override
@@ -192,52 +147,6 @@ public class GroupListFragment extends BaseFragment implements
return TAG; return TAG;
} }
private void loadGroups() {
int revision = adapter.getRevision();
controller.loadGroups(
new UiResultExceptionHandler<Collection<GroupItem>, DbException>(
this) {
@Override
public void onResultUi(Collection<GroupItem> groups) {
if (revision == adapter.getRevision()) {
adapter.incrementRevision();
if (groups.isEmpty()) list.showData();
else adapter.replaceAll(groups);
} else {
LOG.info("Concurrent update, reloading");
loadGroups();
}
}
@Override
public void onExceptionUi(DbException exception) {
handleDbException(exception);
}
});
}
private void loadAvailableGroups() {
controller.loadAvailableGroups(
new UiResultExceptionHandler<Integer, DbException>(this) {
@Override
public void onResultUi(Integer num) {
if (num == 0) {
snackbar.dismiss();
} else {
snackbar.setText(getResources().getQuantityString(
R.plurals.groups_invitations_open, num,
num));
if (!snackbar.isShownOrQueued()) snackbar.show();
}
}
@Override
public void onExceptionUi(DbException exception) {
handleDbException(exception);
}
});
}
/** /**
* This method is handling the available groups snackbar action * This method is handling the available groups snackbar action
*/ */

View File

@@ -1,17 +1,18 @@
package org.briarproject.briar.android.privategroup.list; package org.briarproject.briar.android.privategroup.list;
import org.briarproject.briar.android.activity.ActivityScope; import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.multibindings.IntoMap;
@Module @Module
public class GroupListModule { public abstract class GroupListModule {
@ActivityScope @Binds
@Provides @IntoMap
GroupListController provideGroupListController( @ViewModelKey(GroupListViewModel.class)
GroupListControllerImpl groupListController) { abstract ViewModel bindGroupListViewModel(
return groupListController; GroupListViewModel groupListViewModel);
}
} }

View File

@@ -1,9 +1,12 @@
package org.briarproject.briar.android.privategroup.list; package org.briarproject.briar.android.privategroup.list;
import android.app.Application;
import org.briarproject.bramble.api.contact.ContactManager; import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchGroupException; import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
@@ -16,11 +19,12 @@ import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupAddedEvent; import org.briarproject.bramble.api.sync.event.GroupAddedEvent;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent; import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.briar.android.controller.DbControllerImpl; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.controller.handler.ExceptionHandler; import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler; import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager; import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.client.MessageTracker.GroupCount; import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
import org.briarproject.briar.api.privategroup.PrivateGroup; import org.briarproject.briar.api.privategroup.PrivateGroup;
import org.briarproject.briar.api.privategroup.PrivateGroupManager; import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent; import org.briarproject.briar.api.privategroup.event.GroupDissolvedEvent;
@@ -30,6 +34,7 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -38,10 +43,13 @@ import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.CallSuper; import androidx.annotation.UiThread;
import androidx.annotation.Nullable; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now; import static org.briarproject.bramble.util.LogUtils.now;
@@ -49,11 +57,10 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class GroupListControllerImpl extends DbControllerImpl class GroupListViewModel extends DbViewModel implements EventListener {
implements GroupListController, EventListener {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(GroupListControllerImpl.class.getName()); getLogger(GroupListViewModel.class.getName());
private final PrivateGroupManager groupManager; private final PrivateGroupManager groupManager;
private final GroupInvitationManager groupInvitationManager; private final GroupInvitationManager groupInvitationManager;
@@ -61,120 +68,137 @@ class GroupListControllerImpl extends DbControllerImpl
private final AndroidNotificationManager notificationManager; private final AndroidNotificationManager notificationManager;
private final EventBus eventBus; private final EventBus eventBus;
// UI thread private final MutableLiveData<LiveResult<List<GroupItem>>> groupItems =
@Nullable new MutableLiveData<>();
private GroupListListener listener; private final MutableLiveData<Integer> numInvitations =
new MutableLiveData<>();
@Inject @Inject
GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor, GroupListViewModel(Application application,
LifecycleManager lifecycleManager, PrivateGroupManager groupManager, @DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
PrivateGroupManager groupManager,
GroupInvitationManager groupInvitationManager, GroupInvitationManager groupInvitationManager,
ContactManager contactManager, ContactManager contactManager,
AndroidNotificationManager notificationManager, EventBus eventBus) { AndroidNotificationManager notificationManager, EventBus eventBus) {
super(dbExecutor, lifecycleManager); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.groupManager = groupManager; this.groupManager = groupManager;
this.groupInvitationManager = groupInvitationManager; this.groupInvitationManager = groupInvitationManager;
this.contactManager = contactManager; this.contactManager = contactManager;
this.notificationManager = notificationManager; this.notificationManager = notificationManager;
this.eventBus = eventBus; this.eventBus = eventBus;
this.eventBus.addListener(this);
} }
@Override @Override
public void setGroupListListener(GroupListListener listener) { protected void onCleared() {
this.listener = listener; super.onCleared();
}
@Override
public void unsetGroupListListener(GroupListListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override
@CallSuper
public void onStart() {
if (listener == null) throw new IllegalStateException();
eventBus.addListener(this);
notificationManager.clearAllGroupMessageNotifications();
}
@Override
@CallSuper
public void onStop() {
eventBus.removeListener(this); eventBus.removeListener(this);
} }
void clearAllGroupMessageNotifications() {
notificationManager.clearAllGroupMessageNotifications();
}
void blockAllGroupMessageNotifications() {
notificationManager.blockAllGroupMessageNotifications();
}
void unblockAllGroupMessageNotifications() {
notificationManager.unblockAllGroupMessageNotifications();
}
@Override @Override
@CallSuper
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (listener == null) throw new IllegalStateException();
if (e instanceof GroupMessageAddedEvent) { if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
LOG.info("Private group message added"); LOG.info("Private group message added");
listener.onGroupMessageAdded(g.getHeader()); onGroupMessageAdded(g.getHeader());
} else if (e instanceof GroupInvitationRequestReceivedEvent) { } else if (e instanceof GroupInvitationRequestReceivedEvent) {
LOG.info("Private group invitation received"); LOG.info("Private group invitation received");
listener.onGroupInvitationReceived(); loadNumInvitations();
} else if (e instanceof GroupAddedEvent) { } else if (e instanceof GroupAddedEvent) {
GroupAddedEvent g = (GroupAddedEvent) e; GroupAddedEvent g = (GroupAddedEvent) e;
ClientId id = g.getGroup().getClientId(); ClientId id = g.getGroup().getClientId();
if (id.equals(CLIENT_ID)) { if (id.equals(CLIENT_ID)) {
LOG.info("Private group added"); LOG.info("Private group added");
listener.onGroupAdded(g.getGroup().getId()); loadGroups();
} }
} else if (e instanceof GroupRemovedEvent) { } else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e; GroupRemovedEvent g = (GroupRemovedEvent) e;
ClientId id = g.getGroup().getClientId(); ClientId id = g.getGroup().getClientId();
if (id.equals(CLIENT_ID)) { if (id.equals(CLIENT_ID)) {
LOG.info("Private group removed"); LOG.info("Private group removed");
listener.onGroupRemoved(g.getGroup().getId()); onGroupRemoved(g.getGroup().getId());
} }
} else if (e instanceof GroupDissolvedEvent) { } else if (e instanceof GroupDissolvedEvent) {
GroupDissolvedEvent g = (GroupDissolvedEvent) e; GroupDissolvedEvent g = (GroupDissolvedEvent) e;
LOG.info("Private group dissolved"); LOG.info("Private group dissolved");
listener.onGroupDissolved(g.getGroupId()); onGroupDissolved(g.getGroupId());
} }
} }
@Override void loadGroups() {
public void loadGroups( loadList(this::loadGroups, groupItems::setValue);
ResultExceptionHandler<Collection<GroupItem>, DbException> handler) {
runOnDbThread(() -> {
try {
long start = now();
Collection<PrivateGroup> groups =
groupManager.getPrivateGroups();
List<GroupItem> items = new ArrayList<>(groups.size());
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
for (PrivateGroup g : groups) {
try {
GroupId id = g.getId();
AuthorId authorId = g.getCreator().getId();
AuthorInfo authorInfo;
if (authorInfos.containsKey(authorId)) {
authorInfo = authorInfos.get(authorId);
} else {
authorInfo = contactManager.getAuthorInfo(authorId);
authorInfos.put(authorId, authorInfo);
}
GroupCount count = groupManager.getGroupCount(id);
boolean dissolved = groupManager.isDissolved(id);
items.add(
new GroupItem(g, authorInfo, count, dissolved));
} catch (NoSuchGroupException e) {
// Continue
}
}
logDuration(LOG, "Loading groups", start);
handler.onResult(items);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
} }
@Override @DatabaseExecutor
public void removeGroup(GroupId g, ExceptionHandler<DbException> handler) { private List<GroupItem> loadGroups(Transaction txn) throws DbException {
long start = now();
Collection<PrivateGroup> groups = groupManager.getPrivateGroups(txn);
List<GroupItem> items = new ArrayList<>(groups.size());
Map<AuthorId, AuthorInfo> authorInfos = new HashMap<>();
for (PrivateGroup g : groups) {
GroupId id = g.getId();
AuthorId authorId = g.getCreator().getId();
AuthorInfo authorInfo;
if (authorInfos.containsKey(authorId)) {
authorInfo = requireNonNull(authorInfos.get(authorId));
} else {
authorInfo = contactManager.getAuthorInfo(txn, authorId);
authorInfos.put(authorId, authorInfo);
}
GroupCount count = groupManager.getGroupCount(txn, id);
boolean dissolved = groupManager.isDissolved(txn, id);
items.add(new GroupItem(g, authorInfo, count, dissolved));
}
Collections.sort(items);
logDuration(LOG, "Loading groups", start);
return items;
}
@UiThread
private void onGroupMessageAdded(GroupMessageHeader header) {
GroupId g = header.getGroupId();
List<GroupItem> list = updateListItems(groupItems,
itemToTest -> itemToTest.getId().equals(g),
itemToUpdate -> new GroupItem(itemToUpdate, header));
if (list == null) return;
// re-sort as the order of items may have changed
Collections.sort(list);
groupItems.setValue(new LiveResult<>(list));
}
@UiThread
private void onGroupDissolved(GroupId groupId) {
List<GroupItem> list = updateListItems(groupItems,
itemToTest -> itemToTest.getId().equals(groupId),
itemToUpdate -> new GroupItem(itemToUpdate, true));
if (list == null) return;
groupItems.setValue(new LiveResult<>(list));
}
@UiThread
private void onGroupRemoved(GroupId groupId) {
List<GroupItem> list =
removeListItems(groupItems, i -> i.getId().equals(groupId));
if (list == null) return;
groupItems.setValue(new LiveResult<>(list));
}
void removeGroup(GroupId g) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
long start = now(); long start = now();
@@ -182,23 +206,26 @@ class GroupListControllerImpl extends DbControllerImpl
logDuration(LOG, "Removing group", start); logDuration(LOG, "Removing group", start);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
handler.onException(e);
} }
}); });
} }
@Override void loadNumInvitations() {
public void loadAvailableGroups(
ResultExceptionHandler<Integer, DbException> handler) {
runOnDbThread(() -> { runOnDbThread(() -> {
try { try {
handler.onResult( int i = groupInvitationManager.getInvitations().size();
groupInvitationManager.getInvitations().size()); numInvitations.postValue(i);
} catch (DbException e) { } catch (DbException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
handler.onException(e);
} }
}); });
} }
LiveData<LiveResult<List<GroupItem>>> getGroupItems() {
return groupItems;
}
LiveData<Integer> getNumInvitations() {
return numInvitations;
}
} }

View File

@@ -29,6 +29,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
private final static float ALPHA = 0.42f; private final static float ALPHA = 0.42f;
private final Context ctx;
private final ViewGroup layout; private final ViewGroup layout;
private final TextAvatarView avatar; private final TextAvatarView avatar;
private final TextView name; private final TextView name;
@@ -40,7 +41,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
GroupViewHolder(View v) { GroupViewHolder(View v) {
super(v); super(v);
ctx = v.getContext();
layout = (ViewGroup) v; layout = (ViewGroup) v;
avatar = v.findViewById(R.id.avatarView); avatar = v.findViewById(R.id.avatarView);
name = v.findViewById(R.id.nameView); name = v.findViewById(R.id.nameView);
@@ -51,8 +52,7 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
remove = v.findViewById(R.id.removeButton); remove = v.findViewById(R.id.removeButton);
} }
void bindView(Context ctx, GroupItem group, void bindView(GroupItem group, OnGroupRemoveClickListener listener) {
OnGroupRemoveClickListener listener) {
// Avatar // Avatar
avatar.setText(group.getName().substring(0, 1)); avatar.setText(group.getName().substring(0, 1));
avatar.setBackgroundBytes(group.getId().getBytes()); avatar.setBackgroundBytes(group.getId().getBytes());

View File

@@ -124,7 +124,7 @@ public class GroupMemberListActivity extends BriarActivity
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }

View File

@@ -80,7 +80,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -120,7 +120,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
new UiExceptionHandler<DbException>(this) { new UiExceptionHandler<DbException>(this) {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
} }
@@ -137,7 +137,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
new UiExceptionHandler<DbException>(this) { new UiExceptionHandler<DbException>(this) {
@Override @Override
public void onExceptionUi(DbException exception) { public void onExceptionUi(DbException exception) {
handleDbException(exception); handleException(exception);
} }
}); });
supportFinishAfterTransition(); supportFinishAfterTransition();

View File

@@ -0,0 +1,31 @@
package org.briarproject.briar.android.reporting;
import android.content.Context;
import android.os.Process;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.lang.Thread.UncaughtExceptionHandler;
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
@NotNullByDefault
public class BriarExceptionHandler implements UncaughtExceptionHandler {
private final Context ctx;
private final long appStartTime;
public BriarExceptionHandler(Context ctx) {
this.ctx = ctx;
this.appStartTime = System.currentTimeMillis();
}
@Override
public void uncaughtException(Thread t, Throwable e) {
// activity runs in its own process, so we can kill the old one
startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime);
Process.killProcess(Process.myPid());
System.exit(10);
}
}

View File

@@ -0,0 +1,343 @@
/*
Some of the code in this file was copied from or inspired by ACRA
which is licenced under Apache 2.0 and authored by F43nd1r.
https://github.com/ACRA/acra/blob/3b9034/acra-core/src/main/java/org/acra/collector/
*/
package org.briarproject.briar.android.reporting;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Environment;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo;
import org.briarproject.briar.android.reporting.ReportData.ReportItem;
import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import javax.annotation.concurrent.Immutable;
import androidx.annotation.Nullable;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.content.Context.WIFI_P2P_SERVICE;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.content.ContextCompat.getSystemService;
import static java.util.Locale.US;
import static java.util.Objects.requireNonNull;
import static java.util.TimeZone.getTimeZone;
import static org.briarproject.bramble.util.AndroidUtils.getBluetoothAddressAndMethod;
import static org.briarproject.bramble.util.PrivacyUtils.scrubInetAddress;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@Immutable
@NotNullByDefault
class BriarReportCollector {
private final Context ctx;
BriarReportCollector(Context ctx) {
this.ctx = ctx;
}
public ReportData collectReportData(@Nullable Throwable t,
long appStartTime) {
ReportData reportData = new ReportData()
.add(getBasicInfo(t))
.add(getDeviceInfo());
if (t != null) reportData.add(getStacktrace(t));
return reportData
.add(getTimeInfo(appStartTime))
.add(getMemory())
.add(getStorage())
.add(getConnectivity())
.add(getBuildConfig())
.add(getLogcat())
.add(getDeviceFeatures());
}
private ReportItem getBasicInfo(@Nullable Throwable t) {
String packageName = ctx.getPackageName();
PackageManager pm = ctx.getPackageManager();
String versionName;
int versionCode;
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
versionName = packageInfo.versionName;
versionCode = packageInfo.versionCode;
} catch (NameNotFoundException e) {
versionName = e.toString();
versionCode = 0;
}
MultiReportInfo basicInfo = new MultiReportInfo()
.add("PackageName", packageName)
.add("VersionName", versionName)
.add("VersionCode", versionCode)
.add("IsCrashReport", t != null);
return new ReportItem("BasicInfo", R.string.dev_report_basic_info,
basicInfo, false);
}
private ReportItem getDeviceInfo() {
MultiReportInfo deviceInfo = new MultiReportInfo()
.add("AndroidVersion", Build.VERSION.RELEASE)
.add("AndroidApi", SDK_INT)
.add("Product", Build.PRODUCT)
.add("Model", Build.MODEL)
.add("Brand", Build.BRAND);
return new ReportItem("DeviceInfo", R.string.dev_report_device_info,
deviceInfo);
}
private ReportItem getStacktrace(Throwable t) {
final Writer sw = new StringWriter();
final PrintWriter printWriter = new PrintWriter(sw);
if (!isNullOrEmpty(t.getMessage())) {
printWriter.println(t.getMessage());
}
t.printStackTrace(printWriter);
SingleReportInfo stacktrace = new SingleReportInfo(sw.toString());
return new ReportItem("Stacktrace", R.string.dev_report_stacktrace,
stacktrace);
}
private ReportItem getTimeInfo(long startTime) {
MultiReportInfo timeInfo = new MultiReportInfo()
.add("ReportTime", formatTime(System.currentTimeMillis()));
if (startTime > -1) {
timeInfo.add("AppStartTime", formatTime(startTime));
}
return new ReportItem("TimeInfo", R.string.dev_report_time_info,
timeInfo);
}
private String formatTime(long time) {
SimpleDateFormat format =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", US);
format.setTimeZone(getTimeZone("UTC"));
return format.format(new Date(time));
}
private ReportItem getMemory() {
MultiReportInfo memInfo = new MultiReportInfo();
// System memory
ActivityManager am = getSystemService(ctx, ActivityManager.class);
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
requireNonNull(am).getMemoryInfo(mem);
memInfo.add("SystemMemoryTotal", mem.totalMem);
memInfo.add("SystemMemoryFree", mem.availMem);
memInfo.add("SystemMemoryThreshold", mem.threshold);
// Virtual machine memory
Runtime runtime = Runtime.getRuntime();
memInfo.add("VirtualMachineMemoryAllocated", runtime.totalMemory());
memInfo.add("VirtualMachineMemoryFree", runtime.freeMemory());
memInfo.add("VirtualMachineMemoryMaximum", runtime.maxMemory());
return new ReportItem("Memory", R.string.dev_report_memory, memInfo);
}
private ReportItem getStorage() {
MultiReportInfo storageInfo = new MultiReportInfo();
// Internal storage
File root = Environment.getRootDirectory();
storageInfo.add("InternalStorageTotal", root.getTotalSpace());
storageInfo.add("InternalStorageFree", root.getFreeSpace());
// External storage (SD card)
File sd = Environment.getExternalStorageDirectory();
storageInfo.add("ExternalStorageTotal", sd.getTotalSpace());
storageInfo.add("ExternalStorageFree", sd.getFreeSpace());
return new ReportItem("Storage", R.string.dev_report_storage,
storageInfo);
}
private ReportItem getConnectivity() {
MultiReportInfo connectivityInfo = new MultiReportInfo();
// Is mobile data available?
ConnectivityManager cm = requireNonNull(
getSystemService(ctx, ConnectivityManager.class));
NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
boolean mobileAvailable = mobile != null && mobile.isAvailable();
connectivityInfo.add("MobileDataAvailable", mobileAvailable);
// Is mobile data enabled?
boolean mobileEnabled = false;
try {
Class<?> clazz = Class.forName(cm.getClass().getName());
Method method = clazz.getDeclaredMethod("getMobileDataEnabled");
method.setAccessible(true);
mobileEnabled = (Boolean) requireNonNull(method.invoke(cm));
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalArgumentException
| InvocationTargetException
| IllegalAccessException e) {
connectivityInfo
.add("MobileDataReflectionException", e.toString());
}
connectivityInfo.add("MobileDataEnabled", mobileEnabled);
// Is mobile data connected ?
boolean mobileConnected = mobile != null && mobile.isConnected();
connectivityInfo.add("MobileDataConnected", mobileConnected);
// Is wifi available?
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
boolean wifiAvailable = wifi != null && wifi.isAvailable();
connectivityInfo.add("WifiAvailable", wifiAvailable);
// Is wifi enabled?
WifiManager wm = getSystemService(ctx, WifiManager.class);
boolean wifiEnabled = wm != null &&
wm.getWifiState() == WIFI_STATE_ENABLED;
connectivityInfo.add("WifiEnabled", wifiEnabled);
// Is wifi connected?
boolean wifiConnected = wifi != null && wifi.isConnected();
connectivityInfo.add("WifiConnected", wifiConnected);
// Is wifi direct supported?
boolean wifiDirect = ctx.getSystemService(WIFI_P2P_SERVICE) != null;
connectivityInfo.add("WiFiDirectSupported", wifiDirect);
if (wm != null) {
WifiInfo wifiInfo = wm.getConnectionInfo();
if (wifiInfo != null) {
int ip = wifiInfo.getIpAddress(); // Nice API, Google
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
InetAddress address = InetAddress.getByAddress(ipBytes);
connectivityInfo.add("WiFiAddress",
scrubInetAddress(address));
} catch (UnknownHostException ignored) {
// Should only be thrown if address has illegal length
}
}
}
// Is Bluetooth available?
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
connectivityInfo.add("BluetoothAvailable", false);
} else {
connectivityInfo.add("BluetoothAvailable", true);
// Is Bluetooth enabled?
@SuppressLint("HardwareIds")
boolean btEnabled = bt.isEnabled()
&& !isNullOrEmpty(bt.getAddress());
connectivityInfo.add("BluetoothEnabled", btEnabled);
// Is Bluetooth connectable?
int scanMode = bt.getScanMode();
boolean btConnectable = scanMode == SCAN_MODE_CONNECTABLE ||
scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
connectivityInfo.add("BluetoothConnectable", btConnectable);
// Is Bluetooth discoverable?
boolean btDiscoverable =
scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
connectivityInfo.add("BluetoothDiscoverable", btDiscoverable);
if (SDK_INT >= 21) {
// Is Bluetooth LE scanning and advertising supported?
boolean btLeScan = bt.getBluetoothLeScanner() != null;
connectivityInfo.add("BluetoothLeScanningSupported", btLeScan);
boolean btLeAdvertise =
bt.getBluetoothLeAdvertiser() != null;
connectivityInfo.add("BluetoothLeAdvertisingSupported",
btLeAdvertise);
}
Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt);
String address = p.getFirst();
String method = p.getSecond();
connectivityInfo.add("BluetoothAddress", scrubMacAddress(address));
connectivityInfo.add("BluetoothAddressMethod", method);
}
return new ReportItem("Connectivity", R.string.dev_report_connectivity,
connectivityInfo);
}
private ReportItem getBuildConfig() {
MultiReportInfo buildConfig = new MultiReportInfo()
.add("GitHash", BuildConfig.GitHash)
.add("BuildType", BuildConfig.BUILD_TYPE)
.add("Flavor", BuildConfig.FLAVOR)
.add("Debug", BuildConfig.DEBUG)
.add("BuildTimestamp", formatTime(BuildConfig.BuildTimestamp));
return new ReportItem("BuildConfig", R.string.dev_report_build_config,
buildConfig);
}
private ReportItem getLogcat() {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
StringBuilder sb = new StringBuilder();
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : app.getRecentLogRecords()) {
sb.append(formatter.format(record)).append('\n');
}
return new ReportItem("Logcat", R.string.dev_report_logcat,
sb.toString());
}
private ReportItem getDeviceFeatures() {
PackageManager pm = ctx.getPackageManager();
FeatureInfo[] features = pm.getSystemAvailableFeatures();
MultiReportInfo deviceFeatures = new MultiReportInfo();
for (FeatureInfo feature : features) {
String featureName = feature.name;
if (featureName != null) {
deviceFeatures.add(featureName, true);
} else {
deviceFeatures.add("glEsVersion", feature.getGlEsVersion());
}
}
return new ReportItem("DeviceFeatures",
R.string.dev_report_device_features, deviceFeatures);
}
}

View File

@@ -1,277 +0,0 @@
package org.briarproject.briar.android.reporting;
import android.app.ActivityManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import org.acra.builder.ReportBuilder;
import org.acra.builder.ReportPrimer;
import org.briarproject.bramble.api.Pair;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.logging.BriefLogFormatter;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import androidx.annotation.NonNull;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE;
import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
import static android.content.Context.ACTIVITY_SERVICE;
import static android.content.Context.CONNECTIVITY_SERVICE;
import static android.content.Context.WIFI_P2P_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Collections.unmodifiableMap;
import static org.briarproject.bramble.util.AndroidUtils.getBluetoothAddressAndMethod;
import static org.briarproject.bramble.util.PrivacyUtils.scrubInetAddress;
import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
public class BriarReportPrimer implements ReportPrimer {
@Override
public void primeReport(@NonNull Context ctx,
@NonNull ReportBuilder builder) {
CustomDataTask task = new CustomDataTask(ctx);
FutureTask<Map<String, String>> futureTask = new FutureTask<>(task);
// Use a new thread as the Android executor thread may have died
new SingleShotAndroidExecutor(futureTask).start();
try {
builder.customData(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
builder.customData("Custom data exception", e.toString());
}
}
private static class CustomDataTask
implements Callable<Map<String, String>> {
private final Context ctx;
private CustomDataTask(Context ctx) {
this.ctx = ctx;
}
@Override
public Map<String, String> call() {
Map<String, String> customData = new LinkedHashMap<>();
// Log
BriarApplication app =
(BriarApplication) ctx.getApplicationContext();
StringBuilder sb = new StringBuilder();
Formatter formatter = new BriefLogFormatter();
for (LogRecord record : app.getRecentLogRecords()) {
sb.append(formatter.format(record)).append('\n');
}
customData.put("Log", sb.toString());
// System memory
Object o = ctx.getSystemService(ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) o;
ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mem);
String systemMemory;
systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, "
+ (mem.availMem / 1024 / 1204) + " MiB free, "
+ (mem.threshold / 1024 / 1024) + " MiB threshold";
customData.put("System memory", systemMemory);
// Virtual machine memory
Runtime runtime = Runtime.getRuntime();
long heap = runtime.totalMemory();
long heapFree = runtime.freeMemory();
long heapMax = runtime.maxMemory();
String vmMemory = (heap / 1024 / 1024) + " MiB allocated, "
+ (heapFree / 1024 / 1024) + " MiB free, "
+ (heapMax / 1024 / 1024) + " MiB maximum";
customData.put("Virtual machine memory", vmMemory);
// Internal storage
File root = Environment.getRootDirectory();
long rootTotal = root.getTotalSpace();
long rootFree = root.getFreeSpace();
String internal = (rootTotal / 1024 / 1024) + " MiB total, "
+ (rootFree / 1024 / 1024) + " MiB free";
customData.put("Internal storage", internal);
// External storage (SD card)
File sd = Environment.getExternalStorageDirectory();
long sdTotal = sd.getTotalSpace();
long sdFree = sd.getFreeSpace();
String external = (sdTotal / 1024 / 1024) + " MiB total, "
+ (sdFree / 1024 / 1024) + " MiB free";
customData.put("External storage", external);
// Is mobile data available?
o = ctx.getSystemService(CONNECTIVITY_SERVICE);
ConnectivityManager cm = (ConnectivityManager) o;
NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE);
boolean mobileAvailable = mobile != null && mobile.isAvailable();
// Is mobile data enabled?
boolean mobileEnabled = false;
try {
Class<?> clazz = Class.forName(cm.getClass().getName());
Method method = clazz.getDeclaredMethod("getMobileDataEnabled");
method.setAccessible(true);
mobileEnabled = (Boolean) method.invoke(cm);
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalArgumentException
| InvocationTargetException
| IllegalAccessException e) {
customData.put("Mobile data reflection exception",
e.toString());
}
// Is mobile data connected ?
boolean mobileConnected = mobile != null && mobile.isConnected();
String mobileStatus;
if (mobileAvailable) mobileStatus = "Available, ";
else mobileStatus = "Not available, ";
if (mobileEnabled) mobileStatus += "enabled, ";
else mobileStatus += "not enabled, ";
if (mobileConnected) mobileStatus += "connected";
else mobileStatus += "not connected";
customData.put("Mobile data status", mobileStatus);
// Is wifi available?
NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI);
boolean wifiAvailable = wifi != null && wifi.isAvailable();
// Is wifi enabled?
o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE);
WifiManager wm = (WifiManager) o;
boolean wifiEnabled = wm != null &&
wm.getWifiState() == WIFI_STATE_ENABLED;
// Is wifi connected?
boolean wifiConnected = wifi != null && wifi.isConnected();
String wifiStatus;
if (wifiAvailable) wifiStatus = "Available, ";
else wifiStatus = "Not available, ";
if (wifiEnabled) wifiStatus += "enabled, ";
else wifiStatus += "not enabled, ";
if (wifiConnected) wifiStatus += "connected";
else wifiStatus += "not connected";
customData.put("Wi-Fi status", wifiStatus);
// Is wifi direct supported?
String wifiDirectStatus = "Supported";
if (ctx.getSystemService(WIFI_P2P_SERVICE) == null)
wifiDirectStatus = "Not supported";
customData.put("Wi-Fi Direct", wifiDirectStatus);
if (wm != null) {
WifiInfo wifiInfo = wm.getConnectionInfo();
if (wifiInfo != null) {
int ip = wifiInfo.getIpAddress(); // Nice API, Google
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte) (ip & 0xFF);
ipBytes[1] = (byte) ((ip >> 8) & 0xFF);
ipBytes[2] = (byte) ((ip >> 16) & 0xFF);
ipBytes[3] = (byte) ((ip >> 24) & 0xFF);
try {
InetAddress address = InetAddress.getByAddress(ipBytes);
customData.put("Wi-Fi address",
scrubInetAddress(address));
} catch (UnknownHostException ignored) {
// Should only be thrown if address has illegal length
}
}
}
// Is Bluetooth available?
BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
if (bt == null) {
customData.put("Bluetooth status", "Not available");
} else {
// Is Bluetooth enabled?
boolean btEnabled = bt.isEnabled()
&& !isNullOrEmpty(bt.getAddress());
// Is Bluetooth connectable?
int scanMode = bt.getScanMode();
boolean btConnectable = scanMode == SCAN_MODE_CONNECTABLE ||
scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
// Is Bluetooth discoverable?
boolean btDiscoverable =
scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE;
String btStatus;
if (btEnabled) btStatus = "Available, enabled, ";
else btStatus = "Available, not enabled, ";
if (btConnectable) btStatus += "connectable, ";
else btStatus += "not connectable, ";
if (btDiscoverable) btStatus += "discoverable";
else btStatus += "not discoverable";
customData.put("Bluetooth status", btStatus);
if (SDK_INT >= 21) {
// Is Bluetooth LE scanning and advertising supported?
boolean btLeScan = bt.getBluetoothLeScanner() != null;
boolean btLeAdvertise =
bt.getBluetoothLeAdvertiser() != null;
String btLeStatus;
if (btLeScan) btLeStatus = "Scanning, ";
else btLeStatus = "No scanning, ";
if (btLeAdvertise) btLeStatus += "advertising";
else btLeStatus += "no advertising";
customData.put("Bluetooth LE status", btLeStatus);
}
Pair<String, String> p = getBluetoothAddressAndMethod(ctx, bt);
String address = p.getFirst();
String method = p.getSecond();
customData.put("Bluetooth address", scrubMacAddress(address));
customData.put("Bluetooth address method", method);
}
// Git commit ID
customData.put("Commit ID", BuildConfig.GitHash);
return unmodifiableMap(customData);
}
}
private static class SingleShotAndroidExecutor extends Thread {
private final Runnable runnable;
private SingleShotAndroidExecutor(Runnable runnable) {
this.runnable = runnable;
}
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
handler.post(runnable);
handler.post(() -> {
Looper looper = Looper.myLooper();
if (looper != null) looper.quit();
});
Looper.loop();
}
}
}

View File

@@ -1,46 +0,0 @@
package org.briarproject.briar.android.reporting;
import android.content.Context;
import org.acra.collector.CrashReportData;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderException;
import org.briarproject.bramble.api.reporting.DevReporter;
import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.briar.android.AndroidComponent;
import java.io.File;
import java.io.FileNotFoundException;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import static org.acra.ReportField.REPORT_ID;
public class BriarReportSender implements ReportSender {
private final AndroidComponent component;
@Inject
DevReporter reporter;
BriarReportSender(AndroidComponent component) {
this.component = component;
}
@Override
public void send(@NonNull Context ctx,
@NonNull CrashReportData errorContent)
throws ReportSenderException {
component.inject(this);
String crashReport = errorContent.toJSON().toString();
try {
File reportDir = AndroidUtils.getReportDir(ctx);
String reportId = errorContent.getProperty(REPORT_ID);
reporter.encryptReportToFile(reportDir, reportId, crashReport);
} catch (FileNotFoundException e) {
throw new ReportSenderException("Failed to encrypt report", e);
}
}
}

View File

@@ -1,22 +0,0 @@
package org.briarproject.briar.android.reporting;
import android.content.Context;
import org.acra.config.ACRAConfiguration;
import org.acra.sender.ReportSender;
import org.acra.sender.ReportSenderFactory;
import org.briarproject.briar.android.BriarApplication;
import androidx.annotation.NonNull;
public class BriarReportSenderFactory implements ReportSenderFactory {
@NonNull
@Override
public ReportSender create(@NonNull Context ctx,
@NonNull ACRAConfiguration config) {
// ACRA passes in the Application as context
BriarApplication app = (BriarApplication) ctx;
return new BriarReportSender(app.getApplicationComponent());
}
}

View File

@@ -8,15 +8,36 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class CrashFragment extends Fragment { public class CrashFragment extends BaseFragment {
public final static String TAG = CrashFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
private ReportViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ReportViewModel.class);
}
@Nullable @Nullable
@Override @Override
@@ -27,15 +48,16 @@ public class CrashFragment extends Fragment {
.inflate(R.layout.fragment_crash, container, false); .inflate(R.layout.fragment_crash, container, false);
v.findViewById(R.id.acceptButton).setOnClickListener(view -> v.findViewById(R.id.acceptButton).setOnClickListener(view ->
getDevReportActivity().displayFragment(true)); viewModel.showReport());
v.findViewById(R.id.declineButton).setOnClickListener(view -> v.findViewById(R.id.declineButton).setOnClickListener(view ->
getDevReportActivity().closeReport()); viewModel.closeReport());
return v; return v;
} }
private DevReportActivity getDevReportActivity() { @Override
return (DevReportActivity) requireNonNull(getActivity()); public String getUniqueTag() {
return TAG;
} }
} }

View File

@@ -0,0 +1,117 @@
package org.briarproject.briar.android.reporting;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
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.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.logout.HideUiActivity;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class CrashReportActivity extends BaseActivity
implements BaseFragmentListener {
public static final String EXTRA_THROWABLE = "throwable";
public static final String EXTRA_APP_START_TIME = "appStartTime";
@Inject
ViewModelProvider.Factory viewModelFactory;
private ReportViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dev_report);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ReportViewModel.class);
Intent intent = getIntent();
Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE);
long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1);
viewModel.init(t, appStartTime);
viewModel.getShowReport().observeEvent(this, show -> {
if (show) displayFragment(true);
});
viewModel.getCloseReport().observeEvent(this, res -> {
if (res != 0) {
Toast.makeText(this, res, LENGTH_LONG).show();
}
exit();
});
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (savedInstanceState == null) displayFragment(viewModel.isFeedback());
}
@Override
public void runOnDbThread(Runnable runnable) {
throw new AssertionError("deprecated!!!");
}
@Override
public void onBackPressed() {
exit();
}
void displayFragment(boolean showReportForm) {
BaseFragment f;
if (showReportForm) {
f = new ReportFormFragment();
requireNonNull(getSupportActionBar()).show();
} else {
f = new CrashFragment();
requireNonNull(getSupportActionBar()).hide();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, f.getUniqueTag())
.commit();
}
void exit() {
if (!viewModel.isFeedback()) {
Intent i = new Intent(this, HideUiActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK
| FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
// crash reports run in their own process that we should kill now
// otherwise it keeps running and e.g. doesn't pick up theme changes
new Handler(Looper.getMainLooper()).postDelayed(() -> {
Process.killProcess(Process.myPid());
// kill the process with some delay to keep the Toast visible
}, 5000);
}
finish();
}
}

View File

@@ -1,188 +0,0 @@
package org.briarproject.briar.android.reporting;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import org.acra.dialog.BaseCrashReportDialog;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.Localizer;
import org.briarproject.briar.android.logout.HideUiActivity;
import org.briarproject.briar.android.util.UserFeedback;
import java.io.File;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static java.util.Objects.requireNonNull;
import static org.acra.ACRAConstants.EXTRA_REPORT_FILE;
import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOTS;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class DevReportActivity extends BaseCrashReportDialog {
private AppCompatDelegate delegate;
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);
}
return delegate;
}
@Override
protected void preInit(@Nullable Bundle savedInstanceState) {
super.preInit(savedInstanceState);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
getDelegate().applyDayNight();
// We always need to re-apply the theme
// for day/night the changes to take effect.
// On API 23+, we should bypass setTheme(), which will no-op
// if the theme ID is identical to the current theme ID.
int theme = R.style.BriarTheme_NoActionBar;
if (SDK_INT >= 23) {
onApplyThemeResource(getTheme(), theme, false);
} else {
setTheme(theme);
}
}
@Override
public void init(@Nullable Bundle state) {
super.init(state);
if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE);
getDelegate().setContentView(R.layout.activity_dev_report);
Toolbar toolbar = findViewById(R.id.toolbar);
getDelegate().setSupportActionBar(toolbar);
String title = getString(isFeedback() ? R.string.feedback_title :
R.string.crash_report_title);
requireNonNull(getDelegate().getSupportActionBar()).setTitle(title);
if (state == null) displayFragment(isFeedback());
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
}
@Override
public void onPostCreate(@Nullable Bundle state) {
super.onPostCreate(state);
getDelegate().onPostCreate(state);
}
@Override
protected void onStart() {
super.onStart();
getDelegate().onStart();
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
public void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
getDelegate().onSaveInstanceState(outState);
}
@Override
public void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
public void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public void onBackPressed() {
closeReport();
}
void sendCrashReport(String comment, String email) {
sendCrash(comment, email);
}
private boolean isFeedback() {
return getException() instanceof UserFeedback;
}
void displayFragment(boolean showReportForm) {
Fragment f;
if (showReportForm) {
File file =
(File) getIntent().getSerializableExtra(EXTRA_REPORT_FILE);
f = ReportFormFragment.newInstance(isFeedback(), file);
requireNonNull(getDelegate().getSupportActionBar()).show();
} else {
f = new CrashFragment();
requireNonNull(getDelegate().getSupportActionBar()).hide();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, f.getTag())
.commit();
}
@Override
public void invalidateOptionsMenu() {
super.invalidateOptionsMenu();
getDelegate().invalidateOptionsMenu();
}
void closeReport() {
cancelReports();
exit();
}
void exit() {
if (!isFeedback()) {
Intent i = new Intent(this, HideUiActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK
| FLAG_ACTIVITY_NO_ANIMATION
| FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
}
finish();
}
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.briar.android.reporting;
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 DevReportModule {
@Binds
@IntoMap
@ViewModelKey(ReportViewModel.class)
abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel);
}

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