Compare commits

...

136 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
akwizgran
54893d2716 Bump version numbers for 1.2.12 release. 2020-11-02 14:51:34 +00:00
akwizgran
84657127b8 Update translations. 2020-11-02 14:50:06 +00:00
akwizgran
01a146ba71 Merge branch '1647-illegal-state' into 'master'
Fix IllegalStateException when creating image attachments

Closes #1647

See merge request briar/briar!1187
2020-10-30 16:17:20 +00:00
akwizgran
a30e5b672e Merge branch '1592-image-placeholders' into 'master'
Show Attachment Placeholders

Closes #1592

See merge request briar/briar!1186
2020-10-30 15:54:25 +00:00
Torsten Grote
edb584dc3b Merge branch 'add-contacts-via-bluetooth' into 'master'
Add contacts via Bluetooth if possible

See merge request briar/briar!1292
2020-10-29 16:54:05 +00:00
akwizgran
12a8907c8b Ignore missing location permission on API < 23 where it's not needed. 2020-10-29 14:34:10 +00:00
akwizgran
e0f381a973 Try all transports in order of preference. 2020-10-29 11:48:10 +00:00
Torsten Grote
61d3d133e8 Merge branch '1147-only-alice-performs-discovery' into 'master'
Only Alice should perform Bluetooth discovery

See merge request briar/briar!1291
2020-10-28 11:11:34 +00:00
akwizgran
0caa522f07 Remove error message, return to intro fragment when retrying. 2020-10-27 17:37:22 +00:00
akwizgran
948212103c Require Bluetooth permissions if device supports Bluetooth. 2020-10-27 16:24:34 +00:00
akwizgran
ce1a57c2b4 Prefer Bluetooth for adding contacts. 2020-10-27 16:24:33 +00:00
akwizgran
922a52bf83 Only Alice should perform Bluetooth discovery. 2020-10-27 16:21:30 +00:00
akwizgran
8cbb38ee68 Bump version numbers for 1.2.11 release. 2020-10-14 13:15:29 +01:00
akwizgran
1c4cf7d771 Update translations. 2020-10-14 13:14:05 +01:00
akwizgran
090a1bd84e Merge branch '1781-change-alias' into 'master'
Add method to change contact alias to REST API

Closes #1781

See merge request briar/briar!1286
2020-10-14 11:47:10 +00:00
Nico Alt
44f6f5d416 Add method to change contact alias to REST API
Needed for https://code.briarproject.org/briar/briar-gtk/-/issues/14 and
https://code.briarproject.org/briar/python-briar-wrapper/-/issues/6.

Fixes #1781
2020-10-13 23:33:26 +02:00
Torsten Grote
b88f012880 Merge branch 'make-crash-report-text-selectable' into 'master'
Make the text of crash reports selectable

See merge request briar/briar!1290
2020-10-13 16:23:49 +00:00
akwizgran
93f434e54b Merge branch '1782-delete-all-messages' into 'master'
Add method to delete all private messages to REST API

Closes #1782

See merge request briar/briar!1287
2020-10-13 15:46:01 +00:00
akwizgran
92f4a3a404 Make the text of crash reports selectable.
This makes it possible for users to send device data by other means if
they can't connect to Tor to send a crash report.
2020-10-13 16:44:43 +01:00
Nico Alt
c017a813b0 Add output of DeletionResult to deleteAllMessages call 2020-10-08 15:03:49 +02:00
Nico Alt
6c6dbfd357 Add method to delete all private messages to REST API
Needed for https://code.briarproject.org/briar/briar-gtk/-/issues/11.

Fixes #1782
2020-10-08 14:03:16 +02:00
akwizgran
1f246637e2 Merge branch 'kotlin-no-star-imports' into 'master'
Change Kotlin coding style to not do star imports

See merge request briar/briar!1289
2020-10-05 13:54:51 +00:00
Torsten Grote
1ac17cf859 [headless] Change coding style to not do star imports 2020-10-05 09:54:35 -03:00
akwizgran
0a3ff41feb Merge branch '1780-mark-as-read' into 'master'
Add method to mark message as read to REST API

Closes #1780

See merge request briar/briar!1285
2020-10-05 11:38:33 +00:00
Nico Alt
9738dd2838 Add method to mark message as read to REST API
When exposing unread messages counters in
https://code.briarproject.org/briar/briar/-/merge_requests/1283, I
noticed that they were never set to 0.

Fixes #1780
2020-10-03 23:23:54 +02:00
akwizgran
be0e21d39b Merge branch '1507-extract-tor-binaries-to-lib-dir' into 'master'
Raise targetSdkVersion to 29, package Tor binaries as libraries

Closes #1507 and #1185

See merge request briar/briar!1279
2020-09-29 13:19:42 +00:00
Torsten Grote
6a2c2bed0f Merge branch '1785-bluetooth-adapter-npe' into 'master'
Check whether Bluetooth adapter exists before trying to get address

Closes #1785

See merge request briar/briar!1288
2020-09-29 12:51:22 +00:00
akwizgran
de9c6d4447 Extract version constants into top-level build file. 2020-09-29 13:50:17 +01:00
akwizgran
37a2d9f990 Extract binaries even if older versions already exist. 2020-09-29 13:48:45 +01:00
akwizgran
0e1fb406b5 Extract library filenames into constants. 2020-09-29 13:48:45 +01:00
akwizgran
b72e8fa490 Package Tor binaries as libraries so we're allowed to execute them. 2020-09-29 13:48:45 +01:00
akwizgran
f3157e5276 Raise target SDK version to 29. 2020-09-29 13:48:43 +01:00
akwizgran
e2124ff3c9 Merge branch '1779-headless-messages-sent-acked' into 'master'
Expose message delivery state changes to websockets API

Closes #1779

See merge request briar/briar!1284
2020-09-29 12:46:41 +00:00
akwizgran
66cc9d25e7 Merge branch '1746-headless-unread-counter' into 'master'
Expose unread messages count in API's contacts list

Closes #1746

See merge request briar/briar!1283
2020-09-29 12:45:30 +00:00
akwizgran
e9cdec95e0 Check whether Bluetooth adapter exists before trying to get address. 2020-09-29 13:39:46 +01:00
Nico Alt
63d3a78dda Expose message delivery state changes to websockets API
We already indicate whether a message was sent/acked, but we don't
inform about updates.

Needed for briar-gtk#69.

Fixes #1779
2020-09-25 22:39:40 +02:00
Nico Alt
ccbe6d4bb8 Expose unread messages count in API's contacts list
Fixes #1746
2020-09-25 17:46:55 +02:00
akwizgran
54b852db70 Bump version numbers for 1.2.10 release. 2020-09-25 13:42:28 +01:00
akwizgran
8d55ea3f6f Update translations. 2020-09-25 13:41:31 +01:00
Torsten Grote
4e5f2e31df Merge branch 'deterministic-briar-headless-jar' into 'master'
Make briar-headless.jar deterministic

See merge request briar/briar!1282
2020-09-17 20:25:12 +00:00
akwizgran
518c0370c8 Make briar-headless.jar deterministic. 2020-09-17 16:13:01 +01:00
akwizgran
7ef2fb5f0c Update Dutch translation. 2020-09-17 14:55:11 +01:00
akwizgran
1210b27bd1 Update translations. 2020-09-17 14:48:10 +01:00
Torsten Grote
cdf1a4abcd Merge branch 'update-feed-manager-integration-test-expectations' into 'master'
Update FeedManagerIntegrationTest expectations

See merge request briar/briar!1281
2020-09-10 15:05:39 +00:00
akwizgran
b18ef7e72d Update FeedManagerIntegrationTest expectations.
The "Schneier on Security" RSS feed no longer has a description.
2020-09-10 15:56:26 +01:00
Torsten Grote
48d907dda5 Merge branch '185-transports-activity' into 'master'
Add connections screen with information about transports

Closes #185

See merge request briar/briar!1277
2020-09-04 12:27:52 +00:00
akwizgran
3e5b7f451a Merge branch '1716-duplicate-unlock-screen' into 'master'
Don't show duplicate unlock screen on API 29+

Closes #1716

See merge request briar/briar!1280
2020-09-04 12:07:46 +00:00
akwizgran
95cccd1d15 Don't show duplicate unlock screen on API 29+. 2020-09-04 12:37:00 +01:00
Torsten Grote
0a33c77393 Merge branch 'cancel-rendezvous-polling' into 'master'
Only run the rendezvous polling task when we have pending contacts

See merge request briar/briar!1276
2020-09-01 11:53:16 +00:00
Torsten Grote
80caa7634a Merge branch 'do-not-enable-or-disable-bluetooth-automatically' into 'master'
Don't enable or disable the Bluetooth adapter automatically

Closes #1348

See merge request briar/briar!1278
2020-08-14 17:17:22 +00:00
akwizgran
2a8778d3cc Don't enable or disable the Bluetooth adapter automatically. 2020-08-14 16:18:02 +01:00
akwizgran
2cf146a104 Initialise Bluetooth state when view model is created. 2020-08-14 16:13:29 +01:00
akwizgran
a1e3c81bda Remove unused drawable. 2020-08-14 15:45:34 +01:00
akwizgran
bbcb183c24 Use a single click target that covers all transport indicators. 2020-08-14 15:25:52 +01:00
akwizgran
7fcb3394ca Add optional summary text to transport cards. 2020-08-14 15:25:51 +01:00
akwizgran
4310e4d1af Add help button to transports activity. 2020-08-14 15:25:51 +01:00
akwizgran
82e85bdb39 Remove redundant separator. 2020-08-14 15:25:51 +01:00
akwizgran
5ba0728abc Add onboarding for transports activity. 2020-08-14 15:25:51 +01:00
akwizgran
46bdb3589c Use Briar card style (sets background colour for dark theme). 2020-08-14 15:25:51 +01:00
akwizgran
392bc0d339 Use resource for title of transports activity. 2020-08-14 15:25:51 +01:00
akwizgran
02cf6bfcaa Use constants for default settings. 2020-08-14 15:25:51 +01:00
akwizgran
08a8a0b281 Show reason why Tor is disabled. 2020-08-14 15:25:51 +01:00
akwizgran
b189a38f62 Only show plugin status when it's relevant. 2020-08-14 15:25:50 +01:00
akwizgran
57b0641e5f Update network status. 2020-08-14 15:25:50 +01:00
akwizgran
5b5d513316 Shorter explanations. 2020-08-14 15:25:50 +01:00
akwizgran
6684fb2e1b Add settings button to toolbar. 2020-08-14 15:25:50 +01:00
akwizgran
73c6a29ede Add transports activity. 2020-08-14 15:25:50 +01:00
akwizgran
a8fe0a01ac Only run the rendezvous polling task when we have pending contacts. 2020-08-14 14:49:04 +01:00
Torsten Grote
cf8241e79c Fix IllegalStateException in RecyclerView when backing out very quickly
after adding image attachments for preview before sending
2020-02-13 10:28:00 -03:00
Torsten Grote
61d3fe9055 [android] fix IllegalStateException when creating attachments
Injecting the non-singleton AttachmentCreator keeps an instance around
that gets re-used with a different ViewModel.
When backing out without sending or cancelling the attachments,
we don't reset the state which leads us into an illegal state.
2020-02-13 10:28:00 -03:00
Torsten Grote
bded1edb2b [android] Use ordinary HashMap for to be received attachments
Also don't do list stacking from end for now.
2020-02-13 10:26:43 -03:00
akwizgran
4d27828712 Check for concurrent cache updates. 2020-02-13 10:26:43 -03:00
Torsten Grote
0f6f52c37a [android] Listen to AttachmentReceivedEvents when ConversationActivity is stopped
This way Attachments get shown when the activity resumes.
2020-02-13 10:26:42 -03:00
Torsten Grote
c1cf6f61b9 [android] fix concurrency issues when attachments are received delayed
Do not observe attachment live data multiple times
and don't miss received attachments in ImageActivity resp. ImageViewModel.
2020-02-13 10:26:42 -03:00
Torsten Grote
7c22016b81 [android] attach some smaller image attachment issues 2020-02-13 10:26:42 -03:00
Torsten Grote
31f42d44af [android] Refactor attachment loading to use LiveData 2020-02-13 10:26:42 -03:00
Torsten Grote
a1cf485ecc [android] address first round of code review for attachment placeholders 2020-02-13 10:26:41 -03:00
Torsten Grote
b7d3cd7990 [android] support attachments arriving *before* the message containing them 2020-02-13 10:26:41 -03:00
Torsten Grote
4122e0852a Show placeholders for missing attachments in ImageActivity
and display attachments as they arrive while ImageActivity is open.
2020-02-13 10:26:41 -03:00
Torsten Grote
41411b0e2e Refactor attachment loading to support incremental display once loaded 2020-02-13 10:26:40 -03:00
238 changed files with 6997 additions and 3216 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,15 @@
package org.briarproject.bramble.network;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
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.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.TimeUnit;
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.os.Build.VERSION.SDK_INT;
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.SECONDS;
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
@ParametersNotNullByDefault
class AndroidNetworkManager implements NetworkManager, Service {
private static final Logger LOG =
Logger.getLogger(AndroidNetworkManager.class.getName());
getLogger(AndroidNetworkManager.class.getName());
// See android.net.wifi.WifiManager
private static final String WIFI_AP_STATE_CHANGED_ACTION =
@@ -54,7 +69,8 @@ class AndroidNetworkManager implements NetworkManager, Service {
private final TaskScheduler scheduler;
private final EventBus eventBus;
private final Executor eventExecutor;
private final Context appContext;
private final Application app;
private final ConnectivityManager connectivityManager;
private final AtomicReference<Cancellable> connectivityCheck =
new AtomicReference<>();
private final AtomicBoolean used = new AtomicBoolean(false);
@@ -67,7 +83,9 @@ class AndroidNetworkManager implements NetworkManager, Service {
this.scheduler = scheduler;
this.eventBus = eventBus;
this.eventExecutor = eventExecutor;
this.appContext = app.getApplicationContext();
this.app = app;
connectivityManager = (ConnectivityManager)
requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE));
}
@Override
@@ -82,24 +100,82 @@ class AndroidNetworkManager implements NetworkManager, Service {
filter.addAction(WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(networkStateReceiver, filter);
app.registerReceiver(networkStateReceiver, filter);
}
@Override
public void stopService() {
if (networkStateReceiver != null)
appContext.unregisterReceiver(networkStateReceiver);
app.unregisterReceiver(networkStateReceiver);
}
@Override
public NetworkStatus getNetworkStatus() {
ConnectivityManager cm = (ConnectivityManager)
appContext.getSystemService(CONNECTIVITY_SERVICE);
if (cm == null) throw new AssertionError();
NetworkInfo net = cm.getActiveNetworkInfo();
NetworkInfo net = connectivityManager.getActiveNetworkInfo();
boolean connected = net != null && net.isConnected();
boolean wifi = connected && net.getType() == TYPE_WIFI;
return new NetworkStatus(connected, wifi);
boolean wifi = false, ipv6Only = false;
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() {

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,11 +8,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
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.wifi = wifi;
this.ipv6Only = ipv6Only;
}
public boolean isConnected() {
@@ -22,4 +23,8 @@ public class NetworkStatus {
public boolean isWifi() {
return wifi;
}
public boolean isIpv6Only() {
return ipv6Only;
}
}

View File

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

View File

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

View File

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

View File

@@ -23,6 +23,8 @@ public interface DevReporter {
/**
* 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 java.security.GeneralSecurityException;
import java.util.Locale;
import java.util.regex.Matcher;
import javax.inject.Inject;
@@ -52,7 +53,7 @@ class PendingContactFactoryImpl implements PendingContactFactory {
byte[] raw = new byte[RAW_LINK_BYTES];
raw[0] = FORMAT_VERSION;
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 {

View File

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

View File

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

View File

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

View File

@@ -132,7 +132,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private final CircumventionProvider circumventionProvider;
private final ResourceProvider resourceProvider;
private final int maxLatency, maxIdleTime, socketTimeout;
private final File torDirectory, torFile, geoIpFile, obfs4File, configFile;
private final File torDirectory, geoIpFile, configFile;
private final File doneFile, cookieFile;
private final AtomicBoolean used = new AtomicBoolean(false);
@@ -181,9 +181,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
socketTimeout = Integer.MAX_VALUE;
else socketTimeout = maxIdleTime * 2;
this.torDirectory = torDirectory;
torFile = new File(torDirectory, "tor");
geoIpFile = new File(torDirectory, "geoip");
obfs4File = new File(torDirectory, "obfs4proxy");
configFile = new File(torDirectory, "torrc");
doneFile = new File(torDirectory, "done");
cookieFile = new File(torDirectory, ".tor/control_auth_cookie");
@@ -192,6 +190,14 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
new PoliteExecutor("TorPlugin", ioExecutor, 1);
}
protected File getTorExecutableFile() {
return new File(torDirectory, "tor");
}
protected File getObfs4ExecutableFile() {
return new File(torDirectory, "obfs4proxy");
}
@Override
public TransportId getId() {
return TorConstants.ID;
@@ -224,6 +230,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
LOG.warning("Old auth cookie not deleted");
// Start a new Tor process
LOG.info("Starting Tor");
File torFile = getTorExecutableFile();
String torPath = torFile.getAbsolutePath();
String configPath = configFile.getAbsolutePath();
String pid = String.valueOf(getProcessId());
@@ -322,44 +329,43 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private void installAssets() throws PluginException {
InputStream in = null;
OutputStream out = null;
try {
// The done file may already exist from a previous installation
//noinspection ResultOfMethodCallIgnored
doneFile.delete();
// Unzip the Tor binary to the filesystem
in = getTorInputStream();
out = new FileOutputStream(torFile);
copyAndClose(in, out);
// Make the Tor binary executable
if (!torFile.setExecutable(true, true)) throw new IOException();
// Unzip the GeoIP database to the filesystem
in = getGeoIpInputStream();
out = new FileOutputStream(geoIpFile);
copyAndClose(in, out);
// Unzip the Obfs4 proxy to the filesystem
in = getObfs4InputStream();
out = new FileOutputStream(obfs4File);
copyAndClose(in, out);
// Make the Obfs4 proxy executable
if (!obfs4File.setExecutable(true, true)) throw new IOException();
// Copy the config file to the filesystem
in = getConfigInputStream();
out = new FileOutputStream(configFile);
copyAndClose(in, out);
installTorExecutable();
installObfs4Executable();
extract(getGeoIpInputStream(), geoIpFile);
extract(getConfigInputStream(), configFile);
if (!doneFile.createNewFile())
LOG.warning("Failed to create done file");
} catch (IOException e) {
tryToClose(in, LOG, WARNING);
tryToClose(out, LOG, WARNING);
throw new PluginException(e);
}
}
private InputStream getTorInputStream() throws IOException {
protected void extract(InputStream in, File dest) throws IOException {
OutputStream out = new FileOutputStream(dest);
copyAndClose(in, out);
}
protected void installTorExecutable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing Tor binary for " + architecture);
File torFile = getTorExecutableFile();
extract(getTorInputStream(), torFile);
if (!torFile.setExecutable(true, true)) throw new IOException();
}
protected void installObfs4Executable() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
File obfs4File = getObfs4ExecutableFile();
extract(getObfs4InputStream(), obfs4File);
if (!obfs4File.setExecutable(true, true)) throw new IOException();
}
private InputStream getTorInputStream() throws IOException {
InputStream in = resourceProvider
.getResourceInputStream("tor_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
@@ -376,8 +382,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private InputStream getObfs4InputStream() throws IOException {
if (LOG.isLoggable(INFO))
LOG.info("Installing obfs4proxy binary for " + architecture);
InputStream in = resourceProvider
.getResourceInputStream("obfs4proxy_" + architecture, ".zip");
ZipInputStream zin = new ZipInputStream(in);
@@ -569,6 +573,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enable) {
Collection<String> conf = new ArrayList<>();
conf.add("UseBridges 1");
File obfs4File = getObfs4ExecutableFile();
if (needsMeek) {
conf.add("ClientTransportPlugin meek_lite exec " +
obfs4File.getAbsolutePath());
@@ -858,6 +863,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (!state.isTorRunning()) return;
boolean online = status.isConnected();
boolean wifi = status.isWifi();
boolean ipv6Only = status.isIpv6Only();
String country = locationUtils.getCurrentCountry();
boolean blocked =
circumventionProvider.isTorProbablyBlocked(country);
@@ -874,7 +880,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
boolean automatic = network == PREF_TOR_NETWORK_AUTOMATIC;
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");
else LOG.info("Country code: " + country);
LOG.info("Charging: " + charging);
@@ -911,7 +918,8 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
enableNetwork = true;
if (network == PREF_TOR_NETWORK_WITH_BRIDGES ||
(automatic && bridgesWork)) {
if (circumventionProvider.needsMeek(country)) {
if (ipv6Only ||
circumventionProvider.needsMeek(country)) {
LOG.info("Using meek bridges");
enableBridges = true;
useMeek = true;
@@ -937,6 +945,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (enableNetwork) {
enableBridges(enableBridges, useMeek);
enableConnectionPadding(enableConnectionPadding);
useIpv6(ipv6Only);
}
enableNetwork(enableNetwork);
} catch (IOException e) {
@@ -949,6 +958,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
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
@NotNullByDefault
protected class PluginState {

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,46 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
package="org.briarproject.briar"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.briar">
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false"/>
android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false"/>
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false"/>
<uses-feature android:name="android.software.leanback"
android:required="false" />
android:required="false" />
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!--suppress DeprecatedClassUsageInspection -->
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission-sdk-23 android:name="android.permission.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="org.briarproject.briar.android.BriarApplicationImpl"
android:allowBackup="false"
android:banner="@mipmap/tv_banner"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher_round"
android:banner="@mipmap/tv_banner"
android:supportsRtl="true"
android:theme="@style/BriarTheme"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute"
@@ -50,8 +50,8 @@
android:name="org.briarproject.briar.android.login.SignInReminderReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
</receiver>
@@ -59,49 +59,58 @@
android:name="org.briarproject.briar.android.BriarService"
android:exported="false">
<intent-filter>
<action android:name="org.briarproject.briar.android.BriarService"/>
<action android:name="org.briarproject.briar.android.BriarService" />
</intent-filter>
</service>
<service
android:name="org.briarproject.briar.android.NotificationCleanupService"
android:exported="false">
</service>
android:exported="false" />
<activity
android:name="org.briarproject.briar.android.reporting.DevReportActivity"
android:name="org.briarproject.briar.android.reporting.CrashReportActivity"
android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="true"
android:label="@string/crash_report_title"
android:launchMode="singleInstance"
android:process=":briar_error_handler"
android:theme="@style/BriarTheme.NoActionBar"
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
android:name="org.briarproject.briar.android.splash.ExpiredActivity"
android:label="@string/app_name">
</activity>
android:label="@string/app_name" />
<activity
android:name="org.briarproject.briar.android.login.StartupActivity"
android:label="@string/app_name">
</activity>
android:label="@string/app_name" />
<activity
android:name="org.briarproject.briar.android.account.SetupActivity"
android:label="@string/setup_title"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
</activity>
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" />
<activity
android:name="org.briarproject.briar.android.splash.SplashScreenActivity"
android:label="@string/app_name"
android:theme="@style/BriarTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
@@ -111,17 +120,17 @@
android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar">
<intent-filter android:label="@string/add_contact_remotely_title_case">
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="briar"/>
<data android:scheme="briar" />
</intent-filter>
<intent-filter android:label="@string/add_contact_remotely_title_case">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
@@ -133,7 +142,7 @@
android:windowSoftInputMode="adjustResize|stateUnchanged">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -142,7 +151,7 @@
android:theme="@style/BriarTheme.ActionBarOverlay">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
@@ -152,7 +161,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -163,7 +172,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -172,7 +181,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -181,7 +190,7 @@
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
</activity>
<activity
@@ -190,7 +199,7 @@
android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
</activity>
<activity
@@ -200,7 +209,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity"/>
android:value="org.briarproject.briar.android.privategroup.conversation.GroupActivity" />
</activity>
<activity
@@ -209,7 +218,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -218,7 +227,7 @@
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
@@ -228,7 +237,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -239,7 +248,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -249,7 +258,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.forum.ForumActivity"/>
android:value="org.briarproject.briar.android.forum.ForumActivity" />
</activity>
<activity
@@ -259,7 +268,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
android:value="org.briarproject.briar.android.blog.BlogActivity" />
</activity>
<activity
@@ -268,8 +277,7 @@
android:parentActivityName="org.briarproject.briar.android.forum.ForumActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.forum.ForumActivity"
/>
android:value="org.briarproject.briar.android.forum.ForumActivity" />
</activity>
<activity
@@ -278,7 +286,7 @@
android:parentActivityName="org.briarproject.briar.android.blog.BlogActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
android:value="org.briarproject.briar.android.blog.BlogActivity" />
</activity>
<activity
@@ -287,7 +295,7 @@
android:theme="@style/BriarTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -297,7 +305,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
android:value="org.briarproject.briar.android.blog.BlogActivity" />
</activity>
<activity
@@ -307,7 +315,7 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.blog.BlogActivity"/>
android:value="org.briarproject.briar.android.blog.BlogActivity" />
</activity>
<activity
@@ -317,7 +325,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -326,7 +334,7 @@
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -336,7 +344,7 @@
android:theme="@style/BriarTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
@@ -346,13 +354,12 @@
android:windowSoftInputMode="adjustResize|stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title">
</activity>
android:label="@string/startup_failed_activity_title" />
<activity
android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -361,13 +368,22 @@
android:permission="android.permission.READ_NETWORK_USAGE_HISTORY">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/>
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="org.briarproject.briar.android.navdrawer.TransportsActivity"
android:label="@string/network_settings_title"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.login.ChangePasswordActivity"
android:label="@string/change_password"
@@ -375,7 +391,7 @@
android:windowSoftInputMode="adjustResize|stateAlwaysVisible">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity
@@ -384,7 +400,7 @@
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity
@@ -393,7 +409,7 @@
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity"/>
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity
@@ -402,37 +418,58 @@
android:theme="@style/TranslucentTheme">
<!-- this can never have launchMode singleTask or singleInstance! -->
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name="org.briarproject.briar.android.logout.ExitActivity"
android:theme="@android:style/Theme.NoDisplay">
</activity>
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".android.logout.HideUiActivity"
android:theme="@android:style/Theme.NoDisplay">
</activity>
android:theme="@android:style/Theme.NoDisplay" />
<activity
android:name=".android.account.UnlockActivity"
android:label="@string/lock_unlock"
android:launchMode="singleTask"
android:theme="@style/BriarTheme.NoActionBar"/>
android:theme="@style/BriarTheme.NoActionBar" />
<activity
android:name=".android.contact.add.remote.AddContactActivity"
android:label="@string/add_contact_remotely_title_case"
android:theme="@style/BriarTheme"
android:windowSoftInputMode="adjustResize|stateHidden"/>
android:windowSoftInputMode="adjustResize|stateHidden" />
<activity
android:name=".android.contact.add.remote.PendingContactListActivity"
android:label="@string/pending_contact_requests"
android:theme="@style/BriarTheme"/>
android:theme="@style/BriarTheme" />
</application>
<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>

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

View File

@@ -109,7 +109,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@Nullable
private GroupId blockedGroup = null;
private boolean blockSignInReminder = false;
private boolean blockBlogs = false;
private boolean blockForums = false, blockGroups = false,
blockBlogs = false;
private long lastSound = 0;
private volatile Settings settings = new Settings();
@@ -223,8 +224,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
if (s.getNamespace().equals(SETTINGS_NAMESPACE))
settings = s.getSettings();
} else if (e instanceof ConversationMessageReceivedEvent) {
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
showContactNotification(p.getContactId());
} else if (e instanceof GroupMessageAddedEvent) {
GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
@@ -385,6 +386,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread
private void showGroupMessageNotification(GroupId g) {
if (blockGroups) return;
if (g.equals(blockedGroup)) return;
groupCounts.add(g);
updateGroupMessageNotification(true);
@@ -452,6 +454,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
@UiThread
private void showForumPostNotification(GroupId g) {
if (blockForums) return;
if (g.equals(blockedGroup)) return;
forumCounts.add(g);
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
public void blockAllBlogPostNotifications() {
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.StringUtils;
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.login.LoginModule;
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.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
@@ -63,7 +66,11 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
ContactExchangeModule.class,
LoginModule.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 {
@@ -196,7 +203,10 @@ public class AppModule {
ScreenFilterMonitor provideScreenFilterMonitor(
LifecycleManager lifecycleManager,
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;
}

View File

@@ -14,19 +14,13 @@ import android.preference.PreferenceManager;
import com.vanniktech.emoji.EmojiManager;
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.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BuildConfig;
import org.briarproject.briar.R;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.reporting.BriarReportPrimer;
import org.briarproject.briar.android.reporting.BriarReportSenderFactory;
import org.briarproject.briar.android.reporting.DevReportActivity;
import org.briarproject.briar.android.reporting.BriarExceptionHandler;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Collection;
@@ -34,50 +28,14 @@ import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import androidx.annotation.NonNull;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;
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;
@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
implements BriarApplication {
@@ -85,12 +43,15 @@ public class BriarApplicationImpl extends Application
getLogger(BriarApplicationImpl.class.getName());
private final CachingLogHandler logHandler = new CachingLogHandler();
private final BriarExceptionHandler exceptionHandler =
new BriarExceptionHandler(this);
private AndroidComponent applicationComponent;
private volatile SharedPreferences prefs;
@Override
protected void attachBaseContext(Context base) {
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
if (prefs == null)
prefs = PreferenceManager.getDefaultSharedPreferences(base);
// Loading the language needs to be done here.
@@ -98,7 +59,6 @@ public class BriarApplicationImpl extends Application
super.attachBaseContext(
Localizer.getInstance().setLocale(base));
setTheme(base, prefs);
ACRA.init(this);
}
@Override
@@ -144,7 +104,7 @@ public class BriarApplicationImpl extends Application
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Localizer.getInstance().setLocale(this);
}

View File

@@ -58,7 +58,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
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
*/
private static final String PLAY_SERVICES_PACKAGE =
@@ -108,7 +108,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service {
Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet());
List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packageInfos =
@SuppressLint("QueryPermissionsNeeded") List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) {
if (!allowed.contains(packageInfo.packageName)

View File

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

View File

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

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

View File

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

View File

@@ -47,6 +47,7 @@ import org.briarproject.briar.android.login.OpenDatabaseFragment;
import org.briarproject.briar.android.login.PasswordFragment;
import org.briarproject.briar.android.login.StartupActivity;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import org.briarproject.briar.android.navdrawer.TransportsActivity;
import org.briarproject.briar.android.panic.PanicPreferencesActivity;
import org.briarproject.briar.android.panic.PanicResponderActivity;
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
@@ -59,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.GroupInvitationModule;
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.GroupMemberModule;
import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule;
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
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.SettingsFragment;
import org.briarproject.briar.android.sharing.BlogInvitationActivity;
@@ -90,7 +93,6 @@ import dagger.Component;
ForumModule.class,
GroupInvitationModule.class,
GroupConversationModule.class,
GroupListModule.class,
GroupMemberModule.class,
GroupRevealModule.class,
SharingModule.class
@@ -163,6 +165,8 @@ public interface ActivityComponent {
void inject(SettingsActivity activity);
void inject(TransportsActivity activity);
void inject(TestDataActivity activity);
void inject(ChangePasswordActivity activity);
@@ -181,6 +185,8 @@ public interface ActivityComponent {
void inject(PendingContactListActivity activity);
void inject(CrashReportActivity crashReportActivity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -231,4 +237,8 @@ public interface ActivityComponent {
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.LayoutParams;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
@@ -18,7 +17,6 @@ import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.BaseFragment;
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.widget.TapSafeFrameLayout;
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.FragmentManager;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
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.Logger.getLogger;
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}.
* E.g. {@link DevReportActivity}
*/
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -123,6 +122,7 @@ public abstract class BaseActivity extends AppCompatActivity
return new ActivityModule(this);
}
// TODO use a test module where this is used in tests
protected ForumModule getForumModule() {
return new ForumModule();
}
@@ -202,9 +202,15 @@ public abstract class BaseActivity extends AppCompatActivity
// If the dialog is already visible, filter the tap
ScreenFilterDialogFragment f = findDialogFragment();
if (f != null && f.isVisible()) return false;
Collection<AppDetails> apps = screenFilterMonitor.getApps();
// If all overlay apps have been allowed, allow the tap
if (apps.isEmpty()) return true;
Collection<AppDetails> apps;
// querying all apps is only possible at API 29 and below
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
FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) {
@@ -241,7 +247,7 @@ public abstract class BaseActivity extends AppCompatActivity
}
@UiThread
public void handleDbException(DbException e) {
public void handleException(Exception e) {
supportFinishAfterTransition();
}
@@ -266,7 +272,12 @@ public abstract class BaseActivity extends AppCompatActivity
private void protectToolbar() {
findToolbar();
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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -18,7 +18,6 @@ import javax.inject.Inject;
import androidx.annotation.UiThread;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@UiThread
@@ -54,7 +53,7 @@ public class FeedPostFragment extends BasePostFragment {
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments());
Bundle args = requireArguments();
byte[] b = args.getByteArray(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in args");
blogId = new GroupId(b);
@@ -80,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
@Override
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 Bundle savedInstanceState) {
Bundle args = requireNonNull(getArguments());
Bundle args = requireArguments();
GroupId blogId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =
@@ -101,7 +101,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
@Override
public void onExceptionUi(DbException exception) {
handleDbException(exception);
handleException(exception);
}
});
@@ -128,7 +128,7 @@ public class ReblogFragment extends BaseFragment implements SendListener {
new UiExceptionHandler<DbException>(this) {
@Override
public void onExceptionUi(DbException exception) {
handleDbException(exception);
handleException(exception);
}
});
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.view.ViewCompat.getTransitionName;
import static com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -122,7 +121,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireNonNull(getActivity()).setTitle(R.string.contact_list_button);
requireActivity().setTitle(R.string.contact_list_button);
View contentView = inflater.inflate(R.layout.fragment_contact_list,
container, false);
@@ -274,8 +273,8 @@ public class ContactListFragment extends BaseFragment implements EventListener,
removeItem(((ContactRemovedEvent) e).getContactId());
} else if (e instanceof ConversationMessageReceivedEvent) {
LOG.info("Conversation message received, updating item");
ConversationMessageReceivedEvent p =
(ConversationMessageReceivedEvent) e;
ConversationMessageReceivedEvent<?> p =
(ConversationMessageReceivedEvent<?>) e;
ConversationMessageHeader h = p.getMessageHeader();
updateItem(p.getContactId(), h);
} else if (e instanceof PendingContactAddedEvent ||
@@ -317,7 +316,7 @@ public class ContactListFragment extends BaseFragment implements EventListener,
@UiThread
private void showSnackBar() {
if (snackbar != null) return;
View v = requireNonNull(getView());
View v = requireView();
int stringRes = R.string.pending_contact_requests_snackbar;
snackbar = new BriarSnackbarBuilder()
.setAction(R.string.show, view -> showPendingContactList())

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -28,7 +28,6 @@ import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.NoSuchMessageException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
@@ -65,17 +64,16 @@ import org.briarproject.briar.api.client.ProtocolStateException;
import org.briarproject.briar.api.client.SessionId;
import org.briarproject.briar.api.conversation.ConversationManager;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
import org.briarproject.briar.api.conversation.ConversationMessageVisitor;
import org.briarproject.briar.api.conversation.ConversationRequest;
import org.briarproject.briar.api.conversation.ConversationResponse;
import org.briarproject.briar.api.conversation.DeletionResult;
import org.briarproject.briar.api.conversation.event.ConversationMessageReceivedEvent;
import org.briarproject.briar.api.forum.ForumSharingManager;
import org.briarproject.briar.api.introduction.IntroductionManager;
import org.briarproject.briar.api.messaging.Attachment;
import org.briarproject.briar.api.messaging.AttachmentHeader;
import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import java.util.ArrayList;
@@ -97,9 +95,9 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.selection.Selection;
import androidx.recyclerview.selection.SelectionPredicates;
import androidx.recyclerview.selection.SelectionTracker;
@@ -118,8 +116,6 @@ import static androidx.core.app.ActivityOptionsCompat.makeSceneTransitionAnimati
import static androidx.core.view.ViewCompat.setTransitionName;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static androidx.recyclerview.widget.SortedList.INVALID_POSITION;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.Collections.sort;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
@@ -136,6 +132,7 @@ import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRO
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENTS;
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT_POSITION;
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.ITEM_ID;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
@@ -185,8 +182,6 @@ public class ConversationActivity extends BriarActivity
volatile GroupInvitationManager groupInvitationManager;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, PrivateMessageHeader> missingAttachments =
new ConcurrentHashMap<>();
private final Observer<String> contactNameObserver = name -> {
requireNonNull(name);
loadMessages();
@@ -228,8 +223,9 @@ public class ConversationActivity extends BriarActivity
if (id == -1) throw new IllegalStateException();
contactId = new ContactId(id);
viewModel = ViewModelProviders.of(this, viewModelFactory)
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ConversationViewModel.class);
viewModel.setContactId(contactId);
attachmentRetriever = viewModel.getAttachmentRetriever();
setContentView(R.layout.activity_conversation);
@@ -334,16 +330,6 @@ public class ConversationActivity extends BriarActivity
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
public void onStop() {
super.onStop();
@@ -540,6 +526,7 @@ public class ConversationActivity extends BriarActivity
});
}
@DatabaseExecutor
private void eagerlyLoadMessageSize(PrivateMessageHeader h) {
try {
MessageId id = h.getId();
@@ -556,21 +543,11 @@ public class ConversationActivity extends BriarActivity
// images we use a grid so the size is fixed
List<AttachmentHeader> headers = h.getAttachmentHeaders();
if (headers.size() == 1) {
List<AttachmentItem> items = attachmentRetriever.cacheGet(id);
if (items == null) {
LOG.info("Eagerly loading image size for latest message");
AttachmentHeader header = headers.get(0);
try {
Attachment a = attachmentRetriever
.getMessageAttachment(header);
AttachmentItem item =
attachmentRetriever.getAttachmentItem(a, true);
attachmentRetriever.cachePut(id, singletonList(item));
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
}
}
LOG.info("Eagerly loading image size for latest message");
AttachmentHeader header = headers.get(0);
// get the item to retrieve its size
attachmentRetriever
.cacheAttachmentItemWithSize(h.getId(), header);
}
} catch (DbException e) {
logException(LOG, WARNING, e);
@@ -651,59 +628,18 @@ public class ConversationActivity extends BriarActivity
&& adapter.isScrolledToBottom(layoutManager);
}
private void loadMessageAttachments(PrivateMessageHeader h) {
// TODO: Use placeholders for missing/invalid attachments
runOnDbThread(() -> {
try {
// TODO move getting the items off to IoExecutor, if size == 1
List<AttachmentHeader> headers = h.getAttachmentHeaders();
boolean needsSize = headers.size() == 1;
List<AttachmentItem> items = new ArrayList<>(headers.size());
for (AttachmentHeader header : headers) {
try {
Attachment a = attachmentRetriever
.getMessageAttachment(header);
AttachmentItem item = attachmentRetriever
.getAttachmentItem(a, needsSize);
items.add(item);
} catch (NoSuchMessageException e) {
LOG.info("Attachment not received yet");
missingAttachments.put(header.getMessageId(), h);
return;
}
}
// Don't cache items unless all are present and valid
attachmentRetriever.cachePut(h.getId(), items);
displayMessageAttachments(h.getId(), items);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
});
}
private void displayMessageAttachments(MessageId m,
List<AttachmentItem> items) {
runOnUiThreadUnlessDestroyed(() -> {
Pair<Integer, ConversationMessageItem> pair =
adapter.getMessageItem(m);
if (pair != null) {
boolean scroll = shouldScrollWhenUpdatingMessage();
pair.getSecond().setAttachments(items);
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
}
});
@UiThread
private void updateMessageAttachment(MessageId m, AttachmentItem item) {
Pair<Integer, ConversationMessageItem> pair = adapter.getMessageItem(m);
if (pair != null && pair.getSecond().updateAttachments(item)) {
boolean scroll = shouldScrollWhenUpdatingMessage();
adapter.notifyItemChanged(pair.getFirst());
if (scroll) scrollToBottom();
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received");
onAttachmentReceived(a.getMessageId());
}
}
if (e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if (c.getContactId().equals(contactId)) {
@@ -763,15 +699,6 @@ public class ConversationActivity extends BriarActivity
scrollToBottom();
}
@UiThread
private void onAttachmentReceived(MessageId attachmentId) {
PrivateMessageHeader h = missingAttachments.remove(attachmentId);
if (h != null) {
LOG.info("Missing attachment received");
loadMessageAttachments(h);
}
}
@UiThread
private void onNewConversationMessage(ConversationMessageHeader h) {
if (h instanceof ConversationRequest ||
@@ -780,7 +707,7 @@ public class ConversationActivity extends BriarActivity
observeOnce(viewModel.getContactDisplayName(), this,
name -> addConversationItem(h.accept(visitor)));
} else {
// visitor also loads message text (if existing)
// visitor also loads message text and attachments (if existing)
addConversationItem(h.accept(visitor));
}
}
@@ -1107,8 +1034,9 @@ public class ConversationActivity extends BriarActivity
i.putExtra(ATTACHMENT_POSITION, attachments.indexOf(item));
i.putExtra(NAME, name);
i.putExtra(DATE, messageItem.getTime());
i.putExtra(ITEM_ID, messageItem.getId().getBytes());
// restoring list position should not trigger android bug #224270
String transitionName = item.getTransitionName();
String transitionName = item.getTransitionName(messageItem.getId());
ActivityOptionsCompat options =
makeSceneTransitionAnimation(this, view, transitionName);
ActivityCompat.startActivity(this, i, options.toBundle());
@@ -1147,15 +1075,41 @@ public class ConversationActivity extends BriarActivity
return text;
}
/**
* Called by {@link PrivateMessageHeader#accept(ConversationMessageVisitor)}
*/
@Override
public List<AttachmentItem> getAttachmentItems(PrivateMessageHeader h) {
List<AttachmentItem> attachments =
attachmentRetriever.cacheGet(h.getId());
if (attachments == null) {
loadMessageAttachments(h);
return emptyList();
List<LiveData<AttachmentItem>> liveDataList =
attachmentRetriever.getAttachmentItems(h);
List<AttachmentItem> items = new ArrayList<>(liveDataList.size());
for (LiveData<AttachmentItem> liveData : liveDataList) {
// first remove all our observers to avoid having more than one
// in case we reload the conversation, e.g. after deleting messages
liveData.removeObservers(this);
// add a new observer
liveData.observe(this, new AttachmentObserver(h.getId(), liveData));
items.add(requireNonNull(liveData.getValue()));
}
return items;
}
private class AttachmentObserver implements Observer<AttachmentItem> {
private final MessageId conversationMessageId;
private final LiveData<AttachmentItem> liveData;
private AttachmentObserver(MessageId conversationMessageId,
LiveData<AttachmentItem> liveData) {
this.conversationMessageId = conversationMessageId;
this.liveData = liveData;
}
@Override
public void onChanged(AttachmentItem attachmentItem) {
updateMessageAttachment(conversationMessageId, attachmentItem);
if (attachmentItem.getState().isFinal())
liveData.removeObserver(this);
}
return attachments;
}
}

View File

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

View File

@@ -11,18 +11,24 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.AuthorId;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
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.AttachmentManager;
import org.briarproject.briar.android.attachment.AttachmentResult;
import org.briarproject.briar.android.attachment.AttachmentRetriever;
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.MutableLiveEvent;
import org.briarproject.briar.api.messaging.AttachmentHeader;
@@ -30,6 +36,7 @@ import org.briarproject.briar.api.messaging.MessagingManager;
import org.briarproject.briar.api.messaging.PrivateMessage;
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
import org.briarproject.briar.api.messaging.PrivateMessageHeader;
import org.briarproject.briar.api.messaging.event.AttachmentReceivedEvent;
import java.util.Collection;
import java.util.List;
@@ -40,7 +47,6 @@ import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
@@ -55,10 +61,10 @@ import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_
import static org.briarproject.briar.android.util.UiUtils.observeForeverOnce;
@NotNullByDefault
public class ConversationViewModel extends AndroidViewModel
implements AttachmentManager {
public class ConversationViewModel extends DbViewModel
implements EventListener, AttachmentManager {
private static Logger LOG =
private static final Logger LOG =
getLogger(ConversationViewModel.class.getName());
private static final String SHOW_ONBOARDING_IMAGE =
@@ -66,9 +72,8 @@ public class ConversationViewModel extends AndroidViewModel
private static final String SHOW_ONBOARDING_INTRODUCTION =
"showOnboardingIntroduction";
@DatabaseExecutor
private final Executor dbExecutor;
private final TransactionManager db;
private final EventBus eventBus;
private final MessagingManager messagingManager;
private final ContactManager contactManager;
private final SettingsManager settingsManager;
@@ -100,16 +105,19 @@ public class ConversationViewModel extends AndroidViewModel
@Inject
ConversationViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
EventBus eventBus,
MessagingManager messagingManager,
ContactManager contactManager,
SettingsManager settingsManager,
PrivateMessageFactory privateMessageFactory,
AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator) {
super(application);
this.dbExecutor = dbExecutor;
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db;
this.eventBus = eventBus;
this.messagingManager = messagingManager;
this.contactManager = contactManager;
this.settingsManager = settingsManager;
@@ -119,12 +127,27 @@ public class ConversationViewModel extends AndroidViewModel
messagingGroupId = Transformations
.map(contact, c -> messagingManager.getContactGroup(c).getId());
contactDeleted.setValue(false);
eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
attachmentCreator.deleteUnsentAttachments();
attachmentCreator.cancel(); // also deletes unsent attachments
eventBus.removeListener(this);
}
@Override
public void eventOccurred(Event e) {
if (e instanceof AttachmentReceivedEvent) {
AttachmentReceivedEvent a = (AttachmentReceivedEvent) e;
if (a.getContactId().equals(contactId)) {
LOG.info("Attachment received");
runOnDbThread(() -> attachmentRetriever
.loadAttachmentItem(a.getMessageId()));
}
}
}
/**
@@ -141,7 +164,7 @@ public class ConversationViewModel extends AndroidViewModel
}
private void loadContact(ContactId contactId) {
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
long start = now();
Contact c = contactManager.getContact(contactId);
@@ -159,7 +182,7 @@ public class ConversationViewModel extends AndroidViewModel
}
void markMessageRead(GroupId g, MessageId m) {
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
long start = now();
messagingManager.setReadFlag(g, m, true);
@@ -171,7 +194,7 @@ public class ConversationViewModel extends AndroidViewModel
}
void setContactAlias(String alias) {
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
contactManager.setContactAlias(requireNonNull(contactId),
alias.isEmpty() ? null : alias);
@@ -252,6 +275,7 @@ public class ConversationViewModel extends AndroidViewModel
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
}
@UiThread
private void createMessage(GroupId groupId, @Nullable String text,
List<AttachmentHeader> headers, long timestamp,
boolean hasImageSupport) {
@@ -270,9 +294,10 @@ public class ConversationViewModel extends AndroidViewModel
}
}
@UiThread
private void storeMessage(PrivateMessage m) {
attachmentCreator.onAttachmentsSent(m.getMessage().getId());
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
long start = now();
messagingManager.addLocalMessage(m);
@@ -332,7 +357,7 @@ public class ConversationViewModel extends AndroidViewModel
@UiThread
void recheckFeaturesAndOnboarding(ContactId contactId) {
dbExecutor.execute(() -> {
runOnDbThread(() -> {
try {
checkFeaturesAndOnboarding(contactId);
} catch (DbException e) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,7 +38,7 @@ import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_TEX
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ForumActivity extends
ThreadListActivity<Forum, ForumItem, ThreadItemAdapter<ForumItem>>
ThreadListActivity<Forum, ForumPostItem, ThreadItemAdapter<ForumPostItem>>
implements ForumListener {
@Inject
@@ -50,7 +50,7 @@ public class ForumActivity extends
}
@Override
protected ThreadListController<Forum, ForumItem> getController() {
protected ThreadListController<Forum, ForumPostItem> getController() {
return forumController;
}
@@ -82,7 +82,7 @@ public class ForumActivity extends
}
@Override
protected ThreadItemAdapter<ForumItem> createAdapter(
protected ThreadItemAdapter<ForumPostItem> createAdapter(
LinearLayoutManager layoutManager) {
return new ThreadItemAdapter<>(this, layoutManager);
}
@@ -156,7 +156,7 @@ public class ForumActivity extends
@Override
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;
@NotNullByDefault
interface ForumController extends ThreadListController<Forum, ForumItem> {
interface ForumController extends ThreadListController<Forum, ForumPostItem> {
interface ForumListener extends ThreadListListener<ForumItem> {
interface ForumListener extends ThreadListListener<ForumPostItem> {
@UiThread
void onForumLeft(ContactId c);
}

View File

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

View File

@@ -1,134 +1,47 @@
package org.briarproject.briar.android.forum;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
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.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;
import static android.view.View.VISIBLE;
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;
@NotNullByDefault
class ForumListAdapter extends ListAdapter<ForumListItem, ForumViewHolder> {
class ForumListAdapter
extends BriarAdapter<ForumListItem, ForumListAdapter.ForumViewHolder> {
ForumListAdapter(Context ctx) {
super(ctx, ForumListItem.class);
ForumListAdapter() {
super(new ForumListCallback());
}
@Override
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);
return new ForumViewHolder(v);
}
@Override
public void onBindViewHolder(ForumViewHolder ui, int position) {
ForumListItem item = getItemAt(position);
if (item == null) return;
public void onBindViewHolder(ForumViewHolder viewHolder, int position) {
viewHolder.bind(getItem(position));
}
// Avatar
ui.avatar.setText(item.getForum().getName().substring(0, 1));
ui.avatar.setBackgroundBytes(item.getForum().getId().getBytes());
ui.avatar.setUnreadCount(item.getUnreadCount());
// 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));
@NotNullByDefault
private static class ForumListCallback extends ItemCallback<ForumListItem> {
@Override
public boolean areItemsTheSame(ForumListItem a, ForumListItem b) {
return a.equals(b);
}
// Date
if (item.isEmpty()) {
ui.date.setVisibility(GONE);
} else {
long timestamp = item.getTimestamp();
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);
@Override
public boolean areContentsTheSame(ForumListItem a, ForumListItem b) {
return a.isEmpty() == b.isEmpty() &&
a.getTimestamp() == b.getTimestamp() &&
a.getUnreadCount() == b.getUnreadCount();
}
}
}

View File

@@ -12,81 +12,48 @@ import android.view.ViewGroup;
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.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.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.util.BriarSnackbarBuilder;
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.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import static com.google.android.material.snackbar.Snackbar.LENGTH_INDEFINITE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.api.forum.ForumManager.CLIENT_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ForumListFragment extends BaseEventFragment implements
public class ForumListFragment extends BaseFragment implements
OnClickListener {
public final static String TAG = ForumListFragment.class.getName();
private final static Logger LOG = Logger.getLogger(TAG);
private ForumListViewModel viewModel;
private BriarRecyclerView list;
private ForumListAdapter adapter;
private Snackbar snackbar;
private final ForumListAdapter adapter = new ForumListAdapter();
@Inject
AndroidNotificationManager notificationManager;
// Fields that are accessed from background threads must be volatile
@Inject
volatile ForumManager forumManager;
@Inject
volatile ForumSharingManager forumSharingManager;
ViewModelProvider.Factory viewModelFactory;
public static ForumListFragment newInstance() {
Bundle args = new Bundle();
ForumListFragment fragment = new ForumListFragment();
fragment.setArguments(args);
return fragment;
return new ForumListFragment();
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ForumListViewModel.class);
}
@Nullable
@@ -94,24 +61,35 @@ public class ForumListFragment extends BaseEventFragment implements
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@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 =
inflater.inflate(R.layout.fragment_forum_list, container,
false);
adapter = new ForumListAdapter(getActivity());
list = contentView.findViewById(R.id.forumList);
list = v.findViewById(R.id.forumList);
list.setLayoutManager(new LinearLayoutManager(getActivity()));
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()
.setAction(R.string.show, this)
.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
@@ -122,18 +100,23 @@ public class ForumListFragment extends BaseEventFragment implements
@Override
public void onStart() {
super.onStart();
notificationManager.clearAllForumPostNotifications();
loadForums();
loadAvailableForums();
viewModel.blockAllForumPostNotifications();
viewModel.clearAllForumPostNotifications();
// 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();
}
@Override
public void onStop() {
super.onStop();
adapter.clear();
list.showProgressBar();
list.stopPeriodicUpdate();
viewModel.unblockAllForumPostNotifications();
}
@Override
@@ -145,123 +128,12 @@ public class ForumListFragment extends BaseEventFragment implements
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle presses on the action bar items
switch (item.getItemId()) {
case R.id.action_create_forum:
Intent intent =
new Intent(getContext(), CreateForumActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
if (item.getItemId() == R.id.action_create_forum) {
Intent intent = new Intent(getContext(), CreateForumActivity.class);
startActivity(intent);
return true;
}
}
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);
return super.onOptionsItemSelected(item);
}
@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.ForumPostHeader;
// This class is NOT thread-safe
class ForumListItem {
import javax.annotation.concurrent.Immutable;
import androidx.annotation.Nullable;
@Immutable
class ForumListItem implements Comparable<ForumListItem> {
private final Forum forum;
private int postCount, unread;
private long timestamp;
private final int postCount, unread;
private final long timestamp;
ForumListItem(Forum forum, GroupCount count) {
this.forum = forum;
@@ -18,10 +22,11 @@ class ForumListItem {
this.timestamp = count.getLatestMsgTime();
}
void addHeader(ForumPostHeader h) {
postCount++;
if (!h.isRead()) unread++;
if (h.getTimestamp() > timestamp) timestamp = h.getTimestamp();
ForumListItem(ForumListItem item, ForumPostHeader h) {
this.forum = item.forum;
this.postCount = item.postCount + 1;
this.unread = item.unread + (h.isRead() ? 0 : 1);
this.timestamp = Math.max(item.timestamp, h.getTimestamp());
}
Forum getForum() {
@@ -43,4 +48,29 @@ class ForumListItem {
int getUnreadCount() {
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.BaseActivity;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
@Module
public class ForumModule {
@Module
public interface BindsModule {
@Binds
@IntoMap
@ViewModelKey(ForumListViewModel.class)
ViewModel bindForumListViewModel(ForumListViewModel forumListViewModel);
}
@ActivityScope
@Provides
ForumController provideForumController(BaseActivity activity,

View File

@@ -10,15 +10,15 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.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(),
h.getAuthorInfo(), h.isRead());
}
ForumItem(MessageId messageId, @Nullable MessageId parentId, String text,
long timestamp, Author author, AuthorInfo authorInfo) {
ForumPostItem(MessageId messageId, @Nullable MessageId parentId,
String text, long timestamp, Author author, AuthorInfo authorInfo) {
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.view.MenuItem;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.android.DestroyableContext;
@@ -77,7 +76,7 @@ public abstract class BaseFragment extends Fragment
void showNextFragment(BaseFragment f);
@UiThread
void handleDbException(DbException e);
void handleException(Exception e);
}
@CallSuper
@@ -100,8 +99,8 @@ public abstract class BaseFragment extends Fragment
}
@UiThread
protected void handleDbException(DbException e) {
listener.handleDbException(e);
protected void handleException(Exception e) {
listener.handleException(e);
}
}

View File

@@ -5,6 +5,7 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
@@ -28,6 +29,10 @@ import javax.inject.Inject;
import androidx.appcompat.app.AlertDialog;
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
@ParametersNotNullByDefault
public class ScreenFilterDialogFragment extends DialogFragment {
@@ -37,7 +42,7 @@ public class ScreenFilterDialogFragment extends DialogFragment {
@Inject
ScreenFilterMonitor screenFilterMonitor;
DismissListener dismissListener = null;
private DismissListener dismissListener = null;
public static ScreenFilterDialogFragment newInstance(
Collection<AppDetails> apps) {
@@ -83,10 +88,20 @@ public class ScreenFilterDialogFragment extends DialogFragment {
View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
builder.setView(dialogView);
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);
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);
dialog.dismiss();
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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