Compare commits

..

284 Commits

Author SHA1 Message Date
ameba23 d564fc8713 Add logging on replacing outgoing transport keys 2021-10-04 13:55:03 +02:00
ameba23 41443db817 Bug with checking contactId, improve logging 2021-09-30 15:54:32 +02:00
ameba23 274722d695 Log keys in hex on handshake 2021-09-29 10:15:41 +02:00
ameba23 275c7d261f Fix bug when finding handshake mode keys 2021-09-28 14:45:01 +02:00
ameba23 667f8db4f0 Log keys in hex when failing to recognise tags 2021-09-28 12:47:44 +02:00
ameba23 8398ecdeae Merge branch 'social-backup-poc' into social-backup-handshake-after-recover
* social-backup-poc:
  Rm test which was not present in this branch before cherry pick
  Resolve conflict on cherry pick commit to use ByteBuddyClassImposteriser
  Tell animal sniffer gradle plugin to ignore java.util.Objects
  Improve lost password and setup password dialog for social backup
  When creating social backup, only allow selecting contacts when there are at least 2 contacts in contact list
  Disable help recover account option by default
2021-09-28 08:15:28 +02:00
ameba23 44bc35b949 Rm test which was not present in this branch before cherry pick 2021-09-06 08:50:35 +02:00
akwizgran 75f8d72e54 Resolve conflict on cherry pick commit to use ByteBuddyClassImposteriser 2021-09-03 13:58:52 +02:00
ameba23 67dbc3d9a0 Tell animal sniffer gradle plugin to ignore java.util.Objects 2021-08-27 14:49:25 +02:00
ameba23 3835dcf3a7 Improve lost password and setup password dialog for social backup 2021-08-27 12:42:36 +02:00
ameba23 be8e5c4bf2 When creating social backup, only allow selecting contacts when there are at least 2 contacts in contact list 2021-08-27 12:30:10 +02:00
ameba23 c49c1f78d2 Disable help recover account option by default 2021-08-27 12:08:36 +02:00
ameba23 8437aa1b10 If current keys are not in handshake mode, look for handshake mode keys 2021-08-27 10:04:50 +02:00
ameba23 9cf00efa9c Comment out additional logging when restoring account 2021-08-27 10:00:27 +02:00
ameba23 2a45fd3c91 Use new method to get stream context in handshake mode on incoming duplex sync connection 2021-08-27 09:58:08 +02:00
ameba23 f1f16f8474 Additional logging for outgoing duplex sync connection 2021-08-27 09:56:05 +02:00
ameba23 873675d68f Additional logging when reading tag 2021-08-27 09:55:28 +02:00
ameba23 1ffa8ee024 Add a method to get stream context in handshake mode 2021-08-27 09:54:50 +02:00
ameba23 a425a33209 Add a method to get stream context in handshake mode 2021-08-27 09:54:24 +02:00
ameba23 5adbf18851 Add and retrieve Onion private key to/from Tor transport propeties 2021-08-27 09:52:04 +02:00
ameba23 5e530c25b6 Rm unused import 2021-07-20 12:15:21 +02:00
ameba23 27b402f57c Log warning on failure to set handshake public key 2021-07-20 11:41:17 +02:00
ameba23 6fc6ae727f Handle security exception when setting handshake public key 2021-07-20 11:40:53 +02:00
ameba23 b7d71a21b0 When setting remote handshake key, derive tags 2021-07-20 11:40:14 +02:00
ameba23 1eb6be2407 Only add Tor transport properties to our social backup 2021-06-29 16:40:05 +02:00
ameba23 53c4ba184d Improve logging for DuplexSyncConnection 2021-06-29 16:09:08 +02:00
ameba23 4b9c3a1a96 Call KeyManager#AddContact when restoring contacts 2021-06-29 11:43:14 +02:00
ameba23 eb66a13ded Implement message validator for handshake key exchange 2021-06-29 09:48:19 +02:00
ameba23 b860e73bdc Return an empty list when getting headers 2021-06-28 17:34:38 +02:00
ameba23 851bbb293e Rm logging 2021-06-28 17:20:17 +02:00
ameba23 36aaea40bd Register the client and hooks 2021-06-28 17:18:12 +02:00
ameba23 7563172121 Logging 2021-06-28 17:04:53 +02:00
ameba23 fad9257066 Set a contacts handshake public key on receiving one 2021-06-28 13:46:52 +02:00
ameba23 873b088a42 Add a method to set a contacts handshake public key in the database 2021-06-28 13:46:19 +02:00
ameba23 c7bca253fe Inject HandshakeKeyExchangeModule as an eager singleton 2021-06-28 09:58:39 +02:00
ameba23 47f136904c Create HandshakeKeyExchange client and module 2021-06-28 09:55:41 +02:00
ameba23 0a0b79ad9b Check for null when adding local properties to fix integration test 2021-06-25 21:07:44 +02:00
ameba23 e9f4f084dd Fix incorrect key for shared prefs 2021-06-25 13:36:03 +02:00
ameba23 3484892628 Use hex encoding for storing shards in shared preferences 2021-06-25 13:20:20 +02:00
ameba23 f67d2f0157 Include local tor properties in backup and check for remote handshake public keys 2021-06-25 13:03:45 +02:00
ameba23 ef05ecc342 Clear partially recovered shards from shared preferences when recovered 2021-06-25 09:03:58 +02:00
ameba23 3d807d9950 Save recovered shards in sharedPreferences 2021-06-25 08:44:37 +02:00
ameba23 4cf9ca9c3e Handle transport properties correctly in backup and recovery 2021-06-24 09:34:39 +02:00
ameba23 95d540644b Dont accept shards from mismatched sets - all muss match the first shard received 2021-06-22 16:26:48 +02:00
ameba23 3b4988c109 Change wording on shard notification messages 2021-06-22 09:49:33 +02:00
ameba23 9b7b583e8a Change message on success fragments 2021-06-21 16:28:10 +02:00
ameba23 21f3634050 Catch NoSuchGroupException when deleting contacts 2021-06-21 12:56:39 +02:00
ameba23 33549bcbe1 Implement deleteMessages in SocialBackupManagerImpl 2021-06-21 12:25:14 +02:00
ameba23 d975d1dbdd Implement deleteAllMessages in SocialBackupManagerImpl 2021-06-21 12:20:09 +02:00
ameba23 af64bb056d Add delete all messages to integration test 2021-06-21 12:19:32 +02:00
ameba23 ca54566ce0 SecretOwnerTask also passes a provider of AuthenticatedCipher 2021-06-16 08:34:40 +02:00
ameba23 85683a57f1 Use a provider to instantiate AuthenticatedCipher on each use, to prevent concurrency problems 2021-06-15 21:10:33 +02:00
ameba23 f524af893d Fix bug with qrCodeRead flag 2021-06-15 16:29:39 +02:00
ameba23 7b63471205 Fix bug with qrCodeRead flag 2021-06-15 16:29:06 +02:00
ameba23 4db1c4cc6b Improve strings for ui for displaying existing social backup 2021-05-18 12:06:09 +02:00
ameba23 71e63fb6f1 Tidy SocialBackupManagerImpl 2021-05-18 11:52:28 +02:00
ameba23 cae655b020 JavaDoc comments 2021-05-18 11:50:50 +02:00
ameba23 f601e2945b Improve error handling when creating backups, minor UI changes 2021-05-18 11:50:10 +02:00
ameba23 55e0ba8888 Rm logging 2021-05-05 10:34:32 +02:00
ameba23 3b4a20ce01 on retrying after fail, restart task 2021-05-04 15:10:41 +02:00
ameba23 e5eb82a5a8 Rm unused classes 2021-05-04 15:09:22 +02:00
ameba23 26a35b3212 Dont use onBackPressed when retrying after error 2021-04-27 13:07:45 +02:00
ameba23 68f135adad Additional logging in In/out DuplexSyncConnection 2021-04-27 13:06:55 +02:00
peg 7ecd952f49 Merge branch 'social-backup-restore-activity' into 'social-backup-poc'
Social backup restore activity

See merge request briar/briar!1444
2021-04-27 08:10:18 +00:00
ameba23 4b30b50329 Fix ReturnShardIntegrationTest 2021-04-27 10:05:43 +02:00
ameba23 c2b7657041 Improve UI for showing qr code 2021-04-26 13:03:42 +02:00
ameba23 61a052fc65 QR code display fragment 2021-04-26 12:43:37 +02:00
ameba23 d78a6604fd UI for error and explainer fragments 2021-04-26 12:29:45 +02:00
ameba23 8ffe9a6367 Modify drawables to show one device with a qr code and one without 2021-04-26 12:27:38 +02:00
ameba23 4b9c796c1a Implement handshake on IncomingDuplexSyncConnection 2021-04-26 09:59:19 +02:00
ameba23 6af739e584 Resolve conflict after merging with social-backup-outgoing-handshake 2021-04-25 12:06:49 +02:00
ameba23 50cb3997d9 Merge remote-tracking branch 'origin/social-backup-outgoing-handshake' into social-backup-restore-activity
* origin/social-backup-outgoing-handshake:
  WIP: Add handshake support for outgoing connections.
2021-04-25 12:01:18 +02:00
ameba23 a6f06e328b Dont use an observer when adding contacts 2021-04-25 12:00:44 +02:00
ameba23 a5f0516135 Tidy RestoreAccount 2021-04-23 13:22:10 +02:00
ameba23 3d34a36908 Show success fragment on recovering account 2021-04-23 13:18:47 +02:00
akwizgran e29a5f487e WIP: Add handshake support for outgoing connections. 2021-04-23 12:17:56 +01:00
ameba23 acb9b18507 addContact method which also takes a handshakePublicKey 2021-04-23 12:23:13 +02:00
ameba23 f5455d320b bramble-core 2021-04-23 12:22:18 +02:00
ameba23 07141b688a Add a method to add recovered contacts to db 2021-04-22 17:42:33 +02:00
ameba23 b1c6c602a6 Add DozeView 2021-04-22 17:41:58 +02:00
ameba23 60c6c6a7ae RestoreAccount should be injected as singleton 2021-04-22 11:55:20 +02:00
ameba23 3ac7d71f8a Change to fail state if no backup found 2021-04-22 11:04:24 +02:00
ameba23 41c13f80f4 Copy code from SetPassword fragment to avoid it using the wrong view model 2021-04-22 10:56:20 +02:00
ameba23 2e03967519 Fix dependency problems after refactor 2021-04-22 09:01:44 +02:00
ameba23 4b77a9ab60 Use backup with highest version number 2021-04-21 21:53:06 +02:00
ameba23 327cbe23ee Use DozeHelper 2021-04-21 21:52:48 +02:00
ameba23 34f15b6bdc ContactData and SocialBackup now live in briar-api 2021-04-21 16:35:27 +02:00
ameba23 5c22d233ef Restore account activity/view model 2021-04-21 16:34:56 +02:00
ameba23 afc0bc3f3c Refactor and create a RestoreAccount class which does the combining 2021-04-21 16:34:28 +02:00
ameba23 2a365d986f Restore account method for account manager 2021-04-21 15:36:44 +02:00
ameba23 741c9b63d9 Custodian closes tcp socket following error 2021-04-21 15:19:36 +02:00
ameba23 5420204703 Rm fixed port constant 2021-04-21 09:56:36 +02:00
ameba23 e827b8a190 Tidy 2021-04-21 09:55:09 +02:00
ameba23 c6046a1c38 Fix bug with re-starting listening on a tcp socket 2021-04-21 09:54:42 +02:00
peg b401232736 Merge branch 'social-backup-shard-return' into 'social-backup-poc'
Social backup shard return

See merge request briar/briar!1433
2021-04-20 10:52:25 +00:00
ameba23 46fc510ce2 Improve UI for account recovery 2021-04-20 12:47:14 +02:00
ameba23 b835064b5e Logging and improvements to BackupPayloadDecoderImpl 2021-04-20 12:46:22 +02:00
ameba23 ae35354e82 Encode nonce in backup payload 2021-04-19 11:37:46 +02:00
ameba23 bdbc377c8f Fix UI around receiving multiple returned shards 2021-04-19 10:07:06 +02:00
ameba23 ad3c9e101c Implement BackupPayloadDecoder 2021-04-19 09:00:05 +02:00
ameba23 101b93b3da Interface for BackupPayloadDecoder 2021-04-19 08:59:37 +02:00
ameba23 e392a0dd8b Custodian should instantiate socket at the point of connecting, secret owner parse payload 2021-04-16 21:17:10 +02:00
ameba23 00de32aa87 add encoder to ReturnShardPayload 2021-04-16 21:15:29 +02:00
ameba23 3ff6042d10 Recovery UI 2021-04-16 21:14:34 +02:00
ameba23 b3adfe19a4 Allow ReturnShardPayload instances to be compared for equality 2021-04-16 12:01:39 +02:00
ameba23 708262c512 Refactor and add real shard payload to return shard activity and view model 2021-04-16 12:00:40 +02:00
ameba23 f4d667f50a give and retrieve shard payload 2021-04-15 21:25:13 +02:00
ameba23 79369f4e7a Failure reasons 2021-04-15 21:24:22 +02:00
ameba23 fec74ed343 tasks take and produce shard payload, improve integration test 2021-04-15 21:24:01 +02:00
ameba23 4ba3fdb1e3 Always cancel the task before starting it 2021-04-15 11:47:48 +02:00
ameba23 20df10d7a8 When cancelling, assume nothing is instantiated 2021-04-15 11:40:07 +02:00
Sebastian Kürten b901974488 Start work on an integration test 2021-04-15 10:56:08 +02:00
ameba23 badc2c5d9b Fix bug with localKeypair being generated twice 2021-04-15 09:00:13 +02:00
ameba23 ed1ed7d3e1 Refactor duplicate task code into parent class 2021-04-14 17:59:29 +02:00
ameba23 e6d80ec484 Improve UI for Secret owner shard return 2021-04-14 17:22:59 +02:00
ameba23 536905c260 Encrypted shard return handshake 2021-04-14 17:22:34 +02:00
ameba23 9b4f5be6fe Basic encryption on the custodian side 2021-04-13 21:20:51 +02:00
ameba23 f13cc15661 Enter success state after sending ack 2021-04-13 18:22:19 +02:00
ameba23 b07206c898 Improve UI for shard return 2021-04-13 18:21:55 +02:00
ameba23 d2abd6dcc2 Basic handshake implementation 2021-04-13 17:38:03 +02:00
ameba23 6a143eea8a Update state in UI for Custodian 2021-04-13 17:37:29 +02:00
ameba23 4da20a2412 add timeout to client 2021-04-13 12:29:33 +02:00
ameba23 1bf9f57ad9 secret owner listens, and custodian connects 2021-04-13 12:20:17 +02:00
ameba23 cd1ac43b7d get the actual ip address on local wifi and add it to qr code 2021-04-13 11:56:52 +02:00
ameba23 df37a39cb4 Qr code payload contains socket address and public key 2021-04-13 11:13:28 +02:00
ameba23 f7e40657ee Temporary - allow us to jump to the custodian return shard activity directly from the settings menu, even if we dont hold any shards 2021-04-13 09:20:37 +02:00
ameba23 d6608fd8cb add a CustodianReturnShardModule which provides the view model 2021-04-13 09:19:42 +02:00
ameba23 dba4cc278c dummy ip address for local socket 2021-04-13 08:53:27 +02:00
ameba23 01bcc6d491 ensure that qr code is ready when start button is pressed 2021-04-13 08:52:42 +02:00
ameba23 e106166cfe provide SecretOwnerTask 2021-04-12 16:18:23 +02:00
ameba23 5fd0d0d2d3 OwnerReturnShard activity and view model now running 2021-04-12 16:17:22 +02:00
ameba23 e006e22616 rename ReturnShardModule, it is now only used by secret owner 2021-04-12 16:15:52 +02:00
ameba23 c0827eda77 use the new OwnerReturnShardActivity 2021-04-12 15:57:15 +02:00
ameba23 b55ae1ce18 Changes to implementation for SecertOwnerTask 2021-04-12 15:03:50 +02:00
ameba23 d925f3be0b Secret owner return shard - activity, view model and fragment - qr code generation 2021-04-12 15:03:15 +02:00
ameba23 71c327112c Implement SecretOwnerTask 2021-04-12 15:02:26 +02:00
ameba23 30b2905c2e make SecretOwnerTask states public 2021-04-12 12:21:35 +02:00
ameba23 9d01de9868 Secret owner return shard - activity, view model and fragment 2021-04-12 12:21:16 +02:00
ameba23 71b8c32a3e implement CustodianTask 2021-04-12 10:52:03 +02:00
ameba23 207a8bc7cb make CustodianTask states public 2021-04-12 10:51:34 +02:00
ameba23 9e4ace4ce7 Activity and view model for custodian returning shard 2021-04-12 10:50:47 +02:00
ameba23 e856ee48f9 Merge branch 'social-backup-recovery-task-interfaces' into social-backup-shard-return
* social-backup-recovery-task-interfaces:
  Add interfaces for social backup recovery tasks.
2021-04-09 15:59:03 +02:00
ameba23 ea4bd5f438 WIP activity for custodian to return a shard 2021-04-09 15:58:50 +02:00
akwizgran 5b7bc54e16 Add interfaces for social backup recovery tasks. 2021-04-09 14:46:45 +01:00
ameba23 996eb20556 Additional logging in KeyAgreementConnector 2021-04-09 15:12:56 +02:00
ameba23 61453b96ab Make a ReturnShardFragment, based on ContactExchangeFragment 2021-04-08 16:43:39 +02:00
ameba23 73ce6c2fb0 Fix parser for backup messages 2021-04-08 15:46:56 +02:00
ameba23 3ddda8cf7f Revert package name to socialbackup 2021-04-08 13:11:51 +02:00
ameba23 93ee8df43a Resolve merge conflict with 1872-key-agreement 2021-04-08 12:23:18 +02:00
Torsten Grote 0ee4ade404 One more round of addressing AddNearbyContact review feedback 2021-04-07 16:18:18 -03:00
ameba23 d0b939dafb add provider for ClientHelper 2021-04-07 15:51:04 +02:00
ameba23 4f26230996 implement method to get a ReturnShardPayload as bytes to avoid needing the messageEncoder 2021-04-06 16:28:09 +02:00
ameba23 e804a8d573 method to get a ReturnShardPayload as bytes to avoid needing the messageEncoder 2021-04-06 16:27:51 +02:00
ameba23 6bff0647d6 return shard activity dependencies 2021-04-06 16:26:57 +02:00
Torsten Grote 5b52417d20 Check if Bluetooth is supported before requesting discoverability 2021-04-01 15:36:55 -03:00
ameba23 3688b0b17a Encoding and decoding for returned social backups 2021-03-31 16:01:22 +02:00
ameba23 4f42ce9a01 Public interface of SocialBackupExchangeManager and Message encoder/parser 2021-03-31 16:00:57 +02:00
ameba23 c155020064 CustodianHelpRecoverActivity passes a ReturnShardPayload 2021-03-31 16:00:04 +02:00
ameba23 933397f58b move MessageParser and MessageEncoder to make public 2021-03-31 15:59:24 +02:00
ameba23 d129186bab social backup exchange 2021-03-30 12:06:45 +02:00
ameba23 a11a81f3d4 backup payload and returned shard payload 2021-03-30 12:05:57 +02:00
ameba23 7c6a2a9b46 Refactor from public contact exchange code 2021-03-30 12:05:18 +02:00
ameba23 0258cf9c59 Rename constants to make public 2021-03-30 12:01:55 +02:00
ameba23 caee34f738 Rename constants to make public 2021-03-30 12:01:10 +02:00
Torsten Grote 4f3e4b019a Request user to turn on location for adding contact nearby on API 28+ 2021-03-29 11:30:17 -03:00
ameba23 7afe5a85f8 create a socialBackupExchangeManager copying the ContactExchangeManager 2021-03-26 11:59:52 +01:00
ameba23 943e734ae9 fix bugs relating to return shard activity 2021-03-26 11:00:44 +01:00
ameba23 c2cbba451d return shard activity and view model 2021-03-25 20:43:34 +01:00
ameba23 fc909a317e make add nearby contact stuff public 2021-03-25 20:42:43 +01:00
ameba23 a0df8ded69 recover package and return shard activity 2021-03-25 20:41:34 +01:00
ameba23 9d23c876ae merge with 1872-key-agreement 2021-03-25 14:55:15 +01:00
Torsten Grote 1d44305e34 Catch exception when calling Camera#getParameters()
Fixes #1982
2021-03-24 15:23:16 -03:00
Torsten Grote a37af592cd Use new ActivityResultLauncher to request permissions for AddNearbyContact 2021-03-24 15:03:53 -03:00
Torsten Grote 7f486eef4c Refactor more code into AddNearbyContactViewModel
thus concentrating the logic there needing less back and forth with the activity
2021-03-23 18:09:57 -03:00
ameba23 265d1da566 improve threshold choosing UI 2021-03-23 09:14:18 +01:00
ameba23 b6d57a492b only allow choosing a threshold with > 3 custodians 2021-03-23 08:25:09 +01:00
ameba23 943f107232 Merge branch 'social-backup-poc' of https://code.briarproject.org/briar/briar into social-backup-poc
* 'social-backup-poc' of https://code.briarproject.org/briar/briar:
  Inject social backup eager singletons when Briar core is created.
2021-03-22 17:42:22 +01:00
peg 09e024ea5e Merge branch 'social-backup-eager-singletons' into 'social-backup-poc'
Dark Crystal: Inject social backup eager singletons when Briar core is created

See merge request briar/briar!1414
2021-03-22 16:37:20 +00:00
ameba23 2486a60fea only display custodian help recover explainer screen if you are a custodian 2021-03-22 17:03:22 +01:00
ameba23 365fa58928 add amCustodian method which determines whether you are a custodian for a given contact 2021-03-22 17:02:39 +01:00
akwizgran 45d2e2ce06 Inject social backup eager singletons when Briar core is created. 2021-03-22 15:19:41 +00:00
ameba23 b1f5d71a4e add read flag to shard message header 2021-03-22 16:13:08 +01:00
ameba23 d47c18b392 ShardReceivedEvent in briar api 2021-03-22 09:22:32 +01:00
ameba23 8ed58eaada broadcast a ShardReceivedEvent on getting a shard 2021-03-22 09:22:04 +01:00
ameba23 8478097a3c test setting read flag for shard messages 2021-03-22 08:42:25 +01:00
ameba23 0440c5c7c8 update the message tracker on incoming/outgoing shard messages 2021-03-18 14:55:30 +01:00
ameba23 58db654a9b pass MessageStatus information to shard message headers (sent and seen) 2021-03-18 12:00:19 +01:00
Sebastian Kürten c3d137a73c Move ShardsSentDismissedListener into ShardsSentFragment 2021-03-18 10:21:00 +01:00
Sebastian Kürten db7825d7f6 Try making message tracker assertions 2021-03-18 10:13:22 +01:00
Sebastian Kürten 24059adbd6 Start working on integration test 2021-03-18 09:51:29 +01:00
Torsten Grote bcc0442add Merge activities for adding contact nearby
and rename related classes to consolidate names
2021-03-17 14:05:15 -03:00
Torsten Grote 700f6e05bf Factor out permission related code from KeyAgreementActivity to AddNearbyContactPermissionManager 2021-03-17 14:02:54 -03:00
Torsten Grote d8327d6de2 Re-set orientation lock when fragment is left 2021-03-17 14:02:54 -03:00
Torsten Grote 5a55b3d7e3 Move Plugin related code from activity to ViewModel 2021-03-17 14:02:54 -03:00
Torsten Grote bed87ed439 Move backend comms and logic out of KeyAgreementFragment
into ViewModel
2021-03-17 14:02:53 -03:00
Torsten Grote 6d1f1c7852 Get rid of KeyAgreementEventListener
and communicate via ViewModel
2021-03-17 14:02:53 -03:00
Torsten Grote f6b3bde724 Introduce ContactExchangeResult
to include all result information in LiveData
2021-03-17 14:02:53 -03:00
Torsten Grote 94ec22bef8 Move keyagreement package into contact.add.nearby
and fix some small warnings in the process
2021-03-17 14:02:50 -03:00
ameba23 7d128988a7 listener for custodian scan qr code button 2021-03-17 14:51:11 +01:00
ameba23 9499a078a6 front end fragments for recovery 2021-03-17 14:41:29 +01:00
ameba23 6483b0ed87 display explainer screen when choosing recover account 2021-03-17 10:46:34 +01:00
ameba23 af097dc859 add timestamp to shard message metadata 2021-03-17 09:23:31 +01:00
ameba23 3adc6d002c add isLocal boolean to shard message headers 2021-03-17 09:17:36 +01:00
ameba23 0658e90c65 implement slightly more of conversation client to get delete messages test passing 2021-03-17 08:41:39 +01:00
ameba23 6d0aebd7ec SocialBackupManager implements a ConverationClient for shard message headers 2021-03-16 17:28:00 +01:00
ameba23 0faccfe5a3 SocialBackupManager interface has a getMessageHeaders method 2021-03-16 17:27:00 +01:00
ameba23 c19c40bdc8 when providing SocialBackupManager, register the conversation client 2021-03-16 17:23:47 +01:00
ameba23 363da96709 recover activity 2021-03-16 11:51:03 +01:00
ameba23 e9c2cb2cc5 fix merge conflict 2021-03-16 10:52:11 +01:00
ameba23 505124a22f add the contact group and local group if it doesnt already exist 2021-03-16 09:40:18 +01:00
Sebastian Kürten 9b750291d1 Remove proguard rule that we do not need after all 2021-03-15 18:29:59 +01:00
Sebastian Kürten 9c829ec7c9 Don't let proguard strip important JNA class members 2021-03-15 18:27:05 +01:00
ameba23 1b9ba41110 return false on error when checking if local backup already exists 2021-03-15 15:06:06 +01:00
peg fa39e7c824 Merge branch 'social-backup-poc-separate-namespace' into 'social-backup-poc'
Use different applicationId/app package

See merge request briar/briar!1410
2021-03-15 09:51:32 +00:00
ameba23 37fb3bd79f return false on error when checking if local backup already exists 2021-03-15 09:57:36 +01:00
ameba23 1c5e89b100 handle error when checking for existing backup 2021-03-15 09:40:51 +01:00
Sebastian Kürten 6dc6a34d29 Use different applicationId/app package 2021-03-12 12:03:45 +01:00
Sebastian Kürten 35b2b8c9d2 Add DefaultSocialBackupModule to test components 2021-03-12 12:01:18 +01:00
ameba23 e3ff8a80e5 change SocialBackupModule to DefaultSocialBackupModule 2021-03-12 10:52:26 +01:00
ameba23 e09fedd79f add AndroidSocialBackupModule to AppModule 2021-03-12 10:51:00 +01:00
ameba23 6c3df2a3d4 add AndroidSocialBackupModule 2021-03-11 11:10:38 +01:00
Sebastian Kürten a9edf43df2 Add ShardMessageHeader 2021-03-11 09:52:39 +01:00
ameba23 d91d2e0070 listener for setup new account button 2021-03-10 10:17:55 +01:00
ameba23 58f803a48a add new or recover screen - displays fragment with buttons 2021-03-10 09:08:34 +01:00
ameba23 ac9c71f7eb add new or recover screen WIP 2021-03-09 16:09:26 +01:00
ameba23 b3292f86ab improve ExistingBackupFragment 2021-03-09 12:36:02 +01:00
ameba23 28d2697e38 add custodian names to Existing backup fragment 2021-03-09 11:47:11 +01:00
ameba23 8e4b309a12 Existing backup fragment 2021-03-09 11:33:33 +01:00
ameba23 8a1333e8f2 display a different fragment when a backup already exists 2021-03-09 09:47:48 +01:00
ameba23 bd2a671f9f rm unused drawables 2021-03-09 08:49:40 +01:00
peg 845be86113 Merge branch 'incorporate-mockup-fragments' into 'social-backup-poc'
Incorporate mockup fragments

See merge request briar/briar!1395
2021-03-08 17:07:07 +00:00
ameba23 25bbb5aa36 dependency injection for SocialBackupManager and DatabaseComponent 2021-03-08 18:00:24 +01:00
ameba23 57605d55ce WIP db transaction for DistributedBackupActivity 2021-03-08 17:08:19 +01:00
ameba23 6c079e172a provide default constructor for DistributedBackupActivity 2021-03-08 16:40:24 +01:00
ameba23 a101229f73 add help recover account to conversation action menu 2021-03-08 16:27:30 +01:00
ameba23 3f7f53774b inject SocialBackupManager 2021-03-08 16:26:16 +01:00
ameba23 9beb4d7b81 improve thresholdSelectorFragment 2021-03-08 13:45:02 +01:00
ameba23 378112c00c add comments 2021-03-08 13:13:00 +01:00
ameba23 451a3238be rm comments 2021-03-08 12:57:25 +01:00
ameba23 bf6dd0d924 pass treshold to DistributedBackupActivity 2021-03-08 12:43:04 +01:00
ameba23 085e25cc14 improve thresholdSelectorFragment 2021-03-08 12:31:32 +01:00
ameba23 033c9f4d59 get argument with number of custodians to thresholdselectorfragment 2021-03-08 11:58:05 +01:00
ameba23 5f7bc4a143 dont throw on no group id 2021-03-05 17:17:50 +01:00
ameba23 4972c554dc fix pathname in settings.xml 2021-03-05 12:30:15 +01:00
ameba23 44e33e3d1a add DistributedBackupActivity for AndroidManifest 2021-03-05 11:34:30 +01:00
ameba23 5212bb7a01 add settings menu item 2021-03-05 10:28:53 +01:00
ameba23 83aad185cd add missing string 2021-03-05 10:28:33 +01:00
ameba23 c318dcfb5f rm CustodianDisplayFragment 2021-03-05 10:27:58 +01:00
ameba23 10610930c0 dont inject activist CustodianDisplayFragment 2021-03-05 09:22:36 +01:00
ameba23 2af236b733 add more strings from the mock-ups 2021-03-05 09:03:56 +01:00
ameba23 d46a513208 add remaining strings from the mock-ups 2021-03-05 08:46:38 +01:00
ameba23 022357fb4c rm strings_mockups.xml 2021-03-04 14:26:47 +01:00
ameba23 a576d7abf8 bump secretsharingwrapper to 1.1.0 2021-03-04 14:25:28 +01:00
ameba23 008877a9da bump secretsharingwrapper to 1.1.0 2021-03-04 10:37:20 +01:00
ameba23 01bc94c241 Merge branch 'social-backup-poc' into incorporate-mockup-fragments
* social-backup-poc:
  move DarkCrystal interface to briar-api - import it
  move DarkCrystal interface to briar-api
  make SocialBackupConstants public
  make DarkCrystal interface public
  DarkCrystal implementation which calls SecretSharingWrapper
  updated witness.gradle files
  rm SecretShardingWrapper as dependency of briar-core
  add SecretShardingWrapper as dependency of briar-android
  implement DarkCrystal in briar-android
  add updated witness.gradle
  add secret-sharing-wrapper to build.gradle (WIP)
2021-03-04 09:32:28 +01:00
ameba23 03c1f9c99a fix problems so that the mockup fragments build 2021-03-04 09:32:12 +01:00
ameba23 0b9e4915dc set initial state of threshold representation 2021-03-04 08:52:52 +01:00
ameba23 55e5600214 add some of the strings from the mockups 2021-03-04 08:39:37 +01:00
ameba23 4c357fe87a change threshold svg for placeholder string 2021-03-04 08:38:36 +01:00
ameba23 6a7ceb4a68 use a string as threshold representation rather than svg 2021-03-04 08:38:02 +01:00
ameba23 d917e9d642 move DarkCrystal interface to briar-api - import it 2021-03-02 12:50:43 +01:00
ameba23 c7f6270b2a move DarkCrystal interface to briar-api 2021-03-02 12:48:49 +01:00
ameba23 681b151c8b attempt to incorporate fragments (WIP) 2021-03-02 09:47:13 +01:00
ameba23 b86b0f5fbc make SocialBackupConstants public 2021-03-02 08:52:43 +01:00
ameba23 dc138c713f make DarkCrystal interface public 2021-03-02 08:52:16 +01:00
ameba23 7da49ae6df DarkCrystal implementation which calls SecretSharingWrapper 2021-03-02 08:51:00 +01:00
ameba23 3c61f499d9 updated witness.gradle files 2021-03-02 08:49:57 +01:00
ameba23 fbe839d9ca rm SecretShardingWrapper as dependency of briar-core 2021-03-02 08:49:09 +01:00
ameba23 f2638c9db2 add SecretShardingWrapper as dependency of briar-android 2021-03-02 08:48:38 +01:00
ameba23 808166931e implement DarkCrystal in briar-android 2021-03-02 08:47:59 +01:00
ameba23 77d0c16530 add updated witness.gradle 2021-02-26 11:15:50 +01:00
ameba23 c991cfb926 add secret-sharing-wrapper to build.gradle (WIP) 2021-02-26 10:57:27 +01:00
ameba23 dcda13db64 add fragments (WIP) 2021-02-26 10:45:29 +01:00
ameba23 ff4640b789 update SocialBackupValidator 2021-02-25 11:30:46 +01:00
ameba23 a2426e3b2a rm number of shards and threshold from shard messages from message parserimpl 2021-02-24 16:00:02 +01:00
ameba23 0bc4bf232f Merge branch 'social-backup-poc' of https://code.briarproject.org/briar/briar into social-backup-poc
* 'social-backup-poc' of https://code.briarproject.org/briar/briar:
  add combine shards stub
2021-02-24 15:44:39 +01:00
ameba23 2ed44aa2a8 rm number of shards and threshold from shard messages 2021-02-24 15:44:24 +01:00
ameba23 8496ab0a6a rm number of shards and threshold from shard messages in message encoder 2021-02-24 15:43:50 +01:00
ameba23 b57d811b4a rm number of shards and threshold from shard messages in stub 2021-02-24 15:43:27 +01:00
akwizgran 4077e28999 Merge branch 'combine-shards-stub' into 'social-backup-poc'
add combine shards stub

See merge request briar/briar!1379
2021-02-24 14:19:19 +00:00
ameba23 292fb6d3b1 add combine shards stub 2021-02-24 12:39:51 +01:00
akwizgran 4ead7cd4a1 WIP: Update our backup when contacts are added or removed. 2021-02-23 17:22:56 +00:00
akwizgran 513e696238 Initial implementation of social backup client. 2021-02-23 15:48:19 +00:00
akwizgran f160efb0e7 Use BriarCoreModule for integration tests. 2021-02-23 15:03:28 +00:00
340 changed files with 13577 additions and 5385 deletions
+12 -35
View File
@@ -1,52 +1,30 @@
image: briar/ci-image-android:latest
stages:
- test
- optional_tests
- check_reproducibility
- test
- optional_tests
- check_reproducibility
.base-test:
test:
stage: test
before_script:
- set -e
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check compileOfficialDebugAndroidTestSources compileScreenshotDebugAndroidTestSources
after_script:
# these file change every time and should not be cached
# these file change every time but should not be cached
- rm -f $GRADLE_USER_HOME/caches/modules-2/modules-2.lock
- rm -fr $GRADLE_USER_HOME/caches/*/plugin-resolution/
test:
extends: .base-test
stage: test
script:
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom animalSnifferMain animalSnifferTest
- ./gradlew --no-daemon -Djava.security.egd=file:/dev/urandom check
android test:
extends: .base-test
stage: optional_tests
image: briar/ci-image-android-emulator:latest
script:
# start emulator first, so it can fail early
- start-emulator.sh
# run normal and screenshot tests together (exclude Large tests)
- ./gradlew -Djava.security.egd=file:/dev/urandom connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest
artifacts:
name: "${CI_PROJECT_PATH}_${CI_JOB_STAGE}_${CI_COMMIT_REF_NAME}_${CI_COMMIT_SHA}"
paths:
- kernel.log
- logcat.txt
expire_in: 3 days
when: on_failure
when: manual
except:
- tags
tags:
- kvm
test_reproducible:
stage: check_reproducibility
@@ -62,7 +40,6 @@ test_reproducible:
- export GRADLE_USER_HOME=$PWD/.gradle
cache:
key: "$CI_COMMIT_REF_SLUG"
paths:
- .gradle/wrapper
- .gradle/caches
-9
View File
@@ -31,15 +31,6 @@
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value />
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
@@ -1,51 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Instrumentation Tests (destroys DB)" type="AndroidTestRunConfigurationType" factoryName="Android Instrumented Tests">
<module name="briar.briar-android" />
<option name="TESTING_TYPE" value="1" />
<option name="METHOD_NAME" value="" />
<option name="CLASS_NAME" value="" />
<option name="PACKAGE_NAME" value="org.briarproject.briar.android" />
<option name="INSTRUMENTATION_RUNNER_CLASS" value="" />
<option name="EXTRA_OPTIONS" value="-e notAnnotation androidx.test.filters.LargeTest" />
<option name="INCLUDE_GRADLE_EXTRA_OPTIONS" value="true" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" />
<option name="FORCE_STOP_RUNNING_APP" value="true" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Hybrid>
<Java />
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
+6 -6
View File
@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10220
versionName "1.2.20"
versionCode 10218
versionName "1.2.18"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -50,10 +50,10 @@ dependencies {
compileOnly 'javax.annotation:jsr250-api:1.0'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
}
def torBinariesDir = 'src/main/res/raw'
@@ -9,7 +9,7 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -44,7 +44,7 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
app = context.mock(Application.class);
applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath();
+15 -12
View File
@@ -1,6 +1,6 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.android.tools.analytics-library:protos:27.1.1:protos-27.1.1.jar:13f77e73762e58ab372d140b3a6be6903aea9775b62dd14fbc62d4cc7069c9a4',
'com.android.tools.analytics-library:shared:27.1.1:shared-27.1.1.jar:82930a52001410e97d809930b670f4de3002286975f046b9de5f6b777b06d366',
'com.android.tools.analytics-library:tracker:27.1.1:tracker-27.1.1.jar:31bc5a00be0055bac89c9b2f34751883e987cd89e3ac1783720645c164f591d9',
@@ -31,6 +31,7 @@ dependencyVerification {
'com.android.tools:sdklib:27.1.1:sdklib-27.1.1.jar:08e6b83961ac9724b3c1e3d0eff971f13be6701292c77914b8794480f3391250',
'com.android:signflinger:4.1.1:signflinger-4.1.1.jar:0c66825988873ec2d51057fa463f54a8f18fc7326ff4530b9da363b71e97ce60',
'com.android:zipflinger:4.1.1:zipflinger-4.1.1.jar:0a8c3e52ac13dd031236f9fb5ba4408b1d5dcd12325a05440b36da09d8881446',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.code.gson:gson:2.8.5:gson-2.8.5.jar:233a0149fc365c9f6edbd683cfe266b19bdc773be98eabdaf6b3c924b48e7d81',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
@@ -63,16 +64,16 @@ dependencyVerification {
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'javax.xml.bind:jaxb-api:2.3.1:jaxb-api-2.3.1.jar:88b955a0df57880a26a74708bc34f74dcaf8ebf4e78843a28b50eae945732b06',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'net.sf.jopt-simple:jopt-simple:4.9:jopt-simple-4.9.jar:26c5856e954b5f864db76f13b86919b59c6eecf9fd930b96baa8884626baf2f5',
'net.sf.kxml:kxml2:2.3.0:kxml2-2.3.0.jar:f264dd9f79a1fde10ce5ecc53221eff24be4c9331c830b7d52f2f08a7b633de2',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.apache.commons:commons-compress:1.12:commons-compress-1.12.jar:2c1542faf343185b7cab9c3d55c8ae5471d6d095d3887a4adefdbdf2984dc0b6',
'org.apache.httpcomponents:httpclient:4.5.6:httpclient-4.5.6.jar:c03f813195e7a80e3608d0ddd8da80b21696a4c92a6a2298865bf149071551c7',
'org.apache.httpcomponents:httpcore:4.4.10:httpcore-4.4.10.jar:78ba1096561957db1b55200a159b648876430342d15d461277e62360da19f6fd',
'org.apache.httpcomponents:httpmime:4.5.6:httpmime-4.5.6.jar:0b2b1102c18d3c7e05a77214b9b7501a6f6056174ae5604e0e256776eda7553e',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.bouncycastle:bcpkix-jdk15on:1.56:bcpkix-jdk15on-1.56.jar:7043dee4e9e7175e93e0b36f45b1ec1ecb893c5f755667e8b916eb8dd201c6ca',
'org.bouncycastle:bcprov-jdk15on:1.56:bcprov-jdk15on-1.56.jar:963e1ee14f808ffb99897d848ddcdb28fa91ddda867eb18d303e82728f878349',
'org.briarproject:obfs4proxy-android:0.0.12-dev-40245c4a:obfs4proxy-android-0.0.12-dev-40245c4a.zip:8ab05a8f8391be2cb5ab2b665c281a06d9e3a756bd0f95a40a36ca927866ea82',
@@ -85,8 +86,9 @@ dependencyVerification {
'org.codehaus.mojo:animal-sniffer-annotations:1.18:animal-sniffer-annotations-1.18.jar:47f05852b48ee9baefef80fa3d8cea60efa4753c0013121dd7fe5eef2e5c729d',
'org.glassfish.jaxb:jaxb-runtime:2.3.1:jaxb-runtime-2.3.1.jar:45fecfa5c8217ce1f3652ab95179790ec8cc0dec0384bca51cbeb94a293d9f2f',
'org.glassfish.jaxb:txw2:2.3.1:txw2-2.3.1.jar:34975dde1c6920f1a39791142235689bc3cd357e24d05edd8ff93b885bd68d60',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jetbrains.kotlin:kotlin-reflect:1.3.72:kotlin-reflect-1.3.72.jar:a188d9367de1c4ee9479db630985c0597b20709c83161b1430d24edb27e38c40',
'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72:kotlin-stdlib-common-1.3.72.jar:5e7d1552863e480c1628b1cc39ce230ef829f5b7230106215a05acda5172203a',
'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72:kotlin-stdlib-jdk7-1.3.72.jar:40566c0c08d414b9413ba556ff7f8a0b04b98b9f0f424d122dd2088510efccc4',
@@ -94,17 +96,18 @@ dependencyVerification {
'org.jetbrains.kotlin:kotlin-stdlib:1.3.72:kotlin-stdlib-1.3.72.jar:3856a7349ebacd6d1be6802b2fed9c4dc2c5a564ea92b6b945ac988243d4b16b',
'org.jetbrains.trove4j:trove4j:20160824:trove4j-20160824.jar:1917871c8deb468307a584680c87a44572f5a8b0b98c6d397fc0f5f86596dbe7',
'org.jetbrains:annotations:13.0:annotations-13.0.jar:ace2a10dc8e2d5fd34925ecac03e4988b2c0f851650c94b8cef49ba1bd111478',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'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.jvnet.staxex:stax-ex:1.8:stax-ex-1.8.jar:95b05d9590af4154c6513b9c5dc1fb2e55b539972ba0a9ef28e9a0c01d83ad77',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-analysis:7.0:asm-analysis-7.0.jar:e981f8f650c4d900bb033650b18e122fa6b161eadd5f88978d08751f72ee8474',
'org.ow2.asm:asm-commons:7.0:asm-commons-7.0.jar:fed348ef05958e3e846a3ac074a12af5f7936ef3d21ce44a62c4fa08a771927d',
'org.ow2.asm:asm-tree:7.0:asm-tree-7.0.jar:cfd7a0874f9de36a999c127feeadfbfe6e04d4a71ee954d7af3d853f0be48a6c',
'org.ow2.asm:asm-util:7.0:asm-util-7.0.jar:75fbbca440ef463f41c2b0ab1a80abe67e910ac486da60a7863cbcb5bae7e145',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:7.0:asm-7.0.jar:b88ef66468b3c978ad0c97fd6e90979e56155b4ac69089ba7a44e9aa7ffe9acf',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
]
}
+3 -4
View File
@@ -10,10 +10,9 @@ dependencies {
implementation "com.google.dagger:dagger:2.24"
implementation 'com.google.code.findbugs:jsr305:3.0.2'
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
}
@@ -2,6 +2,7 @@ package org.briarproject.bramble.api.account;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.identity.Identity;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -43,6 +44,17 @@ public interface AccountManager {
*/
boolean createAccount(String name, String password);
/**
* Restores a given identity by registering it with the
* {@link IdentityManager}. Creates a database key, encrypts it with the
* given password and stores it on disk. {@link #accountExists()} will
* return true after this method returns true.
* @param identity
* @param password
* @return
*/
boolean restoreAccount(Identity identity, String password);
/**
* Deletes all account state from disk. {@link #accountExists()} will
* return false after this method returns.
@@ -1,6 +1,6 @@
package org.briarproject.bramble.contact;
package org.briarproject.bramble.api.contact;
interface ContactExchangeConstants {
public interface ContactExchangeConstants {
/**
* The current version of the contact exchange protocol.
@@ -1,4 +1,4 @@
package org.briarproject.bramble.contact;
package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
@@ -6,7 +6,7 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface ContactExchangeCrypto {
public interface ContactExchangeCrypto {
/**
* Derives the header key for a contact exchange stream from the master key.
@@ -0,0 +1,9 @@
package org.briarproject.bramble.api.contact;
/**
* Record types for the contact exchange protocol.
*/
public interface ContactExchangeRecordTypes {
byte CONTACT_INFO = 0;
}
@@ -3,6 +3,7 @@ package org.briarproject.bramble.api.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.UnsupportedVersionException;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DbException;
@@ -48,6 +49,10 @@ public interface ContactManager {
SecretKey rootKey, long timestamp, boolean alice, boolean verified,
boolean active) throws DbException;
ContactId addContact(Transaction txn, Author remote, AuthorId local,
PublicKey handshake, boolean verified)
throws DbException, GeneralSecurityException;
/**
* Stores a contact associated with the given local and remote pseudonyms,
* replacing the given pending contact, derives and stores handshake mode
@@ -205,6 +210,19 @@ public interface ContactManager {
void setContactAlias(ContactId c, @Nullable String alias)
throws DbException;
/**
* Sets the contact's handshake public key
*/
void setHandshakePublicKey(Transaction txn, ContactId c,
PublicKey handshakePublicKey) throws DbException,
GeneralSecurityException;
/**
* Sets the contact's handshake public key
*/
void setHandshakePublicKey(ContactId c, PublicKey handshakePublicKey)
throws DbException, GeneralSecurityException;
/**
* Returns true if a contact with this {@code remoteAuthorId} belongs to
* the local pseudonym with this {@code localAuthorId}.
@@ -24,6 +24,19 @@ public interface HandshakeManager {
HandshakeResult handshake(PendingContactId p, InputStream in,
StreamWriter out) throws DbException, IOException;
/**
* Handshakes with the given contact. Returns an ephemeral master key
* authenticated with both parties' handshake key pairs and a flag
* indicating whether the local peer is Alice or Bob.
*
* @param in An incoming stream for the handshake, which must be secured in
* handshake mode
* @param out An outgoing stream for the handshake, which must be secured
* in handshake mode
*/
HandshakeResult handshake(ContactId c, InputStream in, StreamWriter out)
throws DbException, IOException;
class HandshakeResult {
private final SecretKey masterKey;
@@ -1,12 +1,11 @@
package org.briarproject.bramble.crypto;
package org.briarproject.bramble.api.crypto;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.security.GeneralSecurityException;
@NotNullByDefault
interface AuthenticatedCipher {
public interface AuthenticatedCipher {
/**
* Initializes this cipher for encryption or decryption with a key and an
@@ -546,6 +546,11 @@ public interface DatabaseComponent extends TransactionManager {
void setContactAlias(Transaction txn, ContactId c, @Nullable String alias)
throws DbException;
/**
* Sets the remote handshake public key for a given contact
*/
void setHandshakePublicKey(Transaction txn, ContactId c, PublicKey handshakePublicKey) throws DbException;
/**
* Sets the given group's visibility to the given contact.
*/
@@ -26,6 +26,11 @@ public interface IdentityManager {
*/
void registerIdentity(Identity i);
/**
* Returns the cached local identity or loads it from the database.
*/
Identity getIdentity(Transaction txn) throws DbException;
/**
* Returns the cached local identity or loads it from the database.
*/
@@ -74,6 +74,13 @@ public interface TransportPropertyManager {
TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException;
/**
* Returns the remote transport properties for the given contact and
* transport.
*/
TransportProperties getRemoteProperties(Transaction txn, ContactId c,
TransportId t) throws DbException;
/**
* Merges the given properties with the existing local properties for the
* given transport.
@@ -35,6 +35,20 @@ public interface KeyManager {
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException;
/**
* Derives and stores a set of rotation mode transport keys for
* communicating with the given contact over each transport and returns the
* key set IDs.
* <p/>
* {@link StreamContext StreamContexts} for the contact can be created
* after this method has returned.
*
* @param alice True if the local party is Alice
* @param active Whether the derived keys can be used for outgoing streams
*/
Map<TransportId, KeySetId> addRotationKeys(ContactId c, SecretKey rootKey,
long timestamp, boolean alice, boolean active) throws DbException;
/**
* Informs the key manager that a new contact has been added. Derives and
* stores a set of handshake mode transport keys for communicating with the
@@ -102,4 +116,8 @@ public interface KeyManager {
@Nullable
StreamContext getStreamContext(TransportId t, byte[] tag)
throws DbException;
@Nullable
StreamContext getStreamContextInHandshakeMode(ContactId c, TransportId t)
throws DbException;
}
+15 -12
View File
@@ -1,24 +1,27 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger:2.24:dagger-2.24.jar:550a6e46a6dfcdf1d764887b6090cea94f783327e50e5c73754f18facfc70b64',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.codehaus.mojo.signature:java16:1.1:java16-1.1.signature:53799223a2c98dba2d0add810bed76315460df285c69e4f397ae6098f87dd619',
'org.codehaus.mojo:animal-sniffer-ant-tasks:1.16:animal-sniffer-ant-tasks-1.16.jar:890040976fbe2d584619a6a61b1fd2e925b3b5eb342a85eb2762c467c0d64e90',
'org.codehaus.mojo:animal-sniffer:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
]
}
+13 -4
View File
@@ -21,16 +21,25 @@ dependencies {
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation 'org.hsqldb:hsqldb:2.3.5' // The last version that supports Java 1.6
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation 'net.jodah:concurrentunit:0.4.2'
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.24'
signature 'org.codehaus.mojo.signature:java16:1.1@signature'
}
animalsniffer {
// Allow requireNonNull: Android desugaring rewrites it (so it's safe for us to use),
// and it gets used when passing method references instead of lambdas with Java 11.
// Note that this line allows *all* methods from java.util.Objects.
// That's the best that we can do with the configuration options that Animal Sniffer offers.
ignore 'java.util.Objects'
}
// needed to make test output available to bramble-java
configurations {
testOutput.extendsFrom(testCompile)
@@ -176,6 +176,18 @@ class AccountManagerImpl implements AccountManager {
}
}
public boolean restoreAccount(Identity identity, String password) {
synchronized (stateChangeLock) {
if (hasDatabaseKey())
throw new AssertionError("Already have a database key");
identityManager.registerIdentity(identity);
SecretKey key = crypto.generateSecretKey();
if (!encryptAndStoreDatabaseKey(key, password)) return false;
databaseKey = key;
return true;
}
}
@GuardedBy("stateChangeLock")
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
byte[] plaintext = key.getBytes();
@@ -47,7 +47,9 @@ abstract class Connection {
TransportId transportId) {
StreamContext ctx;
try {
LOG.info("Reading tag...");
byte[] tag = readTag(reader.getInputStream());
LOG.info("Read tag!");
return keyManager.getStreamContext(transportId, tag);
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
@@ -76,7 +76,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new IncomingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
t, d));
t, d, handshakeManager));
}
@Override
@@ -101,7 +101,7 @@ class ConnectionManagerImpl implements ConnectionManager {
ioExecutor.execute(new OutgoingDuplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, ioExecutor,
secureRandom, c, t, d));
secureRandom, handshakeManager, c, t, d));
}
@Override
@@ -3,6 +3,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.connection.InterruptibleConnection;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
@@ -2,6 +2,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -13,9 +14,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Executor;
import static java.util.logging.Level.WARNING;
@@ -24,6 +27,10 @@ import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class IncomingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
private final HandshakeManager handshakeManager;
// FIXME: Exchange timestamp as part of handshake protocol?
private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
IncomingDuplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
@@ -32,14 +39,18 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, TransportId transportId,
DuplexTransportConnection connection) {
DuplexTransportConnection connection,
HandshakeManager handshakeManager) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.handshakeManager = handshakeManager;
}
@Override
public void run() {
LOG.info("Running IncomingDuplexSyncConnection on transport " +
transportId.getString());
// Read and recognise the tag
StreamContext ctx = recogniseTag(reader, transportId);
if (ctx == null) {
@@ -54,10 +65,22 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Received handshake tag, expected rotation mode");
onReadError(true);
return;
if (!performHandshake(ctx, contactId)) {
LOG.warning("Handshake failed");
return;
}
// Allocate a rotation mode stream context
ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
LOG.warning("Got handshake mode context after handshaking");
onWriteError();
return;
}
}
connectionRegistry.registerIncomingConnection(contactId, transportId,
this);
@@ -103,5 +126,39 @@ class IncomingDuplexSyncConnection extends DuplexSyncConnection
onWriteError();
}
}
private boolean performHandshake(StreamContext ctxIn, ContactId contactId) {
LOG.info("Performing handshake (Incoming)");
// Allocate the outgoing stream context
StreamContext ctxOut;
try {
ctxOut = keyManager.getStreamContextInHandshakeMode(contactId, transportId);
} catch (DbException e) {
logException(LOG, WARNING, e);
LOG.warning("Could not allocate stream context");
onReadError(true);
return false;
}
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
// Flush the output stream to send the outgoing stream header
StreamWriter out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
HandshakeManager.HandshakeResult result =
handshakeManager.handshake(contactId, in, out);
keyManager.addRotationKeys(contactId, result.getMasterKey(),
TIMESTAMP, result.isAlice(), true);
LOG.info("Rotation keys added - IncomingDuplexSyncConnection");
return true;
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onReadError(true);
return false;
}
}
}
@@ -2,6 +2,7 @@ package org.briarproject.bramble.connection;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
@@ -14,9 +15,11 @@ import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
import org.briarproject.bramble.api.transport.StreamWriter;
import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
@@ -28,27 +31,40 @@ import static org.briarproject.bramble.util.LogUtils.logException;
class OutgoingDuplexSyncConnection extends DuplexSyncConnection
implements Runnable {
// FIXME: Exchange timestamp as part of handshake protocol?
private static final long TIMESTAMP = 1617235200; // 1 April 2021 00:00 UTC
private final SecureRandom secureRandom;
private final HandshakeManager handshakeManager;
private final ContactId contactId;
OutgoingDuplexSyncConnection(KeyManager keyManager,
OutgoingDuplexSyncConnection(
KeyManager keyManager,
ConnectionRegistry connectionRegistry,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
Executor ioExecutor, SecureRandom secureRandom, ContactId contactId,
TransportId transportId, DuplexTransportConnection connection) {
Executor ioExecutor,
SecureRandom secureRandom,
HandshakeManager handshakeManager,
ContactId contactId,
TransportId transportId,
DuplexTransportConnection connection) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager, ioExecutor, transportId, connection);
this.secureRandom = secureRandom;
this.handshakeManager = handshakeManager;
this.contactId = contactId;
}
@Override
public void run() {
LOG.info("Running OutgoingDuplexSyncConnection on transport " +
transportId.getString());
// Allocate a stream context
StreamContext ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
@@ -56,10 +72,23 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
return;
}
if (ctx.isHandshakeMode()) {
// TODO: Support handshake mode for contacts
LOG.warning("Cannot use handshake mode stream context");
onWriteError();
return;
LOG.info("OutgoingDuplexSyncConnection - context is in handshake mode, performing handshake");
if (!performHandshake(ctx)) {
LOG.warning("Handshake failed");
return;
}
// Allocate a rotation mode stream context
ctx = allocateStreamContext(contactId, transportId);
if (ctx == null) {
LOG.warning("Could not allocate stream context");
onWriteError();
return;
}
if (ctx.isHandshakeMode()) {
LOG.warning("Got handshake mode context after handshaking");
onWriteError();
return;
}
}
// Start the incoming session on another thread
Priority priority = generatePriority();
@@ -127,6 +156,60 @@ class OutgoingDuplexSyncConnection extends DuplexSyncConnection
}
}
private boolean performHandshake(StreamContext ctxOut) {
LOG.info("Performing handshake (Outgoing) for transport " +
ctxOut.getTransportId().getString());
// Flush the output stream to send the outgoing stream header
StreamWriter out;
try {
out = streamWriterFactory.createStreamWriter(
writer.getOutputStream(), ctxOut);
out.getOutputStream().flush();
} catch (IOException e) {
logException(LOG, WARNING, e);
onWriteError();
return false;
}
// Read and recognise the tag
StreamContext ctxIn = recogniseTag(reader, transportId);
// Unrecognised tags are suspicious in this case
if (ctxIn == null) {
LOG.warning("Unrecognised tag for returning stream");
onReadError();
return false;
}
// Check that the stream comes from the expected contact
ContactId inContactId = ctxIn.getContactId();
if (inContactId == null) {
LOG.warning("Expected contact tag, got rendezvous tag");
onReadError();
return false;
}
if (!inContactId.equals(contactId)) {
LOG.warning("Wrong contact ID for returning stream");
onReadError();
return false;
}
// TODO: Register the connection, close it if it's redundant
// Handshake and exchange contacts
try {
InputStream in = streamReaderFactory.createStreamReader(
reader.getInputStream(), ctxIn);
HandshakeManager.HandshakeResult result =
handshakeManager.handshake(contactId, in, out);
keyManager.addRotationKeys(contactId, result.getMasterKey(),
TIMESTAMP, result.isAlice(), true);
LOG.info("Rotation keys added - OutgoingDuplexSyncConnection");
return true;
} catch (IOException | DbException e) {
logException(LOG, WARNING, e);
onWriteError();
return false;
} finally {
// TODO: Unregister the connection
}
}
private void onReadError() {
// 'Recognised' is always true for outgoing connections
onReadError(true);
@@ -1,5 +1,6 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.contact.ContactExchangeCrypto;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PrivateKey;
import org.briarproject.bramble.api.crypto.PublicKey;
@@ -10,12 +11,12 @@ import java.security.GeneralSecurityException;
import javax.inject.Inject;
import static org.briarproject.bramble.contact.ContactExchangeConstants.ALICE_KEY_LABEL;
import static org.briarproject.bramble.contact.ContactExchangeConstants.ALICE_NONCE_LABEL;
import static org.briarproject.bramble.contact.ContactExchangeConstants.BOB_KEY_LABEL;
import static org.briarproject.bramble.contact.ContactExchangeConstants.BOB_NONCE_LABEL;
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.contact.ContactExchangeConstants.SIGNING_LABEL;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.ALICE_KEY_LABEL;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.ALICE_NONCE_LABEL;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.BOB_KEY_LABEL;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.BOB_NONCE_LABEL;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.SIGNING_LABEL;
@NotNullByDefault
class ContactExchangeCryptoImpl implements ContactExchangeCrypto {
@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactExchangeCrypto;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
@@ -47,8 +48,8 @@ import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
import static org.briarproject.bramble.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.contact.ContactExchangeRecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.api.contact.ContactExchangeConstants.PROTOCOL_VERSION;
import static org.briarproject.bramble.api.contact.ContactExchangeRecordTypes.CONTACT_INFO;
import static org.briarproject.bramble.util.ValidationUtils.checkLength;
import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -81,7 +82,8 @@ class ContactExchangeManagerImpl implements ContactExchangeManager {
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final TransportPropertyManager transportPropertyManager;
private final ContactExchangeCrypto contactExchangeCrypto;
private final org.briarproject.bramble.api.contact.ContactExchangeCrypto
contactExchangeCrypto;
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
@@ -1,9 +0,0 @@
package org.briarproject.bramble.contact;
/**
* Record types for the contact exchange protocol.
*/
interface ContactExchangeRecordTypes {
byte CONTACT_INFO = 0;
}
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.contact.PendingContact;
import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.contact.PendingContactState;
import org.briarproject.bramble.api.contact.event.PendingContactStateChangedEvent;
import org.briarproject.bramble.api.crypto.CryptoConstants;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.crypto.SecretKey;
@@ -119,6 +120,18 @@ class ContactManagerImpl implements ContactManager, EventListener {
verified, active));
}
@Override
public ContactId addContact(Transaction txn, Author remote, AuthorId local,
PublicKey handshake, boolean verified)
throws DbException, GeneralSecurityException {
ContactId c = db.addContact(txn, remote, local, handshake, verified);
Contact contact = db.getContact(txn, c);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addContact(txn, c, handshake, ourKeyPair);
for (ContactHook hook : hooks) hook.addingContact(txn, contact);
return c;
}
@Override
public String getHandshakeLink() throws DbException {
KeyPair keyPair = db.transactionWithResult(true,
@@ -234,6 +247,25 @@ class ContactManagerImpl implements ContactManager, EventListener {
db.transaction(false, txn -> setContactAlias(txn, c, alias));
}
@Override
public void setHandshakePublicKey(Transaction txn, ContactId c,
PublicKey handshakePublicKey) throws DbException, GeneralSecurityException {
if (handshakePublicKey.getKeyType() !=
CryptoConstants.KEY_TYPE_AGREEMENT) {
throw new IllegalArgumentException();
}
db.setHandshakePublicKey(txn, c, handshakePublicKey);
KeyPair ourKeyPair = identityManager.getHandshakeKeys(txn);
keyManager.addContact(txn, c, handshakePublicKey, ourKeyPair);
}
@Override
public void setHandshakePublicKey(ContactId c, PublicKey handshakePublicKey)
throws DbException, GeneralSecurityException {
db.transaction(false,
txn -> setHandshakePublicKey(txn, c, handshakePublicKey));
}
@Override
public boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
AuthorId localAuthorId) throws DbException {
@@ -1,5 +1,6 @@
package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.contact.ContactExchangeCrypto;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
@@ -3,6 +3,8 @@ package org.briarproject.bramble.contact;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.Predicate;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.contact.HandshakeManager;
import org.briarproject.bramble.api.contact.PendingContact;
@@ -88,6 +90,27 @@ class HandshakeManagerImpl implements HandshakeManager {
});
PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond();
return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
}
@Override
public HandshakeResult handshake(ContactId c, InputStream in,
StreamWriter out) throws DbException, IOException {
Pair<PublicKey, KeyPair> keys = db.transactionWithResult(true, txn -> {
Contact contact = contactManager.getContact(txn, c);
PublicKey handshakePublicKey = contact.getHandshakePublicKey();
if (handshakePublicKey == null) throw new DbException();
KeyPair keyPair = identityManager.getHandshakeKeys(txn);
return new Pair<>(handshakePublicKey, keyPair);
});
PublicKey theirStaticPublicKey = keys.getFirst();
KeyPair ourStaticKeyPair = keys.getSecond();
return handshake(theirStaticPublicKey, ourStaticKeyPair, in, out);
}
private HandshakeResult handshake(PublicKey theirStaticPublicKey,
KeyPair ourStaticKeyPair, InputStream in, StreamWriter out)
throws IOException {
boolean alice = transportCrypto.isAlice(theirStaticPublicKey,
ourStaticKeyPair);
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
@@ -6,6 +6,7 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyPair;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.KeyAgreementCrypto;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.api.crypto.StreamDecrypterFactory;
@@ -1,6 +1,7 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.FormatException;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamDecrypter;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.crypto.StreamEncrypter;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.spongycastle.crypto.DataLengthException;
@@ -32,6 +32,7 @@ import org.briarproject.bramble.api.transport.KeySetId;
import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeys;
import java.sql.Connection;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -695,6 +696,11 @@ interface Database<T> {
void setHandshakeKeyPair(T txn, AuthorId local, PublicKey publicKey,
PrivateKey privateKey) throws DbException;
/**
* Sets the handshake public key for a given contact
*/
void setHandshakePublicKey(T txn, ContactId c, PublicKey handshakePublicKey) throws DbException;
/**
* Marks the given message as permanent, i.e. not temporary.
*/
@@ -1040,6 +1040,15 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
}
@Override
public void setHandshakePublicKey(Transaction transaction, ContactId c, PublicKey handshakePublicKey) throws DbException {
if (transaction.isReadOnly()) throw new IllegalArgumentException();
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
db.setHandshakePublicKey(txn, c, handshakePublicKey);
}
@Override
public void setHandshakeKeyPair(Transaction transaction, AuthorId local,
PublicKey publicKey, PrivateKey privateKey) throws DbException {
@@ -3058,6 +3058,23 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public void setHandshakePublicKey(Connection txn, ContactId c, PublicKey handshakePublicKey) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE contacts SET handshakePublicKey = ? WHERE contactId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, handshakePublicKey.getEncoded());
ps.setInt(2, c.getInt());
int affected = ps.executeUpdate();
if (affected < 0 || affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public void setGroupVisibility(Connection txn, ContactId c, GroupId g,
boolean shared) throws DbException {
@@ -118,6 +118,11 @@ class IdentityManagerImpl implements IdentityManager, OpenDatabaseHook {
return cached.getLocalAuthor();
}
@Override
public Identity getIdentity(Transaction txn) throws DbException {
return getCachedIdentity(txn);
}
@Override
public LocalAuthor getLocalAuthor(Transaction txn) throws DbException {
return getCachedIdentity(txn).getLocalAuthor();
@@ -127,6 +127,7 @@ class KeyAgreementConnector {
List<Pair<DuplexPlugin, BdfList>> transports = new ArrayList<>();
for (TransportId id : PREFERRED_TRANSPORTS) {
TransportDescriptor d = descriptors.get(id);
LOG.info("id: " + id + " d: " + d);
Plugin p = pluginManager.getPlugin(id);
if (d != null && p instanceof DuplexPlugin) {
if (LOG.isLoggable(INFO))
@@ -137,6 +138,9 @@ class KeyAgreementConnector {
// TODO: If we don't have any transports in common with the peer,
// warn the user and give up (#1224)
if (transports.isEmpty()) {
LOG.info("No transports found");
}
if (!transports.isEmpty()) {
byte[] commitment = remotePayload.getCommitment();
@@ -34,12 +34,12 @@ class KeyAgreementTransport {
Logger.getLogger(KeyAgreementTransport.class.getName());
// Accept records with current protocol version, known record type
private static Predicate<Record> ACCEPT = r ->
private static final Predicate<Record> ACCEPT = r ->
r.getProtocolVersion() == PROTOCOL_VERSION &&
isKnownRecordType(r.getRecordType());
// Ignore records with current protocol version, unknown record type
private static Predicate<Record> IGNORE = r ->
private static final Predicate<Record> IGNORE = r ->
r.getProtocolVersion() == PROTOCOL_VERSION &&
!isKnownRecordType(r.getRecordType());
@@ -179,13 +179,6 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Stopping services");
state = STOPPING;
eventBus.broadcast(new LifecycleEvent(STOPPING));
LOG.info("Sleeping a bit to simulate slowness");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOG.info("Done simulating slowness");
for (Service s : services) {
long start = now();
s.stopService();
@@ -470,6 +470,10 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (now - then >= V3_MIGRATION_PERIOD_MS) retireV2HiddenService();
else publishV2HiddenService(port, privKey2);
}
if (isNullOrEmpty(privKey3)) {
TransportProperties p = callback.getLocalProperties();
privKey3 = p.get(HS_PRIVATE_KEY_V3);
}
publishV3HiddenService(port, privKey3);
}
@@ -511,9 +515,11 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
try {
// Use the control connection to set up the hidden service
if (privKey == null) {
LOG.info("Private key is null");
response = controlConnection.addOnion("NEW:ED25519-V3",
portLines, null);
} else {
LOG.info("Private key is not null");
response = controlConnection.addOnion(privKey, portLines);
}
} catch (IOException e) {
@@ -535,12 +541,15 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
if (privKey == null) {
// Publish the hidden service's onion hostname in transport props
TransportProperties p = new TransportProperties();
String now = String.valueOf(clock.currentTimeMillis());
p.put(PROP_ONION_V3, onion3);
p.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
p.put(HS_V3_CREATED, now);
callback.mergeLocalProperties(p);
// Save the hidden service's private key for next time
Settings s = new Settings();
s.put(HS_PRIVATE_KEY_V3, response.get(HS_PRIVKEY));
s.put(HS_V3_CREATED, String.valueOf(clock.currentTimeMillis()));
s.put(HS_V3_CREATED, now);
callback.mergeSettings(s);
}
}
@@ -294,7 +294,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
public TransportProperties getRemoteProperties(ContactId c, TransportId t)
throws DbException {
return db.transactionWithResult(true, txn ->
getRemoteProperties(txn, db.getContact(txn, c), t));
getRemoteProperties(txn, c, t));
}
@Override
public TransportProperties getRemoteProperties(Transaction txn,
ContactId c, TransportId t) throws DbException {
return getRemoteProperties(txn, db.getContact(txn, c), t);
}
@Override
@@ -101,9 +101,9 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
}
@Override
public Map<TransportId, KeySetId> addRotationKeys(
Transaction txn, ContactId c, SecretKey rootKey, long timestamp,
boolean alice, boolean active) throws DbException {
public Map<TransportId, KeySetId> addRotationKeys(Transaction txn,
ContactId c, SecretKey rootKey, long timestamp, boolean alice,
boolean active) throws DbException {
Map<TransportId, KeySetId> ids = new HashMap<>();
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey();
@@ -114,12 +114,21 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
return ids;
}
@Override
public Map<TransportId, KeySetId> addRotationKeys(ContactId c,
SecretKey rootKey, long timestamp, boolean alice, boolean active)
throws DbException {
return db.transactionWithResult(false, txn ->
addRotationKeys(txn, c, rootKey, timestamp, alice, active));
}
@Override
public Map<TransportId, KeySetId> addContact(Transaction txn, ContactId c,
PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
LOG.info("Deriving root handshake key " + c.toString() + " " + ourKeyPair.getPublic().toString() + " them: " + theirPublicKey.toString());
SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, false);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
@@ -127,6 +136,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
for (Entry<TransportId, TransportKeyManager> e : managers.entrySet()) {
TransportId t = e.getKey();
TransportKeyManager m = e.getValue();
LOG.info("Adding handshake keys for transport " + t.getString());
ids.put(t, m.addHandshakeKeys(txn, c, rootKey, alice));
}
return ids;
@@ -137,7 +147,7 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
PendingContactId p, PublicKey theirPublicKey, KeyPair ourKeyPair)
throws DbException, GeneralSecurityException {
SecretKey staticMasterKey = transportCrypto
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
.deriveStaticMasterKey(theirPublicKey, ourKeyPair);
SecretKey rootKey =
transportCrypto.deriveHandshakeRootKey(staticMasterKey, true);
boolean alice = transportCrypto.isAlice(theirPublicKey, ourKeyPair);
@@ -197,6 +207,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
m.getStreamContext(txn, tag)));
}
@Override
public StreamContext getStreamContextInHandshakeMode(ContactId c, TransportId t)
throws DbException {
return withManager(t, m ->
db.transactionWithNullableResult(false, txn ->
m.getStreamContextInHandshakeMode(txn, c)));
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactRemovedEvent) {
@@ -48,4 +48,7 @@ interface TransportKeyManager {
StreamContext getStreamContext(Transaction txn, byte[] tag)
throws DbException;
@Nullable
StreamContext getStreamContextInHandshakeMode(Transaction txn, ContactId c)
throws DbException;
}
@@ -19,6 +19,7 @@ import org.briarproject.bramble.api.transport.StreamContext;
import org.briarproject.bramble.api.transport.TransportKeySet;
import org.briarproject.bramble.api.transport.TransportKeys;
import org.briarproject.bramble.transport.ReorderingWindow.Change;
import org.briarproject.bramble.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -182,9 +183,12 @@ class TransportKeyManagerImpl implements TransportKeyManager {
if (old == null || (old.getKeys().isHandshakeMode() &&
!ks.getKeys().isHandshakeMode()) ||
old.getKeySetId().getInt() < ks.getKeySetId().getInt()) {
LOG.info("Replacing Outgoing keys!");
if (ks.getContactId() == null)
pendingContactOutContexts.put(ks.getPendingContactId(), ks);
else contactOutContexts.put(ks.getContactId(), ks);
} else {
LOG.info("Not replacing Outgoing keys!");
}
}
}
@@ -372,6 +376,9 @@ class TransportKeyManagerImpl implements TransportKeyManager {
MutableTransportKeySet ks = getOutgoingKeySet(c, p);
if (ks == null) return null;
MutableTransportKeys keys = ks.getKeys();
LOG.info("Using keys for outgoing connection - handshake mode: " +
keys.isHandshakeMode());
MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys();
if (!outKeys.isActive()) throw new AssertionError();
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED) return null;
@@ -395,7 +402,18 @@ class TransportKeyManagerImpl implements TransportKeyManager {
try {
// Look up the incoming keys for the tag
TagContext tagCtx = inContexts.remove(new Bytes(tag));
if (tagCtx == null) return null;
if (tagCtx == null) {
LOG.info("Cannot find tag!");
for (MutableTransportKeySet t : keys.values()) {
LOG.info("Header key: " + StringUtils
.toHexString(t.getKeys().getCurrentIncomingKeys()
.getHeaderKey().getBytes()));
LOG.info("Tag key: " + StringUtils.toHexString(
t.getKeys().getCurrentIncomingKeys().getTagKey()
.getBytes()));
}
return null;
}
MutableIncomingKeys inKeys = tagCtx.inKeys;
// Create a stream context
StreamContext ctx = new StreamContext(tagCtx.contactId,
@@ -443,6 +461,51 @@ class TransportKeyManagerImpl implements TransportKeyManager {
}
}
@Override
public StreamContext getStreamContextInHandshakeMode(Transaction txn,
ContactId c)
throws DbException {
lock.lock();
try {
MutableTransportKeySet ks = getOutgoingKeySet(c, null);
if (ks == null) return null;
MutableTransportKeys currentKeys = ks.getKeys();
LOG.info("Current keys handshake mode? " +
currentKeys.isHandshakeMode());
if (currentKeys.isHandshakeMode())
return getStreamContext(txn, c, null);
for (MutableTransportKeySet keySet : this.keys.values()) {
MutableTransportKeys keys = keySet.getKeys();
if (!keys.isHandshakeMode()) continue;
LOG.info("Found handshake mode keys");
MutableOutgoingKeys outKeys = keys.getCurrentOutgoingKeys();
// if (!outKeys.isActive()) throw new AssertionError();
if (outKeys.getStreamCounter() > MAX_32_BIT_UNSIGNED)
return null;
// Create a stream context
LOG.info("Creating handshake mode stream context");
StreamContext ctx = new StreamContext(c, null, transportId,
outKeys.getTagKey(), outKeys.getHeaderKey(),
outKeys.getStreamCounter(), keys.isHandshakeMode());
LOG.info("Tag key: " +
StringUtils.toHexString(outKeys.getTagKey().getBytes()));
LOG.info("Header key: " +
StringUtils.toHexString(outKeys.getHeaderKey().getBytes()));
// Increment the stream counter and write it back to the DB
outKeys.incrementStreamCounter();
db.incrementStreamCounter(txn, transportId,
keySet.getKeySetId());
LOG.info("Returning");
return ctx;
}
LOG.info("Cannot find handshake mode keys");
return null;
} finally {
lock.unlock();
}
}
@DatabaseExecutor
@Wakeful
private void updateKeys(Transaction txn) throws DbException {
+2 -2
View File
@@ -1,8 +1,8 @@
Bridge obfs4 192.95.36.142:443 CDF2E852BF539B82BD10E27E9115A31734E378C2 cert=qUVQ0srL1JI/vO6V6m/24anYXiJD3QP2HgzUKQtQ7GRqqUvs7P+tG43RtAqdhLOALP7DJQ iat-mode=1
Bridge obfs4 38.229.1.78:80 C8CBDB2464FC9804A69531437BCF2BE31FDD2EE4 cert=Hmyfd2ev46gGY7NoVxA9ngrPF2zCZtzskRTzoWXbxNkzeVnGFPWmrTtILRyqCTjHR+s9dg iat-mode=1
Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1OpbAH0wNqOT6H6BmRQ iat-mode=1
Bridge obfs4 37.218.245.14:38224 D9A82D2F9C2F65A18407B1D2B764F130847F8B5D cert=bjRaMrr1BRiAW8IE9U5z27fQaYgOhX1UCmOpg2pFpoMvo6ZgQMzLsaTzzQNTlm7hNcb+Sg iat-mode=0
Bridge obfs4 85.31.186.26:443 91A6354697E6B02A386312F68D82CF86824D3606 cert=PBwr+S8JTVZo6MPdHnkTwXJPILWADLqfMGoVvhZClMq/Urndyd42BwX9YFJHZnBB3H0XCw iat-mode=0
Bridge obfs4 85.31.186.98:443 011F2599C0E9B27EE74B353155E244813763C3E5 cert=ayq0XzCwhpdysn5o0EyDUbmSOx3X/oTEbzDMvczHOdBJKlvIdHHLJGkZARtT4dcBFArPPg iat-mode=0
Bridge obfs4 144.217.20.138:80 FB70B257C162BF1038CA669D568D76F5B7F0BABB cert=vYIV5MgrghGQvZPIi1tJwnzorMgqgmlKaB77Y3Z9Q/v94wZBOAXkW+fdx4aSxLVnKO+xNw iat-mode=0
Bridge obfs4 193.11.166.194:27015 2D82C2E354D531A68469ADF7F878FA6060C6BACA cert=4TLQPJrTSaDffMK7Nbao6LC7G9OW/NHkUwIdjLSS3KYf0Nv4/nQiiI8dY2TcsQx01NniOg iat-mode=0
Bridge obfs4 193.11.166.194:27020 86AC7B8D430DAC4117E9F42C9EAED18133863AAF cert=0LDeJH4JzMDtkJJrFphJCiPqKx7loozKN7VNfuukMGfHO0Z8OGdzHVkhVAOfo1mUdv9cMg iat-mode=0
Bridge obfs4 193.11.166.194:27025 1AE2C08904527FEA90C4C4F8C1083EA59FBC6FAF cert=ItvYZzW5tn6v3G4UnQa6Qz04Npro6e81AP70YujmK/KXwDFPTs3aHXcHp4n8Vt6w/bv8cA iat-mode=0
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageContext;
import org.briarproject.bramble.test.ValidatorTestCase;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
@@ -38,7 +38,7 @@ public class BdfMessageValidatorTest extends ValidatorTestCase {
private final Metadata meta = new Metadata();
public BdfMessageValidatorTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}
@Test(expected = InvalidMessageException.class)
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.TestUtils;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -1,5 +1,6 @@
package org.briarproject.bramble.crypto;
import org.briarproject.bramble.api.crypto.AuthenticatedCipher;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
@@ -11,8 +11,8 @@ import org.briarproject.bramble.api.keyagreement.PayloadEncoder;
import org.briarproject.bramble.test.BrambleTestCase;
import org.jmock.Expectations;
import org.jmock.auto.Mock;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.jmock.integration.junit4.JUnitRuleMockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Rule;
import org.junit.Test;
@@ -34,7 +34,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
@Rule
public JUnitRuleMockery context = new JUnitRuleMockery() {{
// So we can mock concrete classes like KeyAgreementTransport
setImposteriser(ClassImposteriser.INSTANCE);
setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
}};
private final PublicKey alicePubKey = getAgreementPublicKey();
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.record.RecordWriterFactory;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Test;
import java.io.InputStream;
@@ -58,7 +58,7 @@ public class KeyAgreementTransportTest extends BrambleMockTestCase {
private KeyAgreementTransport kat;
public KeyAgreementTransportTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
inputStream = context.mock(InputStream.class);
outputStream = context.mock(OutputStream.class);
}
@@ -25,7 +25,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Before;
import org.junit.Test;
@@ -69,7 +69,7 @@ public class PollerImplTest extends BrambleMockTestCase {
private PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class);
}
+16 -12
View File
@@ -1,6 +1,7 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
'com.google.dagger:dagger-producers:2.24:dagger-producers-2.24.jar:f10f45b95191954d5d6b043fca9e62fb621d21bf70634b8f8476c7988b504c3a',
@@ -19,11 +20,12 @@ dependencyVerification {
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.i2p.crypto:eddsa:0.2.0:eddsa-0.2.0.jar:a7cb1b85c16e2f0730b9204106929a1d9aaae1df728adc7041a8b8b605692140',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.jodah:concurrentunit:0.4.2:concurrentunit-0.4.2.jar:5583078e1acf91734939e985bc9e7ee947b0e93a8eef679da6bb07bbeb47ced3',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.bitlet:weupnp:0.1.4:weupnp-0.1.4.jar:88df7e6504929d00bdb832863761385c68ab92af945b04f0770b126270a444fb',
'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',
@@ -32,16 +34,18 @@ dependencyVerification {
'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:1.16:animal-sniffer-1.16.jar:72be8bcc226ba43b937c722a08a07852bfa1b11400089265d5df0ee7b38b1d52',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.hsqldb:hsqldb:2.3.5:hsqldb-2.3.5.jar:6676a6977ac98997a80f827ddbd3fe8ca1e0853dad1492512135fd1a222ccfad',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm-all:5.2:asm-all-5.2.jar:7fbffbc1db3422e2101689fd88df8384b15817b52b9b2b267b9f6d2511dc198d',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
'org.whispersystems:curve25519-java:0.5.0:curve25519-java-0.5.0.jar:0aadd43cf01d11e9b58f867b3c4f25c3194e8b0623d1953d32dfbfbee009e38d',
]
}
+3 -4
View File
@@ -23,10 +23,9 @@ dependencies {
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
testImplementation project(path: ':bramble-core', configuration: 'testOutput')
testImplementation 'junit:junit:4.12'
testImplementation "org.jmock:jmock:2.8.2"
testImplementation "org.jmock:jmock-junit4:2.8.2"
testImplementation "org.jmock:jmock-legacy:2.8.2"
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testAnnotationProcessor 'com.google.dagger:dagger-compiler:2.24'
}
+15 -12
View File
@@ -1,6 +1,7 @@
dependencyVerification {
verify = [
'cglib:cglib:3.2.0:cglib-3.2.0.jar:adb13bab79712ad6bdf1bd59f2a3918018a8016e722e8a357065afb9e6690861',
'cglib:cglib:3.2.8:cglib-3.2.8.jar:3f64de999ecc5595dc84ca8ff0879d8a34c8623f9ef3c517a53ed59023fcb9db',
'com.google.code.findbugs:annotations:3.0.1:annotations-3.0.1.jar:6b47ff0a6de0ce17cbedc3abb0828ca5bce3009d53ea47b3723ff023c4742f79',
'com.google.code.findbugs:jsr305:3.0.2:jsr305-3.0.2.jar:766ad2a0783f2687962c8ad74ceecc38a28b9f72a2d085ee438b7813e928d0c7',
'com.google.dagger:dagger-compiler:2.24:dagger-compiler-2.24.jar:3c5afb955fb188da485cb2c048eff37dce0e1530b9780a0f2f7187d16d1ccc1f',
'com.google.dagger:dagger-producers:2.24:dagger-producers-2.24.jar:f10f45b95191954d5d6b043fca9e62fb621d21bf70634b8f8476c7988b504c3a',
@@ -17,24 +18,26 @@ dependencyVerification {
'javax.annotation:jsr250-api:1.0:jsr250-api-1.0.jar:a1a922d0d9b6d183ed3800dfac01d1e1eb159f0e8c6f94736931c1def54a941f',
'javax.inject:javax.inject:1:javax.inject-1.jar:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'junit:junit:4.12:junit-4.12.jar:59721f0805e223d84b90677887d9ff567dc534d7c502ca903c0c2b17f05c116a',
'net.bytebuddy:byte-buddy:1.9.12:byte-buddy-1.9.12.jar:3688c3d434bebc3edc5516296a2ed0f47b65e451071b4afecad84f902f0efc11',
'net.java.dev.jna:jna-platform:4.5.2:jna-platform-4.5.2.jar:f1d00c167d8921c6e23c626ef9f1c3ae0be473c95c68ffa012bc7ae55a87e2d6',
'net.java.dev.jna:jna:4.5.2:jna-4.5.2.jar:0c8eb7acf67261656d79005191debaba3b6bf5dd60a43735a245429381dbecff',
'net.jcip:jcip-annotations:1.0:jcip-annotations-1.0.jar:be5805392060c71474bf6c9a67a099471274d30b83eef84bfc4e0889a4f1dcc0',
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache.ant:ant-launcher:1.9.4:ant-launcher-1.9.4.jar:7bccea20b41801ca17bcbc909a78c835d0f443f12d639c77bd6ae3d05861608d',
'org.apache.ant:ant:1.9.4:ant-1.9.4.jar:649ae0730251de07b8913f49286d46bba7b92d47c5f332610aa426c4f02161d8',
'org.beanshell:bsh:1.3.0:bsh-1.3.0.jar:9b04edc75d19db54f1b4e8b5355e9364384c6cf71eb0a1b9724c159d779879f8',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy:0.0.12-dev-40245c4a:obfs4proxy-0.0.12-dev-40245c4a.zip:172029e7058b3a83ac93ac4991a44bf76e16ce8d46f558f5836d57da3cb3a766',
'org.briarproject:tor:0.3.5.13-1:tor-0.3.5.13-1.zip:ef35c16bf8dc1f4c75ed71d9f55e4514f383d124ec96b859aca647c990927c99',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',
'org.hamcrest:hamcrest-core:1.3:hamcrest-core-1.3.jar:66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9',
'org.hamcrest:hamcrest-library:1.3:hamcrest-library-1.3.jar:711d64522f9ec410983bd310934296da134be4254a125080a0416ec178dfad1c',
'org.jmock:jmock-junit4:2.8.2:jmock-junit4-2.8.2.jar:f7ee4df4f7bd7b7f1cafad3b99eb74d579f109d5992ff625347352edb55e674c',
'org.jmock:jmock-legacy:2.8.2:jmock-legacy-2.8.2.jar:f2b985a5c08a9edb7f37612330c058809da3f6a6d63ce792426ebf8ff0d6d31b',
'org.jmock:jmock-testjar:2.8.2:jmock-testjar-2.8.2.jar:8900860f72c474e027cf97fe78dcbf154a1aa7fc62b6845c5fb4e4f3c7bc8760',
'org.jmock:jmock:2.8.2:jmock-2.8.2.jar:6c73cb4a2e6dbfb61fd99c9a768539c170ab6568e57846bd60dbf19596b65b16',
'org.objenesis:objenesis:2.1:objenesis-2.1.jar:c74330cc6b806c804fd37e74487b4fe5d7c2750c5e15fbc6efa13bdee1bdef80',
'org.ow2.asm:asm:5.0.4:asm-5.0.4.jar:896618ed8ae62702521a78bc7be42b7c491a08e6920a15f89a3ecdec31e9a220',
'org.hamcrest:hamcrest-core:2.1:hamcrest-core-2.1.jar:e09109e54a289d88506b9bfec987ddd199f4217c9464132668351b9a4f00bee9',
'org.hamcrest:hamcrest-library:2.1:hamcrest-library-2.1.jar:b7e2b6895b3b679f0e47b6380fda391b225e9b78505db9d8bdde8d3cc8d52a21',
'org.hamcrest:hamcrest:2.1:hamcrest-2.1.jar:ba93b2e3a562322ba432f0a1b53addcc55cb188253319a020ed77f824e692050',
'org.jmock:jmock-imposters:2.12.0:jmock-imposters-2.12.0.jar:3b836269745a137c9b2347e8d7c2104845b126ef04f012d6bfd94f1a7dea7b09',
'org.jmock:jmock-junit4:2.12.0:jmock-junit4-2.12.0.jar:3233062fc889637c151a24f1ee086bad04321ab7d8264fef279daff0fa27205b',
'org.jmock:jmock-legacy:2.12.0:jmock-legacy-2.12.0.jar:dea3a9cca653d082e2fe7e40232e982fe03a9984c7d67ceff24f3e03fe580dcd',
'org.jmock:jmock-testjar:2.12.0:jmock-testjar-2.12.0.jar:efefbcf6cd294d0e29f0c46eb2a3380d4ca4e1763ff719c69e2f2ac62f564a04',
'org.jmock:jmock:2.12.0:jmock-2.12.0.jar:266d07314c0cd343c46ff8a55601272de8cf406807caf55e6f313295f83d10be',
'org.objenesis:objenesis:3.0.1:objenesis-3.0.1.jar:7a8ff780b9ff48415d7c705f60030b0acaa616e7f823c98eede3b63508d4e984',
'org.ow2.asm:asm:7.1:asm-7.1.jar:4ab2fa2b6d2cc9ccb1eaa05ea329c407b47b13ed2915f62f8c4b8cc96258d4de',
]
}
+10 -8
View File
@@ -26,9 +26,9 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
versionCode 10220
versionName "1.2.20"
applicationId "org.briarproject.briar.android"
versionCode 10218
versionName "1.2.18"
applicationId "org.briarproject.briar.socialbackup"
vectorDrawables.useSupportLibrary = true
buildConfigField "String", "GitHash",
@@ -106,6 +106,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
implementation 'org.magmacollective.darkcrystal:dark-crystal-secret-sharing-wrapper:1.1.0'
implementation 'info.guardianproject.panic:panic:1.0'
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
@@ -136,11 +137,12 @@ dependencies {
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'org.mockito:mockito-core:3.1.0'
testImplementation 'junit:junit:4.13.1'
testImplementation "org.jmock:jmock:$jmockVersion"
testImplementation "org.jmock:jmock-junit4:$jmockVersion"
testImplementation "org.jmock:jmock-legacy:$jmockVersion"
testImplementation 'org.mockito:mockito-core:3.9.0'
testImplementation "junit:junit:$junit_version"
testImplementation "org.jmock:jmock:$jmock_version"
testImplementation "org.jmock:jmock-junit4:$jmock_version"
testImplementation "org.jmock:jmock-imposters:$jmock_version"
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24"
androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput')
+2
View File
@@ -40,3 +40,5 @@
# Dependency injection annotations, needed for UI tests on older API levels
-keep class javax.inject.**
-keep class com.sun.jna.** { *; }
@@ -4,20 +4,16 @@ import android.app.Activity;
import android.content.Intent;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.api.settings.SettingsManager;
import org.briarproject.briar.R;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
@SuppressWarnings("WeakerAccess")
@@ -31,8 +27,6 @@ public abstract class UiTest {
protected AccountManager accountManager;
@Inject
protected LifecycleManager lifecycleManager;
@Inject
protected SettingsManager settingsManager;
public UiTest() {
BriarTestComponentApplication app = getApplicationContext();
@@ -45,8 +39,22 @@ public abstract class UiTest {
protected class CleanAccountTestRule<A extends Activity>
extends IntentsTestRule<A> {
@Nullable
private final Runnable runnable;
public CleanAccountTestRule(Class<A> activityClass) {
super(activityClass);
this.runnable = null;
}
/**
* Use this if you need to run code before launching the activity.
* Note: You need to use {@link #launchActivity(Intent)} yourself
* to start the activity.
*/
public CleanAccountTestRule(Class<A> activityClass, Runnable runnable) {
super(activityClass, false, false);
this.runnable = runnable;
}
@Override
@@ -54,17 +62,16 @@ public abstract class UiTest {
super.beforeActivityLaunched();
accountManager.deleteAccount();
accountManager.createAccount(USERNAME, PASSWORD);
Intent serviceIntent =
new Intent(getApplicationContext(), BriarService.class);
getApplicationContext().startService(serviceIntent);
try {
lifecycleManager.waitForStartup();
// do not show doze white-listing dialog
Settings settings = new Settings();
settings.putBoolean(DOZE_ASK_AGAIN, false);
settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE);
} catch (InterruptedException | DbException e) {
throw new AssertionError(e);
if (runnable != null) {
Intent serviceIntent =
new Intent(getApplicationContext(), BriarService.class);
getApplicationContext().startService(serviceIntent);
try {
lifecycleManager.waitForStartup();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
runnable.run();
}
}
}
@@ -10,15 +10,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import androidx.test.filters.LargeTest;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@LargeTest
@RunWith(Parameterized.class)
public class PngSuiteImageCompressorTest
extends AbstractImageCompressorTest {
@@ -12,14 +12,11 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import androidx.test.filters.LargeTest;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.isOptionalTestEnabled;
import static org.junit.Assume.assumeTrue;
@LargeTest
@RunWith(Parameterized.class)
public class PngSuiteImageSizeCalculatorTest
extends AbstractImageSizeCalculatorTest {
@@ -0,0 +1,39 @@
package org.briarproject.briar.android.conversation;
import android.content.Context;
import android.content.Intent;
import org.briarproject.briar.R;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.conversation.ConversationActivity.CONTACT_ID;
@RunWith(AndroidJUnit4.class)
public class ConversationActivityNotSignedInTest {
@Rule
public ActivityTestRule<ConversationActivity> testRule =
new ActivityTestRule<>(ConversationActivity.class, false, false);
@Test
public void openWithoutSignedIn() {
Context targetContext = getInstrumentation().getTargetContext();
Intent intent = new Intent(targetContext, ConversationActivity.class);
intent.putExtra(CONTACT_ID, 1);
testRule.launchActivity(intent);
onView(withText(R.string.sign_in_button))
.perform(waitUntilMatches(isDisplayed()));
}
}
@@ -16,7 +16,6 @@ import androidx.test.espresso.contrib.DrawerActions;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
@@ -30,9 +29,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.briarproject.briar.android.ViewActions.waitUntilMatches;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assume.assumeTrue;
@RunWith(AndroidJUnit4.class)
public class SettingsActivityScreenshotTest extends ScreenshotTest {
@@ -79,8 +76,6 @@ public class SettingsActivityScreenshotTest extends ScreenshotTest {
@Test
public void appLock() {
assumeTrue("device has no screen lock",
hasScreenLock(getApplicationContext()));
// scroll down
onView(withClassName(is(RecyclerView.class.getName())))
.perform(scrollTo(hasDescendant(
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Briar Debug</string>
<string name="app_package" translatable="false">org.briarproject.briar.android.debug</string>
<string name="app_name" translatable="false">Briar SB Debug</string>
<string name="app_package" translatable="false">org.briarproject.briar.socialbackup.debug</string>
</resources>
+46 -1
View File
@@ -138,6 +138,51 @@
</intent-filter>
</activity>
<activity
android:name="org.briarproject.briar.android.account.NewOrRecoverActivity"
android:label="@string/activity_name_new_or_recover_account"
android:parentActivityName="org.briarproject.briar.android.login.StartupActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.login.StartupActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.socialbackup.DistributedBackupActivity"
android:label="@string/activity_name_distributed_backup"
android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.settings.SettingsActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity"
android:label="@string/activity_name_custodian_help_recovery"
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.conversation.ConversationActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity"
android:label="@string/activity_name_recovery"
android:parentActivityName="org.briarproject.briar.android.account.NewOrRecoverActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.account.NewOrRecoverActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.socialbackup.recover.RestoreAccountActivity"
android:label="@string/activity_name_restore_account"
android:parentActivityName="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity" />
</activity>
<activity
android:name="org.briarproject.briar.android.conversation.ConversationActivity"
android:label="@string/app_name"
@@ -342,7 +387,7 @@
</activity>
<activity
android:name="org.briarproject.briar.android.keyagreement.ContactExchangeActivity"
android:name="org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity"
android:label="@string/add_contact_title"
android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"
android:theme="@style/BriarTheme.NoActionBar">
@@ -8,11 +8,13 @@ import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
import org.briarproject.bramble.api.FeatureFlags;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.client.ClientHelper;
import org.briarproject.bramble.api.connection.ConnectionRegistry;
import org.briarproject.bramble.api.contact.ContactExchangeManager;
import org.briarproject.bramble.api.contact.ContactManager;
import org.briarproject.bramble.api.crypto.CryptoExecutor;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus;
@@ -36,11 +38,6 @@ import org.briarproject.briar.android.attachment.media.MediaModule;
import org.briarproject.briar.android.conversation.glide.BriarModelLoader;
import org.briarproject.briar.android.logging.CachingLogHandler;
import org.briarproject.briar.android.login.SignInReminderReceiver;
import org.briarproject.briar.android.settings.ConnectionsFragment;
import org.briarproject.briar.android.settings.DisplayFragment;
import org.briarproject.briar.android.settings.NotificationsFragment;
import org.briarproject.briar.android.settings.SecurityFragment;
import org.briarproject.briar.android.settings.SettingsFragment;
import org.briarproject.briar.android.view.EmojiTextInputView;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
@@ -64,6 +61,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
import org.briarproject.briar.api.socialbackup.SocialBackupManager;
import org.briarproject.briar.api.test.TestDataCreator;
import java.util.concurrent.Executor;
@@ -189,6 +187,12 @@ public interface AndroidComponent
Thread.UncaughtExceptionHandler exceptionHandler();
SocialBackupManager socialBackupManager();
DatabaseComponent databaseComponent();
ClientHelper clientHelper();
void inject(SignInReminderReceiver briarService);
void inject(BriarService briarService);
@@ -198,14 +202,4 @@ public interface AndroidComponent
void inject(EmojiTextInputView textInputView);
void inject(BriarModelLoader briarModelLoader);
void inject(SettingsFragment settingsFragment);
void inject(DisplayFragment displayFragment);
void inject(ConnectionsFragment connectionsFragment);
void inject(SecurityFragment securityFragment);
void inject(NotificationsFragment notificationsFragment);
}
@@ -1,7 +1,6 @@
package org.briarproject.briar.android;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.StrictMode;
@@ -31,11 +30,10 @@ import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.DozeHelperModule;
import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.account.SetupModule;
import org.briarproject.briar.android.blog.BlogModule;
import org.briarproject.briar.android.contact.ContactListModule;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactModule;
import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.introduction.IntroductionModule;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.logging.LoggingModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.navdrawer.NavDrawerModule;
@@ -44,6 +42,8 @@ import org.briarproject.briar.android.privategroup.list.GroupListModule;
import org.briarproject.briar.android.reporting.DevReportModule;
import org.briarproject.briar.android.settings.SettingsModule;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardModule;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardModule;
import org.briarproject.briar.android.test.TestAvatarCreatorImpl;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
@@ -51,6 +51,7 @@ import org.briarproject.briar.api.android.DozeWatchdog;
import org.briarproject.briar.api.android.LockManager;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.test.TestAvatarCreator;
import org.briarproject.briar.socialbackup.AndroidDarkCrystalModule;
import java.io.File;
import java.security.GeneralSecurityException;
@@ -77,7 +78,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {
SetupModule.class,
DozeHelperModule.class,
ContactExchangeModule.class,
AddNearbyContactModule.class,
LoggingModule.class,
LoginModule.class,
NavDrawerModule.class,
@@ -85,13 +86,15 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
SettingsModule.class,
DevReportModule.class,
ContactListModule.class,
AndroidDarkCrystalModule.class,
IntroductionModule.class,
// below need to be within same scope as ViewModelProvider.Factory
BlogModule.class,
ForumModule.class,
GroupListModule.class,
GroupConversationModule.class,
SharingModule.class,
OwnerReturnShardModule.class,
CustodianReturnShardModule.class
})
public class AppModule {
@@ -116,11 +119,6 @@ public class AppModule {
this.application = application;
}
public static AndroidComponent getAndroidComponent(Context ctx) {
BriarApplication app = (BriarApplication) ctx.getApplicationContext();
return app.getApplicationComponent();
}
@Provides
@Singleton
Application providesApplication() {
@@ -12,7 +12,7 @@ import java.util.Locale;
import javax.annotation.Nullable;
import static android.os.Build.VERSION.SDK_INT;
import static org.briarproject.briar.android.settings.DisplayFragment.PREF_LANGUAGE;
import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE;
@NotNullByDefault
public class Localizer {
@@ -25,7 +25,7 @@ public class Localizer {
private Localizer(SharedPreferences sharedPreferences) {
this(Locale.getDefault(), getLocaleFromTag(
sharedPreferences.getString(PREF_LANGUAGE, "default")));
sharedPreferences.getString(LANGUAGE, "default")));
}
private Localizer(Locale systemLocale, @Nullable Locale userLocale) {
@@ -2,7 +2,7 @@ package org.briarproject.briar.android.account;
import android.content.Context;
interface DozeHelper {
public interface DozeHelper {
boolean needToShowDozeFragment(Context context);
}
@@ -14,7 +14,7 @@ import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting;
@UiThread
@NotNullByDefault
class DozeView extends PowerView {
public class DozeView extends PowerView {
@Nullable
private Runnable onButtonClickListener;
@@ -21,7 +21,7 @@ import static android.os.Build.VERSION.SDK_INT;
@UiThread
@NotNullByDefault
class HuaweiView extends PowerView {
public class HuaweiView extends PowerView {
private final static String PACKAGE_NAME = "com.huawei.systemmanager";
private final static String CLASS_NAME =
@@ -40,8 +40,8 @@ import static android.os.SystemClock.elapsedRealtime;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SecurityFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK;
import static org.briarproject.briar.android.settings.SettingsFragment.PREF_SCREEN_LOCK_TIMEOUT;
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
import static org.briarproject.briar.android.util.UiUtils.hasScreenLock;
@@ -0,0 +1,60 @@
package org.briarproject.briar.android.account;
import android.content.Intent;
import android.os.Bundle;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
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_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
public class NewOrRecoverActivity extends BaseActivity implements
BaseFragment.BaseFragmentListener, SetupNewAccountChosenListener,
RecoverAccountListener {
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
// fade-in after splash screen instead of default animation
// TODO the fade in is not working
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
setContentView(R.layout.activity_fragment_container);
NewOrRecoverFragment fragment = NewOrRecoverFragment.newInstance();
showInitialFragment(fragment);
}
@Override
public void setupNewAccountChosen() {
finish();
Intent i = new Intent(this, SetupActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(i);
}
@Override
public void recoverAccountChosen() {
finish();
Intent i = new Intent(this, OwnerReturnShardActivity.class);
i.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(i);
}
@Override
@Deprecated
public void runOnDbThread(Runnable runnable) {
throw new RuntimeException("Don't use this deprecated method here.");
}
}
@@ -0,0 +1,70 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class NewOrRecoverFragment extends BaseFragment {
public static final String TAG = NewOrRecoverFragment.class.getName();
protected SetupNewAccountChosenListener setupNewAccountListener;
protected RecoverAccountListener recoverAccountListener;
public static NewOrRecoverFragment newInstance() {
Bundle bundle = new Bundle();
NewOrRecoverFragment fragment = new NewOrRecoverFragment();
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requireActivity().setTitle(R.string.setup_title);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable
ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_new_or_recover,
container, false);
Button newAccountButton = view.findViewById(R.id.buttonSetupNewAccount);
newAccountButton.setOnClickListener(e -> {
setupNewAccountListener.setupNewAccountChosen();
});
Button recoverAccountButton = view.findViewById(R.id.buttonRestoreAccount);
recoverAccountButton.setOnClickListener(e -> {
recoverAccountListener.recoverAccountChosen();
});
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
setupNewAccountListener = (SetupNewAccountChosenListener) context;
recoverAccountListener = (RecoverAccountListener) context;
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
}
@@ -24,7 +24,7 @@ import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@UiThread
@NotNullByDefault
abstract class PowerView extends ConstraintLayout {
public abstract class PowerView extends ConstraintLayout {
private final TextView textView;
private final ImageView checkImage;
@@ -156,7 +156,7 @@ abstract class PowerView extends ConstraintLayout {
};
}
interface OnCheckedChangedListener {
public interface OnCheckedChangedListener {
void onCheckedChanged();
}
@@ -0,0 +1,8 @@
package org.briarproject.briar.android.account;
import androidx.annotation.UiThread;
public interface RecoverAccountListener {
@UiThread
void recoverAccountChosen();
}
@@ -0,0 +1,8 @@
package org.briarproject.briar.android.account;
import androidx.annotation.UiThread;
public interface SetupNewAccountChosenListener {
@UiThread
void setupNewAccountChosen();
}
@@ -29,6 +29,7 @@ import static org.briarproject.briar.android.account.SetupViewModel.State.SET_PA
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public
class SetupViewModel extends AndroidViewModel {
enum State {AUTHOR_NAME, SET_PASSWORD, DOZE, CREATED, FAILED}
@@ -2,23 +2,32 @@ package org.briarproject.briar.android.activity;
import android.app.Activity;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.briar.android.AndroidComponent;
import org.briarproject.briar.android.StartupFailureActivity;
import org.briarproject.briar.android.account.AuthorNameFragment;
import org.briarproject.briar.android.account.DozeFragment;
import org.briarproject.briar.android.account.NewOrRecoverActivity;
import org.briarproject.briar.android.account.NewOrRecoverFragment;
import org.briarproject.briar.android.account.SetPasswordFragment;
import org.briarproject.briar.android.account.SetupActivity;
import org.briarproject.briar.android.account.UnlockActivity;
import org.briarproject.briar.android.blog.BlogActivity;
import org.briarproject.briar.android.blog.BlogFragment;
import org.briarproject.briar.android.blog.BlogModule;
import org.briarproject.briar.android.blog.BlogPostFragment;
import org.briarproject.briar.android.blog.FeedFragment;
import org.briarproject.briar.android.blog.FeedPostFragment;
import org.briarproject.briar.android.blog.ReblogActivity;
import org.briarproject.briar.android.blog.ReblogFragment;
import org.briarproject.briar.android.blog.RssFeedImportActivity;
import org.briarproject.briar.android.blog.RssFeedManageActivity;
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
import org.briarproject.briar.android.contact.ContactListFragment;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactActivity;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactErrorFragment;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactFragment;
import org.briarproject.briar.android.contact.add.nearby.AddNearbyContactIntroFragment;
import org.briarproject.briar.android.contact.add.remote.AddContactActivity;
import org.briarproject.briar.android.contact.add.remote.LinkExchangeFragment;
import org.briarproject.briar.android.contact.add.remote.NicknameFragment;
@@ -34,10 +43,6 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.introduction.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
import org.briarproject.briar.android.keyagreement.ContactExchangeActivity;
import org.briarproject.briar.android.keyagreement.ContactExchangeErrorFragment;
import org.briarproject.briar.android.keyagreement.KeyAgreementActivity;
import org.briarproject.briar.android.keyagreement.KeyAgreementFragment;
import org.briarproject.briar.android.login.ChangePasswordActivity;
import org.briarproject.briar.android.login.OpenDatabaseFragment;
import org.briarproject.briar.android.login.PasswordFragment;
@@ -75,19 +80,42 @@ import org.briarproject.briar.android.sharing.ShareBlogFragment;
import org.briarproject.briar.android.sharing.ShareForumActivity;
import org.briarproject.briar.android.sharing.ShareForumFragment;
import org.briarproject.briar.android.sharing.SharingModule;
import org.briarproject.briar.android.socialbackup.recover.CustodianRecoveryModeExplainerFragment;
import org.briarproject.briar.android.socialbackup.CustodianSelectorFragment;
import org.briarproject.briar.android.socialbackup.DistributedBackupActivity;
import org.briarproject.briar.android.socialbackup.ExistingBackupFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardActivity;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardErrorFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardFragment;
import org.briarproject.briar.android.socialbackup.recover.CustodianReturnShardSuccessFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeErrorFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeExplainerFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerRecoveryModeMainFragment;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardActivity;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardFragment;
import org.briarproject.briar.android.socialbackup.ShardsSentFragment;
import org.briarproject.briar.android.socialbackup.ThresholdSelectorFragment;
import org.briarproject.briar.android.socialbackup.creation.CreateBackupModule;
import org.briarproject.briar.android.socialbackup.recover.OwnerReturnShardSuccessFragment;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountActivity;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountDozeFragment;
import org.briarproject.briar.android.socialbackup.recover.RestoreAccountSetPasswordFragment;
import org.briarproject.briar.android.splash.SplashScreenActivity;
import org.briarproject.briar.android.test.TestDataActivity;
import org.briarproject.briar.api.socialbackup.recovery.RestoreAccount;
import dagger.Component;
@ActivityScope
@Component(modules = {
@Component(modules ={
ActivityModule.class,
BlogModule.class,
CreateGroupModule.class,
GroupInvitationModule.class,
GroupMemberModule.class,
GroupRevealModule.class,
SharingModule.SharingLegacyModule.class
SharingModule.SharingLegacyModule.class,
CreateBackupModule.class
}, dependencies = AndroidComponent.class)
public interface ActivityComponent {
@@ -105,9 +133,7 @@ public interface ActivityComponent {
void inject(PanicPreferencesActivity activity);
void inject(ContactExchangeActivity activity);
void inject(KeyAgreementActivity activity);
void inject(AddNearbyContactActivity activity);
void inject(ConversationActivity activity);
@@ -149,6 +175,8 @@ public interface ActivityComponent {
void inject(BlogPostFragment fragment);
void inject(FeedPostFragment fragment);
void inject(ReblogFragment fragment);
void inject(ReblogActivity activity);
@@ -177,6 +205,16 @@ public interface ActivityComponent {
void inject(CrashReportActivity crashReportActivity);
void inject(NewOrRecoverActivity newOrRecoverActivity);
void inject(CustodianReturnShardActivity custodianReturnShardActivity);
void inject(OwnerReturnShardActivity ownerReturnShardActivity);
void inject(OwnerRecoveryModeMainFragment ownerRecoveryModeMainFragment);
void inject(RestoreAccountActivity restoreAccountActivity);
// Fragments
void inject(AuthorNameFragment fragment);
@@ -203,7 +241,9 @@ public interface ActivityComponent {
void inject(FeedFragment fragment);
void inject(KeyAgreementFragment fragment);
void inject(AddNearbyContactIntroFragment fragment);
void inject(AddNearbyContactFragment fragment);
void inject(LinkExchangeFragment fragment);
@@ -221,7 +261,7 @@ public interface ActivityComponent {
void inject(ScreenFilterDialogFragment fragment);
void inject(ContactExchangeErrorFragment fragment);
void inject(AddNearbyContactErrorFragment fragment);
void inject(AliasDialogFragment aliasDialogFragment);
@@ -233,4 +273,37 @@ public interface ActivityComponent {
void inject(ConfirmAvatarDialogFragment fragment);
void inject(ThresholdSelectorFragment thresholdSelectorFragment);
void inject(DistributedBackupActivity distributedBackupActivity);
void inject(DatabaseComponent databaseComponent);
void inject(CustodianSelectorFragment custodianSelectorFragment);
void inject(ShardsSentFragment shardsSentFragment);
void inject(OwnerRecoveryModeExplainerFragment ownerRecoveryModeExplainerFragment);
void inject(ExistingBackupFragment existingBackupFragment);
void inject(NewOrRecoverFragment newOrRecoverFragment);
void inject(CustodianRecoveryModeExplainerFragment custodianRecoveryModeExplainerFragment);
void inject(CustodianReturnShardFragment custodianReturnShardFragment);
void inject(OwnerReturnShardFragment ownerReturnShardFragment);
void inject(CustodianReturnShardSuccessFragment custodianReturnShardSuccessFragment);
void inject(RestoreAccountSetPasswordFragment restoreAccountSetPasswordFragment);
void inject(RestoreAccountDozeFragment restoreAccountDozeFragment);
void inject(OwnerReturnShardSuccessFragment ownerReturnShardSuccessFragment);
void inject(OwnerRecoveryModeErrorFragment ownerRecoveryModeErrorFragment);
void inject(CustodianReturnShardErrorFragment custodianReturnShardErrorFragment);
}
@@ -6,11 +6,10 @@ public interface RequestCodes {
int REQUEST_INTRODUCTION = 2;
int REQUEST_GROUP_INVITE = 3;
int REQUEST_SHARE_FORUM = 4;
int REQUEST_WRITE_BLOG_POST = 5;
int REQUEST_SHARE_BLOG = 6;
int REQUEST_RINGTONE = 7;
int REQUEST_PERMISSION_CAMERA_LOCATION = 8;
int REQUEST_DOZE_WHITELISTING = 9;
int REQUEST_BLUETOOTH_DISCOVERABLE = 10;
int REQUEST_UNLOCK = 11;
int REQUEST_KEYGUARD_UNLOCK = 12;
int REQUEST_ATTACH_IMAGE = 13;
@@ -0,0 +1,48 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.blog.BlogPostHeader;
import java.util.Collection;
import javax.annotation.Nullable;
import androidx.annotation.UiThread;
@NotNullByDefault
interface BaseController {
@UiThread
void onStart();
@UiThread
void onStop();
void loadBlogPosts(GroupId g,
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
void loadBlogPost(BlogPostHeader header,
ResultExceptionHandler<BlogPostItem, DbException> handler);
void loadBlogPost(GroupId g, MessageId m,
ResultExceptionHandler<BlogPostItem, DbException> handler);
void repeatPost(BlogPostItem item, @Nullable String comment,
ExceptionHandler<DbException> handler);
@NotNullByDefault
interface BlogListener {
@UiThread
void onBlogPostAdded(BlogPostHeader header, boolean local);
@UiThread
void onBlogRemoved();
}
}
@@ -0,0 +1,209 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostHeader;
import org.briarproject.briar.util.HtmlUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import androidx.annotation.CallSuper;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class BaseControllerImpl extends DbControllerImpl
implements BaseController, EventListener {
private static final Logger LOG =
Logger.getLogger(BaseControllerImpl.class.getName());
protected final EventBus eventBus;
protected final AndroidNotificationManager notificationManager;
protected final IdentityManager identityManager;
protected final BlogManager blogManager;
private final Map<MessageId, String> textCache = new ConcurrentHashMap<>();
private final Map<MessageId, BlogPostHeader> headerCache =
new ConcurrentHashMap<>();
BaseControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus,
AndroidNotificationManager notificationManager,
IdentityManager identityManager, BlogManager blogManager) {
super(dbExecutor, lifecycleManager);
this.eventBus = eventBus;
this.notificationManager = notificationManager;
this.identityManager = identityManager;
this.blogManager = blogManager;
}
@Override
@CallSuper
public void onStart() {
eventBus.addListener(this);
}
@Override
@CallSuper
public void onStop() {
eventBus.removeListener(this);
}
@Override
public void loadBlogPosts(GroupId groupId,
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
runOnDbThread(() -> {
try {
Collection<BlogPostItem> items = loadItems(groupId);
handler.onResult(items);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
Collection<BlogPostItem> loadItems(GroupId groupId) throws DbException {
long start = now();
Collection<BlogPostHeader> headers =
blogManager.getPostHeaders(groupId);
logDuration(LOG, "Loading headers", start);
Collection<BlogPostItem> items = new ArrayList<>(headers.size());
start = now();
for (BlogPostHeader h : headers) {
headerCache.put(h.getId(), h);
BlogPostItem item = getItem(h);
items.add(item);
}
logDuration(LOG, "Loading bodies", start);
return items;
}
@Override
public void loadBlogPost(BlogPostHeader header,
ResultExceptionHandler<BlogPostItem, DbException> handler) {
String text = textCache.get(header.getId());
if (text != null) {
LOG.info("Loaded text from cache");
handler.onResult(new BlogPostItem(header, text));
return;
}
runOnDbThread(() -> {
try {
long start = now();
BlogPostItem item = getItem(header);
logDuration(LOG, "Loading text", start);
handler.onResult(item);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
@Override
public void loadBlogPost(GroupId g, MessageId m,
ResultExceptionHandler<BlogPostItem, DbException> handler) {
BlogPostHeader header = headerCache.get(m);
if (header != null) {
LOG.info("Loaded header from cache");
loadBlogPost(header, handler);
return;
}
runOnDbThread(() -> {
try {
long start = now();
BlogPostHeader header1 = getPostHeader(g, m);
BlogPostItem item = getItem(header1);
logDuration(LOG, "Loading post", start);
handler.onResult(item);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
@Override
public void repeatPost(BlogPostItem item, @Nullable String comment,
ExceptionHandler<DbException> handler) {
runOnDbThread(() -> {
try {
LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getPersonalBlog(a);
BlogPostHeader h = item.getHeader();
blogManager.addLocalComment(a, b.getId(), comment, h);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
private BlogPostHeader getPostHeader(GroupId g, MessageId m)
throws DbException {
BlogPostHeader header = headerCache.get(m);
if (header == null) {
header = blogManager.getPostHeader(g, m);
headerCache.put(m, header);
}
return header;
}
@DatabaseExecutor
private BlogPostItem getItem(BlogPostHeader h) throws DbException {
String text;
if (h instanceof BlogCommentHeader) {
BlogCommentHeader c = (BlogCommentHeader) h;
BlogCommentItem item = new BlogCommentItem(c);
text = getPostText(item.getPostHeader().getId());
item.setText(text);
return item;
} else {
text = getPostText(h.getId());
return new BlogPostItem(h, text);
}
}
@DatabaseExecutor
private String getPostText(MessageId m) throws DbException {
String text = textCache.get(m);
if (text == null) {
text = HtmlUtils.clean(blogManager.getPostText(m), ARTICLE);
textCache.put(m, text);
}
return text;
}
}
@@ -0,0 +1,121 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.fragment.BaseFragment;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
abstract class BasePostFragment extends BaseFragment {
static final String POST_ID = "briar.POST_ID";
private static final Logger LOG =
getLogger(BasePostFragment.class.getName());
private final Handler handler = new Handler(Looper.getMainLooper());
protected MessageId postId;
private ProgressBar progressBar;
private BlogPostViewHolder ui;
private BlogPostItem post;
private Runnable refresher;
@CallSuper
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
// retrieve MessageId of blog post from arguments
byte[] p = requireArguments().getByteArray(POST_ID);
if (p == null) throw new IllegalStateException("No post ID in args");
postId = new MessageId(p);
View view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, new OnBlogPostClickListener() {
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there
}
@Override
public void onAuthorClick(BlogPostItem post) {
if (getContext() == null) return;
Intent i = new Intent(getContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
getContext().startActivity(i);
}
}, getFragmentManager());
return view;
}
@CallSuper
@Override
public void onStart() {
super.onStart();
startPeriodicUpdate();
}
@CallSuper
@Override
public void onStop() {
super.onStop();
stopPeriodicUpdate();
}
@UiThread
protected void onBlogPostLoaded(BlogPostItem post) {
progressBar.setVisibility(INVISIBLE);
this.post = post;
ui.bindItem(post);
}
private void startPeriodicUpdate() {
refresher = () -> {
LOG.info("Updating Content...");
ui.updateDate(post.getTimestamp());
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
};
LOG.info("Adding Handler Callback");
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
private void stopPeriodicUpdate() {
if (refresher != null) {
LOG.info("Removing Handler Callback");
handler.removeCallbacks(refresher);
}
}
}
@@ -1,216 +0,0 @@
package org.briarproject.briar.android.blog;
import android.app.Application;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.viewmodel.DbViewModel;
import org.briarproject.briar.android.viewmodel.LiveResult;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogCommentHeader;
import org.briarproject.briar.api.blog.BlogManager;
import org.briarproject.briar.api.blog.BlogPostHeader;
import org.briarproject.briar.util.HtmlUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
import static org.briarproject.briar.util.HtmlUtils.ARTICLE;
@NotNullByDefault
abstract class BaseViewModel extends DbViewModel implements EventListener {
private static final Logger LOG = getLogger(BaseViewModel.class.getName());
private final EventBus eventBus;
protected final IdentityManager identityManager;
protected final AndroidNotificationManager notificationManager;
protected final BlogManager blogManager;
protected final MutableLiveData<LiveResult<ListUpdate>> blogPosts =
new MutableLiveData<>();
BaseViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
EventBus eventBus,
IdentityManager identityManager,
AndroidNotificationManager notificationManager,
BlogManager blogManager) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor);
this.eventBus = eventBus;
this.identityManager = identityManager;
this.notificationManager = notificationManager;
this.blogManager = blogManager;
eventBus.addListener(this);
}
@Override
protected void onCleared() {
super.onCleared();
eventBus.removeListener(this);
}
@DatabaseExecutor
protected List<BlogPostItem> loadBlogPosts(Transaction txn, GroupId groupId)
throws DbException {
long start = now();
List<BlogPostHeader> headers =
blogManager.getPostHeaders(txn, groupId);
logDuration(LOG, "Loading headers", start);
List<BlogPostItem> items = new ArrayList<>(headers.size());
start = now();
for (BlogPostHeader h : headers) {
BlogPostItem item = getItem(txn, h);
items.add(item);
}
logDuration(LOG, "Loading bodies", start);
return items;
}
@DatabaseExecutor
protected BlogPostItem getItem(Transaction txn, BlogPostHeader h)
throws DbException {
String text;
if (h instanceof BlogCommentHeader) {
BlogCommentHeader c = (BlogCommentHeader) h;
BlogCommentItem item = new BlogCommentItem(c);
text = getPostText(txn, item.getPostHeader().getId());
item.setText(text);
return item;
} else {
text = getPostText(txn, h.getId());
return new BlogPostItem(h, text);
}
}
@DatabaseExecutor
private String getPostText(Transaction txn, MessageId m)
throws DbException {
return HtmlUtils.clean(blogManager.getPostText(txn, m), ARTICLE);
}
LiveData<LiveResult<BlogPostItem>> loadBlogPost(GroupId g, MessageId m) {
MutableLiveData<LiveResult<BlogPostItem>> result =
new MutableLiveData<>();
runOnDbThread(true, txn -> {
long start = now();
BlogPostHeader header = blogManager.getPostHeader(txn, g, m);
BlogPostItem item = getItem(txn, header);
logDuration(LOG, "Loading post", start);
result.postValue(new LiveResult<>(item));
}, e -> {
logException(LOG, WARNING, e);
result.postValue(new LiveResult<>(e));
});
return result;
}
protected void onBlogPostAdded(BlogPostHeader header, boolean local) {
runOnDbThread(true, txn -> {
BlogPostItem item = getItem(txn, header);
txn.attach(() -> onBlogPostItemAdded(item, local));
}, this::handleException);
}
@UiThread
private void onBlogPostItemAdded(BlogPostItem item, boolean local) {
List<BlogPostItem> items = addListItem(getBlogPostItems(), item);
if (items != null) {
Collections.sort(items);
blogPosts.setValue(new LiveResult<>(new ListUpdate(local, items)));
}
}
void repeatPost(BlogPostItem item, @Nullable String comment) {
runOnDbThread(() -> {
try {
LocalAuthor a = identityManager.getLocalAuthor();
Blog b = blogManager.getPersonalBlog(a);
BlogPostHeader h = item.getHeader();
blogManager.addLocalComment(a, b.getId(), comment, h);
} catch (DbException e) {
handleException(e);
}
});
}
LiveData<LiveResult<ListUpdate>> getBlogPosts() {
return blogPosts;
}
@UiThread
@Nullable
protected List<BlogPostItem> getBlogPostItems() {
LiveResult<ListUpdate> value = blogPosts.getValue();
if (value == null) return null;
ListUpdate result = value.getResultOrNull();
return result == null ? null : result.getItems();
}
/**
* Call this after {@link ListUpdate#getPostAddedWasLocal()} was processed.
* This prevents it from getting processed again.
*/
@UiThread
void resetLocalUpdate() {
LiveResult<ListUpdate> value = blogPosts.getValue();
if (value == null) return;
ListUpdate result = value.getResultOrNull();
result.postAddedWasLocal = null;
}
static class ListUpdate {
@Nullable
private Boolean postAddedWasLocal;
private final List<BlogPostItem> items;
ListUpdate(@Nullable Boolean postAddedWasLocal,
List<BlogPostItem> items) {
this.postAddedWasLocal = postAddedWasLocal;
this.items = items;
}
/**
* @return null when not a single post was added with this update.
* true when a single post was added locally and false if remotely.
*/
@Nullable
public Boolean getPostAddedWasLocal() {
return postAddedWasLocal;
}
public List<BlogPostItem> getItems() {
return items;
}
}
}
@@ -6,11 +6,9 @@ import android.os.Bundle;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.fragment.BaseFragment.BaseFragmentListener;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
@@ -18,10 +16,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import static java.util.Objects.requireNonNull;
import static org.briarproject.briar.android.blog.BlogPostFragment.POST_ID;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
@@ -29,16 +23,7 @@ public class BlogActivity extends BriarActivity
implements BaseFragmentListener {
@Inject
ViewModelProvider.Factory viewModelFactory;
private BlogViewModel viewModel;
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(this, viewModelFactory)
.get(BlogViewModel.class);
}
BlogController blogController;
@Override
public void onCreate(@Nullable Bundle state) {
@@ -46,46 +31,32 @@ public class BlogActivity extends BriarActivity
// GroupId from Intent
Intent i = getIntent();
GroupId groupId =
new GroupId(requireNonNull(i.getByteArrayExtra(GROUP_ID)));
// Get post info from intent
@Nullable byte[] postId = i.getByteArrayExtra(POST_ID);
viewModel.setGroupId(groupId, postId == null);
byte[] b = i.getByteArrayExtra(GROUP_ID);
if (b == null) throw new IllegalStateException("No group ID in intent");
GroupId groupId = new GroupId(b);
blogController.setGroupId(groupId);
setContentView(R.layout.activity_fragment_container_toolbar);
Toolbar toolbar = setUpCustomToolbar(false);
// Open Sharing Status on Toolbar click
toolbar.setOnClickListener(v -> {
Intent i1 = new Intent(BlogActivity.this,
BlogSharingStatusActivity.class);
i1.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i1);
});
viewModel.getBlog().observe(this, blog ->
setTitle(blog.getBlog().getAuthor().getName())
);
viewModel.getSharingInfo().observe(this, info ->
setToolbarSubTitle(info.total, info.online)
);
if (toolbar != null) {
toolbar.setOnClickListener(v -> {
Intent i1 = new Intent(BlogActivity.this,
BlogSharingStatusActivity.class);
i1.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i1);
});
}
if (state == null) {
if (postId == null) {
showInitialFragment(BlogFragment.newInstance(groupId));
} else {
MessageId messageId = new MessageId(postId);
BaseFragment f =
BlogPostFragment.newInstance(groupId, messageId);
showInitialFragment(f);
}
showInitialFragment(BlogFragment.newInstance(groupId));
}
}
private void setToolbarSubTitle(int total, int online) {
requireNonNull(getSupportActionBar())
.setSubtitle(getString(R.string.shared_with, total, online));
@Override
public void injectActivity(ActivityComponent component) {
component.inject(this);
}
}
@@ -8,9 +8,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
// This class is not thread-safe
class BlogCommentItem extends BlogPostItem {
private static final BlogCommentComparator COMPARATOR =
@@ -0,0 +1,46 @@
package org.briarproject.briar.android.blog;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import java.util.Collection;
import androidx.annotation.UiThread;
@NotNullByDefault
public interface BlogController extends BaseController {
void setGroupId(GroupId g);
@UiThread
void setBlogSharingListener(BlogSharingListener listener);
@UiThread
void unsetBlogSharingListener(BlogSharingListener listener);
void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler);
void loadBlogPost(MessageId m,
ResultExceptionHandler<BlogPostItem, DbException> handler);
void loadBlog(ResultExceptionHandler<BlogItem, DbException> handler);
void deleteBlog(ResultExceptionHandler<Void, DbException> handler);
void loadSharingContacts(
ResultExceptionHandler<Collection<ContactId>, DbException> handler);
interface BlogSharingListener extends BlogListener {
@UiThread
void onBlogInvitationAccepted(ContactId c);
@UiThread
void onBlogLeft(ContactId c);
}
}
@@ -1,24 +1,24 @@
package org.briarproject.briar.android.blog;
import android.app.Application;
import android.app.Activity;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.identity.IdentityManager;
import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.bramble.api.system.AndroidExecutor;
import org.briarproject.briar.android.sharing.SharingController;
import org.briarproject.briar.android.sharing.SharingController.SharingInfo;
import org.briarproject.briar.android.controller.ActivityLifecycleController;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.blog.Blog;
import org.briarproject.briar.api.blog.BlogInvitationResponse;
@@ -35,54 +35,85 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.annotation.Nullable;
import static java.util.logging.Logger.getLogger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class BlogViewModel extends BaseViewModel {
class BlogControllerImpl extends BaseControllerImpl
implements ActivityLifecycleController, BlogController, EventListener {
private static final Logger LOG = getLogger(BlogViewModel.class.getName());
private static final Logger LOG =
Logger.getLogger(BlogControllerImpl.class.getName());
private final BlogSharingManager blogSharingManager;
private final SharingController sharingController;
private volatile GroupId groupId;
// UI thread
@Nullable
private BlogSharingListener listener;
private final MutableLiveData<BlogItem> blog = new MutableLiveData<>();
private final MutableLiveData<Boolean> blogRemoved =
new MutableLiveData<>();
private volatile GroupId groupId = null;
@Inject
BlogViewModel(Application application,
@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager,
TransactionManager db,
AndroidExecutor androidExecutor,
EventBus eventBus,
IdentityManager identityManager,
BlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
LifecycleManager lifecycleManager, EventBus eventBus,
AndroidNotificationManager notificationManager,
BlogManager blogManager,
BlogSharingManager blogSharingManager,
SharingController sharingController) {
super(application, dbExecutor, lifecycleManager, db, androidExecutor,
eventBus, identityManager, notificationManager, blogManager);
IdentityManager identityManager, BlogManager blogManager,
BlogSharingManager blogSharingManager) {
super(dbExecutor, lifecycleManager, eventBus, notificationManager,
identityManager, blogManager);
this.blogSharingManager = blogSharingManager;
this.sharingController = sharingController;
}
@Override
public void onActivityCreate(Activity activity) {
}
@Override
public void onActivityStart() {
super.onStart();
notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId);
}
@Override
public void onActivityStop() {
super.onStop();
notificationManager.unblockNotification(groupId);
}
@Override
public void onActivityDestroy() {
}
@Override
public void setGroupId(GroupId g) {
groupId = g;
}
@Override
public void setBlogSharingListener(BlogSharingListener listener) {
this.listener = listener;
}
@Override
public void unsetBlogSharingListener(BlogSharingListener listener) {
if (this.listener == listener) this.listener = null;
}
@Override
public void eventOccurred(Event e) {
if (groupId == null || listener == null)
throw new IllegalStateException();
if (e instanceof BlogPostAddedEvent) {
BlogPostAddedEvent b = (BlogPostAddedEvent) e;
if (b.getGroupId().equals(groupId)) {
LOG.info("Blog post added");
onBlogPostAdded(b.getHeader(), b.isLocal());
listener.onBlogPostAdded(b.getHeader(), b.isLocal());
}
} else if (e instanceof BlogInvitationResponseReceivedEvent) {
BlogInvitationResponseReceivedEvent b =
@@ -90,36 +121,41 @@ class BlogViewModel extends BaseViewModel {
BlogInvitationResponse r = b.getMessageHeader();
if (r.getShareableId().equals(groupId) && r.wasAccepted()) {
LOG.info("Blog invitation accepted");
sharingController.add(b.getContactId());
listener.onBlogInvitationAccepted(b.getContactId());
}
} else if (e instanceof ContactLeftShareableEvent) {
ContactLeftShareableEvent s = (ContactLeftShareableEvent) e;
if (s.getGroupId().equals(groupId)) {
LOG.info("Blog left by contact");
sharingController.remove(s.getContactId());
listener.onBlogLeft(s.getContactId());
}
} else if (e instanceof GroupRemovedEvent) {
GroupRemovedEvent g = (GroupRemovedEvent) e;
if (g.getGroup().getId().equals(groupId)) {
LOG.info("Blog removed");
blogRemoved.setValue(true);
listener.onBlogRemoved();
}
}
}
/**
* Set this before calling any other methods.
*/
@UiThread
public void setGroupId(GroupId groupId, boolean loadAllPosts) {
if (this.groupId == groupId) return; // configuration change
this.groupId = groupId;
loadBlog(groupId);
if (loadAllPosts) loadBlogPosts(groupId);
loadSharingContacts(groupId);
@Override
public void loadBlogPosts(
ResultExceptionHandler<Collection<BlogPostItem>, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
loadBlogPosts(groupId, handler);
}
private void loadBlog(GroupId groupId) {
@Override
public void loadBlogPost(MessageId m,
ResultExceptionHandler<BlogPostItem, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
loadBlogPost(groupId, m, handler);
}
@Override
public void loadBlog(
ResultExceptionHandler<BlogItem, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
runOnDbThread(() -> {
try {
long start = now();
@@ -127,65 +163,50 @@ class BlogViewModel extends BaseViewModel {
Blog b = blogManager.getBlog(groupId);
boolean ours = a.getId().equals(b.getAuthor().getId());
boolean removable = blogManager.canBeRemoved(b);
blog.postValue(new BlogItem(b, ours, removable));
BlogItem blog = new BlogItem(b, ours, removable);
logDuration(LOG, "Loading blog", start);
handler.onResult(blog);
} catch (DbException e) {
handleException(e);
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
void blockAndClearNotifications() {
notificationManager.blockNotification(groupId);
notificationManager.clearBlogPostNotification(groupId);
}
void unblockNotifications() {
notificationManager.unblockNotification(groupId);
}
private void loadBlogPosts(GroupId groupId) {
loadFromDb(txn -> new ListUpdate(null, loadBlogPosts(txn, groupId)),
blogPosts::setValue);
}
private void loadSharingContacts(GroupId groupId) {
runOnDbThread(true, txn -> {
Collection<Contact> contacts =
blogSharingManager.getSharedWith(txn, groupId);
txn.attach(() -> onSharingContactsLoaded(contacts));
}, this::handleException);
}
@UiThread
private void onSharingContactsLoaded(Collection<Contact> contacts) {
Collection<ContactId> contactIds = new ArrayList<>(contacts.size());
for (Contact c : contacts) contactIds.add(c.getId());
sharingController.addAll(contactIds);
}
void deleteBlog() {
@Override
public void deleteBlog(ResultExceptionHandler<Void, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
runOnDbThread(() -> {
try {
long start = now();
Blog b = blogManager.getBlog(groupId);
blogManager.removeBlog(b);
logDuration(LOG, "Removing blog", start);
handler.onResult(null);
} catch (DbException e) {
handleException(e);
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
LiveData<BlogItem> getBlog() {
return blog;
@Override
public void loadSharingContacts(
ResultExceptionHandler<Collection<ContactId>, DbException> handler) {
if (groupId == null) throw new IllegalStateException();
runOnDbThread(() -> {
try {
Collection<Contact> contacts =
blogSharingManager.getSharedWith(groupId);
Collection<ContactId> contactIds =
new ArrayList<>(contacts.size());
for (Contact c : contacts) contactIds.add(c.getId());
handler.onResult(contactIds);
} catch (DbException e) {
logException(LOG, WARNING, e);
handler.onException(e);
}
});
}
LiveData<Boolean> getBlogRemoved() {
return blogRemoved;
}
LiveData<SharingInfo> getSharingInfo() {
return sharingController.getSharingInfo();
}
}
@@ -1,7 +1,9 @@
package org.briarproject.briar.android.blog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -10,25 +12,33 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.blog.BaseViewModel.ListUpdate;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.blog.BlogController.BlogSharingListener;
import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.sharing.BlogSharingStatusActivity;
import org.briarproject.briar.android.sharing.ShareBlogActivity;
import org.briarproject.briar.android.util.BriarSnackbarBuilder;
import org.briarproject.briar.android.view.BriarRecyclerView;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.api.blog.BlogPostHeader;
import java.util.Collection;
import javax.inject.Inject;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView.LayoutManager;
@@ -38,22 +48,31 @@ import static android.widget.Toast.LENGTH_SHORT;
import static com.google.android.material.snackbar.Snackbar.LENGTH_LONG;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
import static org.briarproject.briar.android.controller.SharingController.SharingListener;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogFragment extends BaseFragment
implements OnBlogPostClickListener {
implements BlogSharingListener, SharingListener,
OnBlogPostClickListener {
private final static String TAG = BlogFragment.class.getName();
@Inject
ViewModelProvider.Factory viewModelFactory;
BlogController blogController;
@Inject
SharingController sharingController;
@Nullable
private Parcelable layoutManagerState;
private GroupId groupId;
private BlogViewModel viewModel;
private final BlogPostAdapter adapter = new BlogPostAdapter(false, this);
private BlogPostAdapter adapter;
private LayoutManager layoutManager;
private BriarRecyclerView list;
private MenuItem writeButton, deleteButton;
private boolean isMyBlog = false, canDeleteBlog = false;
static BlogFragment newInstance(GroupId groupId) {
BlogFragment f = new BlogFragment();
@@ -68,8 +87,8 @@ public class BlogFragment extends BaseFragment
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(BlogViewModel.class);
blogController.setBlogSharingListener(this);
sharingController.setSharingListener(this);
}
@Nullable
@@ -84,82 +103,106 @@ public class BlogFragment extends BaseFragment
View v = inflater.inflate(R.layout.fragment_blog, container, false);
adapter = new BlogPostAdapter(requireActivity(), this,
getFragmentManager());
list = v.findViewById(R.id.postList);
LayoutManager layoutManager = new LinearLayoutManager(getActivity());
layoutManager = new LinearLayoutManager(getActivity());
list.setLayoutManager(layoutManager);
list.setAdapter(adapter);
list.showProgressBar();
list.setEmptyText(getString(R.string.blogs_other_blog_empty_state));
viewModel.getBlogPosts().observe(getViewLifecycleOwner(), result ->
result.onError(this::handleException)
.onSuccess(this::onBlogPostsLoaded)
);
viewModel.getBlogRemoved().observe(getViewLifecycleOwner(), removed -> {
if (removed) finish();
});
if (savedInstanceState != null) {
layoutManagerState =
savedInstanceState.getParcelable("layoutManager");
}
return v;
}
@Override
public void onStart() {
super.onStart();
viewModel.blockAndClearNotifications();
sharingController.onStart();
loadBlog();
loadSharedContacts();
loadBlogPosts(false);
list.startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
viewModel.unblockNotifications();
sharingController.onStop();
list.stopPeriodicUpdate();
}
@Override
public void onDestroy() {
super.onDestroy();
blogController.unsetBlogSharingListener(this);
sharingController.unsetSharingListener(this);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (layoutManager != null) {
layoutManagerState = layoutManager.onSaveInstanceState();
outState.putParcelable("layoutManager", layoutManagerState);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.blogs_blog_actions, menu);
MenuItem writeButton = menu.findItem(R.id.action_write_blog_post);
MenuItem deleteButton = menu.findItem(R.id.action_blog_delete);
viewModel.getBlog().observe(getViewLifecycleOwner(), blog -> {
if (blog.isOurs()) writeButton.setVisible(true);
if (blog.canBeRemoved()) deleteButton.setEnabled(true);
});
writeButton = menu.findItem(R.id.action_write_blog_post);
if (isMyBlog) writeButton.setVisible(true);
deleteButton = menu.findItem(R.id.action_blog_delete);
if (canDeleteBlog) deleteButton.setEnabled(true);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_write_blog_post) {
Intent i = new Intent(getActivity(), WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i);
return true;
} else if (itemId == R.id.action_blog_share) {
Intent i = new Intent(getActivity(), ShareBlogActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_SHARE_BLOG);
return true;
} else if (itemId == R.id.action_blog_sharing_status) {
Intent i =
new Intent(getActivity(), BlogSharingStatusActivity.class);
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i);
return true;
} else if (itemId == R.id.action_blog_delete) {
showDeleteDialog();
return true;
switch (item.getItemId()) {
case R.id.action_write_blog_post:
Intent i = new Intent(getActivity(),
WriteBlogPostActivity.class);
i.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i, REQUEST_WRITE_BLOG_POST);
return true;
case R.id.action_blog_share:
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i2.putExtra(GROUP_ID, groupId.getBytes());
startActivityForResult(i2, REQUEST_SHARE_BLOG);
return true;
case R.id.action_blog_sharing_status:
Intent i3 = new Intent(getActivity(),
BlogSharingStatusActivity.class);
i3.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
i3.putExtra(GROUP_ID, groupId.getBytes());
startActivity(i3);
return true;
case R.id.action_blog_delete:
showDeleteDialog();
return true;
default:
return super.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
@Override
public void onActivityResult(int request, int result,
@Nullable Intent data) {
super.onActivityResult(request, result, data);
if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
displaySnackbar(R.string.blogs_blog_post_created, true);
loadBlogPosts(true);
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
displaySnackbar(R.string.blogs_sharing_snackbar, false);
}
}
@@ -169,26 +212,35 @@ public class BlogFragment extends BaseFragment
return TAG;
}
private void onBlogPostsLoaded(ListUpdate update) {
adapter.submitList(update.getItems(), () -> {
Boolean wasLocal = update.getPostAddedWasLocal();
if (wasLocal != null && wasLocal) {
list.scrollToPosition(0);
displaySnackbar(R.string.blogs_blog_post_created,
false);
} else if (wasLocal != null) {
displaySnackbar(R.string.blogs_blog_post_received,
true);
}
viewModel.resetLocalUpdate();
list.showData();
});
@Override
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
blogController.loadBlogPost(header,
new UiResultExceptionHandler<BlogPostItem, DbException>(
this) {
@Override
public void onResultUi(BlogPostItem post) {
adapter.add(post);
if (local) {
list.scrollToPosition(0);
displaySnackbar(R.string.blogs_blog_post_created,
false);
} else {
displaySnackbar(R.string.blogs_blog_post_received,
true);
}
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
}
);
}
@Override
public void onBlogPostClick(BlogPostItem post) {
BlogPostFragment f =
BlogPostFragment.newInstance(groupId, post.getId());
BlogPostFragment f = BlogPostFragment.newInstance(post.getId());
showNextFragment(f);
}
@@ -204,10 +256,111 @@ public class BlogFragment extends BaseFragment
getContext().startActivity(i);
}
private void loadBlogPosts(boolean reload) {
blogController.loadBlogPosts(
new UiResultExceptionHandler<Collection<BlogPostItem>,
DbException>(this) {
@Override
public void onResultUi(Collection<BlogPostItem> posts) {
if (posts.isEmpty()) {
list.showData();
} else {
adapter.addAll(posts);
if (reload || layoutManagerState == null) {
list.scrollToPosition(0);
} else {
layoutManager.onRestoreInstanceState(
layoutManagerState);
}
}
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
});
}
private void loadBlog() {
blogController.loadBlog(
new UiResultExceptionHandler<BlogItem, DbException>(this) {
@Override
public void onResultUi(BlogItem blog) {
setToolbarTitle(blog.getBlog().getAuthor());
if (blog.isOurs())
showWriteButton();
if (blog.canBeRemoved())
enableDeleteButton();
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
});
}
private void setToolbarTitle(Author a) {
getActivity().setTitle(a.getName());
}
private void loadSharedContacts() {
blogController.loadSharingContacts(
new UiResultExceptionHandler<Collection<ContactId>,
DbException>(this) {
@Override
public void onResultUi(Collection<ContactId> contacts) {
sharingController.addAll(contacts);
int online = sharingController.getOnlineCount();
setToolbarSubTitle(contacts.size(), online);
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
});
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
public void onBlogInvitationAccepted(ContactId c) {
sharingController.add(c);
setToolbarSubTitle(sharingController.getTotalCount(),
sharingController.getOnlineCount());
}
@Override
public void onBlogLeft(ContactId c) {
sharingController.remove(c);
setToolbarSubTitle(sharingController.getTotalCount(),
sharingController.getOnlineCount());
}
@Override
public void onSharingInfoUpdated(int total, int online) {
setToolbarSubTitle(total, online);
}
private void setToolbarSubTitle(int total, int online) {
ActionBar actionBar =
((BriarActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
actionBar.setSubtitle(
getString(R.string.shared_with, total, online));
}
}
private void showWriteButton() {
isMyBlog = true;
if (writeButton != null)
writeButton.setVisible(true);
}
private void enableDeleteButton() {
canDeleteBlog = true;
if (deleteButton != null)
deleteButton.setEnabled(true);
}
private void displaySnackbar(int stringId, boolean scroll) {
@@ -220,21 +373,38 @@ public class BlogFragment extends BaseFragment
}
private void showDeleteDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
DialogInterface.OnClickListener okListener =
(dialog, which) -> deleteBlog();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(),
R.style.BriarDialogTheme);
builder.setTitle(getString(R.string.blogs_remove_blog));
builder.setMessage(
getString(R.string.blogs_remove_blog_dialog_message));
builder.setPositiveButton(R.string.cancel, null);
builder.setNegativeButton(R.string.blogs_remove_blog_ok,
(dialog, which) -> deleteBlog());
builder.setNegativeButton(R.string.blogs_remove_blog_ok, okListener);
builder.show();
}
private void deleteBlog() {
viewModel.deleteBlog();
Toast.makeText(getActivity(), R.string.blogs_blog_removed, LENGTH_SHORT)
.show();
blogController.deleteBlog(
new UiResultExceptionHandler<Void, DbException>(this) {
@Override
public void onResultUi(Void result) {
Toast.makeText(getActivity(),
R.string.blogs_blog_removed, LENGTH_SHORT)
.show();
finish();
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
});
}
@Override
public void onBlogRemoved() {
finish();
}
@@ -1,23 +1,35 @@
package org.briarproject.briar.android.blog;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import org.briarproject.briar.android.activity.ActivityScope;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.android.controller.SharingController;
import org.briarproject.briar.android.controller.SharingControllerImpl;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
import dagger.Provides;
@Module
public interface BlogModule {
public class BlogModule {
@Binds
@IntoMap
@ViewModelKey(FeedViewModel.class)
ViewModel bindFeedViewModel(FeedViewModel feedViewModel);
@ActivityScope
@Provides
BlogController provideBlogController(BaseActivity activity,
BlogControllerImpl blogController) {
activity.addLifecycleController(blogController);
return blogController;
}
@Binds
@IntoMap
@ViewModelKey(BlogViewModel.class)
ViewModel bindBlogViewModel(BlogViewModel blogViewModel);
@ActivityScope
@Provides
FeedController provideFeedController(FeedControllerImpl feedController) {
return feedController;
}
@ActivityScope
@Provides
SharingController provideSharingController(
SharingControllerImpl sharingController) {
return sharingController;
}
}
@@ -1,5 +1,6 @@
package org.briarproject.briar.android.blog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -7,44 +8,52 @@ import android.view.ViewGroup;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.util.BriarAdapter;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class BlogPostAdapter extends ListAdapter<BlogPostItem, BlogPostViewHolder> {
class BlogPostAdapter extends BriarAdapter<BlogPostItem, BlogPostViewHolder> {
private final boolean authorClickable;
private final OnBlogPostClickListener listener;
@Nullable
private final FragmentManager fragmentManager;
BlogPostAdapter(boolean authorClickable, OnBlogPostClickListener listener) {
super(new DiffUtil.ItemCallback<BlogPostItem>() {
@Override
public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
return a.getId().equals(b.getId());
}
@Override
public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
return a.isRead() == b.isRead();
}
});
this.authorClickable = authorClickable;
BlogPostAdapter(Context ctx, OnBlogPostClickListener listener,
@Nullable FragmentManager fragmentManager) {
super(ctx, BlogPostItem.class);
this.listener = listener;
this.fragmentManager = fragmentManager;
}
@Override
public BlogPostViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(
View v = LayoutInflater.from(ctx).inflate(
R.layout.list_item_blog_post, parent, false);
return new BlogPostViewHolder(v, false, listener, authorClickable);
return new BlogPostViewHolder(v, false, listener, fragmentManager);
}
@Override
public void onBindViewHolder(BlogPostViewHolder ui, int position) {
ui.bindItem(getItem(position));
ui.bindItem(getItemAt(position));
}
@Override
public int compare(BlogPostItem a, BlogPostItem b) {
return a.compareTo(b);
}
@Override
public boolean areContentsTheSame(BlogPostItem a, BlogPostItem b) {
return a.isRead() == b.isRead();
}
@Override
public boolean areItemsTheSame(BlogPostItem a, BlogPostItem b) {
return a.getId().equals(b.getId());
}
}
@@ -1,159 +1,76 @@
package org.briarproject.briar.android.blog;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.fragment.BaseFragment;
import org.briarproject.briar.android.widget.LinkDialogFragment;
import org.briarproject.briar.android.blog.BaseController.BlogListener;
import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
import org.briarproject.briar.api.blog.BlogPostHeader;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModelProvider;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
@UiThread
@MethodsNotNullByDefault
@ParametersNotNullByDefault
public class BlogPostFragment extends BaseFragment
implements OnBlogPostClickListener {
public class BlogPostFragment extends BasePostFragment implements BlogListener {
private static final String TAG = BlogPostFragment.class.getName();
private static final Logger LOG = getLogger(TAG);
static final String POST_ID = "briar.POST_ID";
protected BlogViewModel viewModel;
private final Handler handler = new Handler(Looper.getMainLooper());
private ProgressBar progressBar;
private BlogPostViewHolder ui;
private BlogPostItem post;
private Runnable refresher;
@Inject
ViewModelProvider.Factory viewModelFactory;
BlogController blogController;
static BlogPostFragment newInstance(GroupId blogId, MessageId postId) {
static BlogPostFragment newInstance(MessageId postId) {
BlogPostFragment f = new BlogPostFragment();
Bundle bundle = new Bundle();
bundle.putByteArray(GROUP_ID, blogId.getBytes());
bundle.putByteArray(POST_ID, postId.getBytes());
f.setArguments(bundle);
return f;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
viewModel = new ViewModelProvider(requireActivity(), viewModelFactory)
.get(BlogViewModel.class);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Bundle args = requireArguments();
GroupId groupId =
new GroupId(requireNonNull(args.getByteArray(GROUP_ID)));
MessageId postId =
new MessageId(requireNonNull(args.getByteArray(POST_ID)));
View view = inflater.inflate(R.layout.fragment_blog_post, container,
false);
progressBar = view.findViewById(R.id.progressBar);
progressBar.setVisibility(VISIBLE);
ui = new BlogPostViewHolder(view, true, this, false);
LifecycleOwner owner = getViewLifecycleOwner();
viewModel.loadBlogPost(groupId, postId).observe(owner, result ->
result.onError(this::handleException)
.onSuccess(this::onBlogPostLoaded)
);
return view;
}
@Override
public void onStart() {
super.onStart();
startPeriodicUpdate();
}
@Override
public void onStop() {
super.onStop();
stopPeriodicUpdate();
}
@UiThread
private void onBlogPostLoaded(BlogPostItem post) {
progressBar.setVisibility(INVISIBLE);
this.post = post;
ui.bindItem(post);
}
@Override
public void onBlogPostClick(BlogPostItem post) {
// We're already there
}
@Override
public void onAuthorClick(BlogPostItem post) {
Intent i = new Intent(requireContext(), BlogActivity.class);
i.putExtra(GROUP_ID, post.getGroupId().getBytes());
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
requireContext().startActivity(i);
}
@Override
public void onLinkClick(String url) {
LinkDialogFragment f = LinkDialogFragment.newInstance(url);
f.show(getParentFragmentManager(), f.getUniqueTag());
}
private void startPeriodicUpdate() {
refresher = () -> {
LOG.info("Updating Content...");
ui.updateDate(post.getTimestamp());
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
};
LOG.info("Adding Handler Callback");
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
}
private void stopPeriodicUpdate() {
if (refresher != null) {
LOG.info("Removing Handler Callback");
handler.removeCallbacks(refresher);
}
}
@Override
public String getUniqueTag() {
return TAG;
}
@Override
public void injectFragment(ActivityComponent component) {
component.inject(this);
}
@Override
public void onStart() {
super.onStart();
blogController.loadBlogPost(postId,
new UiResultExceptionHandler<BlogPostItem, DbException>(
this) {
@Override
public void onResultUi(BlogPostItem post) {
onBlogPostLoaded(post);
}
@Override
public void onExceptionUi(DbException exception) {
handleException(exception);
}
});
}
@Override
public void onBlogPostAdded(BlogPostHeader header, boolean local) {
// doesn't matter here
}
@Override
public void onBlogRemoved() {
finish();
}
}
@@ -17,7 +17,7 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
private final BlogPostHeader header;
@Nullable
protected String text;
private final boolean read;
private boolean read;
BlogPostItem(BlogPostHeader header, @Nullable String text) {
this.header = header;
@@ -74,6 +74,9 @@ public class BlogPostItem implements Comparable<BlogPostItem> {
protected static int compare(BlogPostHeader h1, BlogPostHeader h2) {
// The newest post comes first
return Long.compare(h2.getTimeReceived(), h1.getTimeReceived());
long aTime = h1.getTimeReceived(), bTime = h2.getTimeReceived();
if (aTime > bTime) return -1;
if (aTime < bTime) return 1;
return 0;
}
}

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