Compare commits

..

187 Commits

Author SHA1 Message Date
Ivana
543eda68b2 Update feature_request 2021-08-25 10:33:28 +00:00
Ivana
5033febaed Update feature_request 2021-08-25 10:12:57 +00:00
Ivana
c13b40ec2e Add new file 2021-08-25 10:12:08 +00:00
Ivana
b74f635ba7 Update bug_report.md 2021-08-24 09:23:02 +00:00
Ivana
5a6c1cc0e9 Update bug_report.md 2021-08-24 09:22:39 +00:00
Ivana
a45669a750 Update bug_report.md 2021-08-24 09:21:19 +00:00
Ivana
5dd183ce47 Update bug_report.md 2021-08-24 09:19:54 +00:00
Ivana
32d121566e Add new file - contains bug_report description template 2021-08-24 08:47:16 +00:00
Ivana
c76da20620 Add new directory issue_templates 2021-08-24 08:24:46 +00:00
Ivana
9b1d6d936e Add new directory 2021-08-24 08:16:11 +00: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
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
98 changed files with 1850 additions and 1273 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

0
.gitlab/.gitkeep Normal file
View File

View File

View File

@@ -0,0 +1,35 @@
# Summary
(Briefly describe the bug encontered)
# Steps to reproduce
(Please list the steps you performed when you encountered the bug)
-
-
-
# How often does it happen?
(Every time, Sometimes, It happened only once)
# What did you expect to happen
(Describe the app behaviour that you expected to happen)
# What actually happened?
(Describe what actually happened)
* What version of Briar app do you have installed? [Please fill in]
* Did you send the feedback from your device? [Yes/No]
* Your device manufacturer: [Please fill in]
* Your device model: [Please fill in]
* Android version: [Please fill in]

View File

@@ -0,0 +1,15 @@
# Are you requesting a new feature or an improvement to an existing feature?
# Brief description of the new or improved feature you would like to see in the Briar app
# Please describe in detail what you would like to do in Briar app, for which a new or improved feature is required?
* Does the absence of this feature prevent you from using the Briar app for certain activities? What are those activities?
* Would your use of Briar app increase with this new feature?

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 10306
versionName "1.3.5" versionName "1.3.6"
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

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

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

@@ -31,12 +31,12 @@ public class TestFeatureFlagModule {
} }
@Override @Override
public boolean shouldEnableShareAppViaOfflineHotspot() { public boolean shouldEnableTransferData() {
return true; return true;
} }
@Override @Override
public boolean shouldEnableTransferData() { public boolean shouldEnableShareAppViaOfflineHotspot() {
return true; 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

@@ -26,8 +26,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 10305 versionCode 10306
versionName "1.3.5" versionName "1.3.6"
applicationId "org.briarproject.briar.android" applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -110,7 +110,7 @@ dependencies {
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

@@ -344,7 +344,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"

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

@@ -157,7 +157,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 +169,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

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,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

@@ -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

@@ -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,6 +21,7 @@ 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;
@@ -58,7 +58,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 +69,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);

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,34 @@ 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.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 +91,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 +120,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 +137,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 +187,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 +212,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 +290,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);
@@ -270,27 +347,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 +375,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 +437,16 @@ 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 =
private static final String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no I, O "2346789ABCDEFGHJKLMNPQRTUVWXYZabcdefghijkmnopqrstuvwxyz";
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,21 +14,18 @@ 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;
@MethodsNotNullByDefault @MethodsNotNullByDefault
@@ -49,7 +46,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 +54,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;
} }
@@ -110,16 +108,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

@@ -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);
} }

View File

@@ -75,6 +75,12 @@ class RemovableDriveViewModel extends DbViewModel {
} }
} }
@UiThread
boolean hasNoState() {
return action.getLastValue() == null && state.getValue() == null &&
task == null;
}
/** /**
* Set this as soon as it becomes available. * Set this as soon as it becomes available.
*/ */

View File

@@ -51,6 +51,8 @@ public class SendFragment 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);
@@ -80,6 +82,9 @@ public class SendFragment extends Fragment {
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;
} }
@@ -91,6 +96,23 @@ public class SendFragment 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.startSendData();
}
}
private void onOldTaskResumed(boolean resumed) { private void onOldTaskResumed(boolean resumed) {
if (resumed) { if (resumed) {
Toast.makeText(requireContext(), Toast.makeText(requireContext(),
@@ -127,6 +149,8 @@ public class SendFragment extends Fragment {
private void onDocumentCreated(@Nullable Uri uri) { private void onDocumentCreated(@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.exportData(uri); viewModel.exportData(uri);
} }

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.reporting;
import android.app.Application; import android.app.Application;
import android.os.Process; import android.os.Process;
import android.util.Log;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.logging.LogEncrypter; import org.briarproject.briar.android.logging.LogEncrypter;
@@ -10,6 +11,7 @@ import java.lang.Thread.UncaughtExceptionHandler;
import javax.inject.Inject; import javax.inject.Inject;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity; import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity;
@NotNullByDefault @NotNullByDefault
@@ -28,6 +30,8 @@ class BriarExceptionHandler implements UncaughtExceptionHandler {
@Override @Override
public void uncaughtException(Thread t, Throwable e) { public void uncaughtException(Thread t, Throwable e) {
if (IS_DEBUG_BUILD) Log.w("Uncaught exception", e);
// encrypt logs to disk before handing over to new process // encrypt logs to disk before handing over to new process
// the intent has limited space, so we can't reliably store them there. // the intent has limited space, so we can't reliably store them there.
byte[] logKey = logEncrypter.encryptLogs(); byte[] logKey = logEncrypter.encryptLogs();

View File

@@ -62,7 +62,6 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
@@ -341,11 +340,6 @@ public class UiUtils {
return i; return i;
} }
public static void putShowAdvancedExtra(Intent i) {
i.putExtra(SDK_INT <= 28 ? "android.content.extra.SHOW_ADVANCED" :
"android.provider.extra.SHOW_ADVANCED", true);
}
/** /**
* @return true if location is enabled, * @return true if location is enabled,
* or it isn't required due to this being a SDK < 28 device. * or it isn't required due to this being a SDK < 28 device.
@@ -567,4 +561,5 @@ public class UiUtils {
activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE | activity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_RESIZE |
SOFT_INPUT_STATE_HIDDEN); SOFT_INPUT_STATE_HIDDEN);
} }
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android.view;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.widget.ImageView; import android.widget.ImageView;
@@ -29,6 +30,7 @@ import im.delight.android.identicons.IdenticonDrawable;
import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.content.Context.LAYOUT_INFLATER_SERVICE;
import static android.graphics.Typeface.BOLD; import static android.graphics.Typeface.BOLD;
import static android.util.TypedValue.COMPLEX_UNIT_PX; import static android.util.TypedValue.COMPLEX_UNIT_PX;
import static androidx.appcompat.content.res.AppCompatResources.getDrawable;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName; import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
import static org.briarproject.briar.android.util.UiUtils.resolveAttribute; import static org.briarproject.briar.android.util.UiUtils.resolveAttribute;
import static org.briarproject.briar.api.identity.AuthorInfo.Status.NONE; import static org.briarproject.briar.api.identity.AuthorInfo.Status.NONE;
@@ -177,14 +179,14 @@ public class AuthorView extends ConstraintLayout {
case RSS_FEED: case RSS_FEED:
avatarIcon.setVisibility(INVISIBLE); avatarIcon.setVisibility(INVISIBLE);
date.setVisibility(VISIBLE); date.setVisibility(VISIBLE);
avatar.setImageResource(R.drawable.ic_rss_feed); setRssVectorAvatar();
setAvatarSize(R.dimen.blogs_avatar_normal_size); setAvatarSize(R.dimen.blogs_avatar_normal_size);
setTextSize(authorName, R.dimen.text_size_small); setTextSize(authorName, R.dimen.text_size_small);
break; break;
case RSS_FEED_REBLOGGED: case RSS_FEED_REBLOGGED:
avatarIcon.setVisibility(INVISIBLE); avatarIcon.setVisibility(INVISIBLE);
date.setVisibility(VISIBLE); date.setVisibility(VISIBLE);
avatar.setImageResource(R.drawable.ic_rss_feed); setRssVectorAvatar();
setAvatarSize(R.dimen.blogs_avatar_comment_size); setAvatarSize(R.dimen.blogs_avatar_comment_size);
setTextSize(authorName, R.dimen.text_size_tiny); setTextSize(authorName, R.dimen.text_size_tiny);
break; break;
@@ -204,4 +206,16 @@ public class AuthorView extends ConstraintLayout {
v.setTextSize(COMPLEX_UNIT_PX, textSize); v.setTextSize(COMPLEX_UNIT_PX, textSize);
} }
/**
* Applies special hack to use AppCompat vector drawable support
* when setting the RSS vector drawable to the avatar view.
* {@link ImageView#setImageResource(int)} is not working as
* {@link CircleImageView} is not using
* {@link androidx.appcompat.widget.AppCompatImageView}.
*/
private void setRssVectorAvatar() {
Drawable d = getDrawable(getContext(), R.drawable.ic_rss_feed);
avatar.setImageDrawable(d);
}
} }

View File

@@ -42,10 +42,13 @@ public interface AndroidNotificationManager {
// that will sort below the main channels such as contacts // that will sort below the main channels such as contacts
String ONGOING_CHANNEL_OLD_ID = "zForegroundService"; String ONGOING_CHANNEL_OLD_ID = "zForegroundService";
String ONGOING_CHANNEL_ID = "zForegroundService2"; String ONGOING_CHANNEL_ID = "zForegroundService2";
String FAILURE_CHANNEL_ID = "zStartupFailure";
String REMINDER_CHANNEL_ID = "zSignInReminder"; String REMINDER_CHANNEL_ID = "zSignInReminder";
String HOTSPOT_CHANNEL_ID = "zHotspot"; String HOTSPOT_CHANNEL_ID = "zHotspot";
// This channel is no longer used - keep the ID so we can remove the
// channel from existing installations
String FAILURE_CHANNEL_ID = "zStartupFailure";
// Actions for pending intents // Actions for pending intents
String ACTION_DISMISS_REMINDER = "dismissReminder"; String ACTION_DISMISS_REMINDER = "dismissReminder";
String ACTION_STOP_HOTSPOT = "stopHotspot"; String ACTION_STOP_HOTSPOT = "stopHotspot";

View File

@@ -5,31 +5,30 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/scrollView" android:id="@+id/scrollView"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:fillViewport="true" android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@+id/acceptButton" app:layout_constraintBottom_toTopOf="@+id/acceptButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="@dimen/margin_large"> android:layout_margin="@dimen/margin_xlarge">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/errorIcon" android:id="@+id/errorIcon"
android:layout_width="0dp" android:layout_width="@dimen/hero_square"
android:layout_height="0dp" android:layout_height="@dimen/hero_square"
android:padding="8dp"
app:layout_constraintBottom_toTopOf="@+id/crashed" app:layout_constraintBottom_toTopOf="@+id/crashed"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_max="128dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_crash" app:srcCompat="@drawable/ic_crash"
app:tint="?attr/colorControlNormal" app:tint="?attr/colorControlNormal"
@@ -39,25 +38,23 @@
android:id="@+id/crashed" android:id="@+id/crashed"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center" android:gravity="center"
android:text="@string/briar_crashed" android:text="@string/briar_crashed"
android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textSize="@dimen/text_size_large"
app:layout_constraintBottom_toTopOf="@+id/fault" app:layout_constraintBottom_toTopOf="@+id/fault"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorIcon" app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
tools:layout_editor_absoluteY="8dp" />
<TextView <TextView
android:id="@+id/fault" android:id="@+id/fault"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large" android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center" android:gravity="center"
android:text="@string/not_your_fault" android:text="@string/not_your_fault"
android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textSize="@dimen/text_size_large"
app:layout_constraintBottom_toTopOf="@+id/pleaseSend" app:layout_constraintBottom_toTopOf="@+id/pleaseSend"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -70,8 +67,7 @@
android:layout_marginTop="@dimen/margin_large" android:layout_marginTop="@dimen/margin_large"
android:gravity="center" android:gravity="center"
android:text="@string/please_send_report" android:text="@string/please_send_report"
android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textSize="@dimen/text_size_large"
app:layout_constraintBottom_toTopOf="@+id/encrypted" app:layout_constraintBottom_toTopOf="@+id/encrypted"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -84,8 +80,7 @@
android:layout_marginTop="@dimen/margin_large" android:layout_marginTop="@dimen/margin_large"
android:gravity="center" android:gravity="center"
android:text="@string/report_is_encrypted" android:text="@string/report_is_encrypted"
android:textColor="?android:attr/textColorSecondary" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textSize="@dimen/text_size_large"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -93,7 +88,7 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </androidx.core.widget.NestedScrollView>
<Button <Button
android:id="@+id/declineButton" android:id="@+id/declineButton"
@@ -101,7 +96,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/close" android:text="@string/close"
app:layout_constraintBottom_toBottomOf="@+id/acceptButton" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/acceptButton" app:layout_constraintEnd_toStartOf="@+id/acceptButton"
app:layout_constraintHorizontal_weight="1" app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -1,29 +1,39 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_xlarge">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/errorIcon" android:id="@+id/errorIcon"
android:layout_width="128dp" android:layout_width="@dimen/hero_square"
android:layout_height="128dp" android:layout_height="@dimen/hero_square"
android:layout_margin="8dp" app:layout_constraintBottom_toTopOf="@+id/errorTitle"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/alerts_and_states_error" app:srcCompat="@drawable/alerts_and_states_error"
app:tint="?attr/colorControlNormal" app:tint="@color/briar_red_500"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/errorTitle" android:id="@+id/errorTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center"
android:text="@string/sorry" android:text="@string/sorry"
android:textSize="@dimen/text_size_xlarge" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toTopOf="@+id/errorMessage"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorIcon" /> app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
@@ -32,15 +42,14 @@
android:id="@+id/errorMessage" android:id="@+id/errorMessage"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginLeft="16dp" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:textSize="@dimen/text_size_medium"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorTitle" app:layout_constraintTop_toBottomOf="@+id/errorTitle"
tools:text="@string/startup_failed_service_error" /> tools:text="@string/startup_failed_service_error" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,108 +1,102 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:fillViewport="true">
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@+id/tryAgainButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/errorTitle"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_xlarge">
android:text="@string/connection_error_title"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/errorIcon" android:id="@+id/errorIcon"
android:layout_width="match_parent" android:layout_width="@dimen/hero_rect_width"
android:layout_height="wrap_content" android:layout_height="@dimen/hero_rect_height"
android:layout_marginLeft="16dp" app:layout_constraintBottom_toTopOf="@+id/errorTitle"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorTitle" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/qr_code_error" app:srcCompat="@drawable/qr_code_error"
app:tint="?attr/colorControlNormal" app:tint="?attr/colorControlNormal"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<TextView
android:id="@+id/errorTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_xlarge"
android:gravity="center"
android:text="@string/connection_error_title"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toTopOf="@+id/errorMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorIcon" />
<TextView <TextView
android:id="@+id/errorMessage" android:id="@+id/errorMessage"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginLeft="16dp" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"
app:layout_constraintBottom_toTopOf="@+id/sendFeedback" app:layout_constraintBottom_toTopOf="@+id/sendFeedback"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorIcon" app:layout_constraintTop_toBottomOf="@+id/errorTitle"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed"
tools:text="error explanation" /> tools:text="error explanation" />
<TextView <TextView
android:id="@+id/sendFeedback" android:id="@+id/sendFeedback"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="@string/connection_error_feedback" android:text="@string/connection_error_feedback"
android:textColor="?android:attr/textColorPrimary" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textSize="@dimen/text_size_medium" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/tryAgainButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/errorMessage" /> app:layout_constraintTop_toBottomOf="@+id/errorMessage" />
<Button </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/tryAgainButton"
style="@style/BriarButtonFlat.Positive" </androidx.core.widget.NestedScrollView>
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="@string/try_again_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/cancelButton" />
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/BriarButtonFlat.Negative" style="@style/BriarButtonFlat.Negative"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="@string/cancel" android:text="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/tryAgainButton" app:layout_constraintEnd_toStartOf="@+id/tryAgainButton"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintHorizontal_weight="1" app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/tryAgainButton" />
<Button
android:id="@+id/tryAgainButton"
style="@style/BriarButtonFlat.Positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/try_again_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/cancelButton"
app:layout_constraintTop_toBottomOf="@+id/scrollView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -12,18 +12,16 @@
<ImageView <ImageView
android:id="@+id/iconView" android:id="@+id/iconView"
android:layout_width="0dp" android:layout_width="@dimen/hero_square"
android:layout_height="0dp" android:layout_height="@dimen/hero_square"
android:layout_marginTop="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/titleView" app:layout_constraintBottom_toTopOf="@+id/titleView"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25" app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_max="200dp"
app:layout_constraintWidth_percent="0.4"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
tools:srcCompat="@drawable/alerts_and_states_error" tools:srcCompat="@drawable/alerts_and_states_error"
tools:tint="@color/briar_red_500" /> tools:tint="@color/briar_red_500" />
@@ -32,11 +30,8 @@
android:id="@+id/titleView" android:id="@+id/titleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginLeft="32dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:gravity="center" android:gravity="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
app:layout_constraintBottom_toTopOf="@+id/textView" app:layout_constraintBottom_toTopOf="@+id/textView"
@@ -49,10 +44,9 @@
android:id="@+id/textView" android:id="@+id/textView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="32dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginEnd="32dp" android:layout_marginBottom="@dimen/margin_large"
android:layout_marginBottom="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintBottom_toTopOf="@+id/button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -65,7 +59,7 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_large"
android:text="@string/finish" android:text="@string/finish"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -73,4 +67,4 @@
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </androidx.core.widget.NestedScrollView>

View File

@@ -73,7 +73,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/feedbackButton" app:layout_constraintTop_toBottomOf="@+id/feedbackButton"
tools:layout="@layout/fragment_hotspot_save_apk" /> tools:layout="@layout/fragment_hotspot_fallback" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,7 +8,7 @@
android:id="@id/fallbackTitleView" android:id="@id/fallbackTitleView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="16dp"
android:text="@string/hotspot_help_fallback_title" android:text="@string/hotspot_help_fallback_title"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"

View File

@@ -119,7 +119,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/site4View" app:layout_constraintTop_toBottomOf="@+id/site4View"
tools:layout="@layout/fragment_hotspot_save_apk" /> tools:layout="@layout/fragment_hotspot_fallback" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,39 +1,43 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/scrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".android.hotspot.HotspotIntroFragment"> tools:context=".android.hotspot.HotspotIntroFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="0dp" android:layout_width="@dimen/hero_square"
android:layout_height="0dp" android:layout_height="@dimen/hero_square"
android:layout_marginStart="64dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginLeft="64dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginTop="32dp"
android:layout_marginEnd="64dp"
android:layout_marginRight="64dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@+id/introView" app:layout_constraintBottom_toTopOf="@+id/introView"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread_inside" app:layout_constraintVertical_bias="0.25"
app:srcCompat="@drawable/ic_nickname" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_wifi_tethering"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<TextView <TextView
android:id="@+id/introView" android:id="@+id/introView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="@dimen/margin_large"
android:text="@string/hotspot_intro" android:text="@string/hotspot_intro"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/startButton" app:layout_constraintBottom_toTopOf="@+id/startButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/imageView" />
@@ -42,18 +46,15 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_large"
android:drawablePadding="6dp" android:drawablePadding="8dp"
android:text="@string/hotspot_button_start_sharing" android:text="@string/hotspot_button_start_sharing"
app:drawableLeftCompat="@drawable/ic_wifi_tethering" app:drawableLeftCompat="@drawable/ic_wifi_tethering"
app:drawableStartCompat="@drawable/ic_wifi_tethering" app:drawableStartCompat="@drawable/ic_wifi_tethering"
app:drawableTint="@color/button_text" app:drawableTint="@color/button_text"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.812"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/introView"
app:layout_constraintVertical_bias="1.0"
tools:visibility="visible" /> tools:visibility="visible" />
<ProgressBar <ProgressBar
@@ -73,8 +74,8 @@
android:id="@+id/progressTextView" android:id="@+id/progressTextView"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="@dimen/margin_large"
android:layout_marginLeft="16dp" android:layout_marginLeft="@dimen/margin_large"
android:text="@string/hotspot_progress_text_start" android:text="@string/hotspot_progress_text_start"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="@+id/startButton" app:layout_constraintBottom_toBottomOf="@+id/startButton"
@@ -84,3 +85,5 @@
tools:visibility="visible" /> tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -39,9 +39,9 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:background="@color/briar_primary" android:background="@android:color/white"
android:padding="8dp" android:padding="8dp"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_primary"
android:typeface="monospace" android:typeface="monospace"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -71,9 +71,9 @@
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:background="@color/briar_primary" android:background="@android:color/white"
android:padding="8dp" android:padding="8dp"
android:textColor="@color/briar_text_primary_inverse" android:textColor="@color/briar_primary"
android:typeface="monospace" android:typeface="monospace"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@@ -6,9 +6,10 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/connectedButton" app:layout_constraintBottom_toTopOf="@+id/connectedView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@@ -51,20 +52,32 @@
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior" />
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabLayout" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<TextView
android:id="@+id/connectedView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="1dp"
android:gravity="center"
android:text="@string/hotspot_no_peers_connected"
app:layout_constraintTop_toBottomOf="@+id/coordinatorLayout"
app:layout_constraintBottom_toTopOf="@+id/connectedButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button <Button
android:id="@+id/connectedButton" android:id="@+id/connectedButton"
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="1dp"
android:layout_marginBottom="1dp"
android:text="@string/hotspot_button_connected" android:text="@string/hotspot_button_connected"
app:drawableLeftCompat="@drawable/ic_check_white" app:drawableLeftCompat="@drawable/ic_check_white"
app:drawableStartCompat="@drawable/ic_check_white" app:drawableStartCompat="@drawable/ic_check_white"
@@ -79,7 +92,9 @@
style="@style/BriarButtonFlat.Negative" style="@style/BriarButtonFlat.Negative"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="16dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="2dp"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:text="@string/hotspot_button_stop_sharing" android:text="@string/hotspot_button_stop_sharing"
app:drawableLeftCompat="@drawable/ic_portable_wifi_off" app:drawableLeftCompat="@drawable/ic_portable_wifi_off"

View File

@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/briar_primary"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
@@ -12,30 +13,30 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="4dp" android:elevation="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@drawable/abc_ic_ab_back_material" app:navigationIcon="@drawable/abc_ic_ab_back_material"
tools:title="Onboarding Fullscreen Dialog" /> tools:title="Onboarding Fullscreen Dialog" />
<ScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
android:background="@color/briar_primary" android:layout_weight="1"
android:fillViewport="true"> android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_xlarge">
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="64dp" android:layout_width="@dimen/hero_square"
android:layout_height="64dp" android:layout_height="@dimen/hero_square"
android:layout_margin="16dp" app:layout_constraintBottom_toTopOf="@+id/contentView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.25"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_info_white" app:srcCompat="@drawable/ic_info_white"
app:tint="@color/briar_text_secondary_inverse" app:tint="@color/briar_text_secondary_inverse"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@@ -44,26 +45,25 @@
android:id="@+id/contentView" android:id="@+id/contentView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginTop="@dimen/margin_xlarge"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/briar_text_secondary_inverse" android:textColor="@color/briar_text_secondary_inverse"
app:layout_constraintBottom_toTopOf="@+id/button" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" app:layout_constraintTop_toBottomOf="@+id/imageView"
app:layout_constraintVertical_bias="0.0"
tools:text="@tools:sample/lorem/random" /> tools:text="@tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
<Button <Button
android:id="@+id/button" android:id="@+id/button"
style="@style/BriarButtonFlat.Positive" style="@style/BriarButtonFlat.Positive"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/got_it" android:layout_gravity="end"
app:layout_constraintBottom_toBottomOf="parent" android:text="@string/got_it" />
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout> </LinearLayout>

View File

@@ -12,16 +12,16 @@
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="0dp" android:layout_width="@dimen/hero_square"
android:layout_height="0dp" android:layout_height="@dimen/hero_square"
android:layout_marginTop="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/introView" app:layout_constraintBottom_toTopOf="@+id/introView"
app:layout_constraintDimensionRatio="1,1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread" app:layout_constraintVertical_bias="0.25"
app:layout_constraintWidth_percent="0.4" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_transfer_data" app:srcCompat="@drawable/ic_transfer_data"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@@ -29,12 +29,11 @@
android:id="@+id/introView" android:id="@+id/introView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="32dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginEnd="32dp" android:layout_marginBottom="@dimen/margin_large"
android:layout_marginBottom="16dp"
android:text="@string/removable_drive_intro" android:text="@string/removable_drive_intro"
android:textSize="16sp" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/sendButton" app:layout_constraintBottom_toTopOf="@+id/sendButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -45,7 +44,8 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_marginHorizontal="@dimen/margin_large"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/removable_drive_title_send" android:text="@string/removable_drive_title_send"
app:layout_constraintBottom_toTopOf="@+id/receiveButton" app:layout_constraintBottom_toTopOf="@+id/receiveButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -56,7 +56,7 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_large"
android:text="@string/removable_drive_title_receive" android:text="@string/removable_drive_title_receive"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -12,18 +12,17 @@
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="0dp" android:layout_width="@dimen/hero_rect_width"
android:layout_height="0dp" android:layout_height="@dimen/hero_rect_height"
android:layout_marginTop="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/progressBar" app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintDimensionRatio="1,2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread" app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintWidth_max="300dp" app:layout_constraintVertical_bias="0.25"
app:layout_constraintWidth_percent="0.6" app:srcCompat="@drawable/ic_transfer_data_receive"
app:srcCompat="@drawable/ic_transfer_data_send"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
<ProgressBar <ProgressBar
@@ -31,7 +30,8 @@
style="?android:attr/progressBarStyleHorizontal" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:indeterminate="true" android:indeterminate="true"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/introTextView" app:layout_constraintBottom_toTopOf="@+id/introTextView"
@@ -44,11 +44,11 @@
android:id="@+id/introTextView" android:id="@+id/introTextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginEnd="32dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="16dp" android:layout_marginBottom="@dimen/margin_large"
android:text="@string/removable_drive_receive_intro" android:text="@string/removable_drive_receive_intro"
android:textSize="16sp" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/fileButton" app:layout_constraintBottom_toTopOf="@+id/fileButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -59,7 +59,7 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_large"
android:enabled="false" android:enabled="false"
android:text="@string/removable_drive_receive_button" android:text="@string/removable_drive_receive_button"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -12,17 +12,16 @@
<ImageView <ImageView
android:id="@+id/imageView" android:id="@+id/imageView"
android:layout_width="0dp" android:layout_width="@dimen/hero_rect_width"
android:layout_height="0dp" android:layout_height="@dimen/hero_rect_height"
android:layout_marginTop="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
app:layout_constraintBottom_toTopOf="@+id/progressBar" app:layout_constraintBottom_toTopOf="@+id/progressBar"
app:layout_constraintDimensionRatio="1,2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.6"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="spread" app:layout_constraintVertical_bias="0.25"
app:layout_constraintWidth_max="300dp" app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@drawable/ic_transfer_data_send" app:srcCompat="@drawable/ic_transfer_data_send"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@@ -31,7 +30,8 @@
style="?android:attr/progressBarStyleHorizontal" style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginTop="@dimen/margin_xlarge"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintBottom_toTopOf="@+id/introTextView" app:layout_constraintBottom_toTopOf="@+id/introTextView"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@@ -43,11 +43,11 @@
android:id="@+id/introTextView" android:id="@+id/introTextView"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginHorizontal="@dimen/margin_xlarge"
android:layout_marginEnd="32dp" android:layout_marginTop="@dimen/margin_xlarge"
android:layout_marginBottom="16dp" android:layout_marginBottom="@dimen/margin_large"
android:text="@string/removable_drive_send_intro" android:text="@string/removable_drive_send_intro"
android:textSize="16sp" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintBottom_toTopOf="@+id/fileButton" app:layout_constraintBottom_toTopOf="@+id/fileButton"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@@ -58,7 +58,7 @@
style="@style/BriarButton" style="@style/BriarButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="@dimen/margin_large"
android:enabled="false" android:enabled="false"
android:text="@string/removable_drive_send_button" android:text="@string/removable_drive_send_button"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@@ -27,7 +27,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingStart="@dimen/listitem_vertical_margin" android:paddingStart="@dimen/listitem_vertical_margin"
android:paddingLeft="@dimen/listitem_vertical_margin"
android:paddingEnd="@dimen/listitem_vertical_margin" android:paddingEnd="@dimen/listitem_vertical_margin"
android:paddingRight="@dimen/listitem_vertical_margin"
android:paddingBottom="@dimen/listitem_vertical_margin" android:paddingBottom="@dimen/listitem_vertical_margin"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small" android:textSize="@dimen/text_size_small"

View File

@@ -1,43 +1,111 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<!--Setup--> <!--Setup-->
<string name="setup_title">Добре дошли в Briar</string> <string name="setup_title">Добре дошли в Briar</string>
<string name="setup_next">Следващ</string> <string name="setup_name_explanation">Прякорът ви ще бъде видим до всяка ваша публикация. Няма да можете да го промените след като създадете профил.</string>
<string name="choose_nickname">Изберете име</string> <string name="setup_next">Напред</string>
<string name="setup_password_intro">Изберете парола</string>
<string name="setup_password_explanation">Профилът в Briar се съхранява шифриран на устройството ви, а не в облака. Ако забравите паролата си или премахнете Briar, няма начин да го възстановите.\n\nИзберете дълга, трудна за отгатване парола, например: четири случайни думи или десет случайни букви, числа и знаци.</string>
<string name="setup_doze_title">Свързаност на заден план</string>
<string name="setup_doze_intro">За да получавате съобщения, Briar трябва да е свързан на заден план.</string>
<string name="setup_doze_explanation">За да получавате съобщения, Briar трябва да е свързан на заден план. Изключете оптимизацията на батерията, за да може Briar да остане свързан.</string>
<string name="setup_doze_button">Разрешаване на свързаност</string>
<string name="choose_nickname">Изберете прякор</string>
<string name="choose_password">Изберете парола</string> <string name="choose_password">Изберете парола</string>
<string name="confirm_password">Потвърдете парола</string> <string name="confirm_password">Потвърдете парола</string>
<string name="name_too_long">Името е твърде дълго</string> <string name="name_too_long">Името е твърде дълго</string>
<string name="password_too_weak">Паролата е твърде слаба</string> <string name="password_too_weak">Паролата е твърде слаба</string>
<string name="passwords_do_not_match">Паролите не съвпадат</string> <string name="passwords_do_not_match">Паролите не съвпадат</string>
<string name="create_account_button">Създаване на профил</string> <string name="create_account_button">Създаване на профил</string>
<string name="more_info">Повече информация</string>
<string name="don_t_ask_again">Спиране на този въпрос</string>
<string name="setup_huawei_text">Докоснете бутона по-долу и се уверете, че Briar е защитен в екрана за „Защитени приложения“.</string>
<string name="setup_huawei_button">Защитаване на Briar</string>
<string name="setup_huawei_help">Ако не добавите Briar в списъка на защитени приложения, няма да може да работи на заден план.</string>
<string name="setup_huawei_app_launch_text">Докоснете бутона по-долу, отворете „Стартиране на приложения“ и се уверете, че за Briar е избрано „Ръчно управление“.</string>
<string name="setup_huawei_app_launch_button">Настройки на батерия</string>
<string name="setup_huawei_app_launch_help">Ако за Briar не е избрано „Ръчно управление“ в екрана „Стартиране на приложения“, тогава няма да може да работи на заден план.</string>
<string name="setup_xiaomi_text">За да работи на заден план Briar трябва да бъде заключен в списъка с последно използваните приложения.</string>
<string name="setup_xiaomi_button">Защитаване на Briar</string>
<string name="setup_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string>
<string name="setup_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<string name="setup_xiaomi_dialog_body_new">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Докоснете и задръжте върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<string name="warning_dozed">%s не може да работи на заден план</string>
<!--Login--> <!--Login-->
<string name="enter_password">Парола</string> <string name="enter_password">Парола</string>
<string name="try_again">Грешна парола, опитайте пак</string> <string name="try_again">Грешна парола, опитайте отново</string>
<string name="sign_in_button">Вход</string> <string name="dialog_title_cannot_check_password">Паролата не може да бъде проверена</string>
<string name="dialog_message_cannot_check_password">Briar не може да провери вашата парола. Рестартирате устройството, за да разрешите проблема.</string>
<string name="sign_in_button">Влизане</string>
<string name="forgotten_password">Забравена парола</string> <string name="forgotten_password">Забравена парола</string>
<string name="dialog_title_lost_password">Забравена парола</string> <string name="dialog_title_lost_password">Забравена парола</string>
<string name="dialog_message_lost_password">Briar профилът се съхранява криптиран във вашето устройство, не в облака, така че не можем да зададем нова парола. Искате ли да изтриете профила си и да започнете отначало?\n\nВнимание: Вашият профил, контакти и съобщения ще бъдат изтрити завинаги.</string> <string name="dialog_message_lost_password">Профилът в Briar се съхранява шифриран на вашето устройство, а не в облака и за това паролата не може да бъде сменена. Желаете ли да профилът да бъде премахнат и да бъде направен нов?\n\nВнимание: Профилът, контактите и съобщенията ще бъдат безвъзвратно загубени.</string>
<string name="startup_failed_notification_title">Briar не можа да стартира</string> <string name="startup_failed_notification_title">Briar не можа да стартира</string>
<string name="startup_failed_notification_text">Докоснете за повече информация.</string>
<string name="startup_failed_activity_title">Неуспешно стартиране</string> <string name="startup_failed_activity_title">Неуспешно стартиране</string>
<string name="startup_failed_service_error">Briar не успя да стартира задължителен плъгин. Обикновено преинсталирането на Briar решава този проблем. Моля, имайте предвид, че ще изгубите профила си и всички данни, асоциирани с него, тъй като Briar не съхранява данните ви в централни сървъри.</string> <string name="startup_failed_db_error">По някаква причина банката от данни на Briar е непоправимо повредена. Вашият профил, данни и всичките ви контакти са загубени. За жалост, се налага да преинсталирате Briar или да създадете нов профил, избирайки „Забравена парола“ от екрана за вход.</string>
<string name="expiry_date_reached">Софтуерът е невалиден.\nБлагодарим ви за тестването!</string> <string name="startup_failed_data_too_old_error">Вашия профил е създаден със по-ранно издание на приложението и не може да бъде отворен. Трябва или да инсталирате по-ранното издание или да създадете нов профил, избирайки „Забравена парола“ от екрана за вход.</string>
<string name="startup_failed_data_too_new_error">Тава издание на приложението е твърде старо. Обновете до последно издание и опитайте отново.</string>
<string name="startup_failed_service_error">Briar не може да стартира задължителна приставка. Обикновено преинсталирането на Briar решава този проблем. Имайте предвид, че ще изгубите профила си и всички свързани с него данни, тъй като Briar не ги съхранява в централни сървъри.</string>
<plurals name="expiry_warning">
<item quantity="one">Това е тестова версия на Briar. Вашият акаунт ще бъде изтече след %d ден и не може да бъде подновена.</item>
<item quantity="other">Това е изпитателно издание на Briar. Валидността на профила ще изтече след %d дена и не може да бъде подновена.</item>
</plurals>
<string name="expiry_date_reached">Софтуерът е с изтекъл срок.\nБлагодарим за изпитването!</string>
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
<string name="create_new_account">Ще трябва да създадете нов профил, но ще можете да използвате същия прякор.</string>
<string name="download_briar_button">Изтегляне</string>
<string name="startup_open_database">Хранилището се дешифрира…</string>
<string name="startup_migrate_database">Хранилището се обновява…</string>
<string name="startup_compact_database">Хранилището се уплътнява…</string>
<!--Navigation Drawer--> <!--Navigation Drawer-->
<string name="nav_drawer_open_description">Отвори навигационно чекмедже</string> <string name="nav_drawer_open_description">Отваря навигационната лента</string>
<string name="nav_drawer_close_description">Затвори навигационно чекмедже</string> <string name="nav_drawer_close_description">Затваря навигационната лента</string>
<string name="contact_list_button">Контакти</string> <string name="contact_list_button">Контакти</string>
<string name="groups_button">Частни групи</string> <string name="groups_button">Частни групи</string>
<string name="forums_button">Форуми</string> <string name="forums_button">Форуми</string>
<string name="blogs_button">Блогове</string> <string name="blogs_button">Блогове</string>
<!--This is part of the main menu. The app will be locked when this is tapped.--> <!--This is part of the main menu. The app will be locked when this is tapped.-->
<string name="lock_button">Заключване</string>
<string name="settings_button">Настройки</string> <string name="settings_button">Настройки</string>
<string name="sign_out_button">Отписване</string> <string name="sign_out_button">Отписване</string>
<!--Transports--> <string name="transports_onboarding_text">Докоснете, за да изберете как Briar да се свързва с контактите ви.</string>
<!--Transports: Tor-->
<string name="transport_tor">Интернет</string> <string name="transport_tor">Интернет</string>
<string name="transport_bt">Bluetooth</string> <string name="tor_device_status_online_wifi">Устройството има достъп до интернет през Wi-Fi</string>
<string name="tor_device_status_online_mobile">Устройството има достъп до интернет през мобилни данни</string>
<string name="tor_device_status_offline">Устройството няма достъп до интернет</string>
<string name="tor_plugin_status_enabling">Briar се свързва с интернет</string>
<string name="tor_plugin_status_active">Briar се свързва с интернет</string>
<string name="tor_plugin_status_inactive">Briar не може да се свърже с интернет</string>
<string name="tor_plugin_status_disabled">Briar е настроен да не използва интернет</string>
<string name="tor_plugin_status_disabled_mobile_data">Briar е настроен да не използва мобилни данни</string>
<string name="tor_plugin_status_disabled_battery">Briar е настроен да не използва интернет докато използва батерия</string>
<string name="tor_plugin_status_disabled_country_blocked">Briar е настроен да не използва интернет в тази държава</string>
<!--Transports: Wi-Fi-->
<string name="transport_lan">Wi-Fi</string> <string name="transport_lan">Wi-Fi</string>
<string name="transport_lan_long">Същата безжична мрежа</string>
<string name="lan_device_status_on">Устройството е свързан с безжична мрежа</string>
<string name="lan_device_status_off">Устройството не е свързан с безжична мрежа</string>
<string name="lan_plugin_status_enabling">Briar се свързва с безжична мрежа</string>
<string name="lan_plugin_status_active">Briar е свързан с безжична мрежа</string>
<string name="lan_plugin_status_inactive">Briar не е свързан с безжична мрежа</string>
<string name="lan_plugin_status_disabled">Briar е настроен да не използва безжична мрежа</string>
<!--Transports: Bluetooth-->
<string name="transport_bt">Bluetooth</string>
<string name="bt_device_status_on">Устройството е с включен Bluetooth</string>
<string name="bt_device_status_off">Устройството е с изключен Bluetooth</string>
<string name="bt_plugin_status_enabling">Briar се свързва чрез Bluetooth</string>
<string name="bt_plugin_status_active">Briar е свързан чрез Bluetooth</string>
<string name="bt_plugin_status_inactive">Briar не може да се свърже чрез Bluetooth</string>
<string name="bt_plugin_status_disabled">Briar е настроен да не използва Bluetooth</string>
<!--Notifications--> <!--Notifications-->
<string name="ongoing_notification_title">Вписан сте в Briar</string> <string name="reminder_notification_title">Отписани сте от Briar</string>
<string name="ongoing_notification_text">Докоснете, за да отворите Briar.</string> <string name="reminder_notification_text">Докоснете за повторно влизане.</string>
<string name="reminder_notification_channel_title">Напомняне за вход в Briar</string>
<string name="reminder_notification_dismiss">Отказ</string>
<string name="ongoing_notification_title">Вписани в Briar</string>
<string name="ongoing_notification_text">Докоснете за отваряне на Briar.</string>
<plurals name="private_message_notification_text"> <plurals name="private_message_notification_text">
<item quantity="one">Ново лично съобщение.</item> <item quantity="one">Ново лично съобщение.</item>
<item quantity="other">%d нови лични съобщения.</item> <item quantity="other">%d нови лични съобщения.</item>
@@ -47,281 +115,543 @@
<item quantity="other">%d нови групови съобщения.</item> <item quantity="other">%d нови групови съобщения.</item>
</plurals> </plurals>
<plurals name="forum_post_notification_text"> <plurals name="forum_post_notification_text">
<item quantity="one">Нова форумна публикация.</item> <item quantity="one">Нова публикация във форум.</item>
<item quantity="other">%d нови форумни публикации.</item> <item quantity="other">%d нови публикации във форуми.</item>
</plurals> </plurals>
<plurals name="blog_post_notification_text"> <plurals name="blog_post_notification_text">
<item quantity="one">Нова блог публикация.</item> <item quantity="one">Нова публикация в блог.</item>
<item quantity="other">%d нови блог публикации.</item> <item quantity="other">%d нови публикации в блогове.</item>
</plurals> </plurals>
<!--Misc--> <!--Misc-->
<string name="now">сега</string> <string name="now">току-що</string>
<string name="show">Покажи</string> <string name="show">Показване</string>
<string name="hide">Скрий</string> <string name="hide">Скриване</string>
<string name="ok">ОК</string> <string name="ok">Добре</string>
<string name="cancel">Отказ</string> <string name="cancel">Отказ</string>
<string name="got_it">Разбрах</string> <string name="got_it">Разбрах</string>
<string name="delete">Изтрий</string> <string name="delete">Изтриване</string>
<string name="accept">Приеми</string> <string name="accept">Приемане</string>
<string name="decline">Откажи</string> <string name="decline">Отказване</string>
<string name="options">Опции</string> <string name="online">На линия</string>
<string name="online">Онлайн</string> <string name="offline">Извън линия</string>
<string name="offline">Офлайн</string> <string name="send">Изпращане</string>
<string name="send">Изпрати</string> <string name="allow">Разрешаване</string>
<string name="allow">Позволи</string> <string name="open">Отваряне</string>
<string name="open">Отвори</string> <string name="change">Променяне</string>
<string name="start">Старт</string>
<string name="no_data">Няма данни</string> <string name="no_data">Няма данни</string>
<string name="ellipsis">...</string> <string name="ellipsis">...</string>
<string name="text_too_long">Въведеният текст е твърде дълъг</string> <string name="text_too_long">Въведеният текст е твърде дълъг</string>
<string name="show_onboarding">Показване на помощен диалог</string> <string name="show_onboarding">Показване на помощен диалог</string>
<string name="fix">Поправяне</string>
<string name="help">Помощ</string> <string name="help">Помощ</string>
<string name="sorry">Съжаляваме</string>
<string name="error_start_activity">Недостъпно на вашата система</string>
<string name="status_heading">Състояние</string>
<!--Contacts and Private Conversations--> <!--Contacts and Private Conversations-->
<string name="no_contacts">Няма контакти</string>
<string name="no_contacts_action">Докоснете иконата с +, за да добавите контакти</string>
<string name="date_no_private_messages">Няма съобщения.</string> <string name="date_no_private_messages">Няма съобщения.</string>
<string name="message_hint">Напиши съобщение</string> <string name="no_private_messages">Няма съобщения</string>
<string name="delete_contact">Изтрий контакт</string> <string name="message_hint">Съобщение</string>
<string name="dialog_title_delete_contact">Потвърди изтриването на контакт</string> <string name="message_hint_auto_delete">Изчезващо съобщение</string>
<string name="dialog_message_delete_contact">Сигурни ли сте, че искате да изтриете този контакт и всички съобщения, обменени с този контакт?</string> <string name="message_error">Грешка при изпращане на съобщение</string>
<string name="contact_deleted_toast">Контактът е изтрит</string> <string name="image_caption_hint">Добавете описание (по желание)</string>
<string name="image_attach">Прикачване на изображение</string>
<string name="image_attach_error">Грешка при прикачване на изображения</string>
<string name="image_attach_error_too_big">Изображението е твърде голямо. Има ограничение от %dМБ.</string>
<string name="image_attach_error_invalid_mime_type">Неподдържан формат на изображение: %s</string>
<string name="set_contact_alias">Преименуване на контакт</string>
<string name="set_contact_alias_hint">Име на контакта</string>
<string name="menu_item_disappearing_messages">Изчезващи съобщения</string>
<string name="menu_item_connect_via_bluetooth">Свързване чрез Bluetooth</string>
<string name="dialog_title_connect_via_bluetooth">Свързване чрез Bluetooth</string>
<string name="dialog_message_connect_via_bluetooth">За да сработи този метод, контактът трябва да бъде близо до вас.\n\nДвамата трябва да натиснете „Start“ едновременно.</string>
<string name="toast_connect_via_bluetooth_already_discovering">Има започнат опит за връзка чрез Bluetooth</string>
<string name="toast_connect_via_bluetooth_not_discoverable">Не може да продължи без Bluetooth</string>
<string name="toast_connect_via_bluetooth_no_location_permission">Не може да продължи без разрешение за местоположение</string>
<string name="toast_connect_via_bluetooth_start">Свързване чрез Bluetooth…</string>
<string name="toast_connect_via_bluetooth_success">Успешно свързване чрез Bluetooth</string>
<string name="toast_connect_via_bluetooth_error">Не може да се установи връзка чрез Bluetooth</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_you_enabled">Съобщението ще изчезне след %1$s. %2$s</string>
<!--The placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_you_disabled">Съобщенията ви няма да изчезнат. %1$s</string>
<!--The first placeholder will show a contact's name. The second placeholder will show a duration like "7 days". The third placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_contact_enabled">Съобщението от %1$s ще изчезне след %2$s. %3$s</string>
<plurals name="duration_minutes">
<item quantity="one">%d минута</item>
<item quantity="other">%d минути</item>
</plurals>
<plurals name="duration_hours">
<item quantity="one">%d час</item>
<item quantity="other">%d часа</item>
</plurals>
<plurals name="duration_days">
<item quantity="one">%d ден</item>
<item quantity="other">%d дни</item>
</plurals>
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_contact_disabled">Съобщението от %1$s няма да изчезне. %2$s</string>
<string name="tap_to_learn_more">Научете повече</string>
<string name="auto_delete_changed_warning_title">Промяна при изчезващи съобщения</string>
<string name="auto_delete_changed_warning_message_enabled">Откакто съставяте съобщението е бил включен механизмът за изчезващи съобщения.</string>
<string name="auto_delete_changed_warning_message_disabled">Откакто съставяте съобщението е бил изключен механизмът за изчезващи съобщения.</string>
<string name="auto_delete_changed_warning_send">Изпращане въпреки това</string>
<string name="delete_all_messages">Изтриване на всички</string>
<string name="dialog_title_delete_all_messages">Потвърждение на премахване на съобщение</string>
<string name="dialog_message_delete_all_messages">Сигурни ли сте, че желаете всички съобщения да бъдат премахнати?</string>
<string name="dialog_title_not_all_messages_deleted">Не премахнати съобщения</string>
<string name="dialog_message_not_deleted_ongoing_both">Съобщения, свързани с изпратени покани и запознанства не се премахват докато процесът не приключи.</string>
<string name="dialog_message_not_deleted_ongoing_introductions">Съобщения, свързани с изпратени запознанства не се премахват докато процесът не приключи.</string>
<string name="dialog_message_not_deleted_ongoing_invitations">Съобщения, свързани с изпратени покани не се премахват докато процесът не приключи.</string>
<string name="dialog_message_not_deleted_not_all_selected_both">За да премахнете покана или запознанство трябва да изберете заявката и отговора.</string>
<string name="dialog_message_not_deleted_not_all_selected_introductions">За да премахнете запознанство трябва да изберете заявката и отговора.</string>
<string name="dialog_message_not_deleted_not_all_selected_invitations">За да премахнете покана трябва да изберете заявката и отговора.</string>
<string name="delete_contact">Премахване на контакт</string>
<string name="dialog_title_delete_contact">Потвърждение на премахване на контакт</string>
<string name="dialog_message_delete_contact">Сигурни ли сте, че желаете да изтриете контакта и всички обменени с него съобщения?</string>
<string name="contact_deleted_toast">Контактът е премахнат</string>
<!--This is shown in the action bar when opening an image in fullscreen that the user sent-->
<string name="you">Вие</string>
<string name="save_image">Запазване на изображение</string>
<string name="dialog_title_save_image">Запазване на изображението?</string>
<string name="dialog_message_save_image">Запазвайки изображението ще дадете възможност на други приложения да го достъпват.\n\nСигурни ли сте, че желаете да бъде запазено?</string>
<string name="save_image_success">Изображението е запазено</string>
<string name="save_image_error">Грешка при запазване на изображение</string>
<string name="dialog_title_no_image_support">Недостъпни изображения</string>
<string name="dialog_message_no_image_support">Приложението на вашия контакт все още не поддържа изпращане на изображения. След като го обновят тази икона ще се промени.</string>
<string name="dialog_title_image_support">Вече можете да изпращате изображения на този контакт</string>
<string name="dialog_message_image_support">Докоснете иконата за да изпратите изображение</string>
<string name="messaging_too_many_attachments_toast">Само първите %dизображения ще бъдат изпратени</string>
<!--Adding Contacts--> <!--Adding Contacts-->
<string name="add_contact_title">Добавяне на контакт</string> <string name="add_contact_title">Добавяне на контакт на живо</string>
<string name="face_to_face">Трябва да се срещнете лично с човека, когото искате да добавите в Контакти.\n\nПо този начин никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string> <string name="face_to_face">Трябва да се срещнете лично с човека, чиито контакт искате да добавите.\n\nТака никой не може да се представи за вас или да чете съобщенията ви в бъдеще.</string>
<string name="continue_button">Напред</string> <string name="continue_button">Напред</string>
<string name="try_again_button">Опитай пак</string> <string name="try_again_button">Нов опит</string>
<string name="waiting_for_contact_to_scan">Изчакване контактът да сканира и да се свърже\u2026</string> <string name="waiting_for_contact_to_scan">Изчакване контактът да сканира и да се свърже\u2026</string>
<string name="exchanging_contact_details">Обмен на данни за контакт\u2026</string> <string name="exchanging_contact_details">Обмяна на данни за контакт\u2026</string>
<string name="contact_added_toast">Добавен конктакт: %s</string> <string name="contact_added_toast">Добавен контакт: %s</string>
<string name="contact_already_exists">Контактът %s вече съществува</string> <string name="contact_already_exists">Контактът %s вече съществува</string>
<string name="qr_code_invalid">QR кодът е невалиден</string> <string name="qr_code_invalid">Кодът за QR е недействителен</string>
<string name="qr_code_too_old">Сканираният код за QR е от по-ранно издание на %s.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
<string name="qr_code_too_new">Сканираният код за QR е от по-ново издание на %s.\n\nИнсталирайте последното издание и пробвайте отново.</string>
<string name="camera_error">Грешка в камерата</string>
<string name="connecting_to_device">Свързване с устройство\u2026</string> <string name="connecting_to_device">Свързване с устройство\u2026</string>
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string> <string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
<!--Introductions--> <string name="connection_error_title">Не може да бъде установена връзка с контакта</string>
<string name="introduction_onboarding_title">Представете контактите си</string> <string name="connection_error_feedback">Ако проблемът продължава, <a href="feedback">изпратете обратна връзка</a>, за да ни помогнете да подобрим приложението.</string>
<string name="introduction_onboarding_text">Можете да представите контактите си един на друг, за да не им се налага да се срещат лично, когато се свързват чрез Briar.</string> <!--Adding Contacts Remotely-->
<string name="introduction_menu_item">Представи</string> <string name="add_contact_remotely_title_case">Добавяне на контакт отдалечено</string>
<string name="introduction_activity_title">Избери контакт</string> <string name="add_contact_nearby_title">Добавяне на контакт на живо</string>
<string name="introduction_message_title">Представи контакти</string> <string name="add_contact_remotely_title">Добавяне на контакт отдалечено</string>
<string name="introduction_message_hint">Добавете съобщение (незадължително)</string> <string name="contact_link_intro">Въведете препратката от вашия контакт</string>
<string name="introduction_button">Представи</string> <string name="contact_link_hint">Препратка от контакт</string>
<string name="introduction_sent">Представянето ви е изпратено.</string> <string name="paste_button">Поставяне</string>
<string name="introduction_error">Възникна грешка при представянето.</string> <string name="add_contact_button">Добавяне на контакт</string>
<string name="introduction_response_error">Грешка при отговор на представянето</string> <string name="copy_button">Копиране</string>
<string name="introduction_request_sent">Помолихте да представите %1$s на %2$s.</string> <string name="share_button">Споделяне</string>
<string name="introduction_request_received">%1$s помоли да ви представи %2$s. Искате ли да добавите %2$s към контактите си?</string> <string name="send_link_title">Размяна на препратки</string>
<string name="introduction_request_exists_received">%1$s помоли да ви представи на %2$s, но %2$s вече е в списъка ви с контакти. Тъй като %1$s може би не знае, все пак можете да отговорите:</string> <string name="add_contact_choose_nickname">Избиране на прякор</string>
<string name="introduction_request_answered_received">%1$s помоли да ви представи на %2$s.</string> <string name="add_contact_choose_a_nickname">Прякор</string>
<string name="introduction_response_accepted_sent">Приехте представянето на %1$s.</string> <string name="nickname_intro">Изберете прякор на контакта. Той е видим само за вас.</string>
<string name="introduction_response_declined_sent">Отказахте представянето на %1$s.</string> <string name="your_link">Споделете тази препратка с контакта, когото добавяте</string>
<string name="introduction_response_accepted_received">%1$s прие представянето на %2$s.</string> <string name="link_clip_label">Препратка на Briar</string>
<string name="introduction_response_declined_received">%1$s отказа представянето на %2$s.</string> <string name="link_copied_toast">Препратката е копирана</string>
<string name="introduction_response_declined_received_by_introducee">%1$s казва, че %2$s отказва представянето.</string> <string name="adding_contact_error">Грешка при добавяне на контакт.</string>
<string name="pending_contact_requests_snackbar">Има чакащи заявки за контакт</string>
<string name="pending_contact_requests">Чакащи заявки за контакт</string>
<string name="no_pending_contacts">Няма заявки за контакт</string>
<string name="waiting_for_contact_to_come_online">Изчакване на контакта да излезе на линия…</string>
<string name="connecting">Свързване…</string>
<string name="adding_contact">Добавяне на контакт…</string>
<string name="adding_contact_failed">Грешка при добавяне на контакт</string>
<string name="dialog_title_remove_pending_contact">Потвърждение на премахване</string>
<string name="dialog_message_remove_pending_contact">Контактът е в процес на добавяне. Ако сега го премахнете няма да бъде добавен.</string>
<string name="own_link_error">Въведете препратка от ваш контакт, не своята</string>
<string name="nickname_missing">Въведете прякор</string>
<string name="invalid_link">Препратката е недействителна</string>
<string name="unsupported_link">Препратката е от по-ново издание на Briar. Инсталирайте последното издание и пробвайте отново.</string>
<string name="intent_own_link">Отваряте своята препратка. Използвайте тази от контакта, когото добавяте.</string>
<string name="missing_link">Въведете препратка</string>
<!--This is a numeral indicating the first step in a series of screens-->
<string name="step_1">1</string>
<!--This is a numeral indicating the second step in a series of screens-->
<string name="step_2">2</string>
<plurals name="contact_added_notification_text"> <plurals name="contact_added_notification_text">
<item quantity="one">Добавен нов контакт.</item> <item quantity="one">Добавен е нов контакт.</item>
<item quantity="other">%d добавени нови контакти.</item> <item quantity="other">Добавени са %d нови контакта.</item>
</plurals> </plurals>
<string name="offline_state">Няма връзка с интернет.</string>
<string name="duplicate_link_dialog_title">Дублираща се препратка</string>
<string name="duplicate_link_dialog_text_1">Вече имате чакаща заявка за контакт с тази препратка: %s</string>
<string name="duplicate_link_dialog_text_1_contact">Вече имате контакт с тази препратка: %s</string>
<!--This is a question asking whether two nicknames refer to the same person-->
<string name="duplicate_link_dialog_text_2">%s и %s един и същи човек ли са?</string>
<!--This is a button for answering that two nicknames do indeed refer to the same person. This
string will be used in a dialog button, so if the translation of this string is longer than 20
characters, please use "Yes" instead, and use "No" for the "Different Person" button-->
<string name="same_person_button">Да</string>
<!--This is a button for answering that two nicknames refer to different people. This string
will be used in a dialog button, so if the translation of this string longer than 20 characters,
please use "No" instead, and use "Yes" for the "Same Person" button-->
<string name="different_person_button">Не</string>
<string name="duplicate_link_dialog_text_3">%s и %s са изпратили еднакви препратки.\n\nЕдиния от двамата вероятно се опитва да разбере кои са контактите ви.\n\nНе им споделяйте, че сте получили същата препратка от друг човек.</string>
<string name="pending_contact_updated_toast">Обновена чакаща заявка за контакт</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Запознаване на контакти</string>
<string name="introduction_onboarding_text">Можете да запознавате контакти помежду им. Така няма да им се наложи да се срещат лично, за да се свържат в Briar.</string>
<string name="introduction_menu_item">Запознаване</string>
<string name="introduction_activity_title">Избор на контакт</string>
<string name="introduction_not_possible">С тези контакти вече имате запознанство в процес. Нека първо завърши. Ако вие или контактите ви сте рядко на линия може да отнеме известно време.</string>
<string name="introduction_message_title">Запознаване на контакти</string>
<string name="introduction_message_hint">Добавете съобщение (незадължително)</string>
<string name="introduction_button">Запознаване</string>
<string name="introduction_sent">Покана за запознанство е изпратена.</string>
<string name="introduction_error">Грешка при изпращане на покана за запознанство.</string>
<string name="introduction_request_sent">Пожелахте да запознаете %1$s и %2$s.</string>
<string name="introduction_request_received">%1$s пожела да ви запознае с/ъс %2$s. Желаете ли да добавите %2$s към контактите си?</string>
<string name="introduction_request_exists_received">%1$s пожела да ви запознае с/ъс %2$s, но %2$s вече е в списъка ви с контакти. Тъй като %1$s може би не знае, все пак можете да отговорите:</string>
<string name="introduction_request_answered_received">%1$s пожела да ви запознае с/ъс %2$s.</string>
<string name="introduction_response_accepted_sent">Приехте запознанство с/ъс %1$s.</string>
<string name="introduction_response_accepted_sent_info">Преди %1$s да бъде добавен/а към контактите ви той/тя също трябва да приеме поканата. Може да отнеме известно време.</string>
<string name="introduction_response_declined_sent">Отказахте запознанство с/ъс %1$s.</string>
<string name="introduction_response_declined_auto">Запознанството с/ъс %1$s е отказано автоматично.</string>
<string name="introduction_response_accepted_received">%1$s приема запознанство с/ъс %2$s.</string>
<string name="introduction_response_declined_received">%1$s отказа запознанство с/ъс %2$s.</string>
<string name="introduction_response_declined_received_by_introducee">%1$s казва, че %2$s отказва запознанство.</string>
<!--Private Groups--> <!--Private Groups-->
<string name="groups_created_by">Създаден от %s</string> <string name="groups_list_empty">Няма групи</string>
<string name="groups_list_empty_action">Докоснете иконата с +, за да създадете своя или поискайте от контактите си да споделят група с вас</string>
<string name="groups_created_by">Основател %s</string>
<plurals name="messages"> <plurals name="messages">
<item quantity="one">%d съобщение</item> <item quantity="one">%d съобщение</item>
<item quantity="other">%d съобщения</item> <item quantity="other">%d съобщения</item>
</plurals> </plurals>
<string name="groups_group_is_empty">Групата е празна</string> <string name="groups_group_is_empty">Групата е празна</string>
<string name="groups_group_is_dissolved">Групата се е разпаднала</string> <string name="groups_group_is_dissolved">Групата е разпусната</string>
<string name="groups_remove">Премахни</string> <string name="groups_remove">Премахване</string>
<string name="groups_create_group_title">Създаване на група</string> <string name="groups_create_group_title">Създаване на частна група</string>
<string name="groups_create_group_button">Създай група</string> <string name="groups_create_group_button">Създаване на група</string>
<string name="groups_create_group_invitation_button">Изпрати покана</string> <string name="groups_create_group_invitation_button">Изпращане на покана</string>
<string name="groups_create_group_hint">Изберете име за групата</string> <string name="groups_create_group_hint">Име на частната група</string>
<string name="groups_invitation_sent">Поканата в група е изпратена</string> <string name="groups_invitation_sent">Поканата за членство в група е изпратена</string>
<string name="groups_message_sent">Съобщението е изпратено</string>
<string name="groups_member_list">Списък с участници</string> <string name="groups_member_list">Списък с участници</string>
<string name="groups_invite_members">Поканете участници</string> <string name="groups_invite_members">Покани за членство</string>
<string name="groups_member_created_you">Вие създадохте групата</string> <string name="groups_member_created_you">Вие създадохте групата</string>
<string name="groups_member_created">%s създаде групата</string> <string name="groups_member_created">%s създаде групата</string>
<string name="groups_member_joined_you">Включихте се в групата</string> <string name="groups_member_joined_you">Вие се включихте в групата</string>
<string name="groups_member_joined">%s се включи в групата</string> <string name="groups_member_joined">%s се включи в групата</string>
<string name="groups_leave">Напусни групата</string> <string name="groups_leave">Напускане на групата</string>
<string name="groups_leave_dialog_title">Потвърждение на напускането</string> <string name="groups_leave_dialog_title">Потвърждение на напускане</string>
<string name="groups_leave_dialog_message">Сигурни ли сте, че искате да напуснете тази група?</string> <string name="groups_leave_dialog_message">Сигурни ли сте, че искате да напуснете тази група?</string>
<string name="groups_dissolve">Затвори групата</string> <string name="groups_dissolve">Разпускане на група</string>
<string name="groups_dissolve_dialog_title">Потвърди затварянето на групата</string> <string name="groups_dissolve_dialog_title">Потвърждение на разпускане на група</string>
<string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да затворите групата?\n\nВсички други участници няма да могат да продължат разговора и може да не получат най-новите съобщения. </string> <string name="groups_dissolve_dialog_message">Сигурни ли сте, че искате да разпуснете групата?\n\nОстаналите членове няма да могат да продължат разговорите си и може да не получат последните съобщения.</string>
<string name="groups_dissolve_button">Затвори</string> <string name="groups_dissolve_button">Разпускане</string>
<string name="groups_dissolved_dialog_title">Групата е затворена</string> <string name="groups_dissolved_dialog_title">Разпусната група</string>
<string name="groups_dissolved_dialog_message">Групата е затворена от създателя.\n\nНе можете да пишете нови съобщения в груповия чат и може да не получите най-новите съобщения. </string> <string name="groups_dissolved_dialog_message">Групата е разпусната от нейния основател.\n\nНе можете да изпращате съобщения и може да не сте получили всички изпратени до групата съобщения.</string>
<!--Private Group Invitations--> <!--Private Group Invitations-->
<string name="groups_invitations_title">Покани в група</string> <string name="groups_invitations_title">Покани за членство в група</string>
<string name="groups_invitations_invitation_sent">Поканихте %1$s в групата \"%2$s\"</string> <string name="groups_invitations_invitation_sent">Поканихте %1$s в групата %2$s</string>
<string name="groups_invitations_invitation_received">%1$s ви покани в групата \"%2$s\".</string> <string name="groups_invitations_invitation_received">%1$s ви покани в групата %2$s.</string>
<string name="groups_invitations_joined">Включихте се в групата</string> <string name="groups_invitations_joined">Включихте се в групата</string>
<string name="groups_invitations_declined">Отказана покана в група</string> <string name="groups_invitations_declined">Отказана покана за присъединяване в група</string>
<plurals name="groups_invitations_open"> <plurals name="groups_invitations_open">
<item quantity="one">%d отворена покана в група</item> <item quantity="one">%d получена покана за членство в група</item>
<item quantity="other">%d отворени покани в група</item> <item quantity="other">%d получени покани за членство в група</item>
</plurals> </plurals>
<string name="groups_invitations_response_accepted_sent">Приехте поканата в група на %s.</string> <string name="groups_invitations_response_accepted_sent">Приехте поканата от %s за членство в група.</string>
<string name="groups_invitations_response_declined_sent">Отказахте поканата в група на %s.</string> <string name="groups_invitations_response_declined_sent">Отказахте поканата от %s за членство в група.</string>
<string name="groups_invitations_response_accepted_received">%s прие поканата в група.</string> <string name="groups_invitations_response_declined_auto">Поканата от %s за членство в група е отказана автоматично.</string>
<string name="groups_invitations_response_declined_received">%s отказа поканата в група. </string> <string name="groups_invitations_response_accepted_received">%s прие поканата за членство в група.</string>
<string name="sharing_status_groups">Само създателят може да покани нови участници в групата. По-долу са изброени сегашните участници в групата.</string> <string name="groups_invitations_response_declined_received">%s отказа поканата за членство в група. </string>
<string name="sharing_status_groups">Само основателят може да кани нови участници в групата. По-долу е списъкът с текущите участници.</string>
<!--Private Groups Revealing Contacts--> <!--Private Groups Revealing Contacts-->
<string name="groups_reveal_contacts">Разкрий контакти</string> <string name="groups_reveal_contacts">Разкриване на контакти</string>
<string name="groups_reveal_dialog_message">Може да изберете да разкриете контактите на всички сегашни и бъдещи участници в тази група.\n\nРазкриването на контактите прави връзката с групата по-бърза и сигурна, тъй като можете да общувате с разкрити контакти, дори когато създателят на групата е офлайн.</string> <string name="groups_reveal_dialog_message">Можете да изберете дали да разкриете контактите на всички сегашни и бъдещи членове на групата.\n\nС разкриването им връзката с групата става по-бърза и надеждна, защото общувате с разкритите контакти, даже и основателят на групата да е извън мрежа.</string>
<string name="groups_reveal_visible">Връзка с контакта се вижда от групата</string> <string name="groups_reveal_visible">Отношенията ви с контакта са видими за групата</string>
<string name="groups_reveal_visible_revealed_by_us">Връзка с контакта се вижда от групата (разкрита от вас)</string> <string name="groups_reveal_visible_revealed_by_us">Отношенията ви с контакта са видими за групата (разкрити от вас)</string>
<string name="groups_reveal_visible_revealed_by_contact">Връзка с контакта се вижда от групата (разкрита от %s)</string> <string name="groups_reveal_visible_revealed_by_contact">Отношенията ви с контакта са видими за групата (разкрити от %s)</string>
<string name="groups_reveal_invisible">Връзка с контакта не се вижда от групата</string> <string name="groups_reveal_invisible">Отношенията ви с контакта не са видими за групата</string>
<!--Forums--> <!--Forums-->
<string name="no_forums">Няма форуми</string>
<string name="no_forums_action">Докоснете иконата с +, за да създадете свой или поискайте от контактите си да споделят форум с вас</string>
<string name="create_forum_title">Създаване на форум</string> <string name="create_forum_title">Създаване на форум</string>
<string name="choose_forum_hint">Изберете име за форума</string> <string name="choose_forum_hint">Име на форума</string>
<string name="create_forum_button">Създай форум</string> <string name="create_forum_button">Създаване на форум</string>
<string name="forum_created_toast">Форумът е създаден</string> <string name="forum_created_toast">Форумът е създаден</string>
<string name="no_forum_posts">Няма публикации</string>
<string name="no_posts">Няма публикации</string> <string name="no_posts">Няма публикации</string>
<plurals name="posts"> <plurals name="posts">
<item quantity="one">%d публикация</item> <item quantity="one">%d публикация</item>
<item quantity="other">%d публикации</item> <item quantity="other">%d публикации</item>
</plurals> </plurals>
<string name="forum_message_reply_hint">Нов отговор</string> <string name="forum_new_message_hint">Публикация</string>
<string name="btn_reply">Отговори</string> <string name="forum_message_reply_hint">Отговор</string>
<string name="forum_leave">Напусни форума</string> <string name="btn_reply">Отговаряне</string>
<string name="dialog_title_leave_forum">Потвърдете напускането на форума</string> <string name="forum_leave">Напускане на форума</string>
<string name="dialog_button_leave">Напусни</string> <string name="dialog_title_leave_forum">Потвърждение на напускане на форум</string>
<string name="dialog_message_leave_forum">Сигурни ли сте, че искате да напуснете този форум?\n\nКонтактите, с които сте го споделили може да спрат да получават публикации.</string>
<string name="dialog_button_leave">Напускане</string>
<string name="forum_left_toast">Напуснахте форума</string>
<!--Forum Sharing--> <!--Forum Sharing-->
<string name="forum_share_button">Сподели форум</string> <string name="forum_share_button">Споделяне форума</string>
<string name="contacts_selected">Избрани контакти</string> <string name="contacts_selected">Избрани контакти</string>
<string name="activity_share_toolbar_header">Избиране на контакти</string> <string name="activity_share_toolbar_header">Избиране на контакти</string>
<string name="no_contacts_selector">Няма контакти</string>
<string name="no_contacts_selector_action">Върнете се след като добавите контакти</string>
<string name="forum_shared_snackbar">Форумът е споделен с избраните контакти</string> <string name="forum_shared_snackbar">Форумът е споделен с избраните контакти</string>
<string name="forum_share_message">Добавете съобщение (незадължително)</string> <string name="forum_share_message">Добавете съобщение (незадължително)</string>
<string name="forum_share_error">Възникна грешка при споделянето на този форум.</string> <string name="forum_share_error">Грешка при споделянето на форума.</string>
<string name="forum_invitation_received">%1$s сподели форума \"%2$s\" с вас.</string> <string name="forum_invitation_received">%1$s сподели с вас форума %2$s.</string>
<string name="forum_invitation_sent">Споделихте форума \"%1$s\" с %2$s.</string> <string name="forum_invitation_sent">Споделихте форума %1$sсс %2$s.</string>
<string name="forum_invitations_title">Покани във форум</string> <string name="forum_invitations_title">Покани за членство във форум</string>
<string name="forum_invitation_exists">Вече приехте покана за този форум.\n\nС приемане на повече покани връзката с групата става по-бърза и надеждна.</string>
<string name="forum_joined_toast">Присъединихте се към форума</string>
<string name="forum_declined_toast">Поканата е отказана</string>
<string name="shared_by_format">Споделен от %s</string> <string name="shared_by_format">Споделен от %s</string>
<string name="forum_invitation_already_sharing">Вече е споделен</string> <string name="forum_invitation_already_sharing">Вече е споделен</string>
<string name="forum_invitation_response_accepted_sent">Приехте поканата за форум на %s.</string> <string name="forum_invitation_response_accepted_sent">Приехте поканата от %s за членство във форум.</string>
<string name="forum_invitation_response_declined_sent">Отказахте поканата във форум от %s.</string> <string name="forum_invitation_response_declined_sent">Отказахте поканата на %s за членство във форум.</string>
<string name="forum_invitation_response_declined_auto">Поканата от %s за членство във форум е отказана автоматично.</string>
<string name="forum_invitation_response_accepted_received">%s прие поканата във форум.</string> <string name="forum_invitation_response_accepted_received">%s прие поканата във форум.</string>
<string name="forum_invitation_response_declined_received">%s отказа поканата във форум.</string> <string name="forum_invitation_response_declined_received">%s отказа поканата във форум.</string>
<string name="sharing_status">Статус на споделянето</string> <string name="sharing_status">Състояние на споделяне</string>
<string name="sharing_status_forum">Всеки участник във форума може да го сподели с контактите си. Споделяте този форум със следните контакти. Възможно е да има и други, които не можете да видите.</string> <string name="sharing_status_forum">Всеки участник във форума може да го сподели с контактите си. Споделяте този форум със следните контакти. Възможно е да има и други, които не можете да видите.</string>
<string name="shared_with">Споделен с %1$d (%2$d онлайн)</string> <string name="shared_with">Споделен %1$d (на линия %2$d)</string>
<plurals name="forums_shared"> <plurals name="forums_shared">
<item quantity="one">%d форум, споделен от контакти</item> <item quantity="one">%d форум, споделен от контакти</item>
<item quantity="other">%d форума, споделени от контакти</item> <item quantity="other">%d форума, споделени от контакти</item>
</plurals> </plurals>
<string name="nobody">Никого</string> <string name="nobody">Никого</string>
<!--Blogs--> <!--Blogs-->
<string name="read_more">прочети още</string> <string name="blogs_other_blog_empty_state">Няма публикации</string>
<string name="blogs_write_blog_post">Нова блог публикация</string> <string name="read_more">повече</string>
<string name="blogs_write_blog_post">Нова публикация в блога</string>
<string name="blogs_write_blog_post_body_hint">Въведете своята публикация</string>
<string name="blogs_publish_blog_post">Публикуване</string> <string name="blogs_publish_blog_post">Публикуване</string>
<string name="blogs_blog_post_created">Блог публикацията е създадена</string> <string name="blogs_blog_post_created">Публикацията в блога е създадена</string>
<string name="blogs_blog_post_received">Нова блог публикация</string> <string name="blogs_blog_post_received">Получена е нова публикация в блога</string>
<string name="blogs_blog_post_scroll_to">Отвори</string> <string name="blogs_blog_post_scroll_to">Плъзване до нея</string>
<string name="blogs_feed_empty_state">Няма публикации</string>
<string name="blogs_feed_empty_state_action">Публикации от вашите контакти и абонираните блогове се показват тук.\n\nДокоснете иконата на писалка, за да направите публикация.</string>
<string name="blogs_remove_blog">Премахване на блог</string> <string name="blogs_remove_blog">Премахване на блог</string>
<string name="blogs_remove_blog_dialog_message">Сигурни ли сте, че желаете да изтриете блога?\n\nПубликациите ще бъдат премахнати от устройството ви, но не и от устройствата на другите членове.\n\nКонтактите, с които сте споделили този блог може да спрат да получават обновявания.</string>
<string name="blogs_remove_blog_ok">Премахване</string> <string name="blogs_remove_blog_ok">Премахване</string>
<string name="blogs_blog_removed">Блогът е премахнат</string>
<string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string> <string name="blogs_reblog_comment_hint">Добавете съобщение (незадължително)</string>
<string name="blogs_reblog_button">Реблог</string> <string name="blogs_reblog_button">Препубликуване</string>
<!--Blog Sharing--> <!--Blog Sharing-->
<string name="blogs_sharing_share">Споделяне на блог</string> <string name="blogs_sharing_share">Споделяне на блог</string>
<string name="blogs_sharing_error">Възникна грешка при споделянето на този блог.</string> <string name="blogs_sharing_error">Грешка при споделяне блога.</string>
<string name="blogs_sharing_button">Сподели блог</string> <string name="blogs_sharing_button">Споделяне на блог</string>
<string name="blogs_sharing_snackbar">Блогът е споделен с избраните контакти</string> <string name="blogs_sharing_snackbar">Блогът е споделен с избраните контакти</string>
<string name="blogs_sharing_response_accepted_sent">Приехте поканата в блог от %s.</string> <string name="blogs_sharing_response_accepted_sent">Приехте поканата на %s за абонамент за блог.</string>
<string name="blogs_sharing_response_declined_sent">Отказахте поканата в блог от %s.</string> <string name="blogs_sharing_response_declined_sent">Отказахте поканата на %s за абонамент за блог.</string>
<string name="blogs_sharing_response_accepted_received">%s прие поканата в блог.</string> <string name="blogs_sharing_response_declined_auto">Поканата на %s за абонамент за блог е отказана автоматично.</string>
<string name="blogs_sharing_response_declined_received">%s отказа поканата в блог.</string> <string name="blogs_sharing_response_accepted_received">%s прие поканата за абонамент за блог.</string>
<string name="blogs_sharing_invitation_received">%1$s сподели блога \"%2$s\" с вас.</string> <string name="blogs_sharing_response_declined_received">%s отказа поканата за абонамент за блог.</string>
<string name="blogs_sharing_invitation_sent">Споделихте блога \"%1$s\" с %2$s.</string> <string name="blogs_sharing_invitation_received">%1$s сподели с вас блога „%2$s.</string>
<string name="blogs_sharing_invitations_title">Блог покани</string> <string name="blogs_sharing_invitation_sent">Споделихте блога „%1$s“ с/ъс %2$s.</string>
<string name="blogs_sharing_invitations_title">Покани за абонамент за блог</string>
<string name="blogs_sharing_joined_toast">Абонирахте се за блогa</string>
<string name="blogs_sharing_declined_toast">Поканата е отказана</string>
<string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string> <string name="sharing_status_blog">Всеки абонат на блога може да го сподели с контактите си. Споделяте този блог със следните контакти. Възможно е да има и други, които не можете да видите.</string>
<!--RSS Feeds--> <!--RSS Feeds-->
<string name="blogs_rss_feeds_import">Внасяне на RSS емисия</string> <string name="blogs_rss_feeds_import">Внасяне на емисия на RSS</string>
<string name="blogs_rss_feeds_import_button">Внасяне</string> <string name="blogs_rss_feeds_import_button">Внасяне</string>
<string name="blogs_rss_feeds_import_hint">Въведете URL адреса на RSS емисията</string> <string name="blogs_rss_feeds_import_hint">Aдрес на емисия</string>
<string name="blogs_rss_feeds_import_error">Възникна грешка при внасянето на емисия.</string> <string name="blogs_rss_feeds_import_error">Грешка при внасяне на емисията.</string>
<string name="blogs_rss_feeds_import_exists">Емисията вече е внесена.</string>
<string name="blogs_rss_feeds">Емисии на RSS</string>
<string name="blogs_rss_feeds_manage_imported">Внесена:</string> <string name="blogs_rss_feeds_manage_imported">Внесена:</string>
<string name="blogs_rss_feeds_manage_author">Автор:</string> <string name="blogs_rss_feeds_manage_author">Автор:</string>
<string name="blogs_rss_feeds_manage_updated">Последно актуализиране:</string> <string name="blogs_rss_feeds_manage_updated">Последно обновяване:</string>
<string name="blogs_rss_remove_feed">Премахване на емисия</string> <string name="blogs_rss_remove_feed">Премахване на емисия</string>
<string name="blogs_rss_remove_feed_dialog_message">Сигурни ли сте, че желаете да изтриете емисията?\n\nПубликациите ще бъдат премахнати от устройството ви, но не и от устройствата на другите членове.\n\nКонтактите, с които сте споделили тази емисия може да спрат да получават обновявания.</string>
<string name="blogs_rss_remove_feed_ok">Премахване</string> <string name="blogs_rss_remove_feed_ok">Премахване</string>
<string name="blogs_rss_feeds_manage_empty_state">Няма емисии на RSS\n\nДокоснете иконата с +, за да внесете емисия</string>
<string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string> <string name="blogs_rss_feeds_manage_error">Възникна проблем при зареждането на емисиите ви. Моля, опитайте пак по-късно.</string>
<!--Settings Profile Picture-->
<string name="change_profile_picture">Докоснете за смяна на профилната снимка</string>
<string name="dialog_confirm_profile_picture_title">Смяна на профилна снимка</string>
<string name="dialog_confirm_profile_picture_remark">Само вашите контакти виждат това изображение</string>
<string name="change_profile_picture_failed_message">Грешка при смяна на профилната снимка</string>
<!--Settings Display--> <!--Settings Display-->
<!--Settings Network--> <string name="pref_language_title">Език и регион</string>
<string name="network_settings_title">Мрежа</string> <string name="pref_language_changed">Тази настройка ще име ефект след рестарт на Briar. Отпишете се и рестартирайте Briar.</string>
<string name="bluetooth_setting">Свързване чрез Bluetooth</string> <string name="pref_language_default">Спрямо системата</string>
<string name="bluetooth_setting_enabled">Когато контактите са наблизо</string> <string name="display_settings_title">Външен вид</string>
<string name="bluetooth_setting_disabled">Само при добавяне на контакти</string> <string name="pref_theme_title">Тема</string>
<!--How and when Tor will connect after Automatic: E.g. Don't connect (in China) or Use Tor with bridges (in Belarus)--> <string name="pref_theme_light">Светла</string>
<string name="pref_theme_dark">Тъмна</string>
<string name="pref_theme_auto">Автоматична (ден или нощ)</string>
<string name="pref_theme_system">Спрямо системата</string>
<!--Settings Connections-->
<string name="network_settings_title">Свързаност</string>
<string name="bluetooth_setting">Свързване с контактите чрез Bluetooth</string>
<string name="wifi_setting">Свързване с контактите в същата безжична мрежа</string>
<string name="tor_enable_title">Свързване с контактите през интернет</string>
<string name="tor_enable_summary">За повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor</string>
<string name="tor_network_setting">Начин на свързване към мрежата на Tor</string>
<string name="tor_network_setting_automatic">Автоматично, на база местоположение</string>
<string name="tor_network_setting_without_bridges">Използване на мрежата на Tor без мостове</string>
<string name="tor_network_setting_with_bridges">Използване на мрежата на Tor с мостове</string>
<string name="tor_network_setting_never">Без свързване с интернет</string>
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
<string name="tor_network_setting_summary">Автоматично: %1$s (в/ъв %2$s)</string>
<string name="tor_mobile_data_title">Използване на мобилни данни</string>
<string name="tor_only_when_charging_title">Свързване към интернет само на зарядно</string>
<string name="tor_only_when_charging_summary">Изключва се връзката с интернет, когато устройството се използва на батерия</string>
<!--Settings Security and Panic--> <!--Settings Security and Panic-->
<string name="security_settings_title">Сигурност</string> <string name="security_settings_title">Сигурност</string>
<string name="pref_lock_title">Заключване на приложението</string>
<string name="pref_lock_summary">Заключва се екрана, за да предпази Briar докато сте вписани</string>
<string name="pref_lock_disabled_summary">За да се възползвате от тази възможност, настройте заключване на екрана</string>
<string name="pref_lock_timeout_title">Заключване при бездействие</string>
<!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour--> <!--The %s placeholder is replaced with the following time spans, e.g. 5 Minutes, 1 Hour-->
<string name="pref_lock_timeout_summary">Briar се изключва автоматично при неактивност от %s</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_1">1 минута</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_5">5 минути</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_15">15 минути</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_30">30 минути</string>
<!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"--> <!--Will be shown in a list of lock times. Should fit into the %s of "automatically lock it after %s"-->
<string name="pref_lock_timeout_60">1 час</string>
<string name="pref_lock_timeout_never">Никога</string> <string name="pref_lock_timeout_never">Никога</string>
<string name="pref_lock_timeout_never_summary">Briar никога да не се заключва автоматично</string>
<string name="change_password">Промяна на парола</string> <string name="change_password">Промяна на парола</string>
<string name="current_password">Текуща парола</string>
<string name="choose_new_password">Нова парола</string>
<string name="confirm_new_password">Потвърдете новата парола</string>
<string name="password_changed">Паролата е променена.</string> <string name="password_changed">Паролата е променена.</string>
<string name="panic_setting">Настройка на паник бутон</string> <string name="panic_setting">Настройка на бутон за паника</string>
<string name="panic_setting_title">Паник бутон</string> <string name="panic_setting_title">Бутон за паника</string>
<string name="panic_setting_hint">Конфигурация на приложение за паник бутон</string> <string name="panic_setting_hint">Настройва се реакцията на Briar при използване на приложение за бутон за паника</string>
<string name="panic_app_setting_title">Приложение за паник бутон</string> <string name="panic_app_setting_title">Приложение бутон за паника</string>
<string name="unknown_app">непознато приложение</string> <string name="unknown_app">непознато приложение</string>
<string name="panic_app_setting_summary">Няма зададено приложение</string> <string name="panic_app_setting_summary">Няма зададено приложение</string>
<string name="panic_app_setting_none">Няма</string> <string name="panic_app_setting_none">Няма</string>
<string name="dialog_title_connect_panic_app">Потвърждение на паник приложение</string> <string name="dialog_title_connect_panic_app">Потвърждение на приложение при паника</string>
<string name="dialog_message_connect_panic_app">Сигурни ли сте, че искате да позволите на %1$s да задейства унищожителни действия на паник бутон?</string> <string name="dialog_message_connect_panic_app">Сигурни ли сте, че желаете да позволите на %1$s да задейства разрушителните действия на бутона за паника?</string>
<string name="panic_setting_destructive_action">Разрушителни действия</string>
<string name="panic_setting_signout_title">Отписване</string> <string name="panic_setting_signout_title">Отписване</string>
<string name="panic_setting_signout_summary">Отписване от Briar, ако паник бутонът е натиснат</string> <string name="panic_setting_signout_summary">Отписване от Briar при натиснат бутон за паника</string>
<string name="purge_setting_title">Изтриване на профил</string> <string name="purge_setting_title">Изтриване на профил</string>
<string name="purge_setting_summary">Изтриване на Briar профила, ако паник бутонът е натиснат. Внимание: Изтрива завинаги вашия профил, контакти и съобщения</string> <string name="purge_setting_summary">Профила на Briar се изтрива при натиснат бутон за паника. Внимание: Изтрива безвъзвратно профила, контактите и съобщенията</string>
<string name="uninstall_setting_title">Деинсталиране на Briar</string>
<string name="uninstall_setting_summary">Изисква ръчно потвърждение в паник случай</string>
<!--Settings Notifications--> <!--Settings Notifications-->
<string name="notification_settings_title">Известия</string> <string name="notification_settings_title">Известия</string>
<string name="notify_sign_in_title">Напомняне за вписване</string>
<string name="notify_sign_in_summary">Известие след рестарт на устройството или обновяване на приложението</string>
<string name="notify_private_messages_setting_title">Лични съобщения</string> <string name="notify_private_messages_setting_title">Лични съобщения</string>
<string name="notify_private_messages_setting_summary">Показвай известия за лични съобщения</string> <string name="notify_private_messages_setting_summary">Известия за лични съобщения</string>
<string name="notify_private_messages_setting_summary_26">Настройки на известия за лични съобщения</string>
<string name="notify_group_messages_setting_title">Групови съобщения</string> <string name="notify_group_messages_setting_title">Групови съобщения</string>
<string name="notify_group_messages_setting_summary">Показвай известия за групови съобщения</string> <string name="notify_group_messages_setting_summary">Известия за групови съобщения</string>
<string name="notify_forum_posts_setting_title">Форумни публикации</string> <string name="notify_group_messages_setting_summary_26">Настройки на известия за групови съобщения</string>
<string name="notify_forum_posts_setting_summary">Показвай известия за форумни публикации</string> <string name="notify_forum_posts_setting_title">Публикации във форуми</string>
<string name="notify_blog_posts_setting_title">Блог публикации</string> <string name="notify_forum_posts_setting_summary">Известия за публикации във форуми</string>
<string name="notify_blog_posts_setting_summary">Показвай известия за блог публикации</string> <string name="notify_forum_posts_setting_summary_26">Настройки на известия за публикации във форуми</string>
<string name="notify_blog_posts_setting_title">Публикации в блог</string>
<string name="notify_blog_posts_setting_summary">Известия за публикации в блог</string>
<string name="notify_blog_posts_setting_summary_26">Настройки на известия за публикации в блог</string>
<string name="notify_vibration_setting">Вибрация</string> <string name="notify_vibration_setting">Вибрация</string>
<string name="notify_lock_screen_setting_title">Заключен екран</string>
<string name="notify_lock_screen_setting_summary">Показвай известия за на заключен екран</string>
<string name="notify_sound_setting">Звук</string> <string name="notify_sound_setting">Звук</string>
<string name="notify_sound_setting_default">Мелодия по подразбиране</string> <string name="notify_sound_setting_default">Подразбиран тон на звънене</string>
<string name="notify_sound_setting_disabled">Никакви</string> <string name="notify_sound_setting_disabled">Няма</string>
<string name="choose_ringtone_title">Изберете рингтон</string> <string name="choose_ringtone_title">Избор на тон за звънене</string>
<string name="cannot_load_ringtone">Тонът за звънене не може да бъде зареден</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Изчезващи съобщения</string>
<string name="disappearing_messages_explanation_long">При включване, тази настройка прави бъдещите съобщения в този разговор да изчезват след 7\u00A0дни.
\n\nОтброяването при изпращача започва след като съобщението е било доставено, а при получателя след като го е прочел.
\n\nСъобщенията, които ще изчезнат са отбелязани с бомба.
\n\nИмайте предвид, че получателите могат да правят копия на получените от вас съобщения.
\n\nАко вие промените настройката промяната ще влезе в действие веднага, още върху следващото ви съобщение, а при вашите контакти след получаването му. Контактите ви също могат да правят промяна на тази настройка, което ще се отрази и на двама ви.</string>
<string name="learn_more">Научете повече</string>
<string name="disappearing_messages_summary">Бъдещите съобщения в разговора изчезват след 7\u00A0дни</string>
<!--Settings Feedback--> <!--Settings Feedback-->
<string name="feedback_settings_title">Отзиви</string> <string name="send_feedback">Изпращане на отзив</string>
<string name="send_feedback">Изпращане на отзиви</string>
<!--Link Warning--> <!--Link Warning-->
<string name="link_warning_title">Предупреждение за линк</string> <string name="link_warning_title">Предупреждение за препратка</string>
<string name="link_warning_intro">Линкът ще се отвори във външно приложение.</string> <string name="link_warning_intro">Препратката ще бъде отворена от външно приложение.</string>
<string name="link_warning_text">Линкът може да се използва, за да ви идентифицира. Помислете дали имате доверие на човека, който ви изпраща линка, и обмислете дали да не го отворите с Orfox.</string> <string name="link_warning_text">Така отворена може да бъде използвана, за да бъдете идентифицирани. Преценете дали имате доверие на подателя и обмислете дали да не я отворите с Tor Browser.</string>
<string name="link_warning_open_link">Отвори линк</string> <string name="link_warning_open_link">Отваряне</string>
<!--Crash Reporter--> <!--Crash Reporter-->
<string name="crash_report_title">Доклад на срив</string> <string name="crash_report_title">Доклад на срив</string>
<string name="briar_crashed">Извинете, Briar се срина.</string> <string name="briar_crashed">Извинете, Briar се срина.</string>
<string name="not_your_fault">Не е по ваша вина.</string> <string name="not_your_fault">Не е по ваша вина.</string>
<string name="please_send_report">Моля, помогнете да изградим по-добър Briar, като ни изпратите доклад.</string> <string name="please_send_report">Помогнете да направим Briar по-добър като ни изпратите доклад.</string>
<string name="report_is_encrypted">Гарантираме, че докладът е криптиран и изпратен безопасно.</string> <string name="report_is_encrypted">Даваме обещание, че докладът е шифрован и е изпратен добре защитен.</string>
<string name="feedback_title">Отзиви</string> <string name="feedback_title">Обратна връзка</string>
<string name="describe_crash">Опишете станалото (незадължително)</string> <string name="describe_crash">Опишете случилото се (незадължително)</string>
<string name="enter_feedback">Въведете отзив</string> <string name="enter_feedback">Въведете обратна връзка</string>
<string name="optional_contact_email">Имейл адресът ви (незадължително)</string> <string name="optional_contact_email">Адрес на електронна поща (по желание)</string>
<string name="include_debug_report_crash">Добави анонимни данни за срива</string> <string name="include_debug_report_crash">Изпращане на анонимни данни за срива</string>
<string name="include_debug_report_feedback">Добави анонимни данни за това устройствo</string> <string name="include_debug_report_feedback">Изпращане на анонимни данни за устройството</string>
<string name="could_not_load_report_data">Данните за доклада не можаха да заредят.</string> <string name="dev_report_user_info">Данни за потербителя</string>
<string name="dev_report_basic_info">Основна информация</string>
<string name="dev_report_device_info">Данни за устройството</string>
<string name="dev_report_stacktrace">Следа в стека</string>
<string name="dev_report_time_info">Данни за времената</string>
<string name="dev_report_memory">Памет</string>
<string name="dev_report_storage">Хранилище</string>
<string name="dev_report_connectivity">Свързаност</string>
<string name="dev_report_build_config">Настройка на изданието</string>
<string name="dev_report_logcat">Журнал на приложението</string>
<string name="dev_report_device_features">Характеристики</string>
<string name="send_report">Изпращане на доклад</string> <string name="send_report">Изпращане на доклад</string>
<string name="close">Затваряне</string> <string name="close">Затваряне</string>
<string name="dev_report_sending">Изпращане на доклад…</string>
<string name="dev_report_sent">Обратната връзка е изпратена</string>
<string name="dev_report_saved">Докладът е запазен. Ще бъде изпратен при следващото влизане в Briar.</string> <string name="dev_report_saved">Докладът е запазен. Ще бъде изпратен при следващото влизане в Briar.</string>
<string name="dev_report_error">Грешка при изпращане на доклад</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Отписване от Briar...</string> <string name="progress_title_logout">Отписване от Briar</string>
<!--Screen Filters & Tapjacking--> <!--Screen Filters & Tapjacking-->
<string name="screen_filter_title">Открит е овърлей на екрана</string> <string name="screen_filter_title">Открито е приложение отгоре</string>
<string name="screen_filter_body">Друго приложение се изчертава върху Briar. За ваша сигурност, Briar няма да реагира на докосване, докато друго приложение се изчертава отгоре.\n\nСледните приложения биха могли да се изчертават отгоре:\n\n%1$s</string>
<string name="screen_filter_body_api_30">Друго приложение се изчертава върху Briar. За ваша сигурност, Briar няма да реагира на докосване, докато друго приложение се изчертава отгоре.\n\nПрегледайте приложенията, за да го намерите.</string>
<string name="screen_filter_allow">Разрешаване на тези приложения да се изчертават отгоре</string>
<string name="screen_filter_review_apps">Преглеждане</string>
<!--Permission Requests--> <!--Permission Requests-->
<string name="permission_camera_title">Разрешение за камера</string>
<string name="permission_camera_request_body">За да сканира кода за QR, Briar трябва да използва камерата.</string>
<string name="permission_location_title">Разрешение за местоположение</string>
<string name="permission_location_request_body">За да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_camera_location_title">Камера и местоположение</string>
<string name="permission_camera_location_request_body">За да сканира кодове за QR, на Briar му е необходимо разрешение за достъп до камерата.\n\nЗа да открива устройства чрез Bluetooth, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_camera_denied_body">Отказахте достъп до камерата, но тя е необходима за добавянето на контакти.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_location_denied_body">Отказахте достъп до местоположението, но то е необходимо за откриване на устройства чрез Bluetooth.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="permission_location_setting_title">Настройки на местоположението</string>
<string name="permission_location_setting_body">Местоположението на устройството ви трябва да е включено, за да бъдат откривани устройства чрез Bluetooth. За да продължите включете местоположението. След това можете да го изключите.</string>
<string name="permission_location_setting_button">Включване на местеположение</string>
<string name="qr_code">Код за QR</string>
<string name="show_qr_code_fullscreen">Код за QR на цял екран</string>
<!--App Locking--> <!--App Locking-->
<string name="lock_unlock">Отключете Briar</string>
<string name="lock_unlock_verbose">Въведете своя PIN, фигура или парола</string>
<string name="lock_unlock_fingerprint_description">Докоснете сензора за отпечатъци с регистрирания пръст</string>
<string name="lock_unlock_password">Използване на парола</string>
<string name="lock_is_locked">Briar е заключен</string>
<string name="lock_tap_to_unlock">Докоснете за отключване</string>
<!--Connections Screen-->
<string name="transports_help_text">Briar може да се свърже с контактите ви през интернет, Wi-Fi или Bluetooth.\n\nЗа повече поверителност цялата връзка към интернет се пренасочва през мрежата на Tor.\n\nАко даден контакт може да бъде достъпен чрез няколко метода Briar ги използва успоредно.</string>
<!--Screenshots-->
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_alice">Ани</string>
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_bob">Боби</string>
<!--This is a name to be used in screenshots. Feel free to change it to a local name.-->
<string name="screenshot_carol">Васко</string>
<!--This is a message to be used in screenshots. Please use the same translation for Bob!-->
<string name="screenshot_message_1">Здравей, Боби!</string>
<!--This is a message to be used in screenshots. Please use the same translation for Alice!-->
<string name="screenshot_message_2">Здравей, Ани! Благодаря ти, че ми каза за Briar!</string>
<!--This is a message to be used in screenshots.-->
<string name="screenshot_message_3">Радвам се, че ти харесва! И ти би направил същото 😀</string>
</resources> </resources>

View File

@@ -15,7 +15,7 @@
<string name="confirm_password">Potwierdź hasło</string> <string name="confirm_password">Potwierdź hasło</string>
<string name="name_too_long">Nazwa użytkownika jest zbyt długa</string> <string name="name_too_long">Nazwa użytkownika jest zbyt długa</string>
<string name="password_too_weak">Hasło jest zbyt długie</string> <string name="password_too_weak">Hasło jest zbyt długie</string>
<string name="passwords_do_not_match">Hasła się nie zgadzają</string> <string name="passwords_do_not_match">Hasła różnią się</string>
<string name="create_account_button">Utwórz Konto</string> <string name="create_account_button">Utwórz Konto</string>
<string name="more_info">Więcej Informacji</string> <string name="more_info">Więcej Informacji</string>
<string name="don_t_ask_again">Nie pytaj ponownie</string> <string name="don_t_ask_again">Nie pytaj ponownie</string>
@@ -27,10 +27,11 @@
<!--Login--> <!--Login-->
<string name="enter_password">Hasło</string> <string name="enter_password">Hasło</string>
<string name="try_again">Złe hasło, spróbuj ponownie</string> <string name="try_again">Złe hasło, spróbuj ponownie</string>
<string name="dialog_title_cannot_check_password">Nie można zweryfikować hasła</string>
<string name="sign_in_button">Zaloguj Się</string> <string name="sign_in_button">Zaloguj Się</string>
<string name="forgotten_password">Zapomniałem hasło</string> <string name="forgotten_password">Zapomniałem hasło</string>
<string name="dialog_title_lost_password">Nie pamiętam hasła</string> <string name="dialog_title_lost_password">Nie pamiętam hasła</string>
<string name="dialog_message_lost_password">Twoje konto Briar jest zaszyfrowane na Twoim urządzeniu nie w chmurze, więc nie będzie można zresetować Twojego hasła. Czy chcesz usunąć swoje konto i stworzyć nowe?\n\nUwaga: Twoje hasła, kontakty i wiadomości będą utracone.</string> <string name="dialog_message_lost_password">Twoje konto Briar jest zaszyfrowane na Twoim urządzeniu, nie w chmurze, więc nie będzie można zresetować Twojego hasła. Czy chcesz usunąć swoje konto i stworzyć nowe?\n\nUwaga: Twoje hasła, kontakty i wiadomości będą utracone.</string>
<string name="startup_failed_notification_title">Briar nie mógł się uruchomić</string> <string name="startup_failed_notification_title">Briar nie mógł się uruchomić</string>
<string name="startup_failed_notification_text">Naciśnij, aby uzyskać więcej informacji</string> <string name="startup_failed_notification_text">Naciśnij, aby uzyskać więcej informacji</string>
<string name="startup_failed_activity_title">Briar nie mógł się uruchomić</string> <string name="startup_failed_activity_title">Briar nie mógł się uruchomić</string>
@@ -45,7 +46,9 @@
<item quantity="other">To jest testowa wersja Briar. Twoje konto wygaśnie za %d dni i nie będzie odnowione.</item> <item quantity="other">To jest testowa wersja Briar. Twoje konto wygaśnie za %d dni i nie będzie odnowione.</item>
</plurals> </plurals>
<string name="expiry_date_reached">Ten program wygasł.\nDziękujemy za testy!</string> <string name="expiry_date_reached">Ten program wygasł.\nDziękujemy za testy!</string>
<string name="download_briar">Aby móc nadal korzystać z Briar, pobierz najnowszą wersję.</string>
<string name="create_new_account">Musisz utworzyć nowe konto, ale możesz użyć takiej samej nazwy użytkownika.</string> <string name="create_new_account">Musisz utworzyć nowe konto, ale możesz użyć takiej samej nazwy użytkownika.</string>
<string name="download_briar_button">Pobierz najnowszą wersję</string>
<string name="startup_open_database">Deszyfruję Bazę Danych...</string> <string name="startup_open_database">Deszyfruję Bazę Danych...</string>
<string name="startup_migrate_database">Aktualizuję Bazę Danych...</string> <string name="startup_migrate_database">Aktualizuję Bazę Danych...</string>
<string name="startup_compact_database">Kompaktowanie Bazy Danych…</string> <string name="startup_compact_database">Kompaktowanie Bazy Danych…</string>
@@ -60,12 +63,16 @@
<string name="lock_button">Zablokuj Aplikację</string> <string name="lock_button">Zablokuj Aplikację</string>
<string name="settings_button">Ustawienia</string> <string name="settings_button">Ustawienia</string>
<string name="sign_out_button">Wyloguj się</string> <string name="sign_out_button">Wyloguj się</string>
<string name="transports_onboarding_text">Dotknij tutaj aby zmienić w jaki sposób Briar łączy się z twoimi kontaktami.</string>
<!--Transports: Tor--> <!--Transports: Tor-->
<string name="transport_tor">Internet</string> <string name="transport_tor">Internet</string>
<string name="tor_plugin_status_inactive">Briar nie może połączyć się z Internetem</string>
<!--Transports: Wi-Fi--> <!--Transports: Wi-Fi-->
<string name="transport_lan">Wi-Fi</string> <string name="transport_lan">Wi-Fi</string>
<!--Transports: Bluetooth--> <!--Transports: Bluetooth-->
<string name="transport_bt">Bluetooth</string> <string name="transport_bt">Bluetooth</string>
<string name="bt_device_status_on">Bluetooth jest włączony</string>
<string name="bt_device_status_off">Bluetooth jest wyłączony</string>
<!--Notifications--> <!--Notifications-->
<string name="reminder_notification_title">Wylogowano z Briar</string> <string name="reminder_notification_title">Wylogowano z Briar</string>
<string name="reminder_notification_text">Dotknij, aby zalogować się ponownie</string> <string name="reminder_notification_text">Dotknij, aby zalogować się ponownie</string>
@@ -128,6 +135,7 @@
<string name="date_no_private_messages">Brak wiadomości.</string> <string name="date_no_private_messages">Brak wiadomości.</string>
<string name="no_private_messages">Brak wiadomości do pokazania</string> <string name="no_private_messages">Brak wiadomości do pokazania</string>
<string name="message_hint">Nowa wiadomość</string> <string name="message_hint">Nowa wiadomość</string>
<string name="message_hint_auto_delete">Nowa znikająca wiadomość</string>
<string name="image_caption_hint">Dodaj podpis (opcjonalne)</string> <string name="image_caption_hint">Dodaj podpis (opcjonalne)</string>
<string name="image_attach">Załącz obraz</string> <string name="image_attach">Załącz obraz</string>
<string name="image_attach_error">Nie udało się dołączyć obrazu(ów)</string> <string name="image_attach_error">Nie udało się dołączyć obrazu(ów)</string>
@@ -135,12 +143,18 @@
<string name="image_attach_error_invalid_mime_type">Format obrazu jest nieobsługiwany: %s</string> <string name="image_attach_error_invalid_mime_type">Format obrazu jest nieobsługiwany: %s</string>
<string name="set_contact_alias">Zmień nazwę kontaktu</string> <string name="set_contact_alias">Zmień nazwę kontaktu</string>
<string name="set_contact_alias_hint">Nazwa kontaktu</string> <string name="set_contact_alias_hint">Nazwa kontaktu</string>
<string name="menu_item_disappearing_messages">Znikające wiadomości</string>
<string name="menu_item_connect_via_bluetooth">Połącz przez Bluetooth</string> <string name="menu_item_connect_via_bluetooth">Połącz przez Bluetooth</string>
<string name="dialog_title_connect_via_bluetooth">Połącz przez Bluetooth</string> <string name="dialog_title_connect_via_bluetooth">Połącz przez Bluetooth</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."--> <!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
<!--The placeholder at the end will add "Tap to learn more."--> <!--The placeholder at the end will add "Tap to learn more."-->
<!--The first placeholder will show a contact's name. The second placeholder will show a duration like "7 days". The third placeholder at the end will add "Tap to learn more."--> <!--The first placeholder will show a contact's name. The second placeholder will show a duration like "7 days". The third placeholder at the end will add "Tap to learn more."-->
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."--> <!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
<string name="tap_to_learn_more">Dotknij tutaj aby dowiedzieć się więcej.</string>
<string name="auto_delete_changed_warning_send">Wyślij mimo to</string>
<string name="delete_all_messages">Usuń wszystkie wiadomości</string>
<string name="dialog_title_delete_all_messages">Potwierdź usunięcie wiadomości</string>
<string name="dialog_message_delete_all_messages">Na pewno chcesz usunąć wszystkie wiadomości?</string>
<string name="dialog_title_not_all_messages_deleted">Nie mogłem usunąć wszystkich wiadomości</string> <string name="dialog_title_not_all_messages_deleted">Nie mogłem usunąć wszystkich wiadomości</string>
<string name="delete_contact">Usuń kontakt</string> <string name="delete_contact">Usuń kontakt</string>
<string name="dialog_title_delete_contact">Potwierdź usunięcie kontaktu</string> <string name="dialog_title_delete_contact">Potwierdź usunięcie kontaktu</string>
@@ -173,7 +187,7 @@
<string name="connecting_to_device">Łączenie z urządzeniem\u2026</string> <string name="connecting_to_device">Łączenie z urządzeniem\u2026</string>
<string name="authenticating_with_device">Autoryzowanie z urządzeniem\u2026</string> <string name="authenticating_with_device">Autoryzowanie z urządzeniem\u2026</string>
<string name="connection_error_title">Nie udało się połączyć z kontaktem</string> <string name="connection_error_title">Nie udało się połączyć z kontaktem</string>
<string name="connection_error_feedback">Jeśli problem będzie występować dalej, proszę <a href="feedback">wysłać zgłoszenie</a> aby pomóc nam ulepszyć aplikację.</string> <string name="connection_error_feedback">Jeśli problem będzie występować dalej, proszę <a href="feedback">wysłać opinię</a> aby pomóc nam ulepszyć aplikację.</string>
<!--Adding Contacts Remotely--> <!--Adding Contacts Remotely-->
<string name="add_contact_remotely_title_case">Dodaj Kontakt na odległość</string> <string name="add_contact_remotely_title_case">Dodaj Kontakt na odległość</string>
<string name="add_contact_nearby_title">Dodaj kontakt w pobliżu</string> <string name="add_contact_nearby_title">Dodaj kontakt w pobliżu</string>
@@ -401,6 +415,8 @@
<string name="blogs_rss_feeds_import_button">Zaimportuj</string> <string name="blogs_rss_feeds_import_button">Zaimportuj</string>
<string name="blogs_rss_feeds_import_hint">Wprowadź adres URL do kanału RSS</string> <string name="blogs_rss_feeds_import_hint">Wprowadź adres URL do kanału RSS</string>
<string name="blogs_rss_feeds_import_error">Przepraszamy! Wystąpił błąd podczas importowania twojego kanału RSS</string> <string name="blogs_rss_feeds_import_error">Przepraszamy! Wystąpił błąd podczas importowania twojego kanału RSS</string>
<string name="blogs_rss_feeds_import_exists">Ten feed został już zaimportowany.</string>
<string name="blogs_rss_feeds">Feedy RSS</string>
<string name="blogs_rss_feeds_manage_imported">Zaimportowane:</string> <string name="blogs_rss_feeds_manage_imported">Zaimportowane:</string>
<string name="blogs_rss_feeds_manage_author">Autor:</string> <string name="blogs_rss_feeds_manage_author">Autor:</string>
<string name="blogs_rss_feeds_manage_updated">Ostatnio Zaktualizowane:</string> <string name="blogs_rss_feeds_manage_updated">Ostatnio Zaktualizowane:</string>
@@ -410,6 +426,7 @@
<string name="blogs_rss_feeds_manage_empty_state">Brak RSS do wyświetlenia\n\nDotknij ikonki + aby zaimportować kanał.</string> <string name="blogs_rss_feeds_manage_empty_state">Brak RSS do wyświetlenia\n\nDotknij ikonki + aby zaimportować kanał.</string>
<string name="blogs_rss_feeds_manage_error">Wystąpił problem podczas ładowania twoich kanałów RSS. Proszę spróbować ponownie później.</string> <string name="blogs_rss_feeds_manage_error">Wystąpił problem podczas ładowania twoich kanałów RSS. Proszę spróbować ponownie później.</string>
<!--Settings Profile Picture--> <!--Settings Profile Picture-->
<string name="dialog_confirm_profile_picture_title">Zmień zdjęcie profilowe</string>
<!--Settings Display--> <!--Settings Display-->
<string name="pref_language_title">Język &amp; region</string> <string name="pref_language_title">Język &amp; region</string>
<string name="pref_language_changed">Te ustawienia zostaną zastosowane gdy zrestartujesz Briar. Proszę się wylogować i zrestartować Briar.</string> <string name="pref_language_changed">Te ustawienia zostaną zastosowane gdy zrestartujesz Briar. Proszę się wylogować i zrestartować Briar.</string>
@@ -422,6 +439,7 @@
<string name="pref_theme_system">Domyślny systemu</string> <string name="pref_theme_system">Domyślny systemu</string>
<!--Settings Connections--> <!--Settings Connections-->
<string name="network_settings_title">Połączenia</string> <string name="network_settings_title">Połączenia</string>
<string name="tor_enable_summary">Dla zapewnienia prywatności, wszystkie połączenia przechodzą przez sieć Tor</string>
<string name="tor_network_setting_automatic">Automatycznie bazując na lokalizacji</string> <string name="tor_network_setting_automatic">Automatycznie bazując na lokalizacji</string>
<!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"--> <!--How and when Briar will connect to Tor: E.g. "Don't connect to the Internet (in China)" or "Use Tor network with bridges (in Belarus)"-->
<string name="tor_network_setting_summary">Automatycznie: %1$s (za %2$s)</string> <string name="tor_network_setting_summary">Automatycznie: %1$s (za %2$s)</string>
@@ -489,6 +507,7 @@
<string name="choose_ringtone_title">Wybierz dzwonek</string> <string name="choose_ringtone_title">Wybierz dzwonek</string>
<string name="cannot_load_ringtone">Nie mogę załadować dzwonka</string> <string name="cannot_load_ringtone">Nie mogę załadować dzwonka</string>
<!--Conversation Settings--> <!--Conversation Settings-->
<string name="disappearing_messages_title">Znikające wiadomości</string>
<string name="learn_more">Dowiedz się więcej</string> <string name="learn_more">Dowiedz się więcej</string>
<!--Settings Feedback--> <!--Settings Feedback-->
<string name="send_feedback">Wyślij opinię</string> <string name="send_feedback">Wyślij opinię</string>
@@ -505,7 +524,7 @@
<string name="report_is_encrypted">Obiecujemy, że raport z awarii jest zaszyfrowany i wysyłany bezpiecznie.</string> <string name="report_is_encrypted">Obiecujemy, że raport z awarii jest zaszyfrowany i wysyłany bezpiecznie.</string>
<string name="feedback_title">Twoja opinia</string> <string name="feedback_title">Twoja opinia</string>
<string name="describe_crash">Opisz co się stało (opcjonalne)</string> <string name="describe_crash">Opisz co się stało (opcjonalne)</string>
<string name="enter_feedback">Wprowadź swoje uwagi</string> <string name="enter_feedback">Napisz swoją opinię</string>
<string name="optional_contact_email">Twój adres email (opcjonalne)</string> <string name="optional_contact_email">Twój adres email (opcjonalne)</string>
<string name="include_debug_report_crash">Załącz anonimowe dane na temat awarii</string> <string name="include_debug_report_crash">Załącz anonimowe dane na temat awarii</string>
<string name="include_debug_report_feedback">Załącz anonimowe dane o tym urządzeniu</string> <string name="include_debug_report_feedback">Załącz anonimowe dane o tym urządzeniu</string>
@@ -515,6 +534,8 @@
<string name="dev_report_connectivity">Łączenie</string> <string name="dev_report_connectivity">Łączenie</string>
<string name="send_report">Wyślij raport</string> <string name="send_report">Wyślij raport</string>
<string name="close">Zamknij</string> <string name="close">Zamknij</string>
<string name="dev_report_sending">Wysyłanie opinii...</string>
<string name="dev_report_sent">Wysłano opinię</string>
<string name="dev_report_saved">Raport zapisany. Zostanie wysłany następnym razem kiedy zalogujesz się do Briar.</string> <string name="dev_report_saved">Raport zapisany. Zostanie wysłany następnym razem kiedy zalogujesz się do Briar.</string>
<!--Sign Out--> <!--Sign Out-->
<string name="progress_title_logout">Wylogowywanie z Briar...</string> <string name="progress_title_logout">Wylogowywanie z Briar...</string>

View File

@@ -12,6 +12,11 @@
<dimen name="margin_xlarge">32dp</dimen> <dimen name="margin_xlarge">32dp</dimen>
<dimen name="margin_xxlarge">64dp</dimen> <dimen name="margin_xxlarge">64dp</dimen>
<!-- hero icons -->
<dimen name="hero_square">128dp</dimen>
<dimen name="hero_rect_width">192dp</dimen>
<dimen name="hero_rect_height">96dp</dimen>
<!-- v2 dimens --> <!-- v2 dimens -->
<dimen name="text_size_tiny">12sp</dimen> <dimen name="text_size_tiny">12sp</dimen>
<dimen name="text_size_small">14sp</dimen> <dimen name="text_size_small">14sp</dimen>

View File

@@ -46,14 +46,12 @@
<string name="forgotten_password">I have forgotten my password</string> <string name="forgotten_password">I have forgotten my password</string>
<string name="dialog_title_lost_password">Lost Password</string> <string name="dialog_title_lost_password">Lost Password</string>
<string name="dialog_message_lost_password">Your Briar account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost.</string> <string name="dialog_message_lost_password">Your Briar account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost.</string>
<string name="startup_failed_notification_title">Briar could not start</string>
<string name="startup_failed_notification_text">Tap for more information.</string>
<string name="startup_failed_activity_title">Briar Startup Failure</string> <string name="startup_failed_activity_title">Briar Startup Failure</string>
<string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong. Please set your device\'s clock to the right time and try again.</string> <string name="startup_failed_clock_error">Briar was unable to start because your device\'s clock is wrong.\n\nPlease set your device\'s clock to the right time and try again.</string>
<string name="startup_failed_db_error">For some reason, your Briar database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string> <string name="startup_failed_db_error">Briar was unable to open the database containing your account, your contacts and your messages.\n\nPlease upgrade to the latest version of the app and try again, or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string> <string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version.\n\nYou must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string>
<string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string> <string name="startup_failed_data_too_new_error">Your account was created with a newer version of this app and cannot be opened with this version.\n\nPlease upgrade to the latest version and try again.</string>
<string name="startup_failed_service_error">Briar was unable to start a required plugin. Reinstalling Briar usually solves this problem. However, please note that you will then lose your account and all data associated with it since Briar is not using central servers to store your data on.</string> <string name="startup_failed_service_error">Briar was unable to start a required component.\n\nPlease upgrade to the latest version of the app and try again.</string>
<plurals name="expiry_warning"> <plurals name="expiry_warning">
<item quantity="one">This is a test version of Briar. Your account will expire in %d day and cannot be renewed.</item> <item quantity="one">This is a test version of Briar. Your account will expire in %d day and cannot be renewed.</item>
<item quantity="other">This is a test version of Briar. Your account will expire in %d days and cannot be renewed.</item> <item quantity="other">This is a test version of Briar. Your account will expire in %d days and cannot be renewed.</item>
@@ -630,7 +628,7 @@
<!-- Crash Reporter --> <!-- Crash Reporter -->
<string name="crash_report_title">Briar Crash Report</string> <string name="crash_report_title">Briar Crash Report</string>
<string name="briar_crashed">Sorry, Briar has crashed.</string> <string name="briar_crashed">Sorry, Briar has crashed</string>
<string name="not_your_fault">This is not your fault.</string> <string name="not_your_fault">This is not your fault.</string>
<string name="please_send_report">Please help us build a better Briar by sending us a crash report.</string> <string name="please_send_report">Please help us build a better Briar by sending us a crash report.</string>
<string name="report_is_encrypted">We promise that the report is encrypted and sent securely.</string> <string name="report_is_encrypted">We promise that the report is encrypted and sent securely.</string>
@@ -694,33 +692,9 @@
<!-- Connections Screen --> <!-- Connections Screen -->
<string name="transports_help_text">Briar can connect to your contacts via the Internet, Wi-Fi or Bluetooth.\n\nAll Internet connections go through the Tor network for privacy.\n\nIf a contact can be reached by multiple methods, Briar uses them in parallel.</string> <string name="transports_help_text">Briar can connect to your contacts via the Internet, Wi-Fi or Bluetooth.\n\nAll Internet connections go through the Tor network for privacy.\n\nIf a contact can be reached by multiple methods, Briar uses them in parallel.</string>
<!-- Transfer Data via Removable Drives -->
<string name="removable_drive_menu_title">Transfer data</string>
<string name="removable_drive_intro">You can send encrypted messages to your contact using removable storage such as USB flash drives or SD cards.\n\nIf your contact has sent you a removable drive containing encrypted messages, you can import the messages into Briar by using the receive button below.</string>
<string name="removable_drive_title_send">Send data</string>
<string name="removable_drive_title_receive">Receive data</string>
<string name="removable_drive_send_intro">Tap the button below to create a new file containing the encrypted messages. You can choose where the file will be saved.\n\nIf you want to save the file on a removable drive, insert the drive now.</string>
<string name="removable_drive_send_no_data">There are currently no messages waiting to be sent to this contact.</string>
<string name="removable_drive_send_not_supported">This contact is using an old version of Briar which does not yet support this feature.</string>
<string name="removable_drive_send_button">Choose file for export</string>
<string name="removable_drive_ongoing">Please wait for ongoing task to complete</string>
<string name="removable_drive_receive_intro">Tap the button below to choose the file that your contact sent you.\n\nIf the file is on a removable drive, insert the drive now.</string>
<string name="removable_drive_receive_button">Choose file for import</string>
<string name="removable_drive_success_send_title">Export successful</string>
<string name="removable_drive_success_send_text">Data exported successfully. You now have 28 days to transport the file to your contact.\n\nIf the file is on a removable drive, use the notification in the status bar to eject the drive before unplugging it.</string>
<string name="removable_drive_success_receive_title">Import successful</string>
<string name="removable_drive_success_receive_text">All encrypted messages contained in this file have been received.</string>
<string name="removable_drive_error_send_title">Error exporting data</string>
<string name="removable_drive_error_send_text">There was an error writing data to the file.\n\nIf you are using a removable drive, ensure that it is properly inserted and try again.\n\nIf the error persists, please send feedback to let the Briar team know about the issue.</string>
<string name="removable_drive_error_receive_title">Error importing data</string>
<string name="removable_drive_error_receive_text">The selected file did not contain anything that Briar could recognize.\n\nPlease check that you chose the right file.\n\nIf your contact created the file more than 28 days ago, Briar will not be able to recognize it.</string>
<!-- Share app offline --> <!-- Share app offline -->
<string name="hotspot_title">Share Briar offline</string> <string name="hotspot_title">Share this app offline</string>
<string name="hotspot_intro">Share this app with someone nearby without internet connection by using your phone\'s Wi-Fi. <string name="hotspot_intro">Share this app with someone nearby without Internet connection by using your phone\'s Wi-Fi.
\n\nYour phone will start a Wi-Fi hotspot. People nearby can connect to the hotspot and download the Briar app from your phone.</string> \n\nYour phone will start a Wi-Fi hotspot. People nearby can connect to the hotspot and download the Briar app from your phone.</string>
<string name="hotspot_button_start_sharing">Start hotspot</string> <string name="hotspot_button_start_sharing">Start hotspot</string>
<string name="hotspot_button_stop_sharing">Stop hotspot</string> <string name="hotspot_button_stop_sharing">Stop hotspot</string>
@@ -733,18 +707,22 @@
<string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string> <string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
<string name="wifi_settings_title">Wi-Fi setting</string> <string name="wifi_settings_title">Wi-Fi setting</string>
<string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string> <string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>
<string name="wifi_settings_request_denied_body">You have denied to enable Wi-Fi, but Briar needs to use Wi-Fi.\n\nPlease consider enabling it.</string> <string name="wifi_settings_request_denied_body">You have denied permission to enable Wi-Fi, but Briar needs to use Wi-Fi.\n\nPlease consider enabling it.</string>
<string name="hotspot_tab_manual">Manual</string> <string name="hotspot_tab_manual">Manual</string>
<!-- The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s -->
<string name="hotspot_scanning_a_qr_code">scanning a QR code</string> <string name="hotspot_scanning_a_qr_code">scanning a QR code</string>
<!-- Wi-Fi setup --> <!-- Wi-Fi setup -->
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' --> <!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
<string name="hotspot_manual_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by entering the details below or %s. When they have connected to the hotspot, press \'Next\'.</string> <string name="hotspot_manual_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by adding it in their device\'s Wi-Fi settings using the details below or by %s. When they have connected to the hotspot, press \'Next\'.</string>
<string name="hotspot_manual_wifi_ssid">Network name (SSID)</string> <string name="hotspot_manual_wifi_ssid">Network name</string>
<string name="hotspot_qr_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by scanning this QR code. When they have connected to the hotspot, press \'Next\'.</string> <string name="hotspot_qr_wifi">Your phone is providing a Wi-Fi hotspot. People who want to download Briar can connect to the hotspot by scanning this QR code. When they have connected to the hotspot, press \'Next\'.</string>
<string name="hotspot_peer_connected">Successfully connected</string> <string name="hotspot_no_peers_connected">No devices connected</string>
<string name="hotspot_peer_connected_action">Show download info</string> <plurals name="hotspot_peers_connected">
<item quantity="one">%s device connected</item>
<item quantity="other">%s devices connected</item>
</plurals>
<!-- Download link --> <!-- Download link -->
<!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' --> <!-- The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code' -->
@@ -758,21 +736,21 @@
<string name="website_download_outro">After the download is complete, open the downloaded file and install it.</string> <string name="website_download_outro">After the download is complete, open the downloaded file and install it.</string>
<string name="website_troubleshooting_title">Troubleshooting</string> <string name="website_troubleshooting_title">Troubleshooting</string>
<string name="website_troubleshooting_1">If you cannot download the app, try it with a different web browser app.</string> <string name="website_troubleshooting_1">If you cannot download the app, try it with a different web browser app.</string>
<string name="website_troubleshooting_2_old">To install the downloaded app, you might need to allow installation of apps from \"Unknown sources\" in system settings. Afterwards, you may need to download the app again. We recommend to undo that after successful installation.</string> <string name="website_troubleshooting_2_old">To install the downloaded app, you might need to allow installation of apps from \"Unknown sources\" in system settings. Afterwards, you may need to download the app again. We recommend disabling the \"Unknown sources\" setting after installing the app.</string>
<string name="website_troubleshooting_2_new">To install the downloaded app, you might need to allow your browser to install unknown apps. We recommend to undo that after successful installation.</string> <string name="website_troubleshooting_2_new">To install the downloaded app, you might need to allow your browser to install unknown apps. After installing the app, we recommend removing the browser\'s permission to install unknown apps.</string>
<string name="hotspot_help_wifi_title">Problems with connecting to Wi-Fi:</string> <string name="hotspot_help_wifi_title">Problems with connecting to Wi-Fi:</string>
<string name="hotspot_help_wifi_1">Try disabling and re-enabling Wi-Fi on both phones and try again.</string> <string name="hotspot_help_wifi_1">Try disabling and re-enabling Wi-Fi on both phones and try again.</string>
<string name="hotspot_help_wifi_2">If your phone complains that the Wi-Fi has no internet, tell it that you want to stay connected anyway.</string> <string name="hotspot_help_wifi_2">If your phone complains that the Wi-Fi has no Internet, tell it that you want to stay connected anyway.</string>
<string name="hotspot_help_site_title">Problems visiting the local website:</string> <string name="hotspot_help_site_title">Problems visiting the local website:</string>
<string name="hotspot_help_site_1">Double check that you entered the address exactly as shown. A small error can make it fail.</string> <string name="hotspot_help_site_1">Double check that you entered the address exactly as shown. A small error can make it fail.</string>
<string name="hotspot_help_site_2">Ensure that your phone is still connected to the correct Wi-Fi (see above) when you try to access the site.</string> <string name="hotspot_help_site_2">Ensure that your phone is still connected to the correct Wi-Fi (see above) when you try to access the site.</string>
<string name="hotspot_help_site_3">Check that you don\'t have any active firewall apps that may block the access.</string> <string name="hotspot_help_site_3">If you have a firewall app, check that it isn\'t blocking access.</string>
<string name="hotspot_help_site_4">If you can visit the site, but not download the Briar app, try it with a different web browser app.</string> <string name="hotspot_help_site_4">If you can visit the site, but not download the Briar app, try it with a different web browser app.</string>
<string name="hotspot_help_fallback_title">Nothing works?</string> <string name="hotspot_help_fallback_title">Nothing works?</string>
<string name="hotspot_help_fallback_intro">You can try to save the app as an .apk file to share in some other way. Once on the other device, it can be used to install Briar. <string name="hotspot_help_fallback_intro">You can try to save the app as an .apk file to share in some other way. Once the file has been transferred to the other device, it can be used to install Briar.
\n\nTip: For sharing via Bluetooth, you might need to rename the file to end with .zip first.</string> \n\nTip: For sharing via Bluetooth, you might need to rename the file to end with .zip first.</string>
<string name="hotspot_help_fallback_button">Save app install file</string> <string name="hotspot_help_fallback_button">Save app</string>
<!-- error handling --> <!-- error handling -->
<string name="hotspot_error_intro">Something went wrong while trying to share the app via Wi-Fi:</string> <string name="hotspot_error_intro">Something went wrong while trying to share the app via Wi-Fi:</string>
@@ -780,9 +758,34 @@
<string name="hotspot_error_start_callback_failed">Hotspot failed to start: error %s</string> <string name="hotspot_error_start_callback_failed">Hotspot failed to start: error %s</string>
<string name="hotspot_error_start_callback_failed_unknown">Hotspot failed to start with an unknown error, reason %d</string> <string name="hotspot_error_start_callback_failed_unknown">Hotspot failed to start with an unknown error, reason %d</string>
<string name="hotspot_error_start_callback_no_group_info">Hotspot failed to start: no group info</string> <string name="hotspot_error_start_callback_no_group_info">Hotspot failed to start: no group info</string>
<string name="hotspot_error_web_server_start">Error starting web server!</string> <string name="hotspot_error_web_server_start">Error starting web server</string>
<string name="hotspot_error_web_server_serve">Error presenting website.\n\nPlease send feedback (with anonymous data) via the Briar app if the issue persists.</string> <string name="hotspot_error_web_server_serve">Error presenting website.\n\nPlease send feedback (with anonymous data) via the Briar app if the issue persists.</string>
<string name="hotspot_flag_test">Warning: This app was installed with Android Studio and can NOT be installed on another device.</string> <string name="hotspot_flag_test">Warning: This app was installed with Android Studio and can NOT be installed on another device.</string>
<string name="hotspot_error_framework_busy">Unable to start the hotspot.\n\nIf you have another hotspot running or are sharing your Internet connection via Wi-Fi, try stopping that and try again afterwards.</string>
<!-- Transfer Data via Removable Drives -->
<string name="removable_drive_menu_title">Transfer data</string>
<string name="removable_drive_intro">You can send encrypted messages to your contact using removable storage such as USB flash drives or SD cards.\n\nIf your contact has sent you a removable drive containing encrypted messages, you can import the messages into Briar by using the receive button below.</string>
<string name="removable_drive_title_send">Send data</string>
<string name="removable_drive_title_receive">Receive data</string>
<string name="removable_drive_send_intro">Tap the button below to create a new file containing the encrypted messages. You can choose where the file will be saved.\n\nIf you want to save the file on a removable drive, insert the drive now.</string>
<string name="removable_drive_send_no_data">There are currently no messages waiting to be sent to this contact.</string>
<string name="removable_drive_send_not_supported">This contact is using an old version of Briar or an old device which does not support this feature.</string>
<string name="removable_drive_send_button">Choose file for export</string>
<string name="removable_drive_ongoing">Please wait for ongoing task to complete</string>
<string name="removable_drive_receive_intro">Tap the button below to choose the file that your contact sent you.\n\nIf the file is on a removable drive, insert the drive now.</string>
<string name="removable_drive_receive_button">Choose file for import</string>
<string name="removable_drive_success_send_title">Export successful</string>
<string name="removable_drive_success_send_text">Data exported successfully. You now have 28 days to transport the file to your contact.\n\nIf the file is on a removable drive, use the notification in the status bar to eject the drive before unplugging it.</string>
<string name="removable_drive_success_receive_title">Import successful</string>
<string name="removable_drive_success_receive_text">All encrypted messages contained in this file have been received.</string>
<string name="removable_drive_error_send_title">Error exporting data</string>
<string name="removable_drive_error_send_text">There was an error writing data to the file.\n\nIf you are using a removable drive, ensure that it is properly inserted and try again.\n\nIf the error persists, please send feedback to let the Briar team know about the issue.</string>
<string name="removable_drive_error_receive_title">Error importing data</string>
<string name="removable_drive_error_receive_text">The selected file did not contain anything that Briar could recognize.\n\nPlease check that you chose the right file.\n\nIf your contact created the file more than 28 days ago, Briar will not be able to recognize it.</string>
<!-- Screenshots --> <!-- Screenshots -->

View File

@@ -147,7 +147,7 @@ dependencyVerification {
'com.vanniktech:emoji:0.6.0:emoji-0.6.0.aar:a5fcde58902305c004f03c6dc2241e718400ac4162226079791d87fac83ef639', 'com.vanniktech:emoji:0.6.0:emoji-0.6.0.aar:a5fcde58902305c004f03c6dc2241e718400ac4162226079791d87fac83ef639',
'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569', 'commons-codec:commons-codec:1.10:commons-codec-1.10.jar:4241dfa94e711d435f29a4604a3e2de5c4aa3c165e23bd066be6fc1fc4309569',
'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636', 'commons-logging:commons-logging:1.2:commons-logging-1.2.jar:daddea1ea0be0f56978ab3006b8ac92834afeefbd9b7e4e6316fca57df0fa636',
'de.hdodenhof:circleimageview:3.0.1:circleimageview-3.0.1.aar:7b0f088436ad4dcbb36d779fd09bf2192d9cc1e1a734bb6337904a7648f97617', 'de.hdodenhof:circleimageview:3.1.0:circleimageview-3.1.0.aar:8e9965b54072ee159074a55df216e17d5a622c94ce915ef311b1a1f32660c7fb',
'info.guardianproject.panic:panic:1.0:panic-1.0.jar:35116ab95212e67f94577faf67b88c11a6b21cbf9178b3f5b51d3dff45203ffd', 'info.guardianproject.panic:panic:1.0:panic-1.0.jar:35116ab95212e67f94577faf67b88c11a6b21cbf9178b3f5b51d3dff45203ffd',
'info.guardianproject.trustedintents:trustedintents:0.2:trustedintents-0.2.jar:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e', 'info.guardianproject.trustedintents:trustedintents:0.2:trustedintents-0.2.jar:6221456d8821a8d974c2acf86306900237cf6afaaa94a4c9c44e161350f80f3e',
'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c', 'it.unimi.dsi:fastutil:7.2.0:fastutil-7.2.0.jar:74fa208043740642f7e6eb09faba15965218ad2f50ce3020efb100136e4b591c',
@@ -187,8 +187,8 @@ dependencyVerification {
'org.apache.maven:maven-repository-metadata:2.2.1:maven-repository-metadata-2.2.1.jar:5fe283f47b0e7f7d95a4252af3fa7a0db4d8f080cd9df308608c0472b8f168a1', 'org.apache.maven:maven-repository-metadata:2.2.1:maven-repository-metadata-2.2.1.jar:5fe283f47b0e7f7d95a4252af3fa7a0db4d8f080cd9df308608c0472b8f168a1',
'org.apache.maven:maven-settings:2.2.1:maven-settings-2.2.1.jar:9a9f556713a404e770c9dbdaed7eb086078014c989291960c76fdde6db4192f7', 'org.apache.maven:maven-settings:2.2.1:maven-settings-2.2.1.jar:9a9f556713a404e770c9dbdaed7eb086078014c989291960c76fdde6db4192f7',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca', 'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.52:bcprov-jdk15on-1.52.jar:0dc4d181e4d347893c2ddbd2e6cd5d7287fc651c03648fa64b2341c7366b1773',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349', 'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.bouncycastle:bcprov-jdk15on:1.69:bcprov-jdk15on-1.69.jar:e469bd39f936999f256002631003ff022a22951da9d5bd9789c7abfa9763a292',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d', 'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a', 'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df', 'org.checkerframework:checker-qual:2.8.1:checker-qual-2.8.1.jar:9103499008bcecd4e948da29b17864abb64304e15706444ae209d17ebe0575df',

View File

@@ -2,9 +2,9 @@ dependencyVerification {
verify = [ verify = [
'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3', 'junit:junit:4.13.2:junit-4.13.2.jar:8e495b634469d64fb8acfa3495a065cbacc8a0fff55ce1e31007be4c16dc57d3',
'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:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9', 'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d', 'org.ow2.asm:asm:9.1:asm-9.1.jar:cda4de455fab48ff0bcb7c48b4639447d4de859a7afc30a094a986f0936beba2',
] ]
} }

View File

@@ -31,8 +31,8 @@ dependencyVerification {
'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:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be', 'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.jsoup:jsoup:1.13.1:jsoup-1.13.1.jar:e2b99c0d2fa39f69f27efb1c0016390713feb2f2e02d8ea7f1c36b780271598a', 'org.jsoup:jsoup:1.13.1:jsoup-1.13.1.jar:e2b99c0d2fa39f69f27efb1c0016390713feb2f2e02d8ea7f1c36b780271598a',
'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.slf4j:slf4j-api:1.7.16:slf4j-api-1.7.16.jar:e56288031f5e60652c06e7bb6e9fa410a61231ab54890f7b708fc6adc4107c5b', 'org.slf4j:slf4j-api:1.7.16:slf4j-api-1.7.16.jar:e56288031f5e60652c06e7bb6e9fa410a61231ab54890f7b708fc6adc4107c5b',
] ]
} }

View File

@@ -6,10 +6,10 @@ import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int import com.github.ajalt.clikt.parameters.types.int
import org.bouncycastle.util.encoders.Base64.toBase64String
import org.briarproject.bramble.BrambleCoreEagerSingletons import org.briarproject.bramble.BrambleCoreEagerSingletons
import org.briarproject.briar.BriarCoreEagerSingletons import org.briarproject.briar.BriarCoreEagerSingletons
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
import org.spongycastle.util.encoders.Base64.toBase64String
import java.io.File import java.io.File
import java.io.File.separator import java.io.File.separator
import java.io.IOException import java.io.IOException

View File

@@ -3,9 +3,9 @@ package org.briarproject.briar.headless.contact
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.ForbiddenResponse
import io.javalin.http.HttpResponseException
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import org.bouncycastle.util.encoders.Base64
import org.bouncycastle.util.encoders.DecoderException
import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.connection.ConnectionRegistry
import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.ContactManager
import org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX import org.briarproject.bramble.api.contact.HandshakeLinkConstants.LINK_REGEX
@@ -31,8 +31,6 @@ import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400 import org.eclipse.jetty.http.HttpStatus.BAD_REQUEST_400
import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403 import org.eclipse.jetty.http.HttpStatus.FORBIDDEN_403
import org.spongycastle.util.encoders.Base64
import org.spongycastle.util.encoders.DecoderException
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import javax.annotation.concurrent.Immutable import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject

View File

@@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.javalin.http.BadRequestResponse import io.javalin.http.BadRequestResponse
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import org.bouncycastle.util.encoders.Base64
import org.bouncycastle.util.encoders.DecoderException
import org.briarproject.bramble.api.contact.Contact import org.briarproject.bramble.api.contact.Contact
import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.ContactManager
@@ -36,8 +38,6 @@ import org.briarproject.briar.headless.event.output
import org.briarproject.briar.headless.getContactIdFromPathParam import org.briarproject.briar.headless.getContactIdFromPathParam
import org.briarproject.briar.headless.getFromJson import org.briarproject.briar.headless.getFromJson
import org.briarproject.briar.headless.json.JsonDict import org.briarproject.briar.headless.json.JsonDict
import org.spongycastle.util.encoders.Base64
import org.spongycastle.util.encoders.DecoderException
import java.util.concurrent.Executor import java.util.concurrent.Executor
import javax.annotation.concurrent.Immutable import javax.annotation.concurrent.Immutable
import javax.inject.Inject import javax.inject.Inject

View File

@@ -11,6 +11,7 @@ import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.runs import io.mockk.runs
import org.bouncycastle.util.encoders.Base64
import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactId
import org.briarproject.bramble.api.db.NoSuchContactException import org.briarproject.bramble.api.db.NoSuchContactException
import org.briarproject.bramble.api.sync.MessageId import org.briarproject.bramble.api.sync.MessageId
@@ -39,7 +40,6 @@ import org.briarproject.briar.headless.json.JsonDict
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.spongycastle.util.encoders.Base64
import kotlin.random.Random import kotlin.random.Random
internal class MessagingControllerImplTest : ControllerTest() { internal class MessagingControllerImplTest : ControllerTest() {
@@ -213,7 +213,7 @@ internal class MessagingControllerImplTest : ControllerTest() {
@Test @Test
fun markMessageRead() { fun markMessageRead() {
mockkStatic("org.briarproject.briar.headless.RouterKt") mockkStatic("org.briarproject.briar.headless.RouterKt")
mockkStatic("org.spongycastle.util.encoders.Base64") mockkStatic("org.bouncycastle.util.encoders.Base64")
expectGetContact() expectGetContact()
val messageIdString = message.id.bytes.toString() val messageIdString = message.id.bytes.toString()

View File

@@ -30,7 +30,7 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.5.0' classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.5.3'
classpath files('libs/gradle-witness.jar') classpath files('libs/gradle-witness.jar')
} }
ext.dagger_version = "2.33" ext.dagger_version = "2.33"