Compare commits

..

202 Commits

Author SHA1 Message Date
Torsten Grote
a1c5bf17ca Factor out power management UI code into library 2021-10-27 11:42:11 -03:00
Torsten Grote
4acc5f4d8c Merge branch 'gradle-run-configurations' into 'master'
Convert AS run configurations for tests to Gradle

See merge request briar/briar!1535
2021-10-18 13:31:59 +00:00
akwizgran
4a4d8f4ccf Convert AS run configurations for tests to Gradle. 2021-09-24 14:02:41 +01:00
akwizgran
807677532c Bump version numbers for 1.3.8 release. 2021-08-31 15:38:11 +01:00
Torsten Grote
7e9d64b6ad Merge branch 'fix-headless-build' into 'master'
Remove jar signatures to avoid SecurityException when repacking headless jar

See merge request briar/briar!1532
2021-08-31 14:37:13 +00:00
akwizgran
f963c4cfdd Remove jar signatures to avoid SecurityException when repacking headless jar. 2021-08-31 15:02:43 +01:00
akwizgran
7388da410f Bump version numbers for 1.3.7 release. 2021-08-31 14:16:43 +01:00
akwizgran
3635c35923 Update translations. 2021-08-31 14:15:47 +01:00
akwizgran
7c1399c326 Merge branch 'simpler-hotspot-name-pass' into 'master'
Limit hotspot name and password to only lowercase letters (on 29+)

See merge request briar/briar!1520
2021-08-31 13:10:02 +00:00
akwizgran
c002cc2e73 Merge branch 'enable-connect-via-bluetooth' into 'master'
Enable connect via Bluetooth feature in release builds

See merge request briar/briar!1531
2021-08-31 13:07:59 +00:00
Torsten Grote
f3273260bb Fix Kotlin coding style 2021-08-31 14:32:36 +02:00
Torsten Grote
abf99f0219 Merge branch '2148-shrink-hotspot-qr-codes' into 'master'
Shrink QR code to avoid scrolling

Closes #2148

See merge request briar/briar!1528
2021-08-31 12:21:16 +00:00
Torsten Grote
7405ed7196 Merge branch '2076-revise-transfer-data-explanation' into 'master'
Revise removable drive ui after usability testing

Closes #2076

See merge request briar/briar!1530
2021-08-31 12:11:11 +00:00
Daniel Lublin
b53203581c Enable scrollbar for hotspot scrollviews 2021-08-31 12:10:40 +02:00
Daniel Lublin
d522942bdd Enable scrollbar for scrollview 2021-08-31 12:02:39 +02:00
Daniel Lublin
802015d995 Shrink QR code to avoid scrolling 2021-08-31 09:09:10 +02:00
Daniel Lublin
c36352f2b8 Rename feature for user 2021-08-31 09:08:42 +02:00
Daniel Lublin
21a2f91521 Revise transfer data wording, adding explanation in fullscreen dialog 2021-08-31 09:08:40 +02:00
akwizgran
d8267ce559 Update code style settings. 2021-08-30 15:22:34 +01:00
akwizgran
8f887c609f Enable connect via Bluetooth feature in release builds. 2021-08-30 15:17:53 +01:00
akwizgran
b077e5f94f Update translations. 2021-08-30 15:03:57 +01:00
akwizgran
2b61b01b4e Merge branch '2151-bluetooth-connect-ui' into 'master'
Add simple UI for Connect via Bluetooth feature

Closes #2151 and #1821

See merge request briar/briar!1524
2021-08-30 13:58:26 +00:00
Torsten Grote
822a58c8a6 Replace final Bluetooth connection fragments with toasts 2021-08-30 15:34:37 +02:00
Daniel Lublin
09b065f46e Limit hotspot name and password to only lowercase letters (on 29+) 2021-08-23 10:44:42 +02:00
Torsten Grote
be9255029b Merge branch '2149-hotspot-detection' into 'master'
Use interface name to decide whether we're providing a wifi hotspot

Closes #2149

See merge request briar/briar!1521
2021-08-20 14:55:06 +00:00
Torsten Grote
f596811997 Merge branch '2144-graphics-size' into 'master'
Use fixed sizes and consistent layout for "hero icons"

Closes #2144

See merge request briar/briar!1525
2021-08-20 07:43:29 +00:00
Torsten Grote
1be8ac6e14 Add simple UI for Connect via Bluetooth feature 2021-08-20 09:11:35 +02:00
akwizgran
571ec2257e Remove full stop from string that's now used as a title. 2021-08-19 13:03:52 +01:00
akwizgran
7fb2faba45 Automatically scroll HotspotIntroFragment. 2021-08-19 13:00:54 +01:00
akwizgran
a390bf1c4f Use consistent layout style for all screens with "hero icons". 2021-08-19 12:27:03 +01:00
akwizgran
9d031fa796 Use fixed dp sizes for graphics. 2021-08-17 13:04:45 +01:00
akwizgran
d678043f8e Merge branch '2152-rss-icon' into 'master'
Resolve "RSS icon doesn't appear on older devices"

Closes #2152

See merge request briar/briar!1523
2021-08-17 09:36:56 +00:00
Torsten Grote
b11147265d Merge branch '1724-replace-spongy-castle-with-bouncy-castle' into 'master'
Replace Spongy Castle with Bouncy Castle

Closes #1724

See merge request briar/briar!1522
2021-08-17 07:46:04 +00:00
Torsten Grote
2fe052d77e Fix blog post padding on Android 4 2021-08-17 09:43:04 +02:00
Torsten Grote
8e91322869 Upgrade CircleImageView dependency 2021-08-17 09:25:51 +02:00
Torsten Grote
1de5779e2c Fix RSS icon not showing on API < 19 2021-08-17 09:25:39 +02:00
akwizgran
99b2c8af69 Upgrade Bouncy Castle to 1.69, drop Montgomery ladder tests. 2021-08-16 15:52:33 +01:00
akwizgran
b1cc4fe006 Replace Spongy Castle with Bouncy Castle. 2021-08-16 15:38:36 +01:00
akwizgran
d65afc519a Factor out shared code. 2021-08-16 12:00:42 +01:00
akwizgran
32cbdff532 Use interface name to decide whether we're providing a wifi hotspot. 2021-08-16 11:55:41 +01:00
akwizgran
48292d2e47 Merge branch '2144-transfer-data-graphics' into 'master'
Fix images when sending or receiving data

Closes #2144

See merge request briar/briar!1517
2021-08-11 10:27:24 +00:00
akwizgran
89bd9ee653 Merge branch 'animal-sniffer-11' into 'master'
Fix animal sniffer when run with Java 11

See merge request briar/briar!1516
2021-08-11 10:26:29 +00:00
akwizgran
61aa3a839d Merge branch '2026-ux-offline-sharing' into 'master'
Adjust after UX testing

Closes #2026

See merge request briar/briar!1518
2021-08-11 10:24:29 +00:00
Daniel Lublin
e38e9b943d Squeeze items to free more vertical space for qr code 2021-08-10 17:12:26 +02:00
Daniel Lublin
4eb5c2ac10 Revise share-offline screen wording; always show connected peers counter 2021-08-10 17:02:20 +02:00
Torsten Grote
ebaa3271dd Merge branch 'ssid-password-white-background' into 'master'
Use white background for hotspot name and password

See merge request briar/briar!1519
2021-08-10 11:24:47 +00:00
akwizgran
adb6b4fba5 Use white background for hotspot name and password. 2021-08-10 11:26:23 +01:00
Torsten Grote
917a470559 Upgrade animal sniffer plugin 2021-08-09 15:39:18 +02:00
Torsten Grote
a188e41134 Fix animal sniffer when run with Java 11 2021-08-09 15:38:14 +02:00
Torsten Grote
b9ba813b23 Fix images when sending or receiving data 2021-08-09 14:26:49 +02:00
akwizgran
b7d46b9340 Merge branch '1081-share-app-via-wifi-hotspot' into 'master'
Share app via Wi-Fi hotspot

Closes #1081

See merge request briar/briar!1515
2021-08-04 12:09:08 +00:00
Sebastian Kürten
60aaa4a7c1 HotspotManager: set channel to null after closing it consistently 2021-08-04 12:35:59 +02:00
Sebastian Kürten
d411b99030 Improve handling of HotspotState's field 'consumed' 2021-08-04 12:35:33 +02:00
Torsten Grote
acacb59114 Address review feedback for feature branch 2021-08-03 09:33:59 +02:00
akwizgran
2e07e79e4c Merge branch 'fix-screenshot-tests' into 'master'
Fix screenshot tests

See merge request briar/briar!1514
2021-07-28 12:37:53 +00:00
akwizgran
e9dbceefe8 Merge branch '2117-hotspot-password' into '1081-share-app-via-wifi-hotspot'
hotspot: remove 5 and S, as well as i and l

See merge request briar/briar!1513
2021-07-28 12:36:07 +00:00
Torsten Grote
8cdb314170 Fix screenshot tests 2021-07-27 15:09:10 +02:00
Torsten Grote
39d3f47e19 hotspot: remove 5 and S, as well as i, l and 1 2021-07-27 13:39:28 +02:00
Torsten Grote
522474ac15 Merge branch '2100-refactor-condition-managers' into '1081-share-app-via-wifi-hotspot'
Split ConditionManager into API-specific versions

See merge request briar/briar!1512
2021-07-26 13:49:20 +00:00
akwizgran
ed6c4ba634 Merge branch 'master' into 'master'
Create a better formatted and more informant README.md

See merge request briar/briar!1504
2021-07-26 10:34:46 +00:00
akwizgran
49562cbd79 Merge branch 'log-uncaught-exceptions' into 'master'
Log uncaught exceptions on debug builds

See merge request briar/briar!1497
2021-07-23 10:45:27 +00:00
Sebastian Kürten
6337b86266 Rename ConditionManager classes 2021-07-21 16:29:23 +02:00
Sebastian Kürten
93eadb88f3 Apply review feedback 2021-07-21 16:13:44 +02:00
Sebastian Kürten
46e391645c Reduce visibility of a field and two methods 2021-07-21 13:30:20 +02:00
Sebastian Kürten
355c487ec9 Split ConditionManager into API-specific versions
* On API 29+ we need the location permission to start the hotspot, while
  on lower API levels, we don't. In order to handle permissions and
  other conditions in a clear manner depending the API level of the
  device the app is running on, have separate extensions of the base
  ConditionManager class.
* Take special care to handle situations gracefully where the Wifi is
  disabled and the user tries to start the hotspot. We cannot simply
  rely on Wifi being enabled as a sufficient condition that allows us to
  start the hotspot. We need to wait for WifiP2p to be available. While
  it is tricky to obtain that state (it involves registering a broadcast
  receiver for the WIFI_P2P_STATE_CHANGED_ACTION broadcast, keeping
  track of changes there and even then things are still ugly. It can
  happen that WifiP2p is available *before* Wifi is. Also it can happen
  that WifiP2p never becomes available because some other application
  has already opened a hotspot. Instead of checking that state, we now
  just try (and retry repeatedly after a delay) to start the hotspot
  (and the WifiP2p framework) hoping that is becomes availabe within a
  reasonable amount of time after Wifi has been detected to be on.
  Currently we try 5 times with a delay of 1 second.
* Improve the behavior of disabling and re-enabling the 'start hotspot'
  button, so that it becomes impossible to double-tap it, but still
  making sure that the button get re-enabled as soon as the UI is back
  in a state where the user should be able to tap the button again.
2021-07-21 13:30:14 +02:00
Torsten Grote
d1c0f1b2f6 Merge branch '2109-assertion-in-rssfeedviewmodel' into 'master'
Fix assertion caused by system-initiated process death

Closes #2109

See merge request briar/briar!1510
2021-07-20 13:03:27 +00:00
akwizgran
445ef0818c Bump version numbers for 1.3.6 release. 2021-07-14 13:12:16 +01:00
akwizgran
8af743db71 Update translations. 2021-07-14 13:11:30 +01:00
akwizgran
806fce8c34 Keep the screen on while the hotspot is running. 2021-07-14 14:04:33 +02:00
Sebastian Kürten
f9494d71de Improve texts on offline hotspot for a better UX 2021-07-14 14:04:30 +02:00
Sebastian Kürten
df38187288 Do not increment the attempt variable twice when requesting group info 2021-07-14 14:04:12 +02:00
Sebastian Kürten
b8009c35f1 Do not allow the user to tap the start sharing button twice quickly 2021-07-14 14:04:11 +02:00
Sebastian Kürten
1306761f4a Don't move to HotspotFragment on rotate when user navigated back to introduction 2021-07-14 14:04:11 +02:00
Sebastian Kürten
703ff9835d Fix bug that occurs when HotspotActivity gets destroyed
If HotspotActivity gets destroyed, so will be its viewmodel, resulting
in an undefined state when the activity gets created again. While the
fragments will be restored, the view model and hotspot/webserver state
will not. Fix this by resetting the UI to reflect the reset of hotspot
and webserver.
2021-07-14 14:04:11 +02:00
Sebastian Kürten
4abaeed32f Fix background color of cardview for qr code 2021-07-14 14:04:10 +02:00
Sebastian Kürten
9192ee32cf Use FragmentContainerView for displaying FallbackFragment 2021-07-14 14:04:09 +02:00
Sebastian Kürten
aecd204efe Improve hotspot error fragment UI
* Use different highlighting for error message
* Improve margins in fragment_hotspot_save_apk.xml
* Address some review feedback
2021-07-14 14:04:09 +02:00
Sebastian Kürten
03cb1010e2 Pass error message to feedback activity 2021-07-14 14:04:07 +02:00
Sebastian Kürten
30063f5fbf Create FallbackFragment for alternative apk sharing method 2021-07-14 14:04:05 +02:00
Sebastian Kürten
0fb52a7f53 Log hotspot errors 2021-07-14 14:03:58 +02:00
Sebastian Kürten
094024eb4f Wire feedback button to show feedback fragment 2021-07-14 14:03:58 +02:00
Sebastian Kürten
e39c99fd6c Outline specific error fragment for hotspot 2021-07-14 14:03:57 +02:00
Sebastian Kürten
6cd70e0e7f Let HotspotActivity implement BaseFragmentListener 2021-07-14 14:03:57 +02:00
Torsten Grote
d646635b1f Move hotspot help ActivityResultLauncher into method 2021-07-14 14:03:57 +02:00
Torsten Grote
a534ec2b50 Adapt hotspot buttons to latest design and add a nullability annotation 2021-07-14 14:03:56 +02:00
Torsten Grote
a23de6172f Make HotspotHelpFragment headlines bold 2021-07-14 14:03:56 +02:00
Torsten Grote
ff2dd33435 Handle returned Uri being null 2021-07-14 14:03:54 +02:00
Torsten Grote
d5d0a03638 Save the APK as a hotspot fallback 2021-07-14 14:03:50 +02:00
Sebastian Kürten
344fff4a7a Add feature flag for sharing the app via offline hotspot 2021-07-14 14:03:13 +02:00
Sebastian Kürten
f9749fda80 Recommend to undo settings to install apps from unknown sources 2021-07-14 13:53:21 +02:00
Torsten Grote
aabba3a6c8 Add missing hotspot nullability annotations 2021-07-14 13:53:20 +02:00
Torsten Grote
673f530c14 Move savedNetworkConfig into HotspotManager and use constructor injection 2021-07-14 13:53:20 +02:00
Torsten Grote
36a1478661 Make hotspot SSID and passphrase persistent 2021-07-14 13:53:19 +02:00
Sebastian Kürten
1c056160e1 Use better filename for apk files shared via hotspot 2021-07-14 13:53:19 +02:00
Torsten Grote
ab6b83d4fa Show a snackbar when a peer connected to the hotspot 2021-07-14 13:53:17 +02:00
Torsten Grote
a6c33d300c Don't start hotspot while running and use proper ErrorFragment 2021-07-14 13:53:16 +02:00
Torsten Grote
28d87dd153 Port code from Offline hotspot test app 2021-07-14 13:53:12 +02:00
Torsten Grote
16b79e0482 Fix hotspot notification on old APIs 2021-07-14 13:53:03 +02:00
Torsten Grote
3eee144c6c Rename tab fragments
and remove redundant NonNull annotations
2021-07-14 13:52:58 +02:00
Torsten Grote
1b7007d4ef Show notification while hotspot is active 2021-07-14 13:52:42 +02:00
Torsten Grote
19a5c2f79f Add hotspot troubleshooting info 2021-07-14 13:39:11 +02:00
Torsten Grote
8c163d8f10 Add offline sharing entry point to Settings/Actions 2021-07-14 13:39:10 +02:00
Torsten Grote
c3cd32b12c Let info screens scroll in case of insufficient space 2021-07-14 13:39:09 +02:00
Torsten Grote
7c8aa5bc21 Implement info screens for offline app sharing 2021-07-14 13:39:05 +02:00
Torsten Grote
54b239f45e Implement intro screen for offline app sharing 2021-07-14 13:38:41 +02:00
Torsten Grote
97bd977108 Merge branch '1802-sync-via-removable-storage' into 'master'
Transfer data securely via removable storage

See merge request briar/briar!1511
2021-07-14 11:23:50 +00:00
akwizgran
aaba9f2417 Don't configure plugin unless feature flag is enabled. 2021-07-14 11:48:48 +01:00
akwizgran
6a909b6c5c Rename method, as it no longer involves a notification. 2021-07-13 15:55:29 +01:00
akwizgran
4ef92f1c39 Remove redundant UiUtils method. 2021-07-13 15:49:33 +01:00
akwizgran
8f392b4599 Use getLong() to avoid remote possibility of overflow. 2021-07-13 12:04:09 +01:00
akwizgran
f556bc7249 Update javadoc for RemovableDriveTask. 2021-07-13 11:50:17 +01:00
akwizgran
e48886c95a Update max latency of AndroidRemovableDrivePlugin to 28 days. 2021-07-13 11:44:32 +01:00
Daniel Lublin
e2879cd664 Fix assertion caused by system-initiated process death 2021-07-13 11:31:51 +02:00
akwizgran
c3977e9276 Add comment reminding us to remove obsolete notification channel ID. 2021-07-13 10:30:17 +02:00
akwizgran
b93803060e Remove unused strings. 2021-07-13 10:30:17 +02:00
akwizgran
4498187721 Suggest upgrading if the app fails to start. 2021-07-13 10:30:16 +02:00
akwizgran
8666fe45b1 Show startup failure activity immediately, without a notification. 2021-07-13 10:30:15 +02:00
akwizgran
cd12447c2e Include RemovableDriveModule in UI tests. 2021-07-13 10:30:14 +02:00
Torsten Grote
0a79cc882a Handle the don't keep activities option when using transfer data feature 2021-07-13 10:30:13 +02:00
akwizgran
7f80b5d660 Update text explaining that contact doesn't support removable drives. 2021-07-13 10:30:13 +02:00
akwizgran
92f58e9465 Increase max latency of removable drive plugin to 28 days. 2021-07-13 10:30:12 +02:00
akwizgran
387f7f1545 Check whether we have transport keys before trying to send data. 2021-07-13 10:30:12 +02:00
akwizgran
65e0845376 Don't configure the removable drive plugin on API < 19. 2021-07-13 10:30:11 +02:00
akwizgran
97bb695373 Clear keys from session when moving to AWAIT_ACTIVATE state. 2021-07-13 10:30:11 +02:00
akwizgran
d8230afae3 Reject old timestamps when deriving rotation mode keys. 2021-07-13 10:30:11 +02:00
Torsten Grote
07afb955f7 Remove guidelines for percent based laout width 2021-07-13 10:30:10 +02:00
akwizgran
a57d668fc9 Use guidelines to set image sizes. 2021-07-13 10:30:10 +02:00
Torsten Grote
765dbcc111 Check if the chosen contact supports removable drive transport
and show message if not
2021-07-13 10:30:09 +02:00
Torsten Grote
ccb4f88b89 Combine transfer data graphics to reduce layout complexity
and make scaling work better on smaller screens
2021-07-13 10:30:09 +02:00
Torsten Grote
eee9e1a488 Address review feedback for Transfer Data UI 2021-07-13 10:30:08 +02:00
Torsten Grote
f832f663c9 Migrate all image file pickers to ActivityResultLauncher
startActivityForResult is deprecated and the new API is nicer. Also, we can use the same launcher types in various places.
2021-07-13 10:30:07 +02:00
Torsten Grote
032f56ad67 Try to force file chooser to show internal/external storage by default 2021-07-13 10:30:07 +02:00
Torsten Grote
3f2ac528c1 Calculate percentages for send progress bar 2021-07-13 10:30:06 +02:00
Torsten Grote
d174757ef0 Remove manual initial state and oldTask state argument
The latter is now handled via a LiveEvent
2021-07-13 10:30:06 +02:00
Torsten Grote
f457a5e831 Hide Transfer Data feature behind feature flag 2021-07-13 10:30:06 +02:00
Torsten Grote
ab2fe58d2f Check if there is data to send and show a message if not 2021-07-13 10:30:05 +02:00
Torsten Grote
fe1c384aeb Always inform new observers about current state 2021-07-13 10:30:05 +02:00
Torsten Grote
4c327e9874 Re-organize conversations overflow menu 2021-07-13 10:30:04 +02:00
Torsten Grote
928b951c25 Transfer Data UI 2021-07-13 10:30:04 +02:00
Daniel Lublin
ecba2a51d8 Start of UI for transfer data feature 2021-07-13 10:30:03 +02:00
Torsten Grote
9668f62c6a Remove FIXME in test since we won't fix it this way 2021-07-13 10:30:02 +02:00
Torsten Grote
dc3ba3d8f0 Also test that messages arrive and activate keys 2021-07-13 10:30:01 +02:00
Torsten Grote
3f6f970d36 Add two more tests to TransportKeyAgreementIntegrationTest 2021-07-13 10:30:01 +02:00
Torsten Grote
768356d8e2 Ensure that private key is not stored anymore 2021-07-13 10:30:00 +02:00
Torsten Grote
65110090de Add first integration test for TransportKeyAgreementManager 2021-07-13 10:29:59 +02:00
Torsten Grote
f5cab63052 Add first integration test for TransportKeyAgreementManager 2021-07-13 10:29:56 +02:00
Torsten Grote
399d8adb3b Refactor base of BriarIntegrationTest into BrambleIntegrationTest 2021-07-13 10:27:17 +02:00
Torsten Grote
b40055686b Put FeatureFlags for tests into a TestFeatureFlagModule 2021-07-13 10:26:27 +02:00
akwizgran
2dcecb2a46 Add method for checking whether contact supports transport. 2021-07-13 10:26:27 +02:00
akwizgran
0cc118c849 Add transport property to indicate support for removable drives. 2021-07-13 10:26:26 +02:00
akwizgran
b1148ebc83 Store ID of message that triggered abort. 2021-07-13 10:26:26 +02:00
akwizgran
802f64e309 Check whether system clock is reasonable at startup. 2021-07-13 10:26:26 +02:00
Torsten Grote
80749fec09 Add TransportKeyAgreementValidatorTest 2021-07-13 10:26:25 +02:00
akwizgran
1f1ea8f3ed Add RemovableDriveManager method. 2021-07-13 10:26:25 +02:00
akwizgran
796cbcaf4b Add DB method for checking whether there's anything to send 2021-07-13 10:26:24 +02:00
akwizgran
4cf5242aa5 Add comment explaining second client versioning message. 2021-07-13 10:26:24 +02:00
akwizgran
8921f10ffd Add integration test for eager retransmission. 2021-07-13 10:26:24 +02:00
akwizgran
b60c129acf Update DB method that gets total size of messages to send. 2021-07-13 10:26:23 +02:00
akwizgran
852413b36a Use eager retransmission if the transport is lossy and cheap. 2021-07-13 10:26:23 +02:00
akwizgran
a39b367477 Add tests for eager retransmission. 2021-07-13 10:26:22 +02:00
akwizgran
8be274dc4d Replace inner classes with lambdas. 2021-07-13 10:26:22 +02:00
akwizgran
9ac72296c7 Update SimplexOutgoingSession to support sending unacked messages. 2021-07-13 10:26:21 +02:00
akwizgran
1405f5954a Add database methods for sending unacked messages. 2021-07-13 10:26:21 +02:00
akwizgran
f406de6b0c Timestamp isn't needed for deriving root key. 2021-07-13 10:26:20 +02:00
akwizgran
0df57c82cb Make tests more readable. 2021-07-13 10:26:20 +02:00
akwizgran
4853bcd724 Remove unused remote timestamp from session. 2021-07-13 10:26:20 +02:00
akwizgran
37e95d4ce6 Add transport key agreement client. 2021-07-13 10:26:19 +02:00
akwizgran
23acd186f7 Hold lock while calling notifyObservers(). 2021-07-13 10:26:19 +02:00
akwizgran
5e98bd0b53 Refactor removable drive tasks. 2021-07-13 10:26:18 +02:00
akwizgran
d7238312b1 Add unit tests for addRotationKeys() methods. 2021-07-13 10:26:18 +02:00
akwizgran
ec40da4353 Refactor KeyManager startup so managers are created earlier. 2021-07-13 10:26:18 +02:00
akwizgran
204ad8913f Add a key manager method for adding a single set of transport keys. 2021-07-13 10:26:17 +02:00
akwizgran
c0f5023b63 Add a DB method for checking whether transport keys exist. 2021-07-13 10:26:17 +02:00
akwizgran
b3c105bfa7 Add database method for getting transports with keys. 2021-07-13 10:26:16 +02:00
akwizgran
68acbe5c7d Add javadocs for message states. 2021-07-13 10:26:16 +02:00
akwizgran
12245d960c Allow sync clients to defer delivery of messages. 2021-07-13 10:26:16 +02:00
Daniel Lublin
f82c2517fb Make pkg private 2021-07-13 10:26:15 +02:00
Daniel Lublin
fa49da68a4 Move to new removabledrive package 2021-07-13 10:26:15 +02:00
Daniel Lublin
cffbfdf6f2 Use US locale for now 2021-07-13 10:26:14 +02:00
Daniel Lublin
cd126279ac Add initial RemovableDriveViewModel 2021-07-13 10:26:14 +02:00
akwizgran
bedd6f9a6e Refactor manager and tasks to remove reliance on files. 2021-07-13 10:26:13 +02:00
akwizgran
10e0c8d876 Update progress of writer task. 2021-07-13 10:26:13 +02:00
akwizgran
dc2ad48a7f Ensure that observers see the final state even if they're added late. 2021-07-13 10:26:13 +02:00
akwizgran
c010dd9401 Add integration test for syncing via removable drives. 2021-07-13 10:26:12 +02:00
akwizgran
270ef76057 Implement RemovableDriveWriterTask, except for progress updates. 2021-07-13 10:26:12 +02:00
akwizgran
9d47f27293 Fix typo in class names. 2021-07-13 10:26:11 +02:00
akwizgran
f0687a082a Implement RemovableDriverReaderTask. 2021-07-13 10:26:11 +02:00
akwizgran
edebde2bf4 Add task factory. 2021-07-13 10:26:11 +02:00
akwizgran
71ce74c633 Add removable drive manager with placeholder task implementations. 2021-07-13 10:26:10 +02:00
akwizgran
2dd5239b9d Add Android implementation of RemovableDrivePlugin. 2021-07-13 10:26:10 +02:00
akwizgran
f0145eb8e6 Decouple RemovableDrivePlugin from FileConstants. 2021-07-13 10:26:09 +02:00
akwizgran
556ed8fe16 Don't inject default RemovableDrivePluginFactory on Android. 2021-07-13 10:26:08 +02:00
akwizgran
ed753fd354 Decouple removable drive plugin from java.io.File for portability. 2021-07-13 10:26:08 +02:00
akwizgran
4ecc5e4367 Clean up plugin injection code, remove unused module. 2021-07-13 10:26:02 +02:00
akwizgran
b4ae480d93 Configure removable drive plugin for Android. 2021-07-13 10:25:23 +02:00
akwizgran
9a563e0cdd Add removable drive plugin. 2021-07-13 10:25:23 +02:00
akwizgran
c5d6ee6782 Add DB method for getting amount of data to sync. 2021-07-13 10:25:22 +02:00
akwizgran
f7fdf7745e Update MessagesSentEvent to include amount of data sent. 2021-07-13 10:25:21 +02:00
akwizgran
a48b60a24a Update translations. 2021-07-12 10:18:31 +01:00
Ben Armstead
520f06020c Correct typo 2021-07-08 15:14:30 +00:00
Ben Armstead
f96b60c0d0 Create a better formatted and more informant README.md 2021-07-08 13:16:37 +00:00
akwizgran
e5f78cdc1e Log uncaught exceptions on debug builds. 2021-06-30 10:44:15 +01:00
202 changed files with 4513 additions and 2480 deletions

View File

@@ -33,7 +33,7 @@ test:
stage: test stage: test
script: script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest - ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check - ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources check
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: always when: always

View File

@@ -31,15 +31,6 @@
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value /> <value />
</option> </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" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
@@ -197,9 +188,9 @@
</codeStyleSettings> </codeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" /> <indentOptions>
<option name="VARIABLE_ANNOTATION_WRAP" value="1" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
<option name="ENUM_CONSTANTS_WRAP" value="1" /> </indentOptions>
</codeStyleSettings> </codeStyleSettings>
</code_scheme> </code_scheme>
</component> </component>

View File

@@ -1,21 +1,32 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.briar-android" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-android" /> <list />
</option>
<option name="taskNames">
<list>
<option value=":briar-android:testOfficialDebugUnitTest" />
<option value=":briar-android:testScreenshotDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"> <method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-api" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-core" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-android" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in bramble-java" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-api" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-core" run_configuration_type="AndroidJUnit" /> <option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="GradleRunConfiguration" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All tests in briar-headless" run_configuration_type="AndroidJUnit" />
</method> </method>
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,23 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-android" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in bramble-android" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.bramble-android" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-android" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":bramble-android:testDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-api" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in bramble-api" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.bramble-api" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-api" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":bramble-api:animalSnifferMain" />
<option value=":bramble-api:animalSnifferTest" />
<option value=":bramble-api:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-core" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in bramble-core" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.bramble-core" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-core" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":bramble-core:animalSnifferMain" />
<option value=":bramble-core:animalSnifferTest" />
<option value=":bramble-core:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,15 +1,23 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in bramble-java" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in bramble-java" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.bramble-java" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="VM_PARAMETERS" value="-ea -Djava.library.path=libs" /> <option name="taskDescriptions">
<option name="PARAMETERS" value="" /> <list />
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/bramble-java" /> </option>
<method v="2"> <option name="taskNames">
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <list>
</method> <option value=":bramble-java:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,24 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-android" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in briar-android" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.briar-android" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-android" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":briar-android:testOfficialDebugUnitTest" />
<option value=":briar-android:testScreenshotDebugUnitTest" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-api" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in briar-api" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.briar-api" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-api" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":briar-api:animalSnifferMain" />
<option value=":briar-api:animalSnifferTest" />
<option value=":briar-api:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,14 +1,25 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-core" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in briar-core" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.briar-core" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="PARAMETERS" value="" /> <option name="taskDescriptions">
<option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/briar-core" /> <list />
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="taskNames">
</method> <list>
<option value=":briar-core:animalSnifferMain" />
<option value=":briar-core:animalSnifferTest" />
<option value=":briar-core:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,15 +1,23 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="All tests in briar-headless" type="AndroidJUnit" factoryName="Android JUnit"> <configuration default="false" name="All tests in briar-headless" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.briar-headless" /> <ExternalSystemSettings>
<option name="PACKAGE_NAME" value="org.briarproject.briar.headless" /> <option name="executionName" />
<option name="MAIN_CLASS_NAME" value="" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="METHOD_NAME" value="" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="TEST_OBJECT" value="package" /> <option name="scriptParameters" value="" />
<option name="VM_PARAMETERS" /> <option name="taskDescriptions">
<option name="PARAMETERS" value="" /> <list />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/briar-headless" /> </option>
<method v="2"> <option name="taskNames">
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <list>
</method> <option value=":briar-headless:test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

@@ -1,24 +1,28 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="BridgeTest" type="AndroidJUnit" factoryName="Android JUnit" nameIsGenerated="true"> <configuration default="false" name="BridgeTest" type="GradleRunConfiguration" factoryName="Gradle">
<module name="briar.bramble-java" /> <ExternalSystemSettings>
<useClassPathOnly /> <option name="env">
<extension name="coverage"> <map>
<pattern> <entry key="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" />
<option name="PATTERN" value="org.briarproject.bramble.plugin.tor.*" /> </map>
<option name="ENABLED" value="true" /> </option>
</pattern> <option name="executionName" />
</extension> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="PACKAGE_NAME" value="org.briarproject.bramble.plugin.tor" /> <option name="externalSystemIdString" value="GRADLE" />
<option name="MAIN_CLASS_NAME" value="org.briarproject.bramble.plugin.tor.BridgeTest" /> <option name="scriptParameters" value="--tests &quot;org.briarproject.bramble.plugin.tor.BridgeTest&quot;" />
<option name="METHOD_NAME" value="" /> <option name="taskDescriptions">
<option name="TEST_OBJECT" value="class" /> <list />
<option name="PARAMETERS" value="" /> </option>
<option name="WORKING_DIRECTORY" value="$MODULE_DIR$" /> <option name="taskNames">
<envs> <list>
<env name="OPTIONAL_TESTS" value="org.briarproject.bramble.plugin.tor.BridgeTest" /> <option value=":bramble-java:test" />
</envs> </list>
<method v="2"> </option>
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="vmOptions" value="" />
</method> </ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2" />
</configuration> </configuration>
</component> </component>

View File

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

View File

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

View File

@@ -1 +1,28 @@
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance. # Briar
Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate.
Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices.
If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance.
## Download Briar
[<img src="https://briarproject.org//img/fdroid_badge.png" width="240">](https://briarproject.org/fdroid)
[<img src="https://briarproject.org/img/google_play_badge_web_generic.png" width="240">](https://play.google.com/store/apps/details?id=org.briarproject.briar.android)
You can also [download the APK file](https://briarproject.org/apk) directly from
our site.
## Useful links
[briarproject.org](https://briarproject.org/)
[Source code](https://code.briarproject.org/briar/briar/tree/master)
[Manual](https://briarproject.org/manual/)
[Wiki](https://code.briarproject.org/briar/briar/-/wikis/home)
## Donate
[![Donate using Liberapay](https://briarproject.org/img/liberapay.svg)](https://liberapay.com/Briar/donate) [![Flattr this](https://briarproject.org/img/flattr-badge-large.png "Flattr this")](https://flattr.com/t/592836/)
Bitcoin and BCH: 1NZCKkUCtJV2U2Y9hDb9uq8S7ksFCFGR6K

View File

@@ -15,8 +15,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10305 versionCode 10308
versionName "1.3.5" versionName "1.3.8"
consumerProguardFiles 'proguard-rules.txt' consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -20,7 +20,7 @@ import static org.briarproject.bramble.api.plugin.file.RemovableDriveConstants.I
public class AndroidRemovableDrivePluginFactory implements public class AndroidRemovableDrivePluginFactory implements
SimplexPluginFactory { SimplexPluginFactory {
private static final int MAX_LATENCY = (int) DAYS.toMillis(14); private static final long MAX_LATENCY = DAYS.toMillis(28);
private final Application app; private final Application app;

View File

@@ -29,6 +29,7 @@ import java.net.UnknownHostException;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.net.SocketFactory; import javax.net.SocketFactory;
@@ -48,6 +49,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
@NotNullByDefault @NotNullByDefault
class AndroidLanTcpPlugin extends LanTcpPlugin { class AndroidLanTcpPlugin extends LanTcpPlugin {
@@ -55,6 +57,13 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
private static final Logger LOG = private static final Logger LOG =
getLogger(AndroidLanTcpPlugin.class.getName()); getLogger(AndroidLanTcpPlugin.class.getName());
/**
* The interface name is used as a heuristic for deciding whether the
* device is providing a wifi access point.
*/
private static final Pattern AP_INTERFACE_NAME =
Pattern.compile("^(wlan|ap|p2p)[-0-9]");
private final Executor connectionStatusExecutor; private final Executor connectionStatusExecutor;
private final ConnectivityManager connectivityManager; private final ConnectivityManager connectivityManager;
@Nullable @Nullable
@@ -130,17 +139,14 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
if (info != null && info.getIpAddress() != 0) { if (info != null && info.getIpAddress() != 0) {
return new Pair<>(intToInetAddress(info.getIpAddress()), false); return new Pair<>(intToInetAddress(info.getIpAddress()), false);
} }
List<InterfaceAddress> ifAddrs = getLocalInterfaceAddresses(); // If we're providing an access point, return its address
// If we're providing a normal access point, return its address for (NetworkInterface iface : getNetworkInterfaces()) {
for (InterfaceAddress ifAddr : ifAddrs) { if (AP_INTERFACE_NAME.matcher(iface.getName()).find()) {
if (isAndroidWifiApAddress(ifAddr)) { for (InterfaceAddress ifAddr : iface.getInterfaceAddresses()) {
if (isPossibleWifiApInterface(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true); return new Pair<>(ifAddr.getAddress(), true);
} }
} }
// If we're providing a wifi direct access point, return its address
for (InterfaceAddress ifAddr : ifAddrs) {
if (isAndroidWifiDirectApAddress(ifAddr)) {
return new Pair<>(ifAddr.getAddress(), true);
} }
} }
// Not connected to wifi // Not connected to wifi
@@ -148,33 +154,18 @@ class AndroidLanTcpPlugin extends LanTcpPlugin {
} }
/** /**
* Returns true if the given address belongs to a network provided by an * Returns true if the given address may belong to an interface providing
* Android access point (including the access point's own address). * a wifi access point (including wifi direct legacy mode access points).
* <p> * <p>
* The access point's address is usually 192.168.43.1, but at least one * This method may return true for wifi client interfaces as well, but
* device (Honor 8A) may use other addresses in the range 192.168.43.0/24. * we've already checked for a wifi client connection above.
*/ */
private boolean isAndroidWifiApAddress(InterfaceAddress ifAddr) { private boolean isPossibleWifiApInterface(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false; if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress(); byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4 return ip.length == 4
&& ip[0] == (byte) 192 && ip[0] == (byte) 192
&& ip[1] == (byte) 168 && ip[1] == (byte) 168;
&& ip[2] == (byte) 43;
}
/**
* Returns true if the given address belongs to a network provided by an
* Android wifi direct legacy mode access point (including the access
* point's own address).
*/
private boolean isAndroidWifiDirectApAddress(InterfaceAddress ifAddr) {
if (ifAddr.getNetworkPrefixLength() != 24) return false;
byte[] ip = ifAddr.getAddress().getAddress();
return ip.length == 4
&& ip[0] == (byte) 192
&& ip[1] == (byte) 168
&& ip[2] == (byte) 49;
} }
/** /**

View File

@@ -11,9 +11,7 @@ public interface FeatureFlags {
boolean shouldEnableDisappearingMessages(); boolean shouldEnableDisappearingMessages();
boolean shouldEnableConnectViaBluetooth(); boolean shouldEnableTransferData();
boolean shouldEnableShareAppViaOfflineHotspot(); boolean shouldEnableShareAppViaOfflineHotspot();
boolean shouldEnableTransferData();
} }

View File

@@ -14,9 +14,9 @@ public interface RemovableDriveTask extends Runnable {
TransportProperties getTransportProperties(); TransportProperties getTransportProperties();
/** /**
* Adds an observer to the task. The observer will be notified of state * Adds an observer to the task. The observer will be notified on the
* changes on the event thread. If the task has already finished, the * event thread of the current state of the task and any subsequent state
* observer will be notified of its final state. * changes.
*/ */
void addObserver(Consumer<State> observer); void addObserver(Consumer<State> observer);

View File

@@ -0,0 +1,34 @@
package org.briarproject.bramble.util;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
public class NetworkUtils {
private static final Logger LOG = getLogger(NetworkUtils.class.getName());
public static List<NetworkInterface> getNetworkInterfaces() {
try {
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
// Despite what the docs say, the return value can be null
//noinspection ConstantConditions
return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException e) {
logException(LOG, WARNING, e);
return emptyList();
}
}
}

View File

@@ -10,8 +10,8 @@ dependencyVerification {
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0', 'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52', 'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
@@ -21,7 +21,7 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
] ]
} }

View File

@@ -10,7 +10,7 @@ apply from: '../dagger.gradle'
dependencies { dependencies {
implementation project(path: ':bramble-api', configuration: 'default') implementation project(path: ':bramble-api', configuration: 'default')
implementation 'com.madgag.spongycastle:core:1.58.0.0' implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6 implementation 'com.h2database:h2:1.4.192' // The last version that supports Java 1.6
implementation 'org.bitlet:weupnp:0.1.4' implementation 'org.bitlet:weupnp:0.1.4'
implementation 'net.i2p.crypto:eddsa:0.2.0' implementation 'net.i2p.crypto:eddsa:0.2.0'
@@ -32,6 +32,14 @@ dependencies {
signature 'org.codehaus.mojo.signature:java16:1.1@signature' signature 'org.codehaus.mojo.signature:java16:1.1@signature'
} }
animalsniffer {
// Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use),
// and it gets used when passing method references instead of lambdas with Java 11.
// Note that this line allows *all* methods from java.util.Objects.
// That's the best that we can do with the configuration options that Animal Sniffer offers.
ignore 'java.util.Objects'
}
// needed to make test output available to bramble-java // needed to make test output available to bramble-java
configurations { configurations {
testOutput.extendsFrom(testCompile) testOutput.extendsFrom(testCompile)

View File

@@ -4,6 +4,9 @@ import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator; import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey; import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
@@ -20,9 +23,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.SecureRandomProvider; import org.briarproject.bramble.api.system.SecureRandomProvider;
import org.briarproject.bramble.util.ByteUtils; import org.briarproject.bramble.util.ByteUtils;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;

View File

@@ -1,38 +1,38 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.BasicAgreement;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DerivationFunction;
import org.bouncycastle.crypto.KeyEncoder;
import org.bouncycastle.crypto.Mac;
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESLightEngine;
import org.bouncycastle.crypto.engines.IESEngine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.generators.EphemeralKeyPairGenerator;
import org.bouncycastle.crypto.generators.KDF2BytesGenerator;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.IESWithCipherParameters;
import org.bouncycastle.crypto.parsers.ECIESPublicKeyParser;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BasicAgreement;
import org.spongycastle.crypto.BlockCipher;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.CryptoException;
import org.spongycastle.crypto.DerivationFunction;
import org.spongycastle.crypto.KeyEncoder;
import org.spongycastle.crypto.Mac;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.engines.AESLightEngine;
import org.spongycastle.crypto.engines.IESEngine;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.generators.EphemeralKeyPairGenerator;
import org.spongycastle.crypto.generators.KDF2BytesGenerator;
import org.spongycastle.crypto.macs.HMac;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.AsymmetricKeyParameter;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.crypto.params.IESWithCipherParameters;
import org.spongycastle.crypto.parsers.ECIESPublicKeyParser;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;

View File

@@ -1,9 +1,9 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.generators.SCrypt;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.system.Clock; import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.spongycastle.crypto.generators.SCrypt;
import java.util.logging.Logger; import java.util.logging.Logger;

View File

@@ -1,14 +1,14 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.briarproject.bramble.api.crypto.KeyParser; import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;

View File

@@ -1,8 +1,8 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;

View File

@@ -1,8 +1,8 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import javax.annotation.concurrent.Immutable; import javax.annotation.concurrent.Immutable;

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -9,8 +11,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.IncomingKeys; import org.briarproject.bramble.api.transport.IncomingKeys;
import org.briarproject.bramble.api.transport.OutgoingKeys; import org.briarproject.bramble.api.transport.OutgoingKeys;
import org.briarproject.bramble.api.transport.TransportKeys; import org.briarproject.bramble.api.transport.TransportKeys;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;

View File

@@ -1,13 +1,13 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.engines.XSalsa20Engine;
import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.DataLengthException;
import org.spongycastle.crypto.engines.XSalsa20Engine;
import org.spongycastle.crypto.generators.Poly1305KeyGenerator;
import org.spongycastle.crypto.macs.Poly1305;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;

View File

@@ -2331,7 +2331,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, DELIVERED.getValue()); ps.setInt(2, DELIVERED.getValue());
rs = ps.executeQuery(); rs = ps.executeQuery();
rs.next(); rs.next();
long total = rs.getInt(1); long total = rs.getLong(1);
rs.close(); rs.close();
ps.close(); ps.close();
return total; return total;

View File

@@ -28,11 +28,9 @@ import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -53,7 +51,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED;
import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.INACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.bramble.util.IoUtils.tryToClose; import static org.briarproject.bramble.util.IoUtils.tryToClose;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress; import static org.briarproject.bramble.util.PrivacyUtils.scrubSocketAddress;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@@ -98,7 +96,6 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
/** /**
* Returns true if connections to the given address can be attempted. * Returns true if connections to the given address can be attempted.
*/ */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
protected abstract boolean isConnectable(InterfaceAddress local, protected abstract boolean isConnectable(InterfaceAddress local,
InetSocketAddress remote); InetSocketAddress remote);
@@ -398,17 +395,6 @@ abstract class TcpPlugin implements DuplexPlugin, EventListener {
return addrs; return addrs;
} }
private List<NetworkInterface> getNetworkInterfaces() {
try {
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException e) {
logException(LOG, WARNING, e);
return emptyList();
}
}
@Override @Override
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
if (e instanceof SettingsUpdatedEvent) { if (e instanceof SettingsUpdatedEvent) {

View File

@@ -4,10 +4,10 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA3Digest;
import org.bouncycastle.util.encoders.Base64;
import org.briarproject.bramble.util.Base32; import org.briarproject.bramble.util.Base32;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.SHA3Digest;
import org.spongycastle.util.encoders.Base64;
import java.nio.charset.Charset; import java.nio.charset.Charset;

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.rendezvous; package org.briarproject.bramble.rendezvous;
import org.bouncycastle.crypto.engines.Salsa20Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.rendezvous.KeyMaterialSource; import org.briarproject.bramble.api.rendezvous.KeyMaterialSource;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;

View File

@@ -1,9 +1,9 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils; import org.briarproject.bramble.util.StringUtils;
import org.junit.Test; import org.junit.Test;
import org.spongycastle.crypto.digests.Blake2bDigest;
import java.util.Random; import java.util.Random;

View File

@@ -1,115 +0,0 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.params.ECPublicKeyParameters;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import java.math.BigInteger;
import java.security.SecureRandom;
import static org.junit.Assert.assertEquals;
public class EllipticCurveMultiplicationTest extends BrambleTestCase {
@Test
public void testMultiplierProducesSameResultsAsDefault() throws Exception {
// Instantiate the default implementation of the curve
X9ECParameters defaultX9Parameters =
TeleTrusTNamedCurves.getByName("brainpoolp256r1");
ECCurve defaultCurve = defaultX9Parameters.getCurve();
ECPoint defaultG = defaultX9Parameters.getG();
BigInteger defaultN = defaultX9Parameters.getN();
BigInteger defaultH = defaultX9Parameters.getH();
ECDomainParameters defaultParameters = new ECDomainParameters(
defaultCurve, defaultG, defaultN, defaultH);
// Instantiate an implementation using the Montgomery ladder multiplier
ECDomainParameters montgomeryParameters =
constantTime(defaultParameters);
// Generate two key pairs with each set of parameters, using the same
// deterministic PRNG for both sets of parameters
byte[] seed = new byte[32];
new SecureRandom().nextBytes(seed);
// Montgomery ladder multiplier
SecureRandom random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters montgomeryGeneratorParams =
new ECKeyGenerationParameters(montgomeryParameters, random);
ECKeyPairGenerator montgomeryGenerator = new ECKeyPairGenerator();
montgomeryGenerator.init(montgomeryGeneratorParams);
AsymmetricCipherKeyPair montgomeryKeyPair1 =
montgomeryGenerator.generateKeyPair();
ECPrivateKeyParameters montgomeryPrivate1 =
(ECPrivateKeyParameters) montgomeryKeyPair1.getPrivate();
ECPublicKeyParameters montgomeryPublic1 =
(ECPublicKeyParameters) montgomeryKeyPair1.getPublic();
AsymmetricCipherKeyPair montgomeryKeyPair2 =
montgomeryGenerator.generateKeyPair();
ECPrivateKeyParameters montgomeryPrivate2 =
(ECPrivateKeyParameters) montgomeryKeyPair2.getPrivate();
ECPublicKeyParameters montgomeryPublic2 =
(ECPublicKeyParameters) montgomeryKeyPair2.getPublic();
// Default multiplier
random = new PseudoSecureRandom(seed);
ECKeyGenerationParameters defaultGeneratorParams =
new ECKeyGenerationParameters(defaultParameters, random);
ECKeyPairGenerator defaultGenerator = new ECKeyPairGenerator();
defaultGenerator.init(defaultGeneratorParams);
AsymmetricCipherKeyPair defaultKeyPair1 =
defaultGenerator.generateKeyPair();
ECPrivateKeyParameters defaultPrivate1 =
(ECPrivateKeyParameters) defaultKeyPair1.getPrivate();
ECPublicKeyParameters defaultPublic1 =
(ECPublicKeyParameters) defaultKeyPair1.getPublic();
AsymmetricCipherKeyPair defaultKeyPair2 =
defaultGenerator.generateKeyPair();
ECPrivateKeyParameters defaultPrivate2 =
(ECPrivateKeyParameters) defaultKeyPair2.getPrivate();
ECPublicKeyParameters defaultPublic2 =
(ECPublicKeyParameters) defaultKeyPair2.getPublic();
// The key pairs generated with both sets of parameters should be equal
assertEquals(montgomeryPrivate1.getD(), defaultPrivate1.getD());
assertEquals(montgomeryPublic1.getQ(), defaultPublic1.getQ());
assertEquals(montgomeryPrivate2.getD(), defaultPrivate2.getD());
assertEquals(montgomeryPublic2.getQ(), defaultPublic2.getQ());
// OK, all of the above was just sanity checks - now for the test!
ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(montgomeryPrivate1);
BigInteger sharedSecretMontgomeryMontgomery =
agreement.calculateAgreement(montgomeryPublic2);
agreement.init(montgomeryPrivate1);
BigInteger sharedSecretMontgomeryDefault =
agreement.calculateAgreement(defaultPublic2);
agreement.init(defaultPrivate1);
BigInteger sharedSecretDefaultMontgomery =
agreement.calculateAgreement(montgomeryPublic2);
agreement.init(defaultPrivate1);
BigInteger sharedSecretDefaultDefault =
agreement.calculateAgreement(defaultPublic2);
// Shared secrets calculated with different multipliers should be equal
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretMontgomeryDefault);
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultMontgomery);
assertEquals(sharedSecretMontgomeryMontgomery,
sharedSecretDefaultDefault);
}
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
}

View File

@@ -3,30 +3,26 @@ package org.briarproject.bramble.crypto;
import net.i2p.crypto.eddsa.EdDSASecurityProvider; import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.KeyPairGenerator; import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.spongycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.spongycastle.asn1.teletrust.TeleTrusTNamedCurves; import org.bouncycastle.asn1.teletrust.TeleTrusTNamedCurves;
import org.spongycastle.asn1.x9.X9ECParameters; import org.bouncycastle.asn1.x9.X9ECParameters;
import org.spongycastle.crypto.AsymmetricCipherKeyPair; import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.spongycastle.crypto.BasicAgreement; import org.bouncycastle.crypto.BasicAgreement;
import org.spongycastle.crypto.Digest; import org.bouncycastle.crypto.Digest;
import org.spongycastle.crypto.agreement.ECDHBasicAgreement; import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.spongycastle.crypto.agreement.ECDHCBasicAgreement; import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
import org.spongycastle.crypto.digests.Blake2bDigest; import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.generators.ECKeyPairGenerator; import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.spongycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECKeyGenerationParameters; import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.spongycastle.crypto.params.ParametersWithRandom; import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.spongycastle.crypto.signers.DSADigestSigner; import org.bouncycastle.crypto.signers.DSADigestSigner;
import org.spongycastle.crypto.signers.DSAKCalculator; import org.bouncycastle.crypto.signers.DSAKCalculator;
import org.spongycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.ECDSASigner;
import org.spongycastle.crypto.signers.HMacDSAKCalculator; import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.spongycastle.math.ec.ECCurve;
import org.spongycastle.math.ec.ECPoint;
import org.spongycastle.math.ec.MontgomeryLadderMultiplier;
import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519;
import org.whispersystems.curve25519.Curve25519KeyPair; import org.whispersystems.curve25519.Curve25519KeyPair;
import java.math.BigInteger;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.Provider; import java.security.Provider;
@@ -55,14 +51,12 @@ public class EllipticCurvePerformanceTest {
for (String name : SEC_NAMES) { for (String name : SEC_NAMES) {
ECDomainParameters params = ECDomainParameters params =
convertParams(SECNamedCurves.getByName(name)); convertParams(SECNamedCurves.getByName(name));
runTest(name + " default", params); runTest(name, params);
runTest(name + " constant", constantTime(params));
} }
for (String name : BRAINPOOL_NAMES) { for (String name : BRAINPOOL_NAMES) {
ECDomainParameters params = ECDomainParameters params =
convertParams(TeleTrusTNamedCurves.getByName(name)); convertParams(TeleTrusTNamedCurves.getByName(name));
runTest(name + " default", params); runTest(name, params);
runTest(name + " constant", constantTime(params));
} }
runCurve25519Test(); runCurve25519Test();
runEd25519Test(); runEd25519Test();
@@ -193,13 +187,4 @@ public class EllipticCurvePerformanceTest {
return new ECDomainParameters(in.getCurve(), in.getG(), in.getN(), return new ECDomainParameters(in.getCurve(), in.getG(), in.getN(),
in.getH()); in.getH());
} }
private static ECDomainParameters constantTime(ECDomainParameters in) {
ECCurve curve = in.getCurve().configure().setMultiplier(
new MontgomeryLadderMultiplier()).create();
BigInteger x = in.getG().getAffineXCoord().toBigInteger();
BigInteger y = in.getG().getAffineYCoord().toBigInteger();
ECPoint g = curve.createPoint(x, y);
return new ECDomainParameters(curve, g, in.getN(), in.getH());
}
} }

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.CryptoException;
import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.test.BrambleTestCase; import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Test; import org.junit.Test;
import org.spongycastle.crypto.CryptoException;
import java.security.SecureRandom; import java.security.SecureRandom;

View File

@@ -1,11 +1,11 @@
package org.briarproject.bramble.crypto; package org.briarproject.bramble.crypto;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.Blake2bDigest;
import org.bouncycastle.crypto.engines.Salsa20Engine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.Digest;
import org.spongycastle.crypto.digests.Blake2bDigest;
import org.spongycastle.crypto.engines.Salsa20Engine;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;

View File

@@ -26,7 +26,7 @@ public class TestFeatureFlagModule {
} }
@Override @Override
public boolean shouldEnableConnectViaBluetooth() { public boolean shouldEnableTransferData() {
return true; return true;
} }
@@ -34,11 +34,6 @@ public class TestFeatureFlagModule {
public boolean shouldEnableShareAppViaOfflineHotspot() { public boolean shouldEnableShareAppViaOfflineHotspot() {
return true; return true;
} }
@Override
public boolean shouldEnableTransferData() {
return true;
}
}; };
} }
} }

View File

@@ -15,7 +15,6 @@ dependencyVerification {
'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99', 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava:listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar:b372a037d4230aa57fbeffdef30fd6123f9c0c2db85d0aced00c91b974f33f99',
'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6', 'com.google.j2objc:j2objc-annotations:1.1:j2objc-annotations-1.1.jar:2994a7eb78f2710bd3d3bfb639b2c94e219cedac0d4d084d516e78c16dddecf6',
'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c', 'com.h2database:h2:1.4.192:h2-1.4.192.jar:225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c',
'com.madgag.spongycastle:core:1.58.0.0:core-1.58.0.0.jar:199617dd5698c5a9312b898c0a4cec7ce9dd8649d07f65d91629f58229d72728',
'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291', 'com.squareup:javapoet:1.13.0:javapoet-1.13.0.jar:4c7517e848a71b36d069d12bb3bf46a70fd4cda3105d822b0ed2e19c00b69291',
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f', 'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff', 'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
@@ -27,13 +26,14 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd', 'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047', 'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb', 'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864', 'org.briarproject:jtorctl:0.3:jtorctl-0.3.jar:f2939238a097898998432effe93b0334d97a787972ab3a91a8973a1d309fc864',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619', 'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53', 'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90', 'org.codehaus.mojo:animal-sniffer-ant-tasks:1.20:animal-sniffer-ant-tasks-1.20.jar:bb7d2498144118311d968bb08ff6fae3fc535fb1cb9cca8b8e9ea65b189422ac',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52', 'org.codehaus.mojo:animal-sniffer:1.20:animal-sniffer-1.20.jar:80c422523c38db91260c6d78e5ee4b012862ab61cc55020c9e243dd7b5c62249',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9', 'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21', 'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050', 'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
@@ -48,8 +48,8 @@ dependencyVerification {
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04', 'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984', 'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de', 'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d', 'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
] ]
} }

View File

@@ -9,16 +9,15 @@ import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.util.Enumeration;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import static java.net.NetworkInterface.getNetworkInterfaces;
import static java.util.Collections.list; import static java.util.Collections.list;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -35,11 +34,7 @@ class JavaNetworkManager implements NetworkManager {
public NetworkStatus getNetworkStatus() { public NetworkStatus getNetworkStatus() {
boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false; boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false;
try { try {
Enumeration<NetworkInterface> interfaces = getNetworkInterfaces(); for (NetworkInterface i : getNetworkInterfaces()) {
if (interfaces == null) {
LOG.info("No network interfaces");
} else {
for (NetworkInterface i : list(interfaces)) {
if (i.isLoopback() || !i.isUp()) continue; if (i.isLoopback() || !i.isUp()) continue;
for (InetAddress addr : list(i.getInetAddresses())) { for (InetAddress addr : list(i.getInetAddresses())) {
connected = true; connected = true;
@@ -50,7 +45,6 @@ class JavaNetworkManager implements NetworkManager {
} }
} }
} }
}
} catch (SocketException e) { } catch (SocketException e) {
logException(LOG, WARNING, e); logException(LOG, WARNING, e);
} }

View File

@@ -1,15 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:cc="http://creativecommons.org/ns#" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="500" height="175" viewBox="0 0 500 175"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" id="svg2" version="1.1" sodipodi:docname="bluetooth.svg" inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
xmlns:svg="http://www.w3.org/2000/svg" <sodipodi:namedview
xmlns="http://www.w3.org/2000/svg" pagecolor="#ffffff"
width="499.24374" bordercolor="#666666"
height="175.49413" borderopacity="1"
viewBox="0 0 499.24373 175.49413" objecttolerance="10"
id="svg2" gridtolerance="10"
version="1.1"> guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="892"
id="namedview10"
showgrid="false"
inkscape:zoom="0.93337848"
inkscape:cx="-49.861215"
inkscape:cy="137.66042"
inkscape:window-x="0"
inkscape:window-y="144"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" />
<defs <defs
id="defs4" /> id="defs4" />
<metadata <metadata
@@ -26,22 +39,23 @@
</metadata> </metadata>
<path <path
id="path4201" id="path4201"
d="m 459.80937,171.15983 -4.32657,-4.3343 -7.06956,-2.42737 c -3.88825,-1.33505 -8.72285,-2.99587 -10.74354,-3.6907 l -3.674,-1.26333 3.524,-0.1726 c 8.24183,-0.40367 12.98778,-4.00671 14.33634,-10.88389 0.79628,-4.06078 1.12887,-17.29805 0.83016,-33.04122 -0.15338,-8.08375 -0.16617,-15.41641 -0.0284,-16.29481 0.13775,-0.8784 0.53527,-2.42011 0.88339,-3.42602 1.22247,-3.53243 0.33996,-11.90828 -1.8577,-17.63146 -0.34848,-0.9075 -1.93434,-4.215 -3.52415,-7.35 -4.15849,-8.2003 -4.50458,-8.94188 -4.89292,-10.4841 -0.45865,-1.82145 -0.21161,-5.43531 0.4625,-6.7659 0.66625,-1.31505 2.15695,-2.91616 3.24622,-3.48662 1.29885,-0.68024 2.61568,0.12202 4.6864,2.85512 3.42153,4.51599 14.00135,19.44095 15.73191,22.19301 3.74551,5.95636 5.95558,11.16496 7.9722,18.78849 0.6547,2.475 1.88525,6.9975 2.73456,10.05 0.84931,3.0525 2.71404,10.15792 4.14385,15.78983 l 2.59964,10.23983 4.65,5.19319 c 2.5575,2.85626 5.7975,6.46371 7.2,8.01657 1.4025,1.55286 2.55,2.97543 2.55,3.16127 0,0.33546 -34.49955,29.29931 -34.89913,29.29931 -0.11475,0 -2.15559,-1.95044 -4.5352,-4.3343 z M 365.9443,154.77206 c -1.25762,-0.62844 -2.20557,-1.3788 -2.91402,-2.30663 -2.08931,-2.73629 -1.95034,2.36868 -1.86433,-68.48249 l 0.0777,-64.03881 0.66066,-1.23494 c 1.0152,-1.89767 1.99201,-2.91087 3.73952,-3.87887 l 1.59982,-0.88619 37.78387,-0.0796 c 42.45592,-0.0894 39.40239,-0.2483 42.11646,2.19188 0.87544,0.78709 1.75715,1.95946 2.18393,2.90385 0.71264,1.57698 0.71613,1.63839 0.80561,14.20405 l 0.0899,12.62022 -1.79817,-0.13007 c -1.42577,-0.10313 -2.08143,0.007 -3.16601,0.5321 -2.01294,0.97445 -3.93993,2.89871 -5.11476,5.10753 l -1.03717,1.95 -0.007,-12.825 -0.007,-12.825 -33.6,0 -33.6,0 0,51.3 0,51.3 33.59873,0 33.59874,0 0.0763,-34.425 c 0.073,-32.96021 0.0982,-34.36117 0.59098,-32.925 0.28309,0.825 1.80562,3.9975 3.3834,7.05 5.49252,10.62624 5.40494,9.86009 5.39597,47.20335 -0.007,27.62122 -0.12358,29.95084 -1.66204,33.10906 -1.07144,2.19949 -2.71143,3.71042 -5.05823,4.66019 l -1.67381,0.6774 -36.1677,0.0797 -36.16769,0.0797 -1.864,-0.93145 z m 42.39939,-5.03813 c 2.87119,-1.30885 4.45771,-3.6784 4.43003,-6.61652 -0.0388,-4.11587 -3.1088,-7.22328 -7.1364,-7.22328 -2.11956,0 -3.56727,0.60889 -5.16364,2.17177 -2.24518,2.19807 -2.75398,5.43897 -1.30101,8.28704 0.71312,1.39782 2.52137,3.00905 3.96214,3.53045 1.49707,0.54176 3.84003,0.47454 5.20888,-0.14946 z" d="m 459.80937,167.15983 -4.32657,-4.3343 -7.06956,-2.42737 c -3.88825,-1.33505 -8.72285,-2.99587 -10.74354,-3.6907 l -3.674,-1.26333 3.524,-0.1726 c 8.24183,-0.40367 12.98778,-4.00671 14.33634,-10.88389 0.79628,-4.06078 1.12887,-17.29805 0.83016,-33.04122 -0.15338,-8.08375 -0.16617,-15.41641 -0.0284,-16.29481 0.13775,-0.8784 0.53527,-2.42011 0.88339,-3.42602 1.22247,-3.53243 0.33996,-11.90828 -1.8577,-17.63146 -0.34848,-0.9075 -1.93434,-4.215 -3.52415,-7.35 -4.15849,-8.2003 -4.50458,-8.94188 -4.89292,-10.4841 -0.45865,-1.82145 -0.21161,-5.43531 0.4625,-6.7659 0.66625,-1.31505 2.15695,-2.91616 3.24622,-3.48662 1.29885,-0.68024 2.61568,0.12202 4.6864,2.85512 3.42153,4.51599 14.00135,19.44095 15.73191,22.19301 3.74551,5.95636 5.95558,11.16496 7.9722,18.78849 0.6547,2.475 1.88525,6.9975 2.73456,10.05 0.84931,3.0525 2.71404,10.15792 4.14385,15.78983 l 2.59964,10.23983 4.65,5.19319 c 2.5575,2.85626 5.7975,6.46371 7.2,8.01657 1.4025,1.55286 2.55,2.97543 2.55,3.16127 0,0.33546 -34.49955,29.29931 -34.89913,29.29931 -0.11475,0 -2.15559,-1.95044 -4.5352,-4.3343 z M 365.9443,150.77206 c -1.25762,-0.62844 -2.20557,-1.3788 -2.91402,-2.30663 -2.08931,-2.73629 -1.95034,2.36868 -1.86433,-68.48249 l 0.0777,-64.03881 0.66066,-1.23494 c 1.0152,-1.89767 1.99201,-2.91087 3.73952,-3.87887 l 1.59982,-0.88619 37.78387,-0.0796 c 42.45592,-0.0894 39.40239,-0.2483 42.11646,2.19188 0.87544,0.78709 1.75715,1.95946 2.18393,2.90385 0.71264,1.57698 0.71613,1.63839 0.80561,14.20405 l 0.0899,12.62022 -1.79817,-0.13007 c -1.42577,-0.10313 -2.08143,0.007 -3.16601,0.5321 -2.01294,0.97445 -3.93993,2.89871 -5.11476,5.10753 l -1.03717,1.95 -0.007,-12.825 -0.007,-12.825 h -33.6 -33.6 v 51.3 51.3 h 33.59873 33.59874 l 0.0763,-34.425 c 0.073,-32.96021 0.0982,-34.36117 0.59098,-32.925 0.28309,0.825 1.80562,3.9975 3.3834,7.05 5.49252,10.62624 5.40494,9.86009 5.39597,47.20335 -0.007,27.62122 -0.12358,29.95084 -1.66204,33.10906 -1.07144,2.19949 -2.71143,3.71042 -5.05823,4.66019 l -1.67381,0.6774 -36.1677,0.0797 -36.16769,0.0797 -1.864,-0.93145 z m 42.39939,-5.03813 c 2.87119,-1.30885 4.45771,-3.6784 4.43003,-6.61652 -0.0388,-4.11587 -3.1088,-7.22328 -7.1364,-7.22328 -2.11956,0 -3.56727,0.60889 -5.16364,2.17177 -2.24518,2.19807 -2.75398,5.43897 -1.30101,8.28704 0.71312,1.39782 2.52137,3.00905 3.96214,3.53045 1.49707,0.54176 3.84003,0.47454 5.20888,-0.14946 z"
style="fill:#000000" /> style="fill:#000000;stroke:none" />
<path <path
id="path4201-1" id="path4201-1"
d="m 39.434334,171.15983 4.32657,-4.3343 7.06956,-2.42737 c 3.88825,-1.33505 8.72285,-2.99587 10.74354,-3.6907 l 3.674,-1.26333 -3.524,-0.1726 c -8.24183,-0.40367 -12.98778,-4.00671 -14.33634,-10.88389 -0.79628,-4.06078 -1.12887,-17.29805 -0.83016,-33.04122 0.15338,-8.08375 0.16617,-15.41641 0.0284,-16.29481 -0.13775,-0.8784 -0.53527,-2.42011 -0.88339,-3.42602 -1.22247,-3.53243 -0.33996,-11.90828 1.8577,-17.63146 0.34848,-0.9075 1.93434,-4.215 3.52415,-7.35 4.15849,-8.2003 4.50458,-8.94188 4.89292,-10.4841 0.45865,-1.82145 0.21161,-5.43531 -0.4625,-6.7659 -0.66625,-1.31505 -2.15695,-2.91616 -3.24622,-3.48662 -1.29885,-0.68024 -2.61568,0.12202 -4.6864,2.85512 -3.42153,4.51599 -14.00135,19.44095 -15.73191,22.19301 -3.74551,5.95636 -5.955584,11.16496 -7.972204,18.78849 -0.6547,2.475 -1.88525,6.9975 -2.73456,10.05 -0.84931,3.0525 -2.71404,10.15792 -4.14385,15.78983 L 14.4,129.82379 9.75,135.01698 c -2.5575,2.85626 -5.7975,6.46371 -7.2,8.01657 -1.4025,1.55286 -2.55,2.97543 -2.55,3.16127 0,0.33546 34.499554,29.29931 34.899134,29.29931 0.11475,0 2.15559,-1.95044 4.53519,-4.3343 z m 93.865056,-16.38777 c 1.25762,-0.62844 2.20557,-1.3788 2.91402,-2.30663 2.08931,-2.73629 1.95034,2.36868 1.86433,-68.48249 l -0.0777,-64.03881 -0.66066,-1.23494 c -1.0152,-1.89767 -1.99201,-2.91087 -3.73952,-3.87887 l -1.59982,-0.88619 -37.783856,-0.0796 c -42.45592,-0.0894 -39.40239,-0.2483 -42.11646,2.19188 -0.87544,0.78709 -1.75715,1.95946 -2.18393,2.90385 -0.71264,1.57698 -0.71613,1.63839 -0.80561,14.20405 l -0.0899,12.62022 1.79817,-0.13007 c 1.42577,-0.10313 2.08143,0.007 3.16601,0.5321 2.01294,0.97445 3.93993,2.89871 5.11476,5.10753 l 1.03717,1.95 0.007,-12.825 0.007,-12.825 33.6,0 33.599986,0 0,51.3 0,51.3 -33.598716,0 -33.59874,0 -0.0763,-34.425 c -0.073,-32.96021 -0.0982,-34.36117 -0.59098,-32.925 -0.28309,0.825 -1.80562,3.9975 -3.3834,7.05 -5.49252,10.62624 -5.40494,9.86009 -5.39597,47.20335 0.007,27.62122 0.12358,29.95084 1.66204,33.10906 1.07144,2.19949 2.71143,3.71042 5.05823,4.66019 l 1.67381,0.6774 36.1677,0.0797 36.167676,0.0797 1.864,-0.93145 z m -42.399376,-5.03813 c -2.87119,-1.30885 -4.45771,-3.6784 -4.43003,-6.61652 0.0388,-4.11587 3.1088,-7.22328 7.1364,-7.22328 2.11956,0 3.56727,0.60889 5.16364,2.17177 2.245176,2.19807 2.753976,5.43897 1.301006,8.28704 -0.713116,1.39782 -2.521366,3.00905 -3.962136,3.53045 -1.49707,0.54176 -3.84003,0.47454 -5.20888,-0.14946 z" d="m 39.434334,167.15983 4.32657,-4.3343 7.06956,-2.42737 c 3.88825,-1.33505 8.72285,-2.99587 10.74354,-3.6907 l 3.674,-1.26333 -3.524,-0.1726 c -8.24183,-0.40367 -12.98778,-4.00671 -14.33634,-10.88389 -0.79628,-4.06078 -1.12887,-17.29805 -0.83016,-33.04122 0.15338,-8.08375 0.16617,-15.41641 0.0284,-16.29481 -0.13775,-0.8784 -0.53527,-2.42011 -0.88339,-3.42602 -1.22247,-3.53243 -0.33996,-11.90828 1.8577,-17.63146 0.34848,-0.9075 1.93434,-4.215 3.52415,-7.35 4.15849,-8.2003 4.50458,-8.94188 4.89292,-10.4841 0.45865,-1.82145 0.21161,-5.43531 -0.4625,-6.7659 -0.66625,-1.31505 -2.15695,-2.91616 -3.24622,-3.48662 -1.29885,-0.68024 -2.61568,0.12202 -4.6864,2.85512 -3.42153,4.51599 -14.00135,19.44095 -15.73191,22.19301 -3.74551,5.95636 -5.955584,11.16496 -7.972204,18.78849 -0.6547,2.475 -1.88525,6.9975 -2.73456,10.05 -0.84931,3.0525 -2.71404,10.15792 -4.14385,15.78983 L 14.4,125.82379 9.75,131.01698 c -2.5575,2.85626 -5.7975,6.46371 -7.2,8.01657 -1.4025,1.55286 -2.55,2.97543 -2.55,3.16127 0,0.33546 34.499554,29.29931 34.899134,29.29931 0.11475,0 2.15559,-1.95044 4.53519,-4.3343 z m 93.865056,-16.38777 c 1.25762,-0.62844 2.20557,-1.3788 2.91402,-2.30663 2.08931,-2.73629 1.95034,2.36868 1.86433,-68.48249 l -0.0777,-64.03881 -0.66066,-1.23494 c -1.0152,-1.89767 -1.99201,-2.91087 -3.73952,-3.87887 L 132.00004,9.94413 94.216184,9.86453 c -42.45592,-0.0894 -39.40239,-0.2483 -42.11646,2.19188 -0.87544,0.78709 -1.75715,1.95946 -2.18393,2.90385 -0.71264,1.57698 -0.71613,1.63839 -0.80561,14.20405 l -0.0899,12.62022 1.79817,-0.13007 c 1.42577,-0.10313 2.08143,0.007 3.16601,0.5321 2.01294,0.97445 3.93993,2.89871 5.11476,5.10753 l 1.03717,1.95 0.007,-12.825 0.007,-12.825 h 33.6 33.599986 v 51.3 51.3 H 93.751664 60.152924 l -0.0763,-34.425 c -0.073,-32.96021 -0.0982,-34.36117 -0.59098,-32.925 -0.28309,0.825 -1.80562,3.9975 -3.3834,7.05 -5.49252,10.62624 -5.40494,9.86009 -5.39597,47.20335 0.007,27.62122 0.12358,29.95084 1.66204,33.10906 1.07144,2.19949 2.71143,3.71042 5.05823,4.66019 l 1.67381,0.6774 36.1677,0.0797 36.167676,0.0797 1.864,-0.93145 z m -42.399376,-5.03813 c -2.87119,-1.30885 -4.45771,-3.6784 -4.43003,-6.61652 0.0388,-4.11587 3.1088,-7.22328 7.1364,-7.22328 2.11956,0 3.56727,0.60889 5.16364,2.17177 2.245176,2.19807 2.753976,5.43897 1.301006,8.28704 -0.713116,1.39782 -2.521366,3.00905 -3.962136,3.53045 -1.49707,0.54176 -3.84003,0.47454 -5.20888,-0.14946 z"
style="fill:#000000" /> style="fill:#000000;stroke:none" />
<path <path
id="rect4270" id="rect4270"
d="m 247.25369,75.97921 4.73637,0 c 13.16497,0 23.76348,10.598514 23.76348,23.763485 l 0,32.174615 c 0,13.16497 -10.59851,23.76348 -23.76348,23.76348 l -4.73637,0 c -13.16497,0 -23.76349,-10.59851 -23.76349,-23.76348 l 0,-32.174615 c 0,-13.164971 10.59852,-23.763485 23.76349,-23.763485 z" d="m 247.25369,71.97921 h 4.73637 c 13.16497,0 23.76348,10.598514 23.76348,23.763485 v 32.174615 c 0,13.16497 -10.59851,23.76348 -23.76348,23.76348 h -4.73637 c -13.16497,0 -23.76349,-10.59851 -23.76349,-23.76348 V 95.742695 c 0,-13.164971 10.59852,-23.763485 23.76349,-23.763485 z"
style="fill:#0a3d91" /> style="fill:#0a3d91;stroke:none" />
<path
id="path4272"
d="m 236.31105,102.92749 24.90674,25.07007 -12.00423,14.53574 0,-51.936691 12.00423,13.882451 -24.90674,24.41678"
style="fill:none;stroke:#ffffff;stroke-width:4.32805729" />
<path <path
id="path4844" id="path4844"
d="m 143.67921,27.5571 c -1.59043,2.623831 0.18153,5.574737 2.78461,6.642714 2.69504,1.572445 6.1706,4.987176 9.14793,2.232151 2.23313,-2.039515 0.60129,-5.727894 -2.04109,-6.67035 -3.06433,-1.676733 -6.55172,-5.514807 -9.89145,-2.204515 z M 351.7046,27.12937 c -2.76779,1.960623 -7.06819,2.694997 -8.37782,6.042717 -0.64195,2.73095 1.93572,4.99255 4.58419,4.426453 3.2021,-1.637914 7.05658,-2.8424 9.17849,-5.94769 0.56931,-2.60435 -1.49324,-5.35066 -4.26769,-4.745697 -0.41558,-0.107679 -0.76003,0.03505 -1.11717,0.224217 z m -26.76172,13.17187 c -2.93431,1.708015 -7.32039,1.922759 -8.93076,5.153004 -0.90775,2.654328 1.43202,5.159236 4.12365,4.861044 3.35587,-1.304271 7.31954,-2.112696 9.73662,-5.002268 0.81368,-2.537666 -0.97658,-5.469583 -3.79644,-5.128908 -0.4035,-0.14574 -0.75969,-0.03824 -1.13307,0.117128 z m -155.23633,2.46289 c -1.21948,2.711869 0.75683,5.460507 3.4437,6.14538 2.85847,1.080772 6.66937,4.11785 9.31224,1.23392 2.3151,-2.03652 0.33929,-5.835383 -2.40983,-6.41823 -3.32833,-1.175814 -7.38479,-4.713633 -10.34611,-0.96107 z m 127.27344,7.61719 c -3.12289,1.309174 -7.49226,0.970678 -9.49244,3.970097 -1.23184,2.518748 0.7727,5.298264 3.48037,5.339749 3.48706,-0.907308 7.56742,-1.1128 10.30314,-3.739116 1.11397,-2.422222 -0.31199,-5.546068 -3.1516,-5.551016 -0.3827,-0.195662 -0.75023,-0.126964 -1.13947,-0.01971 z m -97.49023,1.54492 c -2.22858,1.461838 -2.68005,4.865948 -0.27553,6.376909 2.5869,1.470583 5.91459,1.954941 8.83971,2.495346 2.78485,0.332188 4.74603,-2.479173 3.89118,-5.048668 -2.04264,-3.279676 -6.47206,-2.73364 -9.71704,-4.089227 -0.91277,0.08855 -1.82555,0.177093 -2.73832,0.26564 z m 68.51367,4.50782 c -3.27623,0.788856 -7.46081,-0.187875 -9.93635,2.381992 -1.64972,2.268076 -0.15709,5.353662 2.50432,5.86185 3.61172,-0.292365 7.71764,0.09072 10.84009,-2.093962 1.46245,-2.227649 0.52336,-5.531725 -2.28478,-5.959415 -0.34949,-0.249225 -0.72211,-0.239347 -1.12328,-0.190465 z m -39.44922,1.41796 c -1.93309,1.83595 -1.76537,5.266611 0.87081,6.321491 2.80857,0.988807 6.17065,0.846733 9.1444,0.861366 2.79909,-0.172766 4.22635,-3.289835 2.92376,-5.664738 -2.60365,-2.851635 -6.84699,-1.541752 -10.29251,-2.269899 -0.88215,0.250593 -1.76431,0.501187 -2.64646,0.75178 z" d="m 143.67921,23.5571 c -1.59043,2.623831 0.18153,5.574737 2.78461,6.642714 2.69504,1.572445 6.1706,4.987176 9.14793,2.232151 2.23313,-2.039515 0.60129,-5.727894 -2.04109,-6.67035 -3.06433,-1.676733 -6.55172,-5.514807 -9.89145,-2.204515 z M 351.7046,23.12937 c -2.76779,1.960623 -7.06819,2.694997 -8.37782,6.042717 -0.64195,2.73095 1.93572,4.99255 4.58419,4.426453 3.2021,-1.637914 7.05658,-2.8424 9.17849,-5.94769 0.56931,-2.60435 -1.49324,-5.35066 -4.26769,-4.745697 -0.41558,-0.107679 -0.76003,0.03505 -1.11717,0.224217 z m -26.76172,13.17187 c -2.93431,1.708015 -7.32039,1.922759 -8.93076,5.153004 -0.90775,2.654328 1.43202,5.159236 4.12365,4.861044 3.35587,-1.304271 7.31954,-2.112696 9.73662,-5.002268 0.81368,-2.537666 -0.97658,-5.469583 -3.79644,-5.128908 -0.4035,-0.14574 -0.75969,-0.03824 -1.13307,0.117128 z m -155.23633,2.46289 c -1.21948,2.711869 0.75683,5.460507 3.4437,6.14538 2.85847,1.080772 6.66937,4.11785 9.31224,1.23392 2.3151,-2.03652 0.33929,-5.835383 -2.40983,-6.41823 -3.32833,-1.175814 -7.38479,-4.713633 -10.34611,-0.96107 z m 127.27344,7.61719 c -3.12289,1.309174 -7.49226,0.970678 -9.49244,3.970097 -1.23184,2.518748 0.7727,5.298264 3.48037,5.339749 3.48706,-0.907308 7.56742,-1.1128 10.30314,-3.739116 1.11397,-2.422222 -0.31199,-5.546068 -3.1516,-5.551016 -0.3827,-0.195662 -0.75023,-0.126964 -1.13947,-0.01971 z m -97.49023,1.54492 c -2.22858,1.461838 -2.68005,4.865948 -0.27553,6.376909 2.5869,1.470583 5.91459,1.954941 8.83971,2.495346 2.78485,0.332188 4.74603,-2.479173 3.89118,-5.048668 -2.04264,-3.279676 -6.47206,-2.73364 -9.71704,-4.089227 -0.91277,0.08855 -1.82555,0.177093 -2.73832,0.26564 z m 68.51367,4.50782 c -3.27623,0.788856 -7.46081,-0.187875 -9.93635,2.381992 -1.64972,2.268076 -0.15709,5.353662 2.50432,5.86185 3.61172,-0.292365 7.71764,0.09072 10.84009,-2.093962 1.46245,-2.227649 0.52336,-5.531725 -2.28478,-5.959415 -0.34949,-0.249225 -0.72211,-0.239347 -1.12328,-0.190465 z m -39.44922,1.41796 c -1.93309,1.83595 -1.76537,5.266611 0.87081,6.321491 2.80857,0.988807 6.17065,0.846733 9.1444,0.861366 2.79909,-0.172766 4.22635,-3.289835 2.92376,-5.664738 -2.60365,-2.851635 -6.84699,-1.541752 -10.29251,-2.269899 -0.88215,0.250593 -1.76431,0.501187 -2.64646,0.75178 z"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0a3d91;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:7.55000019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:7.55, 22.65;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0a3d91;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:7.55;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:7.55, 22.65;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
<path
d="m 252.50083,112.08988 11.6563,-11.45156 -17.12592,-19.790888 0.0193,25.792048 -9.14185,-9.24454 -3.12531,3.09064 11.5214,11.58063 -11.49773,11.30756 c 3.03061,3.05593 0,0 3.03061,3.05593 l 9.18921,-8.97308 c 0.0443,4.73517 0.004,27.16515 0.004,27.16515 l 17.10224,-20.77257 z m -1.07721,-19.570428 6.79869,7.863838 -6.79869,6.63278 z m -0.0237,40.027828 0.0237,-15.39623 6.89338,6.9879 z"
id="path1536"
style="display:inline;opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.254891"
sodipodi:nodetypes="ccccccccccccccccccccc" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -26,8 +26,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10305 versionCode 10308
versionName "1.3.5" versionName "1.3.8"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -99,18 +99,19 @@ dependencies {
implementation project(path: ':briar-core', configuration: 'default') implementation project(path: ':briar-core', configuration: 'default')
implementation project(path: ':bramble-core', configuration: 'default') implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android') implementation project(':bramble-android')
implementation project(':dont-kill-me-lib')
implementation 'androidx.fragment:fragment:1.3.4' implementation "androidx.fragment:fragment:$androidx_fragment_version"
implementation 'androidx.preference:preference:1.1.1' implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'androidx.exifinterface:exifinterface:1.3.2'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation "androidx.constraintlayout:constraintlayout:$androidx_constraintlayout_version"
implementation 'com.google.android.material:material:1.3.0' implementation "com.google.android.material:material:$google_material_version"
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0' implementation 'androidx.recyclerview:recyclerview-selection:1.1.0'
implementation 'info.guardianproject.panic:panic:1.0' implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
implementation 'de.hdodenhof:circleimageview:3.0.1' implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24 implementation 'com.google.zxing:core:3.3.3' // newer version need minSdk 24
implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.0' implementation 'uk.co.samuelwall:material-tap-target-prompt:3.3.0'
implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21 implementation 'com.vanniktech:emoji-google:0.6.0' // newer versions need minSdk 21

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.system.ClockModule; import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.account.SignInTestCreateAccount; import org.briarproject.briar.android.account.SignInTestCreateAccount;
@@ -21,6 +22,7 @@ import dagger.Component;
AttachmentModule.class, AttachmentModule.class,
ClockModule.class, ClockModule.class,
MediaModule.class, MediaModule.class,
RemovableDriveModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule; import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.plugin.file.RemovableDriveModule;
import org.briarproject.bramble.system.ClockModule; import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
@@ -20,6 +21,7 @@ import dagger.Component;
AttachmentModule.class, AttachmentModule.class,
ClockModule.class, ClockModule.class,
MediaModule.class, MediaModule.class,
RemovableDriveModule.class,
BriarCoreModule.class, BriarCoreModule.class,
BrambleAndroidModule.class, BrambleAndroidModule.class,
BriarAccountModule.class, BriarAccountModule.class,

View File

@@ -39,11 +39,11 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static java.lang.Thread.sleep; import static java.lang.Thread.sleep;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.briar.android.OverlayTapViewAction.visualClick; import static org.briarproject.briar.android.OverlayTapViewAction.visualClick;
import static org.briarproject.briar.android.ViewActions.waitFor; import static org.briarproject.briar.android.ViewActions.waitFor;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches; import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.allOf;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -26,9 +26,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID; import static org.briarproject.bramble.api.plugin.LanTcpConstants.ID;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches; import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;

View File

@@ -34,7 +34,6 @@
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION" /> <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.USE_BIOMETRIC" />
<uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission-sdk-23 android:name="android.permission.FOREGROUND_SERVICE" />
@@ -344,7 +343,13 @@
<activity <activity
android:name="org.briarproject.briar.android.StartupFailureActivity" android:name="org.briarproject.briar.android.StartupFailureActivity"
android:label="@string/startup_failed_activity_title" /> android:excludeFromRecents="true"
android:exported="false"
android:finishOnTaskLaunch="true"
android:label="@string/startup_failed_activity_title"
android:launchMode="singleInstance"
android:process=":briar_startup_failure"
android:windowSoftInputMode="adjustResize|stateHidden" />
<activity <activity
android:name="org.briarproject.briar.android.settings.SettingsActivity" android:name="org.briarproject.briar.android.settings.SettingsActivity"
@@ -456,13 +461,21 @@
android:label="@string/hotspot_title" android:label="@string/hotspot_title"
android:theme="@style/BriarTheme" /> android:theme="@style/BriarTheme" />
<activity
android:name=".android.contact.connect.ConnectViaBluetoothActivity"
android:exported="false"
android:label="@string/connect_via_bluetooth_title"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
android:theme="@style/BriarTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
</application> </application>
<queries> <queries>
<package android:name="info.guardianproject.ripple" /> <package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View File

@@ -92,9 +92,9 @@
<li id="troubleshooting_1">If you can't download the app, try it with a different web <li id="troubleshooting_1">If you can't download the app, try it with a different web
browser app. browser app.
</li> </li>
<li id="troubleshooting_2">Ensure that your browser is allowed to download apps directly by <li id="troubleshooting_2">To install the downloaded app,
giving it the permission or enabling the installation of apps from "Unknown Sources" in you might need to allow your browser to install unknown apps.
system settings. We recommend to undo that after successful installation.
</li> </li>
</ol> </ol>
</div> </div>

View File

@@ -33,8 +33,10 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.system.ClockModule; import org.briarproject.bramble.system.ClockModule;
import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.BriarCoreModule;
import org.briarproject.briar.android.account.DoNotKillMeFragment;
import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.AttachmentModule;
import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.contact.connect.BluetoothIntroFragment;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader; import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.hotspot.AbstractTabsFragment; import org.briarproject.briar.android.hotspot.AbstractTabsFragment;
import org.briarproject.briar.android.hotspot.FallbackFragment; import org.briarproject.briar.android.hotspot.FallbackFragment;
@@ -236,4 +238,8 @@ public interface AndroidComponent
void inject(SendFragment sendFragment); void inject(SendFragment sendFragment);
void inject(ReceiveFragment receiveFragment); void inject(ReceiveFragment receiveFragment);
void inject(BluetoothIntroFragment bluetoothIntroFragment);
void inject(DoNotKillMeFragment dozeFragment);
} }

View File

@@ -35,6 +35,7 @@ import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.blog.BlogModule; import org.briarproject.briar.android.blog.BlogModule;
import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule; import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothModule;
import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.hotspot.HotspotModule; import org.briarproject.briar.android.hotspot.HotspotModule;
import org.briarproject.briar.android.introduction.IntroductionModule; import org.briarproject.briar.android.introduction.IntroductionModule;
@@ -89,6 +90,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
DevReportModule.class, DevReportModule.class,
ContactListModule.class, ContactListModule.class,
IntroductionModule.class, IntroductionModule.class,
ConnectViaBluetoothModule.class,
// below need to be within same scope as ViewModelProvider.Factory // below need to be within same scope as ViewModelProvider.Factory
BlogModule.class, BlogModule.class,
ForumModule.class, ForumModule.class,
@@ -157,7 +159,8 @@ public class AppModule {
@Singleton @Singleton
PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth, PluginConfig providePluginConfig(AndroidBluetoothPluginFactory bluetooth,
AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan, AndroidTorPluginFactory tor, AndroidLanTcpPluginFactory lan,
AndroidRemovableDrivePluginFactory drive) { AndroidRemovableDrivePluginFactory drive,
FeatureFlags featureFlags) {
@NotNullByDefault @NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() { PluginConfig pluginConfig = new PluginConfig() {
@@ -168,7 +171,11 @@ public class AppModule {
@Override @Override
public Collection<SimplexPluginFactory> getSimplexFactories() { public Collection<SimplexPluginFactory> getSimplexFactories() {
return SDK_INT >= 19 ? singletonList(drive) : emptyList(); if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
return singletonList(drive);
} else {
return emptyList();
}
} }
@Override @Override
@@ -306,11 +313,6 @@ public class AppModule {
return true; return true;
} }
@Override
public boolean shouldEnableConnectViaBluetooth() {
return IS_DEBUG_BUILD;
}
@Override @Override
public boolean shouldEnableTransferData() { public boolean shouldEnableTransferData() {
return IS_DEBUG_BUILD; return IS_DEBUG_BUILD;

View File

@@ -3,7 +3,6 @@ package org.briarproject.briar.android;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
@@ -34,11 +33,7 @@ import java.util.logging.Logger;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.core.app.NotificationCompat;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
@@ -55,7 +50,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResul
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull; import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_CHANNEL_OLD_ID;
import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID;
@@ -66,8 +60,6 @@ public class BriarService extends Service {
public static String EXTRA_START_RESULT = public static String EXTRA_START_RESULT =
"org.briarproject.briar.START_RESULT"; "org.briarproject.briar.START_RESULT";
public static String EXTRA_NOTIFICATION_ID =
"org.briarproject.briar.FAILURE_NOTIFICATION_ID";
public static String EXTRA_STARTUP_FAILED = public static String EXTRA_STARTUP_FAILED =
"org.briarproject.briar.STARTUP_FAILED"; "org.briarproject.briar.STARTUP_FAILED";
@@ -135,12 +127,11 @@ public class BriarService extends Service {
ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET); ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET);
ongoingChannel.setShowBadge(false); ongoingChannel.setShowBadge(false);
nm.createNotificationChannel(ongoingChannel); nm.createNotificationChannel(ongoingChannel);
NotificationChannel failureChannel = new NotificationChannel( // Delete the unused channel previously used for startup
FAILURE_CHANNEL_ID, // failure notifications
getString(R.string.startup_failed_notification_title), // TODO: Remove this ID after a reasonable upgrade period
IMPORTANCE_DEFAULT); // (added 2021-07-12)
failureChannel.setLockscreenVisibility(VISIBILITY_SECRET); nm.deleteNotificationChannel(FAILURE_CHANNEL_ID);
nm.createNotificationChannel(failureChannel);
} }
Notification foregroundNotification = Notification foregroundNotification =
notificationManager.getForegroundNotification(); notificationManager.getForegroundNotification();
@@ -156,7 +147,7 @@ public class BriarService extends Service {
} else { } else {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.warning("Startup failed: " + result); LOG.warning("Startup failed: " + result);
showStartupFailureNotification(result); showStartupFailure(result);
stopSelf(); stopSelf();
} }
}, "LifecycleStartup"); }, "LifecycleStartup");
@@ -182,29 +173,13 @@ public class BriarService extends Service {
Localizer.getInstance().setLocale(this); Localizer.getInstance().setLocale(this);
} }
private void showStartupFailureNotification(StartResult result) { private void showStartupFailure(StartResult result) {
androidExecutor.runOnUiThread(() -> { androidExecutor.runOnUiThread(() -> {
NotificationCompat.Builder b = new NotificationCompat.Builder( // Bring the entry activity to the front to clear the back stack
BriarService.this, FAILURE_CHANNEL_ID); Intent i = new Intent(BriarService.this, ENTRY_ACTIVITY);
b.setSmallIcon(android.R.drawable.stat_notify_error);
b.setContentTitle(getText(
R.string.startup_failed_notification_title));
b.setContentText(getText(
R.string.startup_failed_notification_text));
Intent i = new Intent(BriarService.this,
StartupFailureActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
i.putExtra(EXTRA_START_RESULT, result);
i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID);
b.setContentIntent(PendingIntent.getActivity(BriarService.this,
0, i, FLAG_UPDATE_CURRENT));
NotificationManager nm = (NotificationManager)
requireNonNull(getSystemService(NOTIFICATION_SERVICE));
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
// Bring the dashboard to the front to clear the back stack
i = new Intent(BriarService.this, ENTRY_ACTIVITY);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(EXTRA_STARTUP_FAILED, true); i.putExtra(EXTRA_STARTUP_FAILED, true);
i.putExtra(EXTRA_START_RESULT, result);
startActivity(i); startActivity(i);
}); });
} }

View File

@@ -1,57 +1,16 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.PowerManager;
import org.briarproject.android.dontkillmelib.AbstractDozeWatchdogImpl;
import org.briarproject.bramble.api.lifecycle.Service; import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.lifecycle.ServiceException;
import org.briarproject.briar.api.android.DozeWatchdog; import org.briarproject.briar.api.android.DozeWatchdog;
import java.util.concurrent.atomic.AtomicBoolean; class DozeWatchdogImpl extends AbstractDozeWatchdogImpl
implements DozeWatchdog, Service {
import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
class DozeWatchdogImpl implements DozeWatchdog, Service {
private final Context appContext;
private final AtomicBoolean dozed = new AtomicBoolean(false);
private final BroadcastReceiver receiver = new DozeBroadcastReceiver();
DozeWatchdogImpl(Context appContext) { DozeWatchdogImpl(Context appContext) {
this.appContext = appContext; super(appContext);
} }
@Override
public boolean getAndResetDozeFlag() {
return dozed.getAndSet(false);
}
@Override
public void startService() throws ServiceException {
if (SDK_INT < 23) return;
IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED);
appContext.registerReceiver(receiver, filter);
}
@Override
public void stopService() throws ServiceException {
if (SDK_INT < 23) return;
appContext.unregisterReceiver(receiver);
}
private class DozeBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (SDK_INT < 23) return;
PowerManager pm =
(PowerManager) appContext.getSystemService(POWER_SERVICE);
if (pm.isDeviceIdleMode()) dozed.set(true);
}
}
} }

View File

@@ -1,6 +1,5 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.NotificationManager;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@@ -15,9 +14,7 @@ import org.briarproject.briar.android.fragment.ErrorFragment;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import static java.util.Objects.requireNonNull;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult;
import static org.briarproject.briar.android.BriarService.EXTRA_NOTIFICATION_ID;
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT; import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -41,14 +38,6 @@ public class StartupFailureActivity extends BaseActivity implements
private void handleIntent(Intent i) { private void handleIntent(Intent i) {
StartResult result = StartResult result =
(StartResult) i.getSerializableExtra(EXTRA_START_RESULT); (StartResult) i.getSerializableExtra(EXTRA_START_RESULT);
int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
// cancel notification
if (notificationId > -1) {
Object o = getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) requireNonNull(o);
nm.cancel(notificationId);
}
// show proper error message // show proper error message
int errorRes; int errorRes;
@@ -78,5 +67,4 @@ public class StartupFailureActivity extends BaseActivity implements
public void runOnDbThread(@NonNull Runnable runnable) { public void runOnDbThread(@NonNull Runnable runnable) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View File

@@ -0,0 +1,38 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import org.briarproject.android.dontkillmelib.AbstractDoNotKillMeFragment;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import javax.inject.Inject;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class DoNotKillMeFragment extends AbstractDoNotKillMeFragment {
@Inject
ViewModelProvider.Factory viewModelFactory;
SetupViewModel viewModel;
@Override
public void onAttach(Context context) {
super.onAttach(context);
FragmentActivity activity = requireActivity();
getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(SetupViewModel.class);
}
@Override
protected void onButtonClicked() {
viewModel.dozeExceptionConfirmed();
}
}

View File

@@ -1,119 +0,0 @@
package org.briarproject.briar.android.account;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.account.PowerView.OnCheckedChangedListener;
import org.briarproject.briar.android.util.UiUtils;
import androidx.annotation.Nullable;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class DozeFragment extends SetupFragment
implements OnCheckedChangedListener {
private final static String TAG = DozeFragment.class.getName();
private DozeView dozeView;
private HuaweiProtectedAppsView huaweiProtectedAppsView;
private HuaweiAppLaunchView huaweiAppLaunchView;
private XiaomiView xiaomiView;
private Button next;
private boolean secondAttempt = false;
public static DozeFragment newInstance() {
return new DozeFragment();
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
requireActivity().setTitle(getString(R.string.setup_doze_title));
setHasOptionsMenu(false);
View v = inflater.inflate(R.layout.fragment_setup_doze, container,
false);
dozeView = v.findViewById(R.id.dozeView);
dozeView.setOnCheckedChangedListener(this);
huaweiProtectedAppsView = v.findViewById(R.id.huaweiProtectedAppsView);
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
huaweiAppLaunchView.setOnCheckedChangedListener(this);
xiaomiView = v.findViewById(R.id.xiaomiView);
xiaomiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
dozeView.setOnButtonClickListener(this::askForDozeWhitelisting);
next.setOnClickListener(this);
viewModel.getIsCreatingAccount()
.observe(getViewLifecycleOwner(), isCreatingAccount -> {
if (isCreatingAccount) {
next.setVisibility(INVISIBLE);
progressBar.setVisibility(VISIBLE);
}
});
return v;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
protected String getHelpText() {
return getString(R.string.setup_doze_explanation);
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_DOZE_WHITELISTING) {
if (!dozeView.needsToBeShown() || secondAttempt) {
dozeView.setChecked(true);
} else if (getContext() != null) {
secondAttempt = true;
showOnboardingDialog(getContext(), getHelpText());
}
}
}
@Override
public void onCheckedChanged() {
next.setEnabled(dozeView.isChecked() &&
huaweiProtectedAppsView.isChecked() &&
huaweiAppLaunchView.isChecked() &&
xiaomiView.isChecked());
}
@SuppressLint("BatteryLife")
private void askForDozeWhitelisting() {
if (getContext() == null) return;
Intent i = UiUtils.getDozeWhitelistingIntent(getContext());
startActivityForResult(i, REQUEST_DOZE_WHITELISTING);
}
@Override
public void onClick(View view) {
viewModel.dozeExceptionConfirmed();
}
}

View File

@@ -1,8 +0,0 @@
package org.briarproject.briar.android.account;
import android.content.Context;
interface DozeHelper {
boolean needToShowDozeFragment(Context context);
}

View File

@@ -1,5 +1,8 @@
package org.briarproject.briar.android.account; package org.briarproject.briar.android.account;
import org.briarproject.android.dontkillmelib.DozeHelper;
import org.briarproject.android.dontkillmelib.DozeHelperImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;

View File

@@ -14,12 +14,14 @@ import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static androidx.lifecycle.Lifecycle.State.STARTED;
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY; import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME; import static org.briarproject.briar.android.account.SetupViewModel.State.AUTHOR_NAME;
import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED; import static org.briarproject.briar.android.account.SetupViewModel.State.CREATED;
@@ -28,6 +30,7 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.FAILED
import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD; import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PASSWORD;
import static org.briarproject.briar.android.util.UiUtils.setInputStateAlwaysVisible; import static org.briarproject.briar.android.util.UiUtils.setInputStateAlwaysVisible;
import static org.briarproject.briar.android.util.UiUtils.setInputStateHidden; import static org.briarproject.briar.android.util.UiUtils.setInputStateHidden;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -77,7 +80,10 @@ public class SetupActivity extends BaseActivity
@TargetApi(23) @TargetApi(23)
void showDozeFragment() { void showDozeFragment() {
showNextFragment(DozeFragment.newInstance()); Fragment f = new DoNotKillMeFragment();
String tag = DoNotKillMeFragment.TAG;
if (!getLifecycle().getCurrentState().isAtLeast(STARTED)) return;
showFragment(getSupportFragmentManager(), f, tag);
} }
void showApp() { void showApp() {

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.account;
import android.app.Application; import android.app.Application;
import org.briarproject.android.dontkillmelib.DozeHelper;
import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
@@ -95,7 +96,7 @@ class SetupViewModel extends AndroidViewModel {
} }
boolean needToShowDozeFragment() { boolean needToShowDozeFragment() {
return dozeHelper.needToShowDozeFragment(getApplication()); return dozeHelper.needToShowDoNotKillMeFragment(getApplication());
} }
void dozeExceptionConfirmed() { void dozeExceptionConfirmed() {

View File

@@ -28,8 +28,8 @@ import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment; import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment; import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity; import org.briarproject.briar.android.contact.add.remote.PendingContactListActivity;
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothActivity;
import org.briarproject.briar.android.conversation.AliasDialogFragment; import org.briarproject.briar.android.conversation.AliasDialogFragment;
import org.briarproject.briar.android.conversation.BluetoothConnecterDialogFragment;
import org.briarproject.briar.android.conversation.ConversationActivity; import org.briarproject.briar.android.conversation.ConversationActivity;
import org.briarproject.briar.android.conversation.ConversationSettingsDialog; import org.briarproject.briar.android.conversation.ConversationSettingsDialog;
import org.briarproject.briar.android.conversation.ImageActivity; import org.briarproject.briar.android.conversation.ImageActivity;
@@ -238,9 +238,6 @@ public interface ActivityComponent {
void inject(ConversationSettingsDialog dialog); void inject(ConversationSettingsDialog dialog);
void inject(
BluetoothConnecterDialogFragment bluetoothConnecterDialogFragment);
void inject(RssFeedImportFragment fragment); void inject(RssFeedImportFragment fragment);
void inject(RssFeedManageFragment fragment); void inject(RssFeedManageFragment fragment);
@@ -248,4 +245,6 @@ public interface ActivityComponent {
void inject(RssFeedImportFailedDialogFragment fragment); void inject(RssFeedImportFailedDialogFragment fragment);
void inject(RssFeedDeleteFeedDialogFragment fragment); void inject(RssFeedDeleteFeedDialogFragment fragment);
void inject(ConnectViaBluetoothActivity connectViaBluetoothActivity);
} }

View File

@@ -36,11 +36,11 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.PowerUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi; import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
import static org.briarproject.briar.android.util.UiUtils.isSamsung7; import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
@MethodsNotNullByDefault @MethodsNotNullByDefault

View File

@@ -1,10 +1,10 @@
package org.briarproject.briar.android.blog; package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.identity.Author; import org.briarproject.bramble.api.identity.Author;
import org.briarproject.briar.api.identity.AuthorInfo;
import org.briarproject.bramble.api.sync.GroupId; import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId; import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.api.blog.BlogPostHeader; import org.briarproject.briar.api.blog.BlogPostHeader;
import org.briarproject.briar.api.identity.AuthorInfo;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@@ -50,7 +50,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
return text; return text;
} }
public boolean isRssFeed() { boolean isRssFeed() {
return header.isRssFeed(); return header.isRssFeed();
} }

View File

@@ -20,6 +20,7 @@ import androidx.annotation.UiThread;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.GONE; import static android.view.View.GONE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID; import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
@@ -44,6 +45,7 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
private final TextView text; private final TextView text;
private final ViewGroup commentContainer; private final ViewGroup commentContainer;
private final boolean fullText, authorClickable; private final boolean fullText, authorClickable;
private final int padding;
private final OnBlogPostClickListener listener; private final OnBlogPostClickListener listener;
@@ -61,6 +63,8 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
reblogButton = v.findViewById(R.id.commentView); reblogButton = v.findViewById(R.id.commentView);
text = v.findViewById(R.id.textView); text = v.findViewById(R.id.textView);
commentContainer = v.findViewById(R.id.commentContainer); commentContainer = v.findViewById(R.id.commentContainer);
padding = ctx.getResources()
.getDimensionPixelSize(R.dimen.listitem_vertical_margin);
} }
void hideReblogButton() { void hideReblogButton() {
@@ -129,6 +133,12 @@ class BlogPostViewHolder extends RecyclerView.ViewHolder {
} else { } else {
reblogger.setVisibility(GONE); reblogger.setVisibility(GONE);
} }
// Apply Android 4 padding fix after setting up author/reblogger views
if (SDK_INT < 21) {
reblogger.setPadding(padding, padding, padding, padding);
author.setPadding(padding, padding, padding, padding);
}
} }
private void onBindComment(BlogCommentItem item, boolean authorClickable) { private void onBindComment(BlogCommentItem item, boolean authorClickable) {

View File

@@ -57,8 +57,12 @@ public class RssFeedActivity extends BriarActivity
onBackPressed(); onBackPressed();
} }
} else if (result == FAILED) { } else if (result == FAILED) {
String url = viewModel.getUrlFailedImport();
if (url == null) {
throw new AssertionError();
}
RssFeedImportFailedDialogFragment dialog = RssFeedImportFailedDialogFragment dialog =
RssFeedImportFailedDialogFragment.newInstance(); RssFeedImportFailedDialogFragment.newInstance(url);
dialog.show(getSupportFragmentManager(), dialog.show(getSupportFragmentManager(),
RssFeedImportFailedDialogFragment.TAG); RssFeedImportFailedDialogFragment.TAG);
} else if (result == EXISTS) { } else if (result == EXISTS) {

View File

@@ -25,8 +25,15 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private RssFeedViewModel viewModel; private RssFeedViewModel viewModel;
static RssFeedImportFailedDialogFragment newInstance() { private static final String ARG_URL = "url";
return new RssFeedImportFailedDialogFragment();
static RssFeedImportFailedDialogFragment newInstance(String retryUrl) {
Bundle args = new Bundle();
args.putString(ARG_URL, retryUrl);
RssFeedImportFailedDialogFragment f =
new RssFeedImportFailedDialogFragment();
f.setArguments(args);
return f;
} }
@Override @Override
@@ -45,8 +52,8 @@ public class RssFeedImportFailedDialogFragment extends DialogFragment {
R.style.BriarDialogTheme); R.style.BriarDialogTheme);
builder.setMessage(R.string.blogs_rss_feeds_import_error); builder.setMessage(R.string.blogs_rss_feeds_import_error);
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(R.string.try_again_button, builder.setPositiveButton(R.string.try_again_button, (dialog, which) ->
(dialog, which) -> viewModel.retryImportFeed()); viewModel.importFeed(requireArguments().getString(ARG_URL)));
return builder.create(); return builder.create();
} }

View File

@@ -20,7 +20,6 @@ import org.briarproject.briar.api.feed.Feed;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;

View File

@@ -159,11 +159,9 @@ class RssFeedViewModel extends DbViewModel {
}); });
} }
void retryImportFeed() { @Nullable
if (urlFailedImport == null) { String getUrlFailedImport() {
throw new AssertionError(); return urlFailedImport;
}
importFeed(urlFailedImport);
} }
private boolean exists(String url) { private boolean exists(String url) {

View File

@@ -59,7 +59,7 @@ public class LinkExchangeFragment extends BaseFragment
@Override @Override
public void injectFragment(ActivityComponent component) { public void injectFragment(ActivityComponent component) {
component.inject(this); component.inject(this);
viewModel = new ViewModelProvider(getActivity(), viewModelFactory) viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(AddContactViewModel.class); .get(AddContactViewModel.class);
} }

View File

@@ -0,0 +1,87 @@
package org.briarproject.briar.android.contact.connect;
import android.app.Activity;
import android.content.Context;
import org.briarproject.briar.R;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
class BluetoothConditionManager {
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private Permission locationPermission = Permission.UNKNOWN;
/**
* Call this when the using activity or fragment starts,
* because permissions might have changed while it was stopped.
*/
void reset() {
locationPermission = Permission.UNKNOWN;
}
@UiThread
void onLocationPermissionResult(Activity activity,
@Nullable Boolean result) {
if (result != null && result) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(activity,
ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
}
boolean areRequirementsFulfilled(Context ctx,
ActivityResultLauncher<String> permissionRequest,
Runnable onLocationDenied) {
boolean permissionGranted =
SDK_INT < 23 || locationPermission == Permission.GRANTED;
boolean locationEnabled = isLocationEnabled(ctx);
if (permissionGranted && locationEnabled) return true;
if (locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(ctx, onLocationDenied);
} else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx, permissionRequest);
} else if (!locationEnabled) {
showLocationDialog(ctx);
}
return false;
}
private void showDenialDialog(Context ctx, Runnable onLocationDenied) {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_denied_body)
.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
.setNegativeButton(R.string.cancel, (v, d) ->
onLocationDenied.run())
.show();
}
private void showRationale(Context ctx,
ActivityResultLauncher<String> permissionRequest) {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_request_body)
.setPositiveButton(R.string.ok, (dialog, which) ->
permissionRequest.launch(ACCESS_FINE_LOCATION))
.show();
}
}

View File

@@ -0,0 +1,109 @@
package org.briarproject.briar.android.contact.connect;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.briar.android.AppModule.getAndroidComponent;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothIntroFragment extends Fragment {
final static String TAG = BluetoothIntroFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private final BluetoothConditionManager conditionManager =
new BluetoothConditionManager();
private ConnectViaBluetoothViewModel viewModel;
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
registerForActivityResult(new RequestBluetoothDiscoverable(),
this::onBluetoothDiscoverable);
private final ActivityResultLauncher<String> permissionRequest =
registerForActivityResult(new RequestPermission(),
this::onPermissionRequestResult);
@Override
public void onAttach(Context context) {
super.onAttach(context);
getAndroidComponent(requireContext()).inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConnectViaBluetoothViewModel.class);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater
.inflate(R.layout.fragment_bluetooth_intro, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
Button startButton = view.findViewById(R.id.startButton);
startButton.setOnClickListener(this::onStartClicked);
}
@Override
public void onStart() {
super.onStart();
conditionManager.reset();
}
private void onStartClicked(View v) {
if (viewModel.shouldStartFlow()) {
// The dialog starts a permission request which comes back as true
// if the permission is already granted.
// So we can use the request as a generic entry point
// to the whole flow.
permissionRequest.launch(ACCESS_FINE_LOCATION);
}
}
private void onPermissionRequestResult(@Nullable Boolean result) {
Activity a = requireActivity();
// update permission result in BluetoothConnecter
conditionManager.onLocationPermissionResult(a, result);
// what to do when the user denies granting the location permission
Runnable onLocationPermissionDenied = () -> Toast.makeText(
requireContext(),
R.string.connect_via_bluetooth_no_location_permission,
LENGTH_LONG).show();
// if requirements are fulfilled, request Bluetooth discoverability
if (conditionManager.areRequirementsFulfilled(a, permissionRequest,
onLocationPermissionDenied)) {
bluetoothDiscoverableRequest.launch(120); // for 2min
}
}
private void onBluetoothDiscoverable(@Nullable Boolean result) {
if (result != null && result) {
viewModel.onBluetoothDiscoverable();
}
}
}

View File

@@ -0,0 +1,29 @@
package org.briarproject.briar.android.contact.connect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothProgressFragment extends Fragment {
final static String TAG = BluetoothProgressFragment.class.getName();
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater
.inflate(R.layout.fragment_bluetooth_progress, container, false);
}
}

View File

@@ -0,0 +1,98 @@
package org.briarproject.briar.android.contact.connect;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
import static org.briarproject.briar.android.util.UiUtils.showFragment;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ConnectViaBluetoothActivity extends BriarActivity {
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConnectViaBluetoothViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(ConnectViaBluetoothViewModel.class);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = requireNonNull(getIntent());
int contactId = intent.getIntExtra(CONTACT_ID, -1);
if (contactId == -1) throw new IllegalArgumentException("ContactId");
viewModel.setContactId(new ContactId(contactId));
setContentView(R.layout.activity_fragment_container);
viewModel.getState().observeEvent(this, this::onStateChanged);
if (savedInstanceState == null) {
Fragment f = new BluetoothIntroFragment();
String tag = BluetoothIntroFragment.TAG;
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f, tag)
.commitNow();
}
}
@Override
public void onStart() {
super.onStart();
viewModel.reset();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
private void onStateChanged(ConnectViaBluetoothState state) {
if (state instanceof ConnectViaBluetoothState.Connecting) {
Fragment f = new BluetoothProgressFragment();
String tag = BluetoothProgressFragment.TAG;
showFragment(getSupportFragmentManager(), f, tag, false);
} else if (state instanceof ConnectViaBluetoothState.Success) {
Toast.makeText(this, R.string.connect_via_bluetooth_success,
LENGTH_LONG).show();
supportFinishAfterTransition();
} else if (state instanceof ConnectViaBluetoothState.Error) {
Toast.makeText(this,
((ConnectViaBluetoothState.Error) state).errorRes,
LENGTH_LONG).show();
supportFinishAfterTransition();
} else throw new AssertionError();
}
}

View File

@@ -0,0 +1,19 @@
package org.briarproject.briar.android.contact.connect;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class ConnectViaBluetoothModule {
@Binds
@IntoMap
@ViewModelKey(ConnectViaBluetoothViewModel.class)
abstract ViewModel bindContactListViewModel(
ConnectViaBluetoothViewModel connectViaBluetoothViewModel);
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.briar.android.contact.connect;
import androidx.annotation.StringRes;
abstract class ConnectViaBluetoothState {
static class Connecting extends ConnectViaBluetoothState {
}
static class Success extends ConnectViaBluetoothState {
}
static class Error extends ConnectViaBluetoothState {
@StringRes
final int errorRes;
Error(@StringRes int errorRes) {
this.errorRes = errorRes;
}
}
}

View File

@@ -1,19 +1,20 @@
package org.briarproject.briar.android.conversation; package org.briarproject.briar.android.contact.connect;
import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.widget.Toast;
import org.briarproject.bramble.api.connection.ConnectionManager; import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionRegistry; import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId; import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event; import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor; import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent; import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
@@ -21,23 +22,22 @@ import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin; import org.briarproject.bramble.plugin.bluetooth.BluetoothPlugin;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.ContactItem; import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothState.Connecting;
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothState.Success;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import androidx.appcompat.app.AlertDialog;
import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static java.util.Objects.requireNonNull;
import static android.os.Build.VERSION.SDK_INT;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
@@ -46,48 +46,50 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID;
import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
import static org.briarproject.briar.android.util.UiUtils.isLocationEnabled;
import static org.briarproject.briar.android.util.UiUtils.showLocationDialog;
class BluetoothConnecter implements EventListener { @UiThread
@NotNullByDefault
class ConnectViaBluetoothViewModel extends DbViewModel implements
EventListener {
private final Logger LOG = getLogger(BluetoothConnecter.class.getName()); private final Logger LOG =
getLogger(ConnectViaBluetoothViewModel.class.getName());
private final long BT_ACTIVE_TIMEOUT = SECONDS.toMillis(5); private final long BT_ACTIVE_TIMEOUT = SECONDS.toMillis(5);
private enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
private final Application app;
private final PluginManager pluginManager; private final PluginManager pluginManager;
private final Executor ioExecutor; private final Executor ioExecutor;
private final AndroidExecutor androidExecutor;
private final ConnectionRegistry connectionRegistry; private final ConnectionRegistry connectionRegistry;
@Nullable
private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); private final BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
private final EventBus eventBus; private final EventBus eventBus;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
@Nullable
private volatile BluetoothPlugin bluetoothPlugin; private volatile BluetoothPlugin bluetoothPlugin;
@Nullable
private Permission locationPermission = Permission.UNKNOWN;
private ContactId contactId = null; private ContactId contactId = null;
private final MutableLiveEvent<ConnectViaBluetoothState> state =
new MutableLiveEvent<>();
@Inject @Inject
BluetoothConnecter(Application app, ConnectViaBluetoothViewModel(
Application app,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
PluginManager pluginManager, PluginManager pluginManager,
@IoExecutor Executor ioExecutor, @IoExecutor Executor ioExecutor,
AndroidExecutor androidExecutor,
ConnectionRegistry connectionRegistry, ConnectionRegistry connectionRegistry,
EventBus eventBus, EventBus eventBus,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
ConnectionManager connectionManager) { ConnectionManager connectionManager) {
this.app = app; super(app, dbExecutor, lifecycleManager, db, androidExecutor);
this.pluginManager = pluginManager; this.pluginManager = pluginManager;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.bluetoothPlugin = (BluetoothPlugin) pluginManager.getPlugin(ID); this.bluetoothPlugin = (BluetoothPlugin) pluginManager.getPlugin(ID);
this.connectionRegistry = connectionRegistry; this.connectionRegistry = connectionRegistry;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -95,20 +97,22 @@ class BluetoothConnecter implements EventListener {
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
} }
boolean isConnectedViaBluetooth(ContactId contactId) { @Override
return connectionRegistry.isConnected(contactId, ID); protected void onCleared() {
} stopConnecting();
boolean isDiscovering() {
return bluetoothPlugin.isDiscovering();
} }
/** /**
* Call this when the using activity or fragment starts, * Set this as soon as it becomes available.
* because permissions might have changed while it was stopped. */
void setContactId(ContactId contactId) {
this.contactId = contactId;
}
/**
* Call this when the using activity or fragment starts.
*/ */
void reset() { void reset() {
locationPermission = Permission.UNKNOWN;
// When this class is instantiated before we are logged in // When this class is instantiated before we are logged in
// (like when returning to a killed activity), bluetoothPlugin would be // (like when returning to a killed activity), bluetoothPlugin would be
// null and we consider bluetooth not supported. So reset here. // null and we consider bluetooth not supported. So reset here.
@@ -116,94 +120,52 @@ class BluetoothConnecter implements EventListener {
} }
@UiThread @UiThread
void onLocationPermissionResult(Activity activity, boolean shouldStartFlow() {
@Nullable Boolean result) { if (isBluetoothNotSupported()) {
if (result != null && result) { state.setEvent(new ConnectViaBluetoothState.Error(
locationPermission = Permission.GRANTED; R.string.bt_plugin_status_inactive));
} else if (shouldShowRequestPermissionRationale(activity, return false;
ACCESS_FINE_LOCATION)) { } else if (isConnectedViaBluetooth()) {
locationPermission = Permission.SHOW_RATIONALE; state.setEvent(new Success());
} else { return false;
locationPermission = Permission.PERMANENTLY_DENIED; } else if (isDiscovering()) {
state.setEvent(new ConnectViaBluetoothState.Error(
R.string.connect_via_bluetooth_already_discovering));
return false;
} }
return true;
} }
boolean isBluetoothNotSupported() { private boolean isBluetoothNotSupported() {
return bt == null || bluetoothPlugin == null; return bt == null || bluetoothPlugin == null;
} }
boolean areRequirementsFulfilled(Context ctx, private boolean isDiscovering() {
ActivityResultLauncher<String> permissionRequest, // we should not be calling this if isBluetoothNotSupported() is true
Runnable onLocationDenied) { return requireNonNull(bluetoothPlugin).isDiscovering();
boolean permissionGranted =
SDK_INT < 23 || locationPermission == Permission.GRANTED;
boolean locationEnabled = isLocationEnabled(ctx);
if (permissionGranted && locationEnabled) return true;
if (locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(ctx, onLocationDenied);
} else if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx, permissionRequest);
} else if (!locationEnabled) {
showLocationDialog(ctx);
}
return false;
} }
private void showDenialDialog(Context ctx, Runnable onLocationDenied) { private boolean isConnectedViaBluetooth() {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme) return connectionRegistry.isConnected(requireNonNull(contactId), ID);
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_denied_body)
.setPositiveButton(R.string.ok, getGoToSettingsListener(ctx))
.setNegativeButton(R.string.cancel, (v, d) ->
onLocationDenied.run())
.show();
}
private void showRationale(Context ctx,
ActivityResultLauncher<String> permissionRequest) {
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.permission_location_title)
.setMessage(R.string.permission_location_request_body)
.setPositiveButton(R.string.ok, (dialog, which) ->
permissionRequest.launch(ACCESS_FINE_LOCATION))
.show();
} }
@UiThread @UiThread
void onBluetoothDiscoverable(ContactItem contact) { void onBluetoothDiscoverable() {
contactId = contact.getContact().getId(); ContactId contactId = requireNonNull(this.contactId);
connect(); BluetoothPlugin bluetoothPlugin = requireNonNull(this.bluetoothPlugin);
}
@UiThread state.setEvent(new Connecting());
@Override
public void eventOccurred(@NonNull Event e) {
if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
if (c.getContactId().equals(contactId) && c.isIncoming() &&
c.getTransportId() == ID) {
if (bluetoothPlugin != null) {
bluetoothPlugin.stopDiscoverAndConnect();
}
LOG.info("Contact connected to us");
showToast(R.string.toast_connect_via_bluetooth_success);
}
}
}
private void connect() {
bluetoothPlugin.disablePolling(); bluetoothPlugin.disablePolling();
pluginManager.setPluginEnabled(ID, true); pluginManager.setPluginEnabled(ID, true);
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
try { try {
if (!waitForBluetoothActive()) { if (!waitForBluetoothActive()) {
showToast(R.string.bt_plugin_status_inactive); state.postEvent(new ConnectViaBluetoothState.Error(
R.string.bt_plugin_status_inactive));
LOG.warning("Bluetooth plugin didn't become active"); LOG.warning("Bluetooth plugin didn't become active");
return; return;
} }
showToast(R.string.toast_connect_via_bluetooth_start);
eventBus.addListener(this); eventBus.addListener(this);
try { try {
String uuid = null; String uuid = null;
@@ -226,7 +188,7 @@ class BluetoothConnecter implements EventListener {
LOG.info("Could connect, handling connection"); LOG.info("Could connect, handling connection");
connectionManager connectionManager
.manageOutgoingConnection(contactId, ID, conn); .manageOutgoingConnection(contactId, ID, conn);
showToast(R.string.toast_connect_via_bluetooth_success); state.postEvent(new Success());
} }
} finally { } finally {
eventBus.removeListener(this); eventBus.removeListener(this);
@@ -237,8 +199,23 @@ class BluetoothConnecter implements EventListener {
}); });
} }
@UiThread
@Override
public void eventOccurred(@NonNull Event e) {
if (e instanceof ConnectionOpenedEvent) {
ConnectionOpenedEvent c = (ConnectionOpenedEvent) e;
if (c.getContactId().equals(contactId) && c.isIncoming() &&
c.getTransportId() == ID) {
stopConnecting();
LOG.info("Contact connected to us");
state.postEvent(new Success());
}
}
}
@IoExecutor @IoExecutor
private boolean waitForBluetoothActive() { private boolean waitForBluetoothActive() {
BluetoothPlugin bluetoothPlugin = requireNonNull(this.bluetoothPlugin);
long left = BT_ACTIVE_TIMEOUT; long left = BT_ACTIVE_TIMEOUT;
final long sleep = 250; final long sleep = 250;
try { try {
@@ -264,9 +241,9 @@ class BluetoothConnecter implements EventListener {
final long sleep = 250; final long sleep = 250;
try { try {
while (left > 0) { while (left > 0) {
if (isConnectedViaBluetooth(contactId)) { if (isConnectedViaBluetooth()) {
LOG.info("Failed to connect, but contact connected"); LOG.info("Failed to connect, but contact connected");
// no Toast needed here, as it gets shown when // no success state needed here, as it gets shown when
// ConnectionOpenedEvent is received // ConnectionOpenedEvent is received
return; return;
} }
@@ -277,13 +254,19 @@ class BluetoothConnecter implements EventListener {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
LOG.warning("Failed to connect"); LOG.warning("Failed to connect");
showToast(R.string.toast_connect_via_bluetooth_error); state.postEvent(new ConnectViaBluetoothState.Error(
R.string.connect_via_bluetooth_error));
} }
private void showToast(@StringRes int res) { private void stopConnecting() {
androidExecutor.runOnUiThread(() -> BluetoothPlugin bluetoothPlugin = this.bluetoothPlugin;
Toast.makeText(app, res, Toast.LENGTH_LONG).show() if (bluetoothPlugin != null) {
); bluetoothPlugin.stopDiscoverAndConnect();
}
}
LiveEvent<ConnectViaBluetoothState> getState() {
return state;
} }
} }

View File

@@ -27,10 +27,10 @@ import androidx.annotation.CallSuper;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@NotNullByDefault @NotNullByDefault
public class BriarControllerImpl implements BriarController { public class BriarControllerImpl implements BriarController {

View File

@@ -1,151 +0,0 @@
package org.briarproject.briar.android.conversation;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.contact.ContactItem;
import org.briarproject.briar.android.util.ActivityLaunchers.RequestBluetoothDiscoverable;
import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.DialogInterface.BUTTON_POSITIVE;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.Objects.requireNonNull;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BluetoothConnecterDialogFragment extends DialogFragment {
final static String TAG = BluetoothConnecterDialogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
private ConversationViewModel viewModel;
private BluetoothConnecter bluetoothConnecter;
private final ActivityResultLauncher<Integer> bluetoothDiscoverableRequest =
registerForActivityResult(new RequestBluetoothDiscoverable(),
this::onBluetoothDiscoverable);
private final ActivityResultLauncher<String> permissionRequest =
registerForActivityResult(new RequestPermission(),
this::onPermissionRequestResult);
@Override
public void onAttach(Context ctx) {
super.onAttach(ctx);
((BaseActivity) requireActivity()).getActivityComponent().inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(ConversationViewModel.class);
bluetoothConnecter = viewModel.getBluetoothConnecter();
}
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Context ctx = requireContext();
return new AlertDialog.Builder(ctx, R.style.BriarDialogTheme)
.setTitle(R.string.dialog_title_connect_via_bluetooth)
.setMessage(R.string.dialog_message_connect_via_bluetooth)
// actual listener gets set in onResume()
.setPositiveButton(R.string.start, null)
.setNegativeButton(R.string.cancel, null)
.setCancelable(false) // keep it open until dismissed
.create();
}
@Override
public void onStart() {
super.onStart();
bluetoothConnecter.reset();
if (bluetoothConnecter.isBluetoothNotSupported()) {
showToast(R.string.toast_connect_via_bluetooth_error);
dismiss();
return;
}
// MenuItem only gets enabled after contactItem has loaded
ContactItem contact =
requireNonNull(viewModel.getContactItem().getValue());
ContactId contactId = contact.getContact().getId();
if (bluetoothConnecter.isConnectedViaBluetooth(contactId)) {
showToast(R.string.toast_connect_via_bluetooth_success);
dismiss();
return;
}
if (bluetoothConnecter.isDiscovering()) {
showToast(R.string.toast_connect_via_bluetooth_already_discovering);
dismiss();
}
}
@Override
public void onResume() {
super.onResume();
// Set the click listener for the START button here
// to prevent it from automatically dismissing the dialog.
// The dialog is shown in onStart(), so we set the listener here later.
AlertDialog dialog = (AlertDialog) getDialog();
Button positiveButton = dialog.getButton(BUTTON_POSITIVE);
positiveButton.setOnClickListener(this::onStartClicked);
}
private void onStartClicked(View v) {
// The dialog starts a permission request which comes back as true
// if the permission is already granted.
// So we can use the request as a generic entry point to the whole flow.
permissionRequest.launch(ACCESS_FINE_LOCATION);
}
private void onPermissionRequestResult(@Nullable Boolean result) {
Activity a = requireActivity();
// update permission result in BluetoothConnecter
bluetoothConnecter.onLocationPermissionResult(a, result);
// what to do when the user denies granting the location permission
Runnable onLocationPermissionDenied = () -> {
Toast.makeText(requireContext(),
R.string.toast_connect_via_bluetooth_no_location_permission,
LENGTH_LONG).show();
dismiss();
};
// if requirements are fulfilled, request Bluetooth discoverability
if (bluetoothConnecter.areRequirementsFulfilled(a, permissionRequest,
onLocationPermissionDenied)) {
bluetoothDiscoverableRequest.launch(120); // for 2min
}
}
private void onBluetoothDiscoverable(@Nullable Boolean result) {
if (result != null && result) {
// MenuItem only gets enabled after contactItem has loaded
ContactItem contact =
requireNonNull(viewModel.getContactItem().getValue());
bluetoothConnecter.onBluetoothDiscoverable(contact);
dismiss();
} else {
showToast(R.string.toast_connect_via_bluetooth_not_discoverable);
}
}
private void showToast(@StringRes int stringRes) {
Toast.makeText(requireContext(), stringRes, LENGTH_LONG).show();
}
}

View File

@@ -48,6 +48,7 @@ import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.attachment.AttachmentItem; import org.briarproject.briar.android.attachment.AttachmentItem;
import org.briarproject.briar.android.attachment.AttachmentRetriever; import org.briarproject.briar.android.attachment.AttachmentRetriever;
import org.briarproject.briar.android.blog.BlogActivity; import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.contact.connect.ConnectViaBluetoothActivity;
import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache; import org.briarproject.briar.android.conversation.ConversationVisitor.AttachmentCache;
import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache; import org.briarproject.briar.android.conversation.ConversationVisitor.TextCache;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
@@ -104,7 +105,6 @@ import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -379,9 +379,6 @@ public class ConversationActivity extends BriarActivity
this::showIntroductionOnboarding); this::showIntroductionOnboarding);
} }
}); });
if (!featureFlags.shouldEnableConnectViaBluetooth()) {
menu.findItem(R.id.action_connect_via_bluetooth).setVisible(false);
}
// Transfer Data feature only supported on API 19+ // Transfer Data feature only supported on API 19+
if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) { if (SDK_INT >= 19 && featureFlags.shouldEnableTransferData()) {
menu.findItem(R.id.action_transfer_data).setVisible(true); menu.findItem(R.id.action_transfer_data).setVisible(true);
@@ -422,9 +419,9 @@ public class ConversationActivity extends BriarActivity
onAutoDeleteTimerNoticeClicked(); onAutoDeleteTimerNoticeClicked();
return true; return true;
} else if (itemId == R.id.action_connect_via_bluetooth) { } else if (itemId == R.id.action_connect_via_bluetooth) {
FragmentManager fm = getSupportFragmentManager(); Intent intent = new Intent(this, ConnectViaBluetoothActivity.class);
new BluetoothConnecterDialogFragment().show(fm, intent.putExtra(CONTACT_ID, contactId.getInt());
BluetoothConnecterDialogFragment.TAG); startActivity(intent);
return true; return true;
} else if (itemId == R.id.action_transfer_data) { } else if (itemId == R.id.action_transfer_data) {
Intent intent = new Intent(this, RemovableDriveActivity.class); Intent intent = new Intent(this, RemovableDriveActivity.class);

View File

@@ -101,7 +101,6 @@ public class ConversationViewModel extends DbViewModel
private final AttachmentCreator attachmentCreator; private final AttachmentCreator attachmentCreator;
private final AutoDeleteManager autoDeleteManager; private final AutoDeleteManager autoDeleteManager;
private final ConversationManager conversationManager; private final ConversationManager conversationManager;
private final BluetoothConnecter bluetoothConnecter;
@Nullable @Nullable
private ContactId contactId = null; private ContactId contactId = null;
@@ -140,8 +139,7 @@ public class ConversationViewModel extends DbViewModel
AttachmentRetriever attachmentRetriever, AttachmentRetriever attachmentRetriever,
AttachmentCreator attachmentCreator, AttachmentCreator attachmentCreator,
AutoDeleteManager autoDeleteManager, AutoDeleteManager autoDeleteManager,
ConversationManager conversationManager, ConversationManager conversationManager) {
BluetoothConnecter bluetoothConnecter) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor); super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.db = db; this.db = db;
this.eventBus = eventBus; this.eventBus = eventBus;
@@ -154,7 +152,6 @@ public class ConversationViewModel extends DbViewModel
this.attachmentCreator = attachmentCreator; this.attachmentCreator = attachmentCreator;
this.autoDeleteManager = autoDeleteManager; this.autoDeleteManager = autoDeleteManager;
this.conversationManager = conversationManager; this.conversationManager = conversationManager;
this.bluetoothConnecter = bluetoothConnecter;
messagingGroupId = map(contactItem, c -> messagingGroupId = map(contactItem, c ->
messagingManager.getContactGroup(c.getContact()).getId()); messagingManager.getContactGroup(c.getContact()).getId());
eventBus.addListener(this); eventBus.addListener(this);
@@ -414,10 +411,6 @@ public class ConversationViewModel extends DbViewModel
return attachmentRetriever; return attachmentRetriever;
} }
BluetoothConnecter getBluetoothConnecter() {
return bluetoothConnecter;
}
LiveData<ContactItem> getContactItem() { LiveData<ContactItem> getContactItem() {
return contactItem; return contactItem;
} }

View File

@@ -7,7 +7,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
@@ -22,9 +21,11 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.widget.ImageViewCompat; import androidx.core.widget.ImageViewCompat;
import androidx.core.widget.NestedScrollView;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import static android.view.View.FOCUS_DOWN; import static android.view.View.FOCUS_DOWN;
import static android.view.View.GONE;
/** /**
* A fragment to be used at the end of a user flow * A fragment to be used at the end of a user flow
@@ -58,7 +59,7 @@ public class FinalFragment extends Fragment {
return f; return f;
} }
private ScrollView scrollView; private NestedScrollView scrollView;
protected Button buttonView; protected Button buttonView;
@Nullable @Nullable
@@ -69,7 +70,7 @@ public class FinalFragment extends Fragment {
View v = inflater View v = inflater
.inflate(R.layout.fragment_final, container, false); .inflate(R.layout.fragment_final, container, false);
scrollView = (ScrollView) v; scrollView = (NestedScrollView) v;
ImageView iconView = v.findViewById(R.id.iconView); ImageView iconView = v.findViewById(R.id.iconView);
TextView titleView = v.findViewById(R.id.titleView); TextView titleView = v.findViewById(R.id.titleView);
TextView textView = v.findViewById(R.id.textView); TextView textView = v.findViewById(R.id.textView);
@@ -81,7 +82,12 @@ public class FinalFragment extends Fragment {
int color = getResources().getColor(args.getInt(ARG_ICON_TINT)); int color = getResources().getColor(args.getInt(ARG_ICON_TINT));
ColorStateList tint = ColorStateList.valueOf(color); ColorStateList tint = ColorStateList.valueOf(color);
ImageViewCompat.setImageTintList(iconView, tint); ImageViewCompat.setImageTintList(iconView, tint);
textView.setText(args.getInt(ARG_TEXT)); int textRes = args.getInt(ARG_TEXT);
if (textRes == 0) {
textView.setVisibility(GONE);
} else {
textView.setText(textRes);
}
buttonView.setOnClickListener(view -> onBackButtonPressed()); buttonView.setOnClickListener(view -> onBackButtonPressed());

View File

@@ -0,0 +1,84 @@
package org.briarproject.briar.android.hotspot;
import android.content.Context;
import android.content.DialogInterface;
import android.net.wifi.WifiManager;
import org.briarproject.briar.R;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentActivity;
import static android.content.Context.WIFI_SERVICE;
/**
* Abstract base class for the ConditionManagers that ensure that the conditions
* to open a hotspot are fulfilled. There are different extensions of this for
* API levels lower than 29 and 29+.
*/
abstract class AbstractConditionManager {
enum Permission {
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED
}
protected final Consumer<Boolean> permissionUpdateCallback;
protected FragmentActivity ctx;
WifiManager wifiManager;
AbstractConditionManager(Consumer<Boolean> permissionUpdateCallback) {
this.permissionUpdateCallback = permissionUpdateCallback;
}
/**
* Pass a FragmentActivity context here during `onCreateView()`.
*/
void init(FragmentActivity ctx) {
this.ctx = ctx;
this.wifiManager = (WifiManager) ctx.getApplicationContext()
.getSystemService(WIFI_SERVICE);
}
/**
* Call this during onStart() in the fragment where the ConditionManager
* is used.
*/
abstract void onStart();
/**
* Check if all required conditions are met such that the hotspot can be
* started. If any precondition is not met yet, bring up relevant dialogs
* asking the user to grant relevant permissions or take relevant actions.
*
* @return true if conditions are fulfilled and flow can continue.
*/
abstract boolean checkAndRequestConditions();
void showDenialDialog(FragmentActivity ctx,
@StringRes int title, @StringRes int body,
DialogInterface.OnClickListener onOkClicked, Runnable onDismiss) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(title);
builder.setMessage(body);
builder.setPositiveButton(R.string.ok, onOkClicked);
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> ctx.supportFinishAfterTransition());
builder.setOnDismissListener(dialog -> onDismiss.run());
builder.show();
}
void showRationale(Context ctx, @StringRes int title,
@StringRes int body, Runnable onContinueClicked,
Runnable onDismiss) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> onContinueClicked.run());
builder.setOnDismissListener(dialog -> onDismiss.run());
builder.show();
}
}

View File

@@ -9,6 +9,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator; import com.google.android.material.tabs.TabLayoutMediator;
@@ -43,6 +44,7 @@ public abstract class AbstractTabsFragment extends Fragment {
protected Button stopButton; protected Button stopButton;
protected Button connectedButton; protected Button connectedButton;
protected TextView connectedView;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -85,6 +87,9 @@ public abstract class AbstractTabsFragment extends Fragment {
finishAfterTransition(requireActivity()); finishAfterTransition(requireActivity());
}); });
connectedButton = view.findViewById(R.id.connectedButton); connectedButton = view.findViewById(R.id.connectedButton);
connectedView = view.findViewById(R.id.connectedView);
viewModel.getPeersConnectedEvent()
.observe(getViewLifecycleOwner(), this::onPeerConnected);
} }
@Override @Override
@@ -126,4 +131,13 @@ public abstract class AbstractTabsFragment extends Fragment {
} }
} }
private void onPeerConnected(int peers) {
if (peers == 0) {
connectedView.setText(R.string.hotspot_no_peers_connected);
} else {
connectedView.setText(getResources().getQuantityString(
R.plurals.hotspot_peers_connected, peers, peers));
}
}
} }

View File

@@ -1,171 +1,83 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.net.wifi.WifiManager;
import android.provider.Settings; import android.provider.Settings;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import androidx.activity.result.ActivityResult; import java.util.logging.Logger;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import static android.Manifest.permission.ACCESS_FINE_LOCATION; import androidx.activity.result.ActivityResultCaller;
import static android.content.Context.WIFI_SERVICE; import androidx.activity.result.ActivityResultLauncher;
import static android.os.Build.VERSION.SDK_INT; import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale; import androidx.core.util.Consumer;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
/** /**
* This class ensures that the conditions to open a hotspot are fulfilled. * This class ensures that the conditions to open a hotspot are fulfilled on
* <p> * API levels < 29.
* Be sure to call {@link #onRequestPermissionResult(Boolean)} and
* {@link #onRequestWifiEnabledResult()} when you get the
* {@link ActivityResult}.
* <p> * <p>
* As soon as {@link #checkAndRequestConditions()} returns true, * As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled. * all conditions are fulfilled.
*/ */
@NotNullByDefault class ConditionManager extends AbstractConditionManager {
class ConditionManager {
private enum Permission { private static final Logger LOG =
UNKNOWN, GRANTED, SHOW_RATIONALE, PERMANENTLY_DENIED getLogger(ConditionManager.class.getName());
}
private Permission locationPermission = Permission.UNKNOWN;
private Permission wifiSetting = Permission.SHOW_RATIONALE;
private final FragmentActivity ctx;
private final WifiManager wifiManager;
private final ActivityResultLauncher<String> locationRequest;
private final ActivityResultLauncher<Intent> wifiRequest; private final ActivityResultLauncher<Intent> wifiRequest;
ConditionManager(FragmentActivity ctx, ConditionManager(ActivityResultCaller arc,
ActivityResultLauncher<String> locationRequest, Consumer<Boolean> permissionUpdateCallback) {
ActivityResultLauncher<Intent> wifiRequest) { super(permissionUpdateCallback);
this.ctx = ctx; wifiRequest = arc.registerForActivityResult(
this.wifiManager = (WifiManager) ctx.getApplicationContext() new StartActivityForResult(),
.getSystemService(WIFI_SERVICE); result -> permissionUpdateCallback
this.locationRequest = locationRequest; .accept(wifiManager.isWifiEnabled()));
this.wifiRequest = wifiRequest;
} }
/** @Override
* Call this to reset state when UI starts, void onStart() {
* because state might have changed. // nothing to do here
*/
void resetPermissions() {
locationPermission = Permission.UNKNOWN;
wifiSetting = Permission.SHOW_RATIONALE;
}
/**
* This makes a request for location permission.
* If {@link #checkAndRequestConditions()} returns true, you can continue.
*/
void startConditionChecks() {
locationRequest.launch(ACCESS_FINE_LOCATION);
}
/**
* @return true if conditions are fulfilled and flow can continue.
*/
boolean checkAndRequestConditions() {
if (areEssentialPermissionsGranted()) return true;
// If an essential permission has been permanently denied, ask the
// user to change the setting
if (locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.permission_location_title,
R.string.permission_hotspot_location_denied_body,
getGoToSettingsListener(ctx));
return false;
}
if (wifiSetting == Permission.PERMANENTLY_DENIED) {
showDenialDialog(R.string.wifi_settings_title,
R.string.wifi_settings_request_denied_body,
(d, w) -> requestEnableWiFi());
return false;
}
// Should we show the rationale for location permission or Wi-Fi?
if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(R.string.permission_location_title,
R.string.permission_hotspot_location_request_body,
this::requestPermissions);
} else if (wifiSetting == Permission.SHOW_RATIONALE) {
showRationale(R.string.wifi_settings_title,
R.string.wifi_settings_request_enable_body,
this::requestEnableWiFi);
}
return false;
}
void onRequestPermissionResult(@Nullable Boolean granted) {
if (granted != null && granted) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(ctx,
ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
}
void onRequestWifiEnabledResult() {
wifiSetting = wifiManager.isWifiEnabled() ? Permission.GRANTED :
Permission.PERMANENTLY_DENIED;
} }
private boolean areEssentialPermissionsGranted() { private boolean areEssentialPermissionsGranted() {
if (SDK_INT < 29) { if (LOG.isLoggable(INFO)) {
LOG.info(String.format("areEssentialPermissionsGranted(): " +
"wifiManager.isWifiEnabled()? %b",
wifiManager.isWifiEnabled()));
}
return wifiManager.isWifiEnabled();
}
@Override
boolean checkAndRequestConditions() {
if (areEssentialPermissionsGranted()) return true;
if (!wifiManager.isWifiEnabled()) { if (!wifiManager.isWifiEnabled()) {
//noinspection deprecation // Try enabling the Wifi and return true if that seems to have been
return wifiManager.setWifiEnabled(true); // successful, i.e. "Wifi is either already in the requested state, or
} // in progress toward the requested state".
if (wifiManager.setWifiEnabled(true)) {
LOG.info("Enabled wifi");
return true; return true;
} else {
return locationPermission == Permission.GRANTED
&& wifiManager.isWifiEnabled();
}
} }
private void showDenialDialog(@StringRes int title, @StringRes int body, // Wifi is not enabled and we can't seem to enable it, so ask the user
OnClickListener onOkClicked) { // to enable it for us.
AlertDialog.Builder builder = new AlertDialog.Builder(ctx); showRationale(ctx, R.string.wifi_settings_title,
builder.setTitle(title); R.string.wifi_settings_request_enable_body,
builder.setMessage(body); this::requestEnableWiFi,
builder.setPositiveButton(R.string.ok, onOkClicked); () -> permissionUpdateCallback.accept(false));
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> ctx.supportFinishAfterTransition());
builder.show();
} }
private void showRationale(@StringRes int title, @StringRes int body, return false;
Runnable onContinueClicked) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> onContinueClicked.run());
builder.show();
}
private void requestPermissions() {
locationRequest.launch(ACCESS_FINE_LOCATION);
} }
private void requestEnableWiFi() { private void requestEnableWiFi() {
Intent i = SDK_INT < 29 ? wifiRequest.launch(new Intent(Settings.ACTION_WIFI_SETTINGS));
new Intent(Settings.ACTION_WIFI_SETTINGS) :
new Intent(Settings.Panel.ACTION_WIFI);
wifiRequest.launch(i);
} }
} }

View File

@@ -0,0 +1,136 @@
package org.briarproject.briar.android.hotspot;
import android.content.Intent;
import android.provider.Settings;
import org.briarproject.briar.R;
import java.util.logging.Logger;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.util.Consumer;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale;
import static java.lang.Boolean.TRUE;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.util.UiUtils.getGoToSettingsListener;
/**
* This class ensures that the conditions to open a hotspot are fulfilled on
* API levels >= 29.
* <p>
* As soon as {@link #checkAndRequestConditions()} returns true,
* all conditions are fulfilled.
*/
@RequiresApi(29)
class ConditionManager29 extends AbstractConditionManager {
private static final Logger LOG =
getLogger(ConditionManager29.class.getName());
private Permission locationPermission = Permission.UNKNOWN;
private final ActivityResultLauncher<String> locationRequest;
private final ActivityResultLauncher<Intent> wifiRequest;
ConditionManager29(ActivityResultCaller arc,
Consumer<Boolean> permissionUpdateCallback) {
super(permissionUpdateCallback);
locationRequest = arc.registerForActivityResult(
new RequestPermission(), granted -> {
onRequestPermissionResult(granted);
permissionUpdateCallback.accept(TRUE.equals(granted));
});
wifiRequest = arc.registerForActivityResult(
new StartActivityForResult(),
result -> permissionUpdateCallback
.accept(wifiManager.isWifiEnabled())
);
}
@Override
void onStart() {
locationPermission = Permission.UNKNOWN;
}
private boolean areEssentialPermissionsGranted() {
boolean isWifiEnabled = wifiManager.isWifiEnabled();
if (LOG.isLoggable(INFO)) {
LOG.info(String.format("areEssentialPermissionsGranted(): " +
"locationPermission? %s, " +
"wifiManager.isWifiEnabled()? %b",
locationPermission, isWifiEnabled));
}
return locationPermission == Permission.GRANTED && isWifiEnabled;
}
@Override
boolean checkAndRequestConditions() {
if (areEssentialPermissionsGranted()) return true;
if (locationPermission == Permission.UNKNOWN) {
locationRequest.launch(ACCESS_FINE_LOCATION);
return false;
}
// If the location permission has been permanently denied, ask the
// user to change the setting
if (locationPermission == Permission.PERMANENTLY_DENIED) {
showDenialDialog(ctx, R.string.permission_location_title,
R.string.permission_hotspot_location_denied_body,
getGoToSettingsListener(ctx),
() -> permissionUpdateCallback.accept(false));
return false;
}
// Should we show the rationale for location permission?
if (locationPermission == Permission.SHOW_RATIONALE) {
showRationale(ctx, R.string.permission_location_title,
R.string.permission_hotspot_location_request_body,
this::requestPermissions,
() -> permissionUpdateCallback.accept(false));
return false;
}
// If Wifi is not enabled, we show the rationale for enabling Wifi?
if (!wifiManager.isWifiEnabled()) {
showRationale(ctx, R.string.wifi_settings_title,
R.string.wifi_settings_request_enable_body,
this::requestEnableWiFi,
() -> permissionUpdateCallback.accept(false));
return false;
}
// we shouldn't usually reach this point, but if we do, return false
// anyway to force a recheck. Maybe some condition changed in the
// meantime.
return false;
}
private void onRequestPermissionResult(@Nullable Boolean granted) {
if (granted != null && granted) {
locationPermission = Permission.GRANTED;
} else if (shouldShowRequestPermissionRationale(ctx,
ACCESS_FINE_LOCATION)) {
locationPermission = Permission.SHOW_RATIONALE;
} else {
locationPermission = Permission.PERMANENTLY_DENIED;
}
}
private void requestPermissions() {
locationRequest.launch(ACCESS_FINE_LOCATION);
}
private void requestEnableWiFi() {
wifiRequest.launch(new Intent(Settings.Panel.ACTION_WIFI));
}
}

View File

@@ -15,6 +15,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment; import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.util.ActivityLaunchers.CreateDocumentAdvanced;
import java.util.List; import java.util.List;
@@ -22,7 +23,6 @@ import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -33,12 +33,10 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static androidx.activity.result.contract.ActivityResultContracts.CreateDocument;
import static androidx.transition.TransitionManager.beginDelayedTransition; import static androidx.transition.TransitionManager.beginDelayedTransition;
import static org.briarproject.briar.android.AppModule.getAndroidComponent; import static org.briarproject.briar.android.AppModule.getAndroidComponent;
import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName; import static org.briarproject.briar.android.hotspot.HotspotViewModel.getApkFileName;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
public class FallbackFragment extends BaseFragment { public class FallbackFragment extends BaseFragment {
@@ -50,7 +48,7 @@ public class FallbackFragment extends BaseFragment {
private HotspotViewModel viewModel; private HotspotViewModel viewModel;
private final ActivityResultLauncher<String> launcher = private final ActivityResultLauncher<String> launcher =
registerForActivityResult(new CreateDocument(), registerForActivityResult(new CreateDocumentAdvanced(),
this::onDocumentCreated); this::onDocumentCreated);
private Button fallbackButton; private Button fallbackButton;
private ProgressBar progressBar; private ProgressBar progressBar;
@@ -75,7 +73,7 @@ public class FallbackFragment extends BaseFragment {
@Nullable ViewGroup container, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) { @Nullable Bundle savedInstanceState) {
return inflater return inflater
.inflate(R.layout.fragment_hotspot_save_apk, container, false); .inflate(R.layout.fragment_hotspot_fallback, container, false);
} }
@Override @Override
@@ -92,8 +90,7 @@ public class FallbackFragment extends BaseFragment {
if (SDK_INT >= 19) launcher.launch(getApkFileName()); if (SDK_INT >= 19) launcher.launch(getApkFileName());
else viewModel.exportApk(); else viewModel.exportApk();
}); });
viewModel.getSavedApkToUri() viewModel.getSavedApkToUri().observeEvent(this, this::shareUri);
.observeEvent(this, uri -> shareUri(this, uri));
} }
private void onDocumentCreated(@Nullable Uri uri) { private void onDocumentCreated(@Nullable Uri uri) {
@@ -107,12 +104,12 @@ public class FallbackFragment extends BaseFragment {
progressBar.setVisibility(INVISIBLE); progressBar.setVisibility(INVISIBLE);
} }
static void shareUri(Fragment fragment, Uri uri) { void shareUri(Uri uri) {
Intent i = new Intent(ACTION_SEND); Intent i = new Intent(ACTION_SEND);
i.putExtra(EXTRA_STREAM, uri); i.putExtra(EXTRA_STREAM, uri);
i.setType("*/*"); // gives us all sharing options i.setType("*/*"); // gives us all sharing options
i.addFlags(FLAG_GRANT_READ_URI_PERMISSION); i.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
Context ctx = fragment.requireContext(); Context ctx = requireContext();
if (SDK_INT <= 19) { if (SDK_INT <= 19) {
// Workaround for Android bug: // Workaround for Android bug:
// ctx.grantUriPermission also needed for Android 4 // ctx.grantUriPermission also needed for Android 4
@@ -124,7 +121,7 @@ public class FallbackFragment extends BaseFragment {
FLAG_GRANT_READ_URI_PERMISSION); FLAG_GRANT_READ_URI_PERMISSION);
} }
} }
fragment.startActivity(Intent.createChooser(i, null)); startActivity(Intent.createChooser(i, null));
} }
} }

View File

@@ -59,12 +59,13 @@ public class HotspotActivity extends BriarActivity
// check if fragment is already added // check if fragment is already added
// to not lose state on configuration changes // to not lose state on configuration changes
if (fm.findFragmentByTag(tag) == null) { if (fm.findFragmentByTag(tag) == null) {
if (!started.consume()) { if (started.wasNotYetConsumed()) {
started.consume();
showFragment(fm, new HotspotFragment(), tag); showFragment(fm, new HotspotFragment(), tag);
} }
} }
} else if (hotspotState instanceof HotspotError) { } else if (hotspotState instanceof HotspotError) {
HotspotError error = ((HotspotError) hotspotState); HotspotError error = (HotspotError) hotspotState;
showErrorFragment(error.getError()); showErrorFragment(error.getError());
} }
}); });

View File

@@ -3,12 +3,8 @@ package org.briarproject.briar.android.hotspot;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import com.google.android.material.snackbar.Snackbar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -25,8 +21,6 @@ public class HotspotFragment extends AbstractTabsFragment {
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
connectedButton.setOnClickListener(v -> showNextFragment()); connectedButton.setOnClickListener(v -> showNextFragment());
viewModel.getPeerConnectedEvent().observeEvent(getViewLifecycleOwner(),
this::onPeerConnected);
} }
@Override @Override
@@ -39,17 +33,6 @@ public class HotspotFragment extends AbstractTabsFragment {
return QrHotspotFragment.newInstance(true); return QrHotspotFragment.newInstance(true);
} }
private void onPeerConnected(boolean connected) {
if (!connected) return;
new BriarSnackbarBuilder()
.setAction(R.string.hotspot_peer_connected_action, v ->
showNextFragment())
.make(connectedButton, R.string.hotspot_peer_connected,
Snackbar.LENGTH_LONG)
.setAnchorView(connectedButton)
.show();
}
private void showNextFragment() { private void showNextFragment() {
Fragment f = new WebsiteFragment(); Fragment f = new WebsiteFragment();
String tag = WebsiteFragment.TAG; String tag = WebsiteFragment.TAG;

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
@@ -10,6 +9,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
@@ -20,15 +20,14 @@ import org.briarproject.briar.R;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY; import static android.content.pm.ApplicationInfo.FLAG_TEST_ONLY;
import static android.os.Build.VERSION.SDK_INT;
import static android.view.View.FOCUS_DOWN;
import static android.view.View.INVISIBLE; import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static androidx.transition.TransitionManager.beginDelayedTransition; import static androidx.transition.TransitionManager.beginDelayedTransition;
@@ -45,22 +44,15 @@ public class HotspotIntroFragment extends Fragment {
ViewModelProvider.Factory viewModelFactory; ViewModelProvider.Factory viewModelFactory;
private HotspotViewModel viewModel; private HotspotViewModel viewModel;
private ConditionManager conditionManager;
private Button startButton; private Button startButton;
private ProgressBar progressBar; private ProgressBar progressBar;
private TextView progressTextView; private TextView progressTextView;
private ScrollView scrollView;
private final ActivityResultLauncher<String> locationRequest = private final AbstractConditionManager conditionManager = SDK_INT < 29 ?
registerForActivityResult(new RequestPermission(), granted -> { new ConditionManager(this, this::onPermissionUpdate) :
conditionManager.onRequestPermissionResult(granted); new ConditionManager29(this, this::onPermissionUpdate);
startHotspot();
});
private final ActivityResultLauncher<Intent> wifiRequest =
registerForActivityResult(new StartActivityForResult(), result -> {
conditionManager.onRequestWifiEnabledResult();
startHotspot();
});
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
@@ -69,8 +61,6 @@ public class HotspotIntroFragment extends Fragment {
getAndroidComponent(activity).inject(this); getAndroidComponent(activity).inject(this);
viewModel = new ViewModelProvider(activity, viewModelFactory) viewModel = new ViewModelProvider(activity, viewModelFactory)
.get(HotspotViewModel.class); .get(HotspotViewModel.class);
conditionManager =
new ConditionManager(activity, locationRequest, wifiRequest);
} }
@Override @Override
@@ -83,11 +73,11 @@ public class HotspotIntroFragment extends Fragment {
startButton = v.findViewById(R.id.startButton); startButton = v.findViewById(R.id.startButton);
progressBar = v.findViewById(R.id.progressBar); progressBar = v.findViewById(R.id.progressBar);
progressTextView = v.findViewById(R.id.progressTextView); progressTextView = v.findViewById(R.id.progressTextView);
scrollView = v.findViewById(R.id.scrollView);
startButton.setOnClickListener(button -> { startButton.setOnClickListener(this::onButtonClick);
startButton.setEnabled(false);
conditionManager.startConditionChecks(); conditionManager.init(requireActivity());
});
return v; return v;
} }
@@ -95,11 +85,17 @@ public class HotspotIntroFragment extends Fragment {
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
conditionManager.resetPermissions(); conditionManager.onStart();
// Scroll down in case the screen is small, so the button is visible
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
} }
private void startHotspot() { private void onButtonClick(View view) {
startButton.setEnabled(true); startButton.setEnabled(false);
startHotspotIfConditionsFulfilled();
}
private void startHotspotIfConditionsFulfilled() {
if (conditionManager.checkAndRequestConditions()) { if (conditionManager.checkAndRequestConditions()) {
showInstallWarningIfNeeded(); showInstallWarningIfNeeded();
beginDelayedTransition((ViewGroup) requireView()); beginDelayedTransition((ViewGroup) requireView());
@@ -110,6 +106,13 @@ public class HotspotIntroFragment extends Fragment {
} }
} }
private void onPermissionUpdate(boolean recheckPermissions) {
startButton.setEnabled(true);
if (recheckPermissions) {
startHotspotIfConditionsFulfilled();
}
}
private void showInstallWarningIfNeeded() { private void showInstallWarningIfNeeded() {
Context ctx = requireContext(); Context ctx = requireContext();
ApplicationInfo applicationInfo; ApplicationInfo applicationInfo;

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@@ -10,6 +11,7 @@ import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.ActionListener; import android.net.wifi.p2p.WifiP2pManager.ActionListener;
import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
import android.os.Handler; import android.os.Handler;
import android.os.PowerManager;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DatabaseExecutor;
@@ -34,6 +36,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread; import androidx.annotation.UiThread;
import static android.content.Context.POWER_SERVICE;
import static android.content.Context.WIFI_P2P_SERVICE; import static android.content.Context.WIFI_P2P_SERVICE;
import static android.content.Context.WIFI_SERVICE; import static android.content.Context.WIFI_SERVICE;
import static android.net.wifi.WifiManager.WIFI_MODE_FULL; import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
@@ -44,30 +47,35 @@ import static android.net.wifi.p2p.WifiP2pManager.ERROR;
import static android.net.wifi.p2p.WifiP2pManager.NO_SERVICE_REQUESTS; import static android.net.wifi.p2p.WifiP2pManager.NO_SERVICE_REQUESTS;
import static android.net.wifi.p2p.WifiP2pManager.P2P_UNSUPPORTED; import static android.net.wifi.p2p.WifiP2pManager.P2P_UNSUPPORTED;
import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION.SDK_INT;
import static android.os.PowerManager.FULL_WAKE_LOCK;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
import static org.briarproject.briar.android.util.UiUtils.handleException; import static org.briarproject.briar.android.util.UiUtils.handleException;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
class HotspotManager implements ActionListener { class HotspotManager {
interface HotspotListener { interface HotspotListener {
@UiThread
void onStartingHotspot(); void onStartingHotspot();
@IoExecutor @IoExecutor
void onHotspotStarted(NetworkConfig networkConfig); void onHotspotStarted(NetworkConfig networkConfig);
@UiThread @UiThread
void onDeviceConnected(); void onPeersUpdated(int peers);
void onHotspotStopped();
@UiThread
void onHotspotError(String error); void onHotspotError(String error);
} }
private static final Logger LOG = getLogger(HotspotManager.class.getName()); private static final Logger LOG = getLogger(HotspotManager.class.getName());
private static final int MAX_FRAMEWORK_ATTEMPTS = 5;
private static final int MAX_GROUP_INFO_ATTEMPTS = 5; private static final int MAX_GROUP_INFO_ATTEMPTS = 5;
private static final int RETRY_DELAY_MILLIS = 1000; private static final int RETRY_DELAY_MILLIS = 1000;
private static final String HOTSPOT_NAMESPACE = "hotspot"; private static final String HOTSPOT_NAMESPACE = "hotspot";
@@ -84,14 +92,17 @@ class HotspotManager implements ActionListener {
private final SecureRandom random; private final SecureRandom random;
private final WifiManager wifiManager; private final WifiManager wifiManager;
private final WifiP2pManager wifiP2pManager; private final WifiP2pManager wifiP2pManager;
private final PowerManager powerManager;
private final Handler handler; private final Handler handler;
private final String lockTag; private final String lockTag;
private HotspotListener listener; private HotspotListener listener;
private WifiManager.WifiLock wifiLock; private WifiManager.WifiLock wifiLock;
private PowerManager.WakeLock wakeLock;
private WifiP2pManager.Channel channel; private WifiP2pManager.Channel channel;
@Nullable
@RequiresApi(29) @RequiresApi(29)
private volatile NetworkConfig savedNetworkConfig; private volatile NetworkConfig savedNetworkConfig = null;
@Inject @Inject
HotspotManager(Application ctx, HotspotManager(Application ctx,
@@ -110,6 +121,7 @@ class HotspotManager implements ActionListener {
.getSystemService(WIFI_SERVICE); .getSystemService(WIFI_SERVICE);
wifiP2pManager = wifiP2pManager =
(WifiP2pManager) ctx.getSystemService(WIFI_P2P_SERVICE); (WifiP2pManager) ctx.getSystemService(WIFI_P2P_SERVICE);
powerManager = (PowerManager) ctx.getSystemService(POWER_SERVICE);
handler = new Handler(ctx.getMainLooper()); handler = new Handler(ctx.getMainLooper());
lockTag = ctx.getPackageName() + ":app-sharing-hotspot"; lockTag = ctx.getPackageName() + ":app-sharing-hotspot";
} }
@@ -126,37 +138,47 @@ class HotspotManager implements ActionListener {
return; return;
} }
listener.onStartingHotspot(); listener.onStartingHotspot();
acquireLocks();
startWifiP2pFramework(1);
}
/**
* As soon as Wifi is enabled, we try starting the WifiP2p framework.
* If Wifi has just been enabled, it is possible that will fail. If that
* happens we try again for MAX_FRAMEWORK_ATTEMPTS times after a delay of
* RETRY_DELAY_MILLIS after each attempt.
* <p>
* Rationale: it can take a few milliseconds for WifiP2p to become available
* after enabling Wifi. Depending on the API level it is possible to check this
* using {@link WifiP2pManager#requestP2pState} or register a BroadcastReceiver
* on the WIFI_P2P_STATE_CHANGED_ACTION to get notified when WifiP2p is really
* available. Trying to implement a solution that works reliably using these
* checks turned out to be a long rabbit-hole with lots of corner cases and
* workarounds for specific situations.
* Instead we now rely on this trial-and-error approach of just starting
* the framework and retrying if it fails.
* <p>
* We'll realize that the framework is busy when the ActionListener passed
* to {@link WifiP2pManager#createGroup} is called with onFailure(BUSY)
*/
@UiThread
private void startWifiP2pFramework(int attempt) {
if (LOG.isLoggable(INFO)) {
LOG.info("startWifiP2pFramework attempt: " + attempt);
}
/*
* It is important that we call WifiP2pManager#initialize again
* for every attempt to starting the framework because otherwise,
* createGroup() will continue to fail with a BUSY state.
*/
channel = wifiP2pManager.initialize(ctx, ctx.getMainLooper(), null); channel = wifiP2pManager.initialize(ctx, ctx.getMainLooper(), null);
if (channel == null) { if (channel == null) {
listener.onHotspotError( releaseHotspotWithError(
ctx.getString(R.string.hotspot_error_no_wifi_direct)); ctx.getString(R.string.hotspot_error_no_wifi_direct));
return; return;
} }
try {
if (SDK_INT >= 29) {
dbExecutor.execute(() -> {
// load savedNetworkConfig before starting hotspot
loadSavedNetworkConfig();
androidExecutor.runOnUiThread(() -> {
WifiP2pConfig config = new WifiP2pConfig.Builder()
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
.setNetworkName(savedNetworkConfig.ssid)
.setPassphrase(savedNetworkConfig.password)
.build();
acquireLock();
wifiP2pManager.createGroup(channel, config, this);
});
});
} else {
acquireLock();
wifiP2pManager.createGroup(channel, this);
}
} catch (SecurityException e) {
// this should never happen, because we request permissions before
throw new AssertionError(e);
}
}
ActionListener listener = new ActionListener() {
@Override @Override
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot() // Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
public void onSuccess() { public void onSuccess() {
@@ -166,16 +188,20 @@ class HotspotManager implements ActionListener {
@Override @Override
// Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot() // Callback for wifiP2pManager#createGroup() during startWifiP2pHotspot()
public void onFailure(int reason) { public void onFailure(int reason) {
if (LOG.isLoggable(INFO)) {
LOG.info("onFailure: " + reason);
}
if (reason == BUSY) { if (reason == BUSY) {
// Hotspot already running // WifiP2p not ready yet or hotspot already running
requestGroupInfo(1); restartWifiP2pFramework(attempt);
} else if (reason == P2P_UNSUPPORTED) { } else if (reason == P2P_UNSUPPORTED) {
releaseHotspotWithError(ctx.getString( releaseHotspotWithError(ctx.getString(
R.string.hotspot_error_start_callback_failed, R.string.hotspot_error_start_callback_failed,
"p2p unsupported")); "p2p unsupported"));
} else if (reason == ERROR) { } else if (reason == ERROR) {
releaseHotspotWithError(ctx.getString( releaseHotspotWithError(ctx.getString(
R.string.hotspot_error_start_callback_failed, "p2p error")); R.string.hotspot_error_start_callback_failed,
"p2p error"));
} else if (reason == NO_SERVICE_REQUESTS) { } else if (reason == NO_SERVICE_REQUESTS) {
releaseHotspotWithError(ctx.getString( releaseHotspotWithError(ctx.getString(
R.string.hotspot_error_start_callback_failed, R.string.hotspot_error_start_callback_failed,
@@ -187,24 +213,77 @@ class HotspotManager implements ActionListener {
reason)); reason));
} }
} }
};
try {
if (SDK_INT >= 29) {
Runnable createGroup = () -> {
NetworkConfig c = requireNonNull(savedNetworkConfig);
WifiP2pConfig config = new WifiP2pConfig.Builder()
.setGroupOperatingBand(GROUP_OWNER_BAND_2GHZ)
.setNetworkName(c.ssid)
.setPassphrase(c.password)
.build();
wifiP2pManager.createGroup(channel, config, listener);
};
if (savedNetworkConfig == null) {
// load savedNetworkConfig before starting hotspot
dbExecutor.execute(() -> {
loadSavedNetworkConfig();
androidExecutor.runOnUiThread(createGroup);
});
} else {
// savedNetworkConfig was already loaded, create group now
createGroup.run();
}
} else {
wifiP2pManager.createGroup(channel, listener);
}
} catch (SecurityException e) {
// this should never happen, because we request permissions before
throw new AssertionError(e);
}
}
@UiThread
private void restartWifiP2pFramework(int attempt) {
LOG.info("retrying to start WifiP2p framework");
if (attempt < MAX_FRAMEWORK_ATTEMPTS) {
if (SDK_INT >= 27 && channel != null) channel.close();
channel = null;
handler.postDelayed(() -> startWifiP2pFramework(attempt + 1),
RETRY_DELAY_MILLIS);
} else {
releaseHotspotWithError(
ctx.getString(R.string.hotspot_error_framework_busy));
}
}
@UiThread
void stopWifiP2pHotspot() { void stopWifiP2pHotspot() {
if (channel == null) return; if (channel == null) return;
wifiP2pManager.removeGroup(channel, new ActionListener() { wifiP2pManager.removeGroup(channel, new ActionListener() {
@Override @Override
public void onSuccess() { public void onSuccess() {
releaseHotspot(); closeChannelAndReleaseLocks();
} }
@Override @Override
public void onFailure(int reason) { public void onFailure(int reason) {
// not propagating back error // not propagating back error
releaseHotspot(); if (LOG.isLoggable(WARNING)) {
LOG.warning("Error removing Wifi P2P group: " + reason);
}
closeChannelAndReleaseLocks();
} }
}); });
} }
private void acquireLock() { @SuppressLint("WakelockTimeout")
private void acquireLocks() {
// FLAG_KEEP_SCREEN_ON is not respected on some Huawei devices.
wakeLock = powerManager.newWakeLock(FULL_WAKE_LOCK, lockTag);
wakeLock.acquire();
// WIFI_MODE_FULL has no effect on API >= 29 // WIFI_MODE_FULL has no effect on API >= 29
int lockType = int lockType =
SDK_INT >= 29 ? WIFI_MODE_FULL_HIGH_PERF : WIFI_MODE_FULL; SDK_INT >= 29 ? WIFI_MODE_FULL_HIGH_PERF : WIFI_MODE_FULL;
@@ -212,22 +291,21 @@ class HotspotManager implements ActionListener {
wifiLock.acquire(); wifiLock.acquire();
} }
private void releaseHotspot() { @UiThread
listener.onHotspotStopped();
closeChannelAndReleaseLock();
}
private void releaseHotspotWithError(String error) { private void releaseHotspotWithError(String error) {
listener.onHotspotError(error); listener.onHotspotError(error);
closeChannelAndReleaseLock(); closeChannelAndReleaseLocks();
} }
private void closeChannelAndReleaseLock() { @UiThread
if (SDK_INT >= 27) channel.close(); private void closeChannelAndReleaseLocks() {
if (SDK_INT >= 27 && channel != null) channel.close();
channel = null; channel = null;
wifiLock.release(); if (wakeLock.isHeld()) wakeLock.release();
if (wifiLock.isHeld()) wifiLock.release();
} }
@UiThread
private void requestGroupInfo(int attempt) { private void requestGroupInfo(int attempt) {
if (LOG.isLoggable(INFO)) { if (LOG.isLoggable(INFO)) {
LOG.info("requestGroupInfo attempt: " + attempt); LOG.info("requestGroupInfo attempt: " + attempt);
@@ -257,7 +335,8 @@ class HotspotManager implements ActionListener {
ioExecutor.execute(() -> { ioExecutor.execute(() -> {
String content = createWifiLoginString(group.getNetworkName(), String content = createWifiLoginString(group.getNetworkName(),
group.getPassphrase()); group.getPassphrase());
Bitmap qrCode = QrCodeUtils.createQrCode(dm, content); Bitmap qrCode = QrCodeUtils.createQrCode(
(int) (dm.heightPixels * HOTSPOT_QRCODE_FACTOR), content);
NetworkConfig config = new NetworkConfig(group.getNetworkName(), NetworkConfig config = new NetworkConfig(group.getNetworkName(),
group.getPassphrase(), qrCode); group.getPassphrase(), qrCode);
listener.onHotspotStarted(config); listener.onHotspotStarted(config);
@@ -270,27 +349,22 @@ class HotspotManager implements ActionListener {
LOG.info("group is null"); LOG.info("group is null");
return false; return false;
} else if (!group.getNetworkName().startsWith("DIRECT-")) { } else if (!group.getNetworkName().startsWith("DIRECT-")) {
if (LOG.isLoggable(INFO)) { LOG.info("received networkName without prefix 'DIRECT-'");
LOG.info("received networkName without prefix 'DIRECT-': " +
group.getNetworkName());
}
return false; return false;
} else if (SDK_INT >= 29) { } else if (SDK_INT >= 29) {
// if we get here, the savedNetworkConfig must have a value // if we get here, the savedNetworkConfig must have a value
String networkName = savedNetworkConfig.ssid; String networkName = requireNonNull(savedNetworkConfig).ssid;
if (!networkName.equals(group.getNetworkName())) { if (!networkName.equals(group.getNetworkName())) {
if (LOG.isLoggable(INFO)) { LOG.info("expected networkName does not match received one");
LOG.info("expected networkName: " + networkName);
LOG.info("received networkName: " + group.getNetworkName());
}
return false; return false;
} }
} }
return true; return true;
} }
@UiThread
private void retryRequestingGroupInfo(int attempt) { private void retryRequestingGroupInfo(int attempt) {
LOG.info("retrying"); LOG.info("retrying to request group info");
// On some devices we need to wait for the group info to become available // On some devices we need to wait for the group info to become available
if (attempt < MAX_GROUP_INFO_ATTEMPTS) { if (attempt < MAX_GROUP_INFO_ATTEMPTS) {
handler.postDelayed(() -> requestGroupInfo(attempt + 1), handler.postDelayed(() -> requestGroupInfo(attempt + 1),
@@ -303,19 +377,13 @@ class HotspotManager implements ActionListener {
@UiThread @UiThread
private void requestGroupInfoForConnection() { private void requestGroupInfoForConnection() {
if (LOG.isLoggable(INFO)) {
LOG.info("requestGroupInfo for connection"); LOG.info("requestGroupInfo for connection");
}
GroupInfoListener groupListener = group -> { GroupInfoListener groupListener = group -> {
if (group == null || group.getClientList().isEmpty()) { if (group != null) {
listener.onPeersUpdated(group.getClientList().size());
}
handler.postDelayed(this::requestGroupInfoForConnection, handler.postDelayed(this::requestGroupInfoForConnection,
RETRY_DELAY_MILLIS); RETRY_DELAY_MILLIS);
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("client list " + group.getClientList());
}
listener.onDeviceConnected();
}
}; };
try { try {
if (channel == null) return; if (channel == null) return;
@@ -371,26 +439,15 @@ class HotspotManager implements ActionListener {
return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;"; return "WIFI:S:" + ssid + ";T:WPA;P:" + password + ";;";
} }
private static final String digits = "123456789"; // avoid 0 // exclude chars that are easy to confuse: 0 (O), 5 (S), 1 l (I)
private static final String letters = "abcdefghijkmnopqrstuvwxyz"; // no l private static final String chars = "2346789abcdefghijkmnopqrstuvwxyz";
private static final String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no I, O
private String getRandomString(int length) { private String getRandomString(int length) {
char[] c = new char[length]; char[] c = new char[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
if (random.nextBoolean()) { c[i] = chars.charAt(random.nextInt(chars.length()));
c[i] = random(digits);
} else if (random.nextBoolean()) {
c[i] = random(letters);
} else {
c[i] = random(LETTERS);
}
} }
return new String(c); return new String(c);
} }
private char random(String universe) {
return universe.charAt(random.nextInt(universe.length()));
}
} }

View File

@@ -57,16 +57,19 @@ abstract class HotspotState {
return websiteConfig; return websiteConfig;
} }
@UiThread
boolean wasNotYetConsumed() {
return !consumed;
}
/** /**
* Mark this state as consumed, i.e. the UI has already done something * Mark this state as consumed, i.e. the UI has already done something
* as a result of the state changing to this. This can be used in order * as a result of the state changing to this. This can be used in order
* to not repeat actions such as showing fragments on rotation changes. * to not repeat actions such as showing fragments on rotation changes.
*/ */
@UiThread @UiThread
boolean consume() { void consume() {
boolean old = consumed;
consumed = true; consumed = true;
return old;
} }
} }

View File

@@ -63,8 +63,8 @@ class HotspotViewModel extends DbViewModel
private final MutableLiveData<HotspotState> state = private final MutableLiveData<HotspotState> state =
new MutableLiveData<>(); new MutableLiveData<>();
private final MutableLiveEvent<Boolean> peerConnected = private final MutableLiveData<Integer> peersConnected =
new MutableLiveEvent<>(); new MutableLiveData<>();
private final MutableLiveEvent<Uri> savedApkToUri = private final MutableLiveEvent<Uri> savedApkToUri =
new MutableLiveEvent<>(); new MutableLiveEvent<>();
@@ -137,14 +137,8 @@ class HotspotViewModel extends DbViewModel
@UiThread @UiThread
@Override @Override
public void onDeviceConnected() { public void onPeersUpdated(int peers) {
peerConnected.setEvent(true); peersConnected.setValue(peers);
}
@Override
public void onHotspotStopped() {
LOG.info("stopping webserver");
ioExecutor.execute(webServerManager::stopWebServer);
} }
@Override @Override
@@ -170,7 +164,7 @@ class HotspotViewModel extends DbViewModel
public void onWebServerError() { public void onWebServerError() {
state.postValue(new HotspotError(getApplication() state.postValue(new HotspotError(getApplication()
.getString(R.string.hotspot_error_web_server_start))); .getString(R.string.hotspot_error_web_server_start)));
hotspotManager.stopWifiP2pHotspot(); stopHotspot();
} }
void exportApk(Uri uri) { void exportApk(Uri uri) {
@@ -219,8 +213,8 @@ class HotspotViewModel extends DbViewModel
return state; return state;
} }
LiveEvent<Boolean> getPeerConnectedEvent() { LiveData<Integer> getPeersConnectedEvent() {
return peerConnected; return peersConnected;
} }
LiveEvent<Uri> getSavedApkToUri() { LiveEvent<Uri> getSavedApkToUri() {

View File

@@ -1,12 +1,14 @@
package org.briarproject.briar.android.hotspot; package org.briarproject.briar.android.hotspot;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
@@ -16,7 +18,6 @@ import org.briarproject.briar.android.hotspot.HotspotState.HotspotStarted;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Consumer;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@@ -60,19 +61,23 @@ public class QrHotspotFragment extends Fragment {
TextView qrIntroView = v.findViewById(R.id.qrIntroView); TextView qrIntroView = v.findViewById(R.id.qrIntroView);
ImageView qrCodeView = v.findViewById(R.id.qrCodeView); ImageView qrCodeView = v.findViewById(R.id.qrCodeView);
Consumer<HotspotStarted> consumer; boolean forWifi = requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT);
if (requireArguments().getBoolean(ARG_FOR_WIFI_CONNECT)) {
qrIntroView.setText(R.string.hotspot_qr_wifi); qrIntroView.setText(forWifi ? R.string.hotspot_qr_wifi :
consumer = state -> R.string.hotspot_qr_site);
qrCodeView.setImageBitmap(state.getNetworkConfig().qrCode);
} else {
qrIntroView.setText(R.string.hotspot_qr_site);
consumer = state ->
qrCodeView.setImageBitmap(state.getWebsiteConfig().qrCode);
}
viewModel.getState().observe(getViewLifecycleOwner(), state -> { viewModel.getState().observe(getViewLifecycleOwner(), state -> {
if (state instanceof HotspotStarted) { if (state instanceof HotspotStarted) {
consumer.accept((HotspotStarted) state); HotspotStarted s = (HotspotStarted) state;
Bitmap qrCode = forWifi ? s.getNetworkConfig().qrCode :
s.getWebsiteConfig().qrCode;
if (qrCode == null) {
Toast.makeText(requireContext(), R.string.error,
Toast.LENGTH_SHORT).show();
qrCodeView.setImageResource(R.drawable.ic_image_broken);
} else {
qrCodeView.setImageBitmap(qrCode);
}
} }
}); });
return v; return v;

View File

@@ -14,22 +14,20 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InterfaceAddress; import java.net.InterfaceAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import static java.util.Collections.emptyList;
import static java.util.Collections.list;
import static java.util.logging.Level.INFO; import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.NetworkUtils.getNetworkInterfaces;
import static org.briarproject.briar.android.hotspot.WebServer.PORT; import static org.briarproject.briar.android.hotspot.WebServer.PORT;
import static org.briarproject.briar.android.util.QrCodeUtils.HOTSPOT_QRCODE_FACTOR;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault @ParametersNotNullByDefault
@@ -49,7 +47,7 @@ class WebServerManager {
private final WebServer webServer; private final WebServer webServer;
private final DisplayMetrics dm; private final DisplayMetrics dm;
private WebServerListener listener; private volatile WebServerListener listener;
@Inject @Inject
WebServerManager(Application ctx) { WebServerManager(Application ctx) {
@@ -57,6 +55,7 @@ class WebServerManager {
dm = ctx.getResources().getDisplayMetrics(); dm = ctx.getResources().getDisplayMetrics();
} }
@UiThread
void setListener(WebServerListener listener) { void setListener(WebServerListener listener) {
this.listener = listener; this.listener = listener;
} }
@@ -85,7 +84,8 @@ class WebServerManager {
} }
url = "http://" + address.getHostAddress() + ":" + PORT; url = "http://" + address.getHostAddress() + ":" + PORT;
} }
Bitmap qrCode = QrCodeUtils.createQrCode(dm, url); Bitmap qrCode = QrCodeUtils.createQrCode(
(int) (dm.heightPixels * HOTSPOT_QRCODE_FACTOR), url);
listener.onWebServerStarted(new WebsiteConfig(url, qrCode)); listener.onWebServerStarted(new WebsiteConfig(url, qrCode));
} }
@@ -110,16 +110,4 @@ class WebServerManager {
} }
return null; return null;
} }
private static List<NetworkInterface> getNetworkInterfaces() {
try {
Enumeration<NetworkInterface> ifaces =
NetworkInterface.getNetworkInterfaces();
return ifaces == null ? emptyList() : list(ifaces);
} catch (SocketException e) {
logException(LOG, WARNING, e);
return emptyList();
}
}
} }

View File

@@ -29,6 +29,7 @@ import org.briarproject.bramble.api.plugin.TorConstants;
import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.BriarApplication;
import org.briarproject.briar.android.StartupFailureActivity;
import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity; import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.FeedFragment; import org.briarproject.briar.android.blog.FeedFragment;
@@ -73,6 +74,7 @@ import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE;
import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING;
import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING;
import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED;
import static org.briarproject.briar.android.BriarService.EXTRA_START_RESULT;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; 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.navdrawer.IntentRouter.handleExternalIntent;
@@ -250,6 +252,11 @@ public class NavDrawerActivity extends BriarActivity implements
private void exitIfStartupFailed(Intent intent) { private void exitIfStartupFailed(Intent intent) {
if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) { if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) {
// Launch StartupFailureActivity in its own process, then exit
Intent i = new Intent(this, StartupFailureActivity.class);
i.putExtra(EXTRA_START_RESULT,
intent.getSerializableExtra(EXTRA_START_RESULT));
startActivity(i);
finish(); finish();
LOG.info("Exiting"); LOG.info("Exiting");
System.exit(0); System.exit(0);

View File

@@ -25,11 +25,11 @@ import androidx.lifecycle.MutableLiveData;
import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger; import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.PowerUtils.needsDozeWhitelisting;
import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@NotNullByDefault @NotNullByDefault
public class NavDrawerViewModel extends DbViewModel { public class NavDrawerViewModel extends DbViewModel {

View File

@@ -11,6 +11,7 @@ import android.widget.ScrollView;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.widget.OnboardingFullDialogFragment;
import javax.inject.Inject; import javax.inject.Inject;
@@ -51,6 +52,10 @@ public class ChooserFragment extends Fragment {
container, false); container, false);
scrollView = (ScrollView) v; scrollView = (ScrollView) v;
Button buttonLearnMore = v.findViewById(R.id.buttonLearnMore);
buttonLearnMore.setOnClickListener(e -> showLearnMoreDialog());
Button sendButton = v.findViewById(R.id.sendButton); Button sendButton = v.findViewById(R.id.sendButton);
sendButton.setOnClickListener(i -> viewModel.startSendData()); sendButton.setOnClickListener(i -> viewModel.startSendData());
@@ -75,4 +80,10 @@ public class ChooserFragment extends Fragment {
} }
} }
private void showLearnMoreDialog() {
OnboardingFullDialogFragment.newInstance(
R.string.removable_drive_menu_title,
R.string.removable_drive_explanation
).show(getChildFragmentManager(), OnboardingFullDialogFragment.TAG);
}
} }

View File

@@ -47,6 +47,8 @@ public class ReceiveFragment extends Fragment {
private Button button; private Button button;
private ProgressBar progressBar; private ProgressBar progressBar;
private boolean checkForStateLoss = false;
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
@@ -73,6 +75,10 @@ public class ReceiveFragment extends Fragment {
.observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed); .observeEvent(getViewLifecycleOwner(), this::onOldTaskResumed);
viewModel.getState() viewModel.getState()
.observe(getViewLifecycleOwner(), this::onStateChanged); .observe(getViewLifecycleOwner(), this::onStateChanged);
// need to check for lost ViewModel state when creating with prior state
if (savedInstanceState != null) checkForStateLoss = true;
return v; return v;
} }
@@ -84,6 +90,23 @@ public class ReceiveFragment extends Fragment {
scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN));
} }
@Override
public void onResume() {
super.onResume();
// This code gets called *after* launcher had a chance
// to return the activity result.
if (checkForStateLoss && viewModel.hasNoState()) {
// We were recreated, but have lost the ViewModel state,
// because our activity was destroyed.
//
// Remove the current fragment from the stack
// to prevent duplicates on the back stack.
getParentFragmentManager().popBackStack();
// Start again (picks up existing task or allows to start a new one)
viewModel.startReceiveData();
}
}
private void onOldTaskResumed(boolean resumed) { private void onOldTaskResumed(boolean resumed) {
if (resumed) { if (resumed) {
Toast.makeText(requireContext(), Toast.makeText(requireContext(),
@@ -104,6 +127,8 @@ public class ReceiveFragment extends Fragment {
private void onDocumentChosen(@Nullable Uri uri) { private void onDocumentChosen(@Nullable Uri uri) {
if (uri == null) return; if (uri == null) return;
// we just got our document, so don't treat this as a state loss
checkForStateLoss = false;
viewModel.importData(uri); viewModel.importData(uri);
} }

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