Compare commits

..

946 Commits

Author SHA1 Message Date
Greyson Parrelli cfdf5603af Bump version to 4.62.4 2020-06-09 00:39:55 -04:00
Greyson Parrelli 45bfb8c6b6 Updated language translations. 2020-06-09 00:38:19 -04:00
Alex Hart 65608a51b8 Fix API 19 crash by using different resource. 2020-06-09 00:33:38 -04:00
Greyson Parrelli b6314597fe Bump version to 4.62.3 2020-06-08 16:32:08 -04:00
Greyson Parrelli 20a588199a Updated language translations. 2020-06-08 16:32:00 -04:00
Greyson Parrelli 59916f1e95 Add 'Add to contacts' button to bottom sheet. 2020-06-08 16:07:14 -04:00
Alan Evans 8b91f8f9e7 Disable disappearing messages option and remove from menu. 2020-06-08 12:31:55 -03:00
Alex Hart cbc3cce66f Fix API 19 drawable crash in ManageGroupFragment. 2020-06-08 11:34:39 -03:00
Alex Hart b4b63b5860 Add auto-mirroring to ic_forward_outline. 2020-06-08 10:23:43 -03:00
Alex Hart b9ae15a890 Fix group name RTL alignment. 2020-06-08 10:21:55 -03:00
Greyson Parrelli d955389c46 Bump version to 4.62.2 2020-06-07 22:05:02 -04:00
Greyson Parrelli 975eb885c1 Updated language translations. 2020-06-07 22:05:02 -04:00
Alan Evans a3aed96757 Sort contacts without names after contacts with names. 2020-06-07 22:05:02 -04:00
Alan Evans dc70bfabaf Lighter ultramarine + in dark mode. 2020-06-07 22:05:02 -04:00
Greyson Parrelli 6932340671 Add ability to copy a number via long-press. 2020-06-07 19:59:42 -04:00
Alan Evans f6637b7caf Restore mute in conversation menu. 2020-06-07 19:59:42 -04:00
Alan Evans 4f4be44caa Load identities in transaction. 2020-06-07 19:59:42 -04:00
Greyson Parrelli 7832497ba7 Shorten logging in ConversationActivity. 2020-06-07 19:59:42 -04:00
Alex Hart 7d06e2395f Rework how ConversationFragment RecyclerView responds to data updates. 2020-06-07 19:59:42 -04:00
Greyson Parrelli 3a479d7eef Reduce database notifications for disappearing conversations. 2020-06-07 19:59:42 -04:00
Greyson Parrelli 8fe8a1e9ee Put refresh and upload profile jobs in the same queue. 2020-06-07 19:59:42 -04:00
Alan Evans 2d8b2e7fb0 Transitions for group settings. 2020-06-07 19:59:42 -04:00
Alan Evans 9c0365f92c Open group settings from group avatar click. 2020-06-07 19:59:42 -04:00
Greyson Parrelli b48abb08d2 Show custom notifications for API < 26. 2020-06-07 19:59:42 -04:00
Alan Evans d8f3e032c7 Fix group name clearing after avatar change. 2020-06-07 19:59:42 -04:00
Alan Evans 8dbcb255ad Hide Block and Leave options when not available in group settings, add unblock. 2020-06-07 19:59:42 -04:00
Greyson Parrelli db06cbbc86 Remove unnecessary recipient refreshes. 2020-06-07 19:59:42 -04:00
Cody Henthorne 98ab23c1a3 Make Custom Notification dialog dismiss itself on up press. 2020-06-07 10:23:41 -04:00
Cody Henthorne d0ca9ba6a6 Make text button color responsive to theme. 2020-06-07 09:43:14 -04:00
Alan Evans b242368675 Remove group members button. 2020-06-07 09:31:18 -03:00
Alan Evans 664527ce63 Fix sort order for group members. 2020-06-07 08:25:31 -03:00
Alan Evans 99e4f80be0 Allow whole row selection for Shared media in group settings. 2020-06-07 08:12:25 -03:00
Alan Evans 702dae9fcd Fix double tap required for "See all" media in group settings. 2020-06-07 07:47:28 -03:00
Alan Evans 48fe1ba559 Fix group settings divider shade in dark mode. 2020-06-07 07:42:28 -03:00
Greyson Parrelli 382ac7ba0d Bump version to 4.62.1 2020-06-06 20:28:18 -04:00
Greyson Parrelli a46f47f352 Updated language translations. 2020-06-06 20:27:48 -04:00
Greyson Parrelli e984d8a42c Change Gif -> GIF. 2020-06-06 20:27:22 -04:00
Greyson Parrelli 554bad6b8d Improve DB access in group sends. 2020-06-06 20:25:02 -04:00
Jim Gustafson ed13c97ad7 Handle legacy hangup properly. 2020-06-06 20:25:02 -04:00
Greyson Parrelli d33873d59a Fix possible crash with null thread body. 2020-06-06 20:25:02 -04:00
Greyson Parrelli 1234899ea1 Add support for non-blocking media sends. 2020-06-06 20:25:02 -04:00
Cody Henthorne 13027dc44b Fix leaking MessageDetailsActivity via list items. 2020-06-06 20:25:02 -04:00
Cody Henthorne 5b4d74b7fe Move group resolution for conversations to background LiveData. 2020-06-06 20:25:02 -04:00
Alan Evans 18c7bc2b5b Prevent edit of a group post leave. 2020-06-06 20:25:02 -04:00
Alan Evans bbbee0f372 Fix group create arrow for RTL. 2020-06-06 20:25:02 -04:00
Alex Hart cf9d090154 Start Paging @ Unread count instead of -1. 2020-06-06 20:25:02 -04:00
Alan Evans 718471917f Separate text only message layouts. 2020-06-06 20:25:02 -04:00
Greyson Parrelli bb97407cde Bump version to 4.62.0 2020-06-05 22:04:18 -04:00
Greyson Parrelli 92ce678e29 Updated language translations. 2020-06-05 22:04:16 -04:00
Cody Henthorne e100aea2c7 Preserve scroll position in Message Details on update. 2020-06-05 21:46:04 -04:00
Greyson Parrelli fea3b6cb4a Don't show 'conversation settings' for groups. 2020-06-05 21:46:04 -04:00
Cody Henthorne afbc132faa Fix conversation item and data source memory leaks. 2020-06-05 21:46:04 -04:00
Alan Evans b27198286d MMS proof new group UI. 2020-06-05 21:46:04 -04:00
Greyson Parrelli ac93d81032 Remove pins4all feature flag. 2020-06-05 21:46:04 -04:00
Alan Evans 9981e5ca76 Enable new group UI. 2020-06-05 20:19:03 -03:00
Cody Henthorne 7dd3efeb53 Remove listeners when detaching conversation item views. 2020-06-05 19:29:55 -03:00
Greyson Parrelli d38d702adf Parallelize group sends. 2020-06-05 18:10:50 -04:00
Alex Hart 04a000a8a8 Always display labels in contact search. 2020-06-05 15:16:55 -03:00
Fumiaki Yoshimatsu 3bbf0741ee Use localized string for Phone number.
Fixes #9626
2020-06-05 15:01:20 -03:00
Fumiaki Yoshimatsu e9a336100b Display backup date in users locale on restore.
Fixes #9693
2020-06-05 15:01:20 -03:00
Cody Henthorne fb600e9829 Update SMS/MMS as sending when retrying failed send.
This was only impacting SMS/MMS as Push already reset the status.
2020-06-05 13:46:25 -04:00
Alex Hart 4a455ff958 Implement new Add Members UI. 2020-06-05 13:44:02 -03:00
Cody Henthorne 707e238e5c Make borderless button style responsive to theme. 2020-06-05 12:01:00 -04:00
Alan Evans 90f22a4b66 Include face position and projection matrix into elements matrix. 2020-06-04 19:49:22 -03:00
Alex Hart b4f134adf7 Add more descriptive messages for media notifications and chat previews. 2020-06-04 13:13:42 -03:00
Greyson Parrelli 1e00fc6149 Bump version to 4.61.6 2020-06-04 10:21:23 -04:00
Greyson Parrelli f52133a69c Updated language translations. 2020-06-04 10:21:23 -04:00
Alan Evans 91b142e0d9 Fix waveform array out of bounds. 2020-06-04 10:21:10 -04:00
Greyson Parrelli 26a9dd98c1 Bump version to 4.61.5 2020-06-03 19:01:03 -04:00
Greyson Parrelli 99e38e1d23 Updated language translations. 2020-06-03 18:58:34 -04:00
Greyson Parrelli a2d8a25fd9 Blur UI tweaks. 2020-06-03 18:51:38 -04:00
Alan Evans d86d625bcc Smoother blur rendering. 2020-06-03 19:47:51 -03:00
Greyson Parrelli 18e3fb6609 Fix string format. 2020-06-03 17:19:32 -04:00
Greyson Parrelli da33ba0ed5 Update blur UI. 2020-06-03 17:12:47 -04:00
Greyson Parrelli 66f021d01a Fix issue where rail wasn't showing in some situations. 2020-06-03 17:12:47 -04:00
Greyson Parrelli 40231ea45f Fix issue with view-once toggle and face blurring. 2020-06-03 17:12:42 -04:00
Alex Hart cd80a47c04 Made edit profile save button move with the keyboard. 2020-06-03 17:12:27 -04:00
Alan Evans 1033bd7bda Blur faces rotation and crop and zoom support. 2020-06-03 14:02:24 -03:00
Greyson Parrelli b4f60f3acb Bump version to 4.61.4 2020-06-03 06:40:09 -04:00
Greyson Parrelli bed3b571cc Updated language translations. 2020-06-03 06:39:34 -04:00
Greyson Parrelli c8dd4e5254 Added support for blurring faces.
Co-authored-by: Alan Evans <alan@signal.org>
2020-06-03 06:39:20 -04:00
Alan Evans 514048171b Add Image Editor support for blur mask layer. 2020-06-03 03:33:06 -03:00
Greyson Parrelli 32e9901592 Bump version to 4.61.3 2020-06-02 19:22:22 -04:00
Greyson Parrelli d83f86a469 Revert "Make notifications and chat previews for media messages more descriptive."
This reverts commit a3f9737e63.
2020-06-02 19:19:30 -04:00
Greyson Parrelli 403d53586c Bump version to 4.61.2 2020-06-02 17:40:56 -04:00
Greyson Parrelli 6acae58694 Updated language translations. 2020-06-02 17:33:41 -04:00
Alex Hart a3f9737e63 Make notifications and chat previews for media messages more descriptive. 2020-06-02 17:34:50 -03:00
Cody Henthorne 263af7c139 Add registration lock status to support email. 2020-06-02 16:14:19 -04:00
Alex Hart 7f2439f1e9 Fix contact selection behavior when searching and clear search on selection. 2020-06-02 16:27:04 -03:00
Alex Hart ae87d23003 Always use the new group settings screen if the flag is enabled. 2020-06-02 16:09:48 -03:00
Alex Hart 3192cc0aac Add outlined view-once close icon. 2020-06-02 16:05:16 -03:00
Alex Hart 6102e9aa72 Apply better coordinatorlayout animation and RTL support. 2020-06-02 15:02:35 -03:00
Alan Evans f4a152b0fe Fetch own profile after GV2 feature flag is enabled, improve GV2 capability check. 2020-06-02 11:48:40 -03:00
Greyson Parrelli 2b11bca7dc Guard against possible invalid conversation data loads. 2020-06-02 10:20:55 -04:00
Artem Varaksa 07d19f38e3 Fix typos in logging for remote delete. 2020-06-02 10:22:29 -03:00
Greyson Parrelli cd228c439e Be more explicit with the ID we use for account updates. 2020-06-02 09:03:54 -04:00
Alan Evans 7a859c8961 For smaller width devices, use original 210dp for audio messages. 2020-06-02 09:32:59 -03:00
Alan Evans 543f38c75d Fix Wave form IOException thread issue. 2020-06-02 07:38:15 -03:00
Greyson Parrelli f7b150f2d2 Bump version to 4.61.1 2020-06-01 17:43:05 -04:00
Greyson Parrelli 11328f643f Updated language translations. 2020-06-01 17:43:05 -04:00
Greyson Parrelli f270a6b8c4 Fix potential crash by removing an unnecessary column.
The column I removed is already in the recipient half of the projection.
Having two representations of the groupId made reading the groupId out
of the cursor non-deterministic, and when compounded with another bug,
could cause a crash if one of them was null.
2020-06-01 17:43:05 -04:00
Alan Evans 3fec23fd36 Show remaining time on wave form view and cache wave form in database. 2020-06-01 17:43:05 -04:00
Alex Hart e01838e996 Fix text size for pending members. 2020-06-01 17:43:05 -04:00
Greyson Parrelli f70e41e7cd Don't allow account record updates to delete our profile key. 2020-06-01 17:43:05 -04:00
Greyson Parrelli c4ec0c9897 Handle devices disallowing start of FcmFetchService.
Some devices are overzealous with battery management and disallow
starting services even when they're in response to a high-priority FCM
message (which should be allowed). So in these situations, we just
fall back to what we were doing before.
2020-06-01 17:43:05 -04:00
Greyson Parrelli 989b071a6d Ignore contacts that don't have a phone number. 2020-06-01 17:43:05 -04:00
Greyson Parrelli c39751f9db Add info about play services to the debug log. 2020-06-01 17:43:05 -04:00
jimio-signal dbf74a2234 Update copyright in README.md 2020-05-31 10:39:19 -07:00
Greyson Parrelli 837230d72d Bump version to 4.61.0 2020-05-29 19:18:55 -04:00
Greyson Parrelli f544ec4126 Updated language translations. 2020-05-29 19:18:02 -04:00
Greyson Parrelli 79dbf85c1e Improve local encrypted PIN storage. 2020-05-29 19:15:56 -04:00
Greyson Parrelli 61fe6cc961 Enable the ability to react with any emoji. 2020-05-29 19:14:37 -04:00
Greyson Parrelli 70c88b68e2 Store recent reactions separately from keyboard emoji. 2020-05-29 19:14:37 -04:00
Greyson Parrelli d70c33d20f Add support for mark as unread. 2020-05-29 19:14:37 -04:00
Greyson Parrelli 6b2e000e61 Prevent waiting for old queues in our retrieval strategies. 2020-05-29 19:14:37 -04:00
Alan Evans b9f11dafff New internal testing flag and V1 group creation button. All menus create GV1. 2020-05-29 19:14:37 -04:00
Alan Evans 9b32eaeb8a Do not log URLs. 2020-05-29 19:14:37 -04:00
Alan Evans a99c0d438e Rename GV2 "version" to "revision". 2020-05-29 19:14:37 -04:00
Alex Hart c634c24afb Utilize Wrapper instead of dynamic theme. 2020-05-29 19:14:37 -04:00
Alex Hart 2ddd1437cf Utilize exclusive AudioFocus. 2020-05-29 19:14:37 -04:00
Alan Evans 9da309ca48 Enforce a local GV2 capacity limit driven by a feature flag. 2020-05-29 19:14:37 -04:00
henry cfcd451db7 Fix crash on unlink device when offline. 2020-05-29 09:51:21 -04:00
Alex Hart 5ab72fd1a9 Ask for permission before launching avatar sheet. 2020-05-29 09:51:21 -04:00
Alan Evans daace9bd1a Audio wave forms on voice notes. 2020-05-29 09:51:21 -04:00
Alan Evans 69adcd1d69 Tap avatar in chat preferences or group management to see full screen. 2020-05-29 09:51:21 -04:00
Alex Hart 0711a22188 Add overflow toast and fix edit menu option. 2020-05-29 09:51:21 -04:00
Greyson Parrelli 3a06412cd8 Throttle notifications when doing the intial message fetch. 2020-05-29 09:51:21 -04:00
Alan Evans 51c82702e2 Remove expectation of ActionBar in DeviceProvisioningActivity.
Fixes #9661
2020-05-29 09:51:21 -04:00
Greyson Parrelli 1b01196ec6 Refactor ThreadRecord. 2020-05-29 09:51:21 -04:00
Greyson Parrelli 1cd6b58ece Don't enqueue duplicate PushDecryptMessageJobs. 2020-05-29 09:51:21 -04:00
Greyson Parrelli ea8e13b1c8 Create a WebsocketDrainedConstraint. 2020-05-29 09:51:21 -04:00
Greyson Parrelli f392229393 Extract MessageNotifier interface. 2020-05-29 09:51:21 -04:00
Greyson Parrelli a299bafe89 Create a new system for fetching the intial batch of messages. 2020-05-29 09:51:21 -04:00
Alex Hart d2bf539504 Clear sticky WebRtcViewModel events when initiating a new call. 2020-05-29 09:51:21 -04:00
Alex Hart 903c3989b9 Fix chip jank and other groups v2 ux issues. 2020-05-29 09:51:21 -04:00
Alan Evans 00996f0d7a Rename back to build.gradle 2020-05-29 09:51:21 -04:00
Alan Evans 4aded3a436 Close keyboard on contact list scroll. 2020-05-29 09:51:20 -04:00
Alan Evans 9acdc37729 Alphabetical member order. 2020-05-29 09:51:20 -04:00
Greyson Parrelli d4cdcbe54f Improve logging around group sends. 2020-05-29 09:51:20 -04:00
Alex Hart 6fa2a0f411 Polish UX for groups v2 management. 2020-05-29 09:51:20 -04:00
Alex Hart 558a8e4a14 Add polish to groups v2 creation flow. 2020-05-29 09:51:20 -04:00
Alan Evans 8947b82034 Make GV2 feature flags remote capable. 2020-05-29 09:51:20 -04:00
Alan Evans 56551025e9 Detect if group v2 is active from membership. 2020-05-29 09:51:20 -04:00
Alan Evans befb4939d5 Restore groups from storage service. 2020-05-29 09:51:20 -04:00
Alan Evans 289f7aba63 Add versioned profiles feature flag. 2020-05-29 09:51:20 -04:00
Alan Evans 28bd245b96 While testing GV2 without UUID, fail jobs that hit UuidRecipientError. 2020-05-29 09:51:20 -04:00
Alan Evans c5e7300df2 Fix matches logic in contact selection. 2020-05-29 09:51:20 -04:00
Greyson Parrelli fe25d941bb Prevent FCM bottlenecking. 2020-05-29 09:51:20 -04:00
Alan Evans 4cda267f3b Show pending count and allow view of zero pending screen. 2020-05-29 09:51:20 -04:00
Alex Hart 82ba7e2b8b Display "You" at end of members list in ConversationTitleView. 2020-05-29 09:51:20 -04:00
Alex Hart 41ebaf3938 Clean up Overflow menu for GV2 groups. 2020-05-29 09:51:20 -04:00
Alex Hart 090c400037 Collapse title into toolbar on scroll in ManageGroupFragment. 2020-05-29 09:51:20 -04:00
Alan Evans 12b1232ac0 Fix groups v2 patch response handler. 2020-05-29 09:51:20 -04:00
Alex Hart 204a84c522 Apply proper spacing to RecipientBottomSheetDialogFragment. 2020-05-29 09:51:20 -04:00
Alan Evans 526afd539b Fix avatar tap in conversation multi-select mode. 2020-05-29 09:51:20 -04:00
Greyson Parrelli d708984abd Require users be a system contact or whitelisted to appear in the contact list. 2020-05-29 09:51:20 -04:00
Greyson Parrelli 9d39db6428 Add additional account restore logging, prevent double avatar fetch. 2020-05-29 09:51:20 -04:00
Alan Evans 67a8ec0d39 Only admin can cancel any invite. 2020-05-29 09:51:20 -04:00
Alan Evans 297a7d0ef8 Handle absent change during invite. 2020-05-29 09:51:20 -04:00
Bastian Köcher 4712833853 Always convert HEIC images to JPEG.
This pr changes the behavior of sending HEIC images to always convert
them to JPEG. This conversion is required to support image inline
viewing accross different devices and operating systems. This follows
the same strategy as on IOS: https://github.com/signalapp/Signal-iOS/pull/2511

Fixes: https://github.com/signalapp/Signal-iOS/issues/4374 & https://github.com/signalapp/Signal-Android/issues/9395
2020-05-29 09:51:20 -04:00
Alan Evans 11d17f7496 GV2 storage service syncing. 2020-05-29 09:51:20 -04:00
Alan Evans 36df3f234f Enable the Zk group library. 2020-05-29 09:51:20 -04:00
Greyson Parrelli 098b298646 Add a network constraint to RemoteConfigRefreshJob. 2020-05-29 09:51:20 -04:00
Alan Evans 2f9320989a Server signed group v2 changes sent and received P2P. 2020-05-29 09:51:20 -04:00
Alan Evans ec8d5defd4 Protect against unknown GV2 UUIDs. 2020-05-29 09:51:20 -04:00
Greyson Parrelli 981676c7f8 Bump version to 4.60.9 2020-05-29 09:40:26 -04:00
Greyson Parrelli 7c5ae57784 Updated language translations. 2020-05-29 09:38:57 -04:00
Alex Hart fc7be87468 Downgrade AudioManagerCompat errors to warnings. 2020-05-29 10:31:36 -03:00
Greyson Parrelli e55d8007fc Bump version to 4.60.8 2020-05-28 18:34:06 -04:00
Greyson Parrelli 43b7aa2d52 Updated language translations. 2020-05-28 18:33:46 -04:00
Alex Hart cd1bad0718 Fix bluetooth behavior. 2020-05-28 17:36:40 -03:00
Greyson Parrelli 6b47618351 Bump version to 4.60.7 2020-05-26 18:27:05 -04:00
Greyson Parrelli b6d384120d Updated language translations. 2020-05-26 18:26:39 -04:00
Greyson Parrelli 1268b26c1f Auto-dismiss PIN reminder dialog as you type. 2020-05-26 18:13:19 -04:00
Greyson Parrelli f1233bfddc Bump version to 4.60.6 2020-05-25 14:57:59 -04:00
Greyson Parrelli 1aa3e6afea Updated language translations. 2020-05-25 14:57:59 -04:00
Greyson Parrelli ce21eb241a Fix potential crash with data size in ConversationDataSource. 2020-05-25 14:57:59 -04:00
Greyson Parrelli f96fb72eb1 Don't show PIN reminders if you're not registered.
Fixes #9657
2020-05-25 13:14:38 -04:00
Greyson Parrelli 207c467c6b Don't insert identity verification message for the initial restore. 2020-05-24 13:00:16 -04:00
Alex Hart 9d1d9e33ed Bumped version to 4.60.5 2020-05-21 20:03:31 -03:00
Alex Hart e4a76c0690 Updated language translations. 2020-05-21 20:03:31 -03:00
Alex Hart 124c3e25e9 Implement layout changes to new call screen UX. 2020-05-21 20:03:31 -03:00
Greyson Parrelli 5cb1201903 Add the ability to disable PIN reminders. 2020-05-21 19:56:30 -03:00
Greyson Parrelli bb6ca80d5a Don't create identity change methods for brand new contacts. 2020-05-21 19:56:30 -03:00
Greyson Parrelli dc7c54a1f8 Ensure we upload the profile after a PIN restore. 2020-05-21 19:56:30 -03:00
Greyson Parrelli 23401440bf Prevent insertion of UUID-only contacts at the database level. 2020-05-21 19:56:30 -03:00
Greyson Parrelli f8f959e05a Make rate limit message more generic. 2020-05-20 14:22:33 -04:00
Alex Hart edbd4d2d03 Properly set profile key update flag. 2020-05-20 12:15:54 -03:00
Greyson Parrelli a0b4065be3 Fix potention OOB error when pulse-highlighting a message.
This basically happened if you used full-text search to search for the
latest message in a conversation, but when you navigated there, it
*also* had a header set (like a typing indicator or unknownSenderView).
2020-05-19 17:09:25 -04:00
Greyson Parrelli 1b2f964f32 Fix possible crash around loading initial conversation pages. 2020-05-19 16:20:20 -04:00
Alex Hart eaf5280d99 Bumped version to 4.60.4 2020-05-19 16:51:33 -03:00
Alex Hart d435da980f Updated language translations. 2020-05-19 16:51:33 -03:00
Greyson Parrelli 8d3a91f3a4 Fix possible data source invalidation loop. 2020-05-19 16:51:33 -03:00
Greyson Parrelli b80c339c5a Fix an issue where the add profile prompt wasn't dismissed. 2020-05-19 16:51:33 -03:00
Alan Evans 34159fc9da Log successful pin setting. 2020-05-19 16:34:52 -03:00
Alex Hart b509ee9ee0 Bumped version to 4.60.3 2020-05-18 17:03:45 -03:00
Alex Hart a6819448b9 fixup! Consolidate Call UI visibility selection logic. 2020-05-18 16:43:30 -03:00
Alex Hart f2847f9aa5 Bumped version to 4.60.2 2020-05-18 16:31:20 -03:00
Alex Hart 8f01e5e1c3 Updated language translations. 2020-05-18 16:31:20 -03:00
Alan Evans acb2f43620 Make Manage Group menu item replace Edit Group for GV2. 2020-05-18 16:31:20 -03:00
Greyson Parrelli 62ac65e4d8 Improve paging performance on slower devices. 2020-05-18 16:31:20 -03:00
Alex Hart 8f183bdcdc Consolidate Call UI visibility selection logic. 2020-05-18 16:31:20 -03:00
Greyson Parrelli 3d135d155e Disable view prefetching for now. 2020-05-18 16:31:20 -03:00
Alan Evans 090c811391 Force app compat version 1.1.0-beta01. 2020-05-18 16:31:20 -03:00
Alex Hart 2a9e8dc525 Bumped version to 4.60.1 2020-05-15 19:02:18 -03:00
Alex Hart cb0b22cf2c Updated language translations. 2020-05-15 19:02:18 -03:00
Alex Hart 5aba3517ce Upgrade to RingRTC 2.0.3 and implement rounded corners for local pip. 2020-05-15 19:02:18 -03:00
Alex Hart 726f665388 Upgrade AppCompat to 1.2.0-rc01. 2020-05-15 19:02:18 -03:00
Alex Hart e2ac55e9ac Fix ellapsed call timer restarting between activity restarts. 2020-05-15 19:02:18 -03:00
Greyson Parrelli fa5729bac6 Better handle identity key changes in response to storage service syncs. 2020-05-15 19:02:18 -03:00
Greyson Parrelli e714cb6423 Fix potential issues with ConversationDataSource boundaries. 2020-05-15 19:02:18 -03:00
Alex Hart 35a0162d5c Utilize EmojiTextView instead of TextView. 2020-05-15 19:02:18 -03:00
Alex Hart 76740adc3f Fix controls are removed when remote video is disabled. 2020-05-15 13:05:00 -03:00
Alex Hart 1c814141a2 Fix NullPointerException when trying to launch InviteActivity. 2020-05-15 10:43:25 -03:00
Alan Evans 5545daf992 Live group membership count in conversation. 2020-05-15 10:28:48 -03:00
Alan Evans d300615d90 Ensure new group UI behind feature flag. 2020-05-15 10:27:39 -03:00
Alex Hart 908a5260c2 Enable note to self as recipient in share activity. 2020-05-15 10:03:49 -03:00
Alex Hart 7aac6644c3 Expand tappable area in header. 2020-05-14 16:27:52 -03:00
Alan Evans 3b673c07a0 Support gv2 avatar removal. 2020-05-14 15:57:40 -03:00
Alan Evans d726da822c Add network constraint to GV2 messages. 2020-05-14 15:23:15 -03:00
Alex Hart 7894f72b0f Enable speaker when initiating a video call. 2020-05-14 14:18:49 -03:00
Alan Evans 4c5822ac67 GV2 Update message description. 2020-05-14 13:59:34 -03:00
Alex Hart b917cccbee Bumped version to 4.60.0 2020-05-14 11:22:28 -03:00
Alex Hart 01d2d05d8e Updated language translations. 2020-05-14 11:22:28 -03:00
Alan Evans 4de86cb6cf Prevent ZkGroup link crashes. 2020-05-14 11:22:28 -03:00
henry 8861ad76ed Fix start SubmitDebugLog from registration and passphrase prompt. 2020-05-14 11:22:28 -03:00
Alan Evans ef86372635 Ensure that the unknown UUID does not create an entry. 2020-05-14 11:22:28 -03:00
Alex Hart ccff7b1148 Implement new group creation screens behind flag. 2020-05-14 11:22:28 -03:00
Greyson Parrelli ed0825112d Fix some ordering problems with conversation data loading. 2020-05-14 11:22:28 -03:00
Alan Evans b8df90531f GV2 message contexts. 2020-05-14 11:22:28 -03:00
Greyson Parrelli f099c3591c Run PushProcessMessageJobs in parallel. 2020-05-14 11:22:28 -03:00
Greyson Parrelli ed33e048ad Add CachedLayoutInflater to improve conversation render performance. 2020-05-14 11:22:28 -03:00
Greyson Parrelli 7fd3bfa30c Revert "Check to see if FCM is available at app launch."
This reverts commit eea7174f1d.
2020-05-14 11:22:28 -03:00
Alex Hart 07a492a32c Add dot character to reactions bottom sheet all tab label. 2020-05-14 11:22:28 -03:00
Alan Evans 11fffbd79e Remove P2P group change sending. 2020-05-14 11:22:28 -03:00
Alan Evans eff564ad88 Adapt message requests to support invite flow. 2020-05-14 11:22:28 -03:00
Greyson Parrelli d3d53e6099 Reduce recipient dirty state logging verbosity. 2020-05-14 11:22:28 -03:00
Greyson Parrelli 53d122ed55 Fix jumping to last seen position. 2020-05-14 11:22:28 -03:00
Alan Evans 1778c1ef7d Prevent some IOExceptions when past the end of stream. 2020-05-14 11:22:28 -03:00
Alan Evans a510bc74e6 Recipient Id cache. 2020-05-14 11:22:28 -03:00
Alan Evans a9ecdbdfec Groups V2 capability set by the feature flag. 2020-05-14 11:22:28 -03:00
Alan Evans 06ab3cf013 Fix cases of inlined & missing log tags. 2020-05-14 11:22:28 -03:00
Alan Evans 3db5da1c8d Generalize media input for use with Audio. 2020-05-14 11:22:28 -03:00
Greyson Parrelli 5937a50b6d Fix message receive timestamps on media messages. 2020-05-14 11:22:28 -03:00
Alan Evans b4191ee5cc Fix usages of service logging in app. 2020-05-14 11:22:28 -03:00
Alan Evans c63e42715e New logging lint checks.
[LogNotAppSignal] tells you about using signal service logger in the app.
[LogTagInlined] tells you about not using a constant tag.
2020-05-14 11:22:28 -03:00
Alex Hart 26e582d806 Integrate RingRTC v2.0.1 2020-05-14 11:22:28 -03:00
Alan Evans ee9270845a Create GV2 group behind feature flag. 2020-05-14 11:22:28 -03:00
Alan Evans 6cf33897c0 Remove superfluous groups v2 capability checks. 2020-05-14 11:22:28 -03:00
Freddy Tuxworth 2161bbb8fa Display "No matching countries" when no filter matches found.
Fixes #9518
2020-05-14 11:22:28 -03:00
Greyson Parrelli b75088874e Migrate conversation rendering to the paging library. 2020-05-14 11:22:28 -03:00
Alan Evans 9ac1897880 Job changes for GroupsV2 message receive and profile key updates. 2020-05-14 11:22:28 -03:00
Alan Evans 36c43ed2fa Ensure latest V2 group state from server upon conversation open. 2020-05-14 11:22:28 -03:00
Alan Evans 8084822f16 Connect GV2 title and avatar updates and prevent no-change avatar updates. 2020-05-14 11:22:28 -03:00
Alan Evans 959718618f Deprecate some ViewUtil methods. Inline others. Remove some old API code. 2020-05-14 11:22:28 -03:00
Alan Evans 75f3fe0cec Correct access control for MMS groups. 2020-05-14 11:22:28 -03:00
Alan Evans b800477365 GV2 leave and eject operations. 2020-05-14 11:22:28 -03:00
Alex Hart b191341c57 Add some polish to the groups V2 manager UI. 2020-05-14 11:22:28 -03:00
leet 88a40be901 Fix backup timestamp language.
Fixes #8842
Fixes #8986
2020-05-14 11:22:28 -03:00
Greyson Parrelli 3fef58057e Add additional info to support emails and debuglogs. 2020-05-14 11:22:28 -03:00
Greyson Parrelli b156e4a79a Always use the UD cert with a UUID. 2020-05-14 11:22:28 -03:00
Alan Evans 30ac264cd3 Rename Group update message classes. 2020-05-14 11:22:28 -03:00
Alan Evans a9b00e1cd3 Remove instances of Android logging. 2020-05-14 11:22:28 -03:00
Alex Hart d94fc4bc13 Implement ability to react with any emoji behind a flag. 2020-05-14 11:22:28 -03:00
Greyson Parrelli 40b5339ef8 Allow auto-download for users you've shared your profile with. 2020-05-14 11:22:28 -03:00
Alan Evans 86f0456e8c Group Manager V2 operations. 2020-05-14 11:22:28 -03:00
Alan Evans 48a693793f GV2 Group Manager. 2020-05-14 11:22:28 -03:00
Alan Evans ff28d72db6 New GV2 internal prefix and scrubber. 2020-05-13 16:18:18 -04:00
Alan Evans 456857bbbd Add custom lint rule project. 2020-05-13 16:18:18 -04:00
Alan Evans 7f17b66a6c Upgrade gradle and gradle plugin. 2020-05-13 16:18:18 -04:00
Greyson Parrelli 310ec8f296 Remove CellServiceConstraint in favor of NetworkOrCellServiceConstraint.
If a job was enqueued with a CellServiceConstraint (which is currently
only SMS jobs), then it'll never run until it gets service, even if you
flip the "enable SMS sending over wifi" toggle.

This has created bad situations in the past, where SMS jobs just get
stuck on devices that never report having cell service (like VM's or
wifi only devices).

This fixes it by *always* using NetworkOrCellServiceConstraint, and then
deciding whether a constraint is met by checking the "wifi SMS" setting
at decision-time.
2020-05-13 16:18:18 -04:00
Alan Evans 0c2afa9438 Fix FCM token via phone call registration.
Fixes #8992
2020-05-13 16:18:18 -04:00
Alex Hart c3832cf8b1 New group notifications management ui. 2020-05-13 16:18:18 -04:00
Greyson Parrelli a2de8a2a05 Ensure you can't set null values in DefaultValueLiveData. 2020-05-13 16:18:18 -04:00
Greyson Parrelli 3b601896d2 Fix crash in SubmitDebugLogActivity. 2020-05-13 16:18:18 -04:00
Greyson Parrelli e1a90bcb00 Transition conversation loading from a Loader to a Repository. 2020-05-13 16:18:18 -04:00
Greyson Parrelli 2b65916344 Show calling foreground notification on all OS versions.
Fixes #9516
Fixes #9414
2020-05-13 16:18:18 -04:00
Greyson Parrelli f149005026 Add support for remote config v1.1 2020-05-13 16:18:18 -04:00
Alex Hart 5eb663aa1b New group avatar and name selection screen. 2020-05-13 16:18:18 -04:00
Alan Evans 12b7d6c0e3 Use bottom sheet shape. 2020-05-13 16:18:18 -04:00
Alan Evans 723639d928 New group management screen. 2020-05-13 16:18:18 -04:00
Greyson Parrelli e0502c24e1 Only search for visible parts of a contact. 2020-05-13 16:18:18 -04:00
Alex Hart 358d6333c3 Open new recipient bottom sheet when accessing contact from group context. 2020-05-13 16:18:18 -04:00
Alan Evans 0b279d1df3 Group contact chips behind feature flag. 2020-05-13 16:18:18 -04:00
Alan Evans 8e0fba7992 New group button behind new Group UI feature flag. 2020-05-13 16:18:18 -04:00
Alex Hart d5419ec9fa Implement new call screen UI/UX. 2020-05-13 16:18:18 -04:00
Alan Evans 33e3f78be6 LiveDataUtil combineLatest. 2020-05-13 16:17:29 -04:00
Alex Hart 3c5ad519dd Decrease QuoteView reveal animation duration to 150ms. 2020-05-13 16:17:29 -04:00
Alan Evans 17c5b858b5 Recipient bottom sheet. 2020-05-13 16:17:29 -04:00
Greyson Parrelli f6f6496c9c Bump version to 4.59.10 2020-05-13 15:40:44 -04:00
Greyson Parrelli b1d725e23a Updated language translations. 2020-05-13 15:40:44 -04:00
Greyson Parrelli a74622997e Bump libsignal-metadata to 0.1.2
Includes fix for how we're prioritizing UUID vs E164.

Fixes #9621
2020-05-13 15:40:42 -04:00
Greyson Parrelli b1a200001e Bump version to 4.59.9 2020-05-09 13:19:57 -04:00
Greyson Parrelli 3b1041fa1f Updated language translations. 2020-05-09 13:19:31 -04:00
Greyson Parrelli a83ccc18bb Fix processing of early messages.
1. Eliminated any possibility of infinite recursion.
2. Handle the fact that you can have multiple 'early contents' for a
   single message.
2020-05-09 13:16:45 -04:00
Greyson Parrelli 618b1b5ace Handle PIN creation failure better. 2020-05-09 13:16:45 -04:00
Greyson Parrelli 14858adc88 Bump version to 4.59.8 2020-05-04 18:22:45 -04:00
Greyson Parrelli c07f35f3aa Updated language translations. 2020-05-04 18:21:50 -04:00
Alan Evans 87eab27996 Prevent the creation of 'weak' PINs.
Simple checks to prevent the same number, or sequentially
increasing/decreasing PINs. e.g. 1111, 1234, 54321, etc.
2020-05-04 18:17:36 -04:00
Greyson Parrelli b7296a4fe3 Bump version to 4.59.7 2020-05-04 11:06:28 -04:00
Greyson Parrelli 3fb9ae1fb4 Updated language translations. 2020-05-04 11:05:55 -04:00
Greyson Parrelli 9705939489 Fix issue with editing and forwarding a received video. 2020-05-04 10:54:55 -04:00
Greyson Parrelli eca67b1204 Broaden exception handling in custom DNS.
A set of LG devices is crashing when using the custom DNS. Safest thing
for now would be to treat all failures as network errors while we we try
to get a repro to figure out what's happening.
2020-05-04 10:54:55 -04:00
Greyson Parrelli c59fc3581a Make LiveRecipientCache throw exceptions instead of errors.
Errors were causing crash loops if they occur in a job. This will still
allow the app to crash, but prevent loops.
2020-05-04 00:48:09 -04:00
Greyson Parrelli e00f8c94ff Bumped version to 4.59.6 2020-04-30 17:03:28 -04:00
Greyson Parrelli 4186153f0c Updated language translations. 2020-04-30 17:03:28 -04:00
Greyson Parrelli 6c01807f4f Fix issue with PIN verification. 2020-04-30 17:03:28 -04:00
Greyson Parrelli 9d35fb397b Fix issue with re-using forwarded attachment pointers.
We were deleting upload data for incoming attachments when we shouldn't
have.

Fixes #9570
2020-04-30 16:36:06 -04:00
Jim Gustafson c9f2f57427 Update to ringrtc v1.3.2 2020-04-30 08:12:31 -07:00
Greyson Parrelli c862ab0c56 Bump version to 4.59.5 2020-04-28 10:41:37 -04:00
Greyson Parrelli 7aaaa57c14 Updated language translations. 2020-04-28 10:41:06 -04:00
Greyson Parrelli 11b6394a87 Fix issue with group storage IDs. 2020-04-28 10:38:58 -04:00
Greyson Parrelli bdd48fd2df Show PIN reminder for non-reglock users. 2020-04-28 10:38:49 -04:00
Alan Evans e99af75400 Fix crash when blocking group. 2020-04-27 16:52:17 -03:00
Greyson Parrelli 321440e13f Bump version to 4.59.4 2020-04-27 13:07:41 -04:00
Greyson Parrelli 0556d984e0 Updated language translations. 2020-04-27 13:07:19 -04:00
Greyson Parrelli 0ba1f66136 Use the same Recipient.self() instance in storage sync. 2020-04-27 13:05:22 -04:00
Greyson Parrelli 7562555687 Add additional storage sync validations. 2020-04-27 12:38:07 -04:00
Greyson Parrelli 668ccfcd12 Clean up logging. 2020-04-27 11:29:52 -04:00
Greyson Parrelli 9c0337c4ef Fix threading issue with message resends. 2020-04-27 11:11:24 -04:00
Greyson Parrelli 3fde06ab0f Bump version to 4.59.3 2020-04-24 19:48:20 -04:00
Greyson Parrelli 73959f328a Updated language translations. 2020-04-24 19:47:57 -04:00
Greyson Parrelli cca85bfee3 Fix some PinState bugs. 2020-04-24 19:40:50 -04:00
Greyson Parrelli 575caa53d3 Fix some storage service consistency issues. 2020-04-24 19:14:08 -04:00
Greyson Parrelli 33874a8866 Fix attachment upload bug. 2020-04-24 09:24:43 -04:00
Greyson Parrelli b8e909a134 Revert to preferring system photos over profile photos. 2020-04-24 08:45:58 -04:00
Greyson Parrelli 5193a5d309 Prevent some crash loops. 2020-04-23 22:25:56 -04:00
Greyson Parrelli 7db288b9aa Make PINs work with password managers. 2020-04-23 22:25:56 -04:00
Greyson Parrelli 9f033e64aa Fix lint error. 2020-04-23 22:25:56 -04:00
Greyson Parrelli 5a15ba97dc Bump version to 4.59.2 2020-04-23 13:33:59 -04:00
Greyson Parrelli ce6ec72683 Updated language translations. 2020-04-23 13:33:59 -04:00
Greyson Parrelli eedbcdd564 Fix issue with group storage sync. 2020-04-23 13:33:59 -04:00
Greyson Parrelli 0ca2848e01 Improve logging for storage service. 2020-04-23 12:03:31 -04:00
Greyson Parrelli 208275b6a9 Improve logging for PinState. 2020-04-23 11:24:23 -04:00
Greyson Parrelli 4bdcaa72cd Fix some more UX issues with blocked users. 2020-04-23 11:06:52 -04:00
Greyson Parrelli 8c6001fa5a Improve correctness and performance of camera contact search. 2020-04-23 10:25:45 -04:00
Greyson Parrelli c4e88abce1 Update PIN change strings. 2020-04-22 19:42:17 -04:00
Greyson Parrelli eea7174f1d Check to see if FCM is available at app launch. 2020-04-22 19:32:40 -04:00
Greyson Parrelli 3f7d0688fc Bump version to 4.59.1 2020-04-22 14:18:28 -04:00
Greyson Parrelli 6d319618c6 Updated language translations. 2020-04-22 14:18:28 -04:00
Greyson Parrelli 4250fa707b Fix crash when videos are missing a duration.
Fixes #9556
2020-04-22 14:18:28 -04:00
Greyson Parrelli 7734cd2c8f Clean up some corner cases in storage syncing. 2020-04-22 14:18:28 -04:00
Alan Evans 57467bb338 Dismiss group participant list on contact click. 2020-04-22 14:18:28 -04:00
Alex Hart 8ad61a52b9 Fix call termination when muting before call is connected. 2020-04-22 14:18:28 -04:00
Alan Evans 9742a212a2 Fix Transifex string name clash. 2020-04-22 14:18:28 -04:00
Greyson Parrelli fd21fc1a31 Fix some UX issues with blocked users. 2020-04-22 14:18:28 -04:00
Greyson Parrelli 1b5a0ab9f3 Sync the profile photo to linked devices when appropriate. 2020-04-22 10:34:43 -04:00
Greyson Parrelli f466fef20a Fix issue where contact photos weren't being shown at all. 2020-04-22 10:13:56 -04:00
Greyson Parrelli 9bc70adbbd Update PIN setting strings. 2020-04-21 19:23:47 -04:00
Greyson Parrelli 6f39f9849a Bump version to 4.59.0 2020-04-21 16:09:30 -04:00
Greyson Parrelli 5bc950ed28 Updated language translations. 2020-04-21 16:09:30 -04:00
Alan Evans b80d460a8f Account for deleted conversations in profile key send job. 2020-04-21 16:09:30 -04:00
Alan Evans 3f555ce5e2 Extract method for creating safety number intents. 2020-04-21 16:09:30 -04:00
Jim Gustafson 9513b476ef Update to ringrtc v1.3.1 2020-04-21 16:09:30 -04:00
Greyson Parrelli 8f9e79ae37 Updated PIN strings. 2020-04-21 16:09:30 -04:00
Alan Evans 53b681ef67 Make reaction and remote delete jobs GV2 ready. 2020-04-21 16:09:30 -04:00
Alan Evans 9a8094cb8a Guard against malformed group ids. 2020-04-21 16:09:30 -04:00
Alex Hart 00ee6d0bbd Dialog theme rename. 2020-04-21 16:09:30 -04:00
Greyson Parrelli 83f6640bd3 Add a more generic system for handling early messages. 2020-04-21 16:09:30 -04:00
Alex Hart 2afb939ee6 Implement send support for resumable uploads behind a flag. 2020-04-21 16:09:30 -04:00
Greyson Parrelli 7c442865c5 Interpret non-present message bodies as 'null'. 2020-04-21 16:09:30 -04:00
Greyson Parrelli b3d57edb24 Update and centralize block strings. 2020-04-21 16:09:30 -04:00
Alex Hart 6d6e017c71 Proactively share profile key after accepting a message request. 2020-04-21 16:09:30 -04:00
Greyson Parrelli fc6b5c1d7c Add ultramarine as a conversation color option. 2020-04-21 16:09:30 -04:00
Greyson Parrelli 6ecd3b59fd Add pre-alpha receive support for remote delete. 2020-04-21 16:09:13 -04:00
Ehren Kret 456bcf3d57 Require CDN number match rather than use default CDN
This marks messages as failed if the CDN number does not match a
configured CDN number rather than falling back to the default CDN in
the event the CDN is not recognized.
2020-04-21 13:33:41 -04:00
Greyson Parrelli f12a9b9ac7 Store the server timestamp for a message. 2020-04-21 13:33:41 -04:00
Greyson Parrelli 00b6a222bd Remove jumpiness when rendering reactions. 2020-04-21 13:33:41 -04:00
Greyson Parrelli b8ccc4453e Update pins4all flag.
We still have to keep the legacy one though so that people in the old
bucket stay in the new one.
2020-04-21 13:33:41 -04:00
Alan Evans dbb31420af Ensure all support article urls are the correct format and not translatable. 2020-04-21 13:33:41 -04:00
Greyson Parrelli 35f4f3f81e Add support for passing data between jobs. 2020-04-21 13:33:41 -04:00
Greyson Parrelli acbfff89d3 Update registration to allow PIN entry. 2020-04-21 13:33:41 -04:00
Greyson Parrelli 6b37675a81 Remove long-press action in settings. 2020-04-21 13:33:41 -04:00
Greyson Parrelli a471ffa6d8 Fix UD indicators for sent transcripts. 2020-04-21 13:33:41 -04:00
Alan Evans 7bf090fdab GroupsV2 state mapping. 2020-04-21 13:33:41 -04:00
Alan Evans 4e0279200f Refactor out MediaPreviewActivity Intent creation method. 2020-04-21 13:33:41 -04:00
Alan Evans 78055e3ccb GroupsV2 update sending and local context storage. 2020-04-21 13:33:41 -04:00
Alan Evans f5e6fd6340 Allow RetrieveProfileJob to be used for self. 2020-04-21 13:33:33 -04:00
Alex Hart 2d60d5fb1f Check menu item visibility when calculating menu size. 2020-04-21 13:33:33 -04:00
Alan Evans c6dd25a119 Ensure group membership for typing indicators. 2020-04-21 13:33:33 -04:00
Alan Evans 68d29d9a0f Allow pending member invite cancelation. 2020-04-21 13:33:33 -04:00
Alan Evans 1d63970a25 Hardcode all class names in old work manager migration. 2020-04-21 13:33:33 -04:00
Alan Evans 2b1ffac564 Groups V2 avatar download job. 2020-04-21 13:33:33 -04:00
Alan Evans e2d3a43593 Use readBodyBytes for correct exceptions. 2020-04-21 13:33:33 -04:00
Greyson Parrelli 8e13403cca Separate PINs from Registration Lock.
You can now have a PIN without having registration lock.

Note: We still need to change the registration flow to allow non-reglock
users to enter their PIN.
2020-04-21 13:33:33 -04:00
Ehren Kret 3c6a7b76ca Send increased protocol version number if CDN key or attachment are used. 2020-04-21 13:33:33 -04:00
Alan Evans 428128651e Move database protos to separate files. 2020-04-21 13:33:33 -04:00
Alan Evans 326678f214 Add support for GV2 group update messages. 2020-04-21 13:33:29 -04:00
Alex Hart 1f994495f8 Clear search if user sends message. 2020-04-21 13:33:29 -04:00
Greyson Parrelli fb1637006d Include screen size details in debuglogs. 2020-04-21 13:33:29 -04:00
Ehren Kret 37a35e8f70 Add initial support for send/receive on CDN2. 2020-04-21 13:33:29 -04:00
Alan Evans 1290d0ead9 Add pending member activity. 2020-04-21 13:33:25 -04:00
Greyson Parrelli ef0f26b64c Remove borders from images in the gallery picker. 2020-04-03 12:20:15 -04:00
Greyson Parrelli 485d211768 Remove border from images in the attachment keyboard. 2020-04-03 12:20:15 -04:00
Greyson Parrelli f1ea035197 Re-enable and clean up Signal PINs.
- Require PINs during registration agian.
- Change min length to 4.
- Allow the full-screen megaphone to be enabled remotely.
- Clean up and remove some code.
2020-04-03 12:20:15 -04:00
Martijn van den Hoek 6f961ade74 Fix crash when importing vcf after exporting it.
Fixes #9465
2020-04-03 12:20:15 -04:00
Alan Evans b8e17e0116 Enable video trimming feature by default. 2020-04-03 12:20:15 -04:00
Alan Evans 040e1fe8f6 Apply dark theme to scroll to bottom button. 2020-04-03 12:20:15 -04:00
Alan Evans e9c92bdf51 Show unblock dialog when tap blocked contact. 2020-04-03 12:20:15 -04:00
Alan Evans 48c33f3dcd GroupsV2 service changes. 2020-04-03 12:20:15 -04:00
Alex Hart 6b2bc924dd Prefer profile photo over system contact photo. 2020-04-03 12:20:15 -04:00
Ehren Kret a65c4f90f4 Avoid potential race condition in attachment uploads. 2020-04-03 12:19:13 -04:00
Alan Evans 04bb4b351a Refactor group leave dialog out of conversation. 2020-04-03 12:19:13 -04:00
Alan Evans e02e4d52b4 Prevent empty message processing. 2020-04-03 12:19:13 -04:00
Alex Hart 6f3c4434f6 Add animation when replying to a message. 2020-04-03 12:19:13 -04:00
Greyson Parrelli 711715ca1e Add DNS fallback system. 2020-04-03 12:19:12 -04:00
Greyson Parrelli d6000af843 Re-use recently-acquired attachment pointers. 2020-04-03 12:19:12 -04:00
Greyson Parrelli 9b0954a898 Bump version to 4.58.5 2020-04-03 11:18:39 -04:00
Greyson Parrelli 42a2c33fd7 Updated language translations. 2020-04-03 11:18:13 -04:00
Greyson Parrelli a4d18a18d9 Don't use vector assets for notification icon. 2020-04-03 11:14:36 -04:00
Alex Hart bf32409d4e Split drawable into light and dark. 2020-04-03 12:14:09 -03:00
Greyson Parrelli e38aec225f Bump version to 4.58.4 2020-04-01 14:48:47 -04:00
Greyson Parrelli 995b7a4712 Updated language translations. 2020-04-01 14:48:25 -04:00
Alex Hart 9fe3026941 Fix AdaptiveActionsToolbar sizing algorithm. 2020-04-01 11:26:19 -03:00
Greyson Parrelli 520658e1b8 Bump version to 4.58.3 2020-03-31 17:10:00 -04:00
Greyson Parrelli f822d8eddb Updated language translations. 2020-03-31 16:59:36 -04:00
Alex Hart 2f879ce4d6 Remove MMS groups from message request logic. 2020-03-31 16:00:26 -03:00
Greyson Parrelli 24528bf101 Fix accent color in alert dialogs in dark theme. 2020-03-31 14:59:00 -04:00
Greyson Parrelli 822682caba Fix NPE in BitmapUtil.toJpeg()
Fixes #9513
2020-03-31 11:44:18 -04:00
Greyson Parrelli 5dc3cc65a8 Bump version to 4.58.2 2020-03-30 17:53:08 -04:00
Greyson Parrelli 0f80caffb5 Updated language translations. 2020-03-30 17:53:08 -04:00
Greyson Parrelli 6c428b2777 Fix issue with some notifications linking to the wrong conversation. 2020-03-30 17:53:08 -04:00
Greyson Parrelli c9be37b84a Fix camera rotation issues.
Had to manually detect when CameraX is giving us bad data.

Fixes #9509
2020-03-30 16:04:28 -04:00
Jim Gustafson 87ea2f86c0 Update ringrtc to v1.2.0 2020-03-30 13:01:14 -07:00
Greyson Parrelli 7e80be5ca0 Separate out model info in debug logs. 2020-03-30 15:24:29 -04:00
Greyson Parrelli 989a818a67 Fix issue where reaction notifications may jump to the wrong message. 2020-03-30 12:49:08 -04:00
Greyson Parrelli af2e17df9e Ensure old typing observers are unsubscribed. 2020-03-30 11:33:21 -04:00
Greyson Parrelli 728ec1c16d Fix issue where leave messages were pending forever. 2020-03-30 10:11:15 -04:00
Greyson Parrelli f859c5b1b5 Prevent conscrypt crash during profile retrieval.
This was a mitigation that was previously in place that was forgotten
during the recent avatar refactor.
2020-03-29 18:53:30 -04:00
Greyson Parrelli ab600d7df1 Bump version to 4.58.1 2020-03-27 16:51:21 -04:00
Greyson Parrelli 4644f64fd6 Updated language translations. 2020-03-27 16:51:21 -04:00
Greyson Parrelli c274312265 Fix scrolling in the emoji variation popup. 2020-03-27 16:51:21 -04:00
Greyson Parrelli f8e63098a2 Don't show empty date popovers. 2020-03-27 16:40:55 -04:00
Greyson Parrelli 264d353ec2 Bump quality of camera1 photos. 2020-03-27 16:30:36 -04:00
Alex Hart 2b58dcbe7f Remove explicit CameraX initialization. 2020-03-27 16:30:36 -04:00
Greyson Parrelli dc791487c5 Jump to the relevant message when tapping a reaction notification.
Fixes #9503
2020-03-27 16:30:36 -04:00
Greyson Parrelli 5637f132d4 Fixed issue where leave message wasn't displayed locally. 2020-03-27 16:30:36 -04:00
Alan Evans 9e6cca1cd0 GV2 database. 2020-03-27 16:30:36 -04:00
Alan Evans 640c82d517 GV2 group context proto. 2020-03-27 16:30:36 -04:00
Greyson Parrelli 20d1a93b09 Don't refresh own profile if not registered. 2020-03-27 16:30:36 -04:00
Greyson Parrelli f5d1b11bda Fix some dark theme text contrast issues. 2020-03-27 16:30:36 -04:00
Alan Evans 66c7f8bcb2 GroupId for GV2. 2020-03-27 11:28:48 -03:00
Alex Hart d8fa46c558 Copy action should display if message body is not empty.
Fixes #9491
2020-03-27 11:18:02 -03:00
Greyson Parrelli 10bfc8a753 Migrate avatars and group avatars. 2020-03-26 22:38:33 -04:00
Greyson Parrelli 9848599807 Bump version to 4.58.0 2020-03-26 17:49:10 -04:00
Greyson Parrelli 2e38ebcfbb Updated language translations. 2020-03-26 17:46:51 -04:00
Greyson Parrelli f875623cd0 Resize avatars to 1024x1024. 2020-03-26 17:37:52 -04:00
Greyson Parrelli e6f9cb9929 Remove TextSecurePreferences.getAvatarId() 2020-03-26 17:37:52 -04:00
Greyson Parrelli 6aac3baa55 Remove TextSecurePreferences.getProfileName() 2020-03-26 17:37:52 -04:00
Alan Evans a860315587 GroupId class. 2020-03-26 17:37:52 -04:00
Greyson Parrelli a73a73e42c Fix AudioView tinting on Android 10. 2020-03-26 17:37:52 -04:00
Greyson Parrelli a3358e5b21 Rotate profile key after blocking if shared via group. 2020-03-26 17:37:52 -04:00
Alex Hart 7e9e2fead2 Fix NPE after call failure. 2020-03-26 17:37:52 -04:00
Alan Evans 0269a3eb6f Groups V2 protobufs and local conflict resolution. 2020-03-26 17:37:51 -04:00
Alex Hart f449a45912 Utilize normal fallback for homescreen icons. 2020-03-26 17:37:51 -04:00
Alan Evans f69d4ccd22 Increase Lib Signal Service compatibility to Java 8. 2020-03-26 17:37:51 -04:00
Alan Evans 0e2df2adbb Image Editor: Keep text on top.
Sorts children by a new z-order.
2020-03-26 17:37:51 -04:00
Alex Hart d46894e5db Upgrade CameraX to Beta01. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 951a61117a Add storage support for the AccountRecord. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 7a038ab09d Add interim storage support for GroupV2Record. 2020-03-26 17:37:51 -04:00
Alex Hart 707a2aca0a Swap profile megaphone icon and use user avatar if present. 2020-03-26 17:37:51 -04:00
Alan Evans 624837fcf1 Include zkgroup 0.4.1
All behind feature flag, excluding .so files for space.
2020-03-26 17:37:51 -04:00
Greyson Parrelli e3ea36c76f Remove unnecessary okhttp close when canceling.
Canceling should handle closing stuff now. And if we close from a
different thread than the calling thread, okhttp will crash.
2020-03-26 17:37:51 -04:00
Alan Evans 453996c374 Restrict CI branches. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 8add9ba0a6 Removed ExperienceUpgradeActivity.
Pour one out.
2020-03-26 17:37:51 -04:00
Alan Evans da11b56eab Check for and clear quote on new intent.
Fixes #9478
2020-03-26 17:37:51 -04:00
Greyson Parrelli 19377c2132 Remote maxInstance restriction on RetrieveProfileAvatarJob. 2020-03-26 17:37:51 -04:00
Greyson Parrelli b2bff39fe1 Don't send group info requests in response to group info requests. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 5f7075d39a Update and refactor storage service syncing.
Switched to proto3, updated protos, and generally refactored things to
make it easier to add new storage record types.
2020-03-26 17:37:51 -04:00
Alex Hart 40d9d663ec Disable auto-mirror for help icon 2020-03-26 17:37:51 -04:00
Greyson Parrelli 31f9b77c32 Ignore empty names when populating contact list. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 690a66a093 Show any user with a displayable name in the contact list. 2020-03-26 17:37:51 -04:00
Ehren Kret e7e7d36774 Configure Android Studio to pickup protobuf generated sources 2020-03-26 17:37:51 -04:00
Greyson Parrelli f95a37956c Improve emoji sticker suggestions.
There was a bug around some emoji being marked as 'obsolete' and
therefore not being found.

I also made a change so that you can use skin variations of emoji and
still find emoji tagged with the default yellow version of it.

Fixes #9471
2020-03-26 17:37:51 -04:00
Greyson Parrelli 1e2a27f902 Close dangling groups cursor. 2020-03-26 17:37:51 -04:00
Greyson Parrelli d90e3dc210 Fix crash when syncing empty usernames. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 5df4b56c0d Update okhttp to 3.12.10 2020-03-26 17:37:51 -04:00
Jim Gustafson 436da1cb32 Update ringrtc to v1.1.0 2020-03-26 17:37:51 -04:00
Greyson Parrelli 4d0dbbc6cd Add ability to listen to jobs based on a filter. 2020-03-26 17:37:51 -04:00
Alan Evans 033bf77cbb Allow future display of pending member count. 2020-03-26 17:37:51 -04:00
Greyson Parrelli 1068c3ca7e Fix UnknownSenderView in dark theme. 2020-03-26 17:37:51 -04:00
Greyson Parrelli df4422369d Update icons and colors. 2020-03-26 17:37:51 -04:00
Greyson Parrelli a62183c9e0 Reduce AttachmentCipherTest flakiness. 2020-03-19 14:50:06 -04:00
Greyson Parrelli de48cf8243 Bump version to 4.57.2 2020-03-19 14:49:52 -04:00
Greyson Parrelli acd4fc4518 Fix issue where minimum PIN length was miscalculated.
Fixes #9484
2020-03-19 14:49:10 -04:00
Greyson Parrelli da59ed019f Bump version to 4.57.1 2020-03-07 16:43:35 -05:00
Greyson Parrelli e73b174d1d Updated language translations. 2020-03-07 16:43:08 -05:00
Alan Evans 2753a22e3a Remove old activity from manifest. 2020-03-07 16:40:17 -05:00
Alex Hart 79fc33630b Add toast instead of crash if no email app installed. 2020-03-07 16:40:17 -05:00
Greyson Parrelli bf5331ba6e Bump version to 4.57.0 2020-03-05 18:51:45 -05:00
Greyson Parrelli 3be47d3e54 Updated language translations. 2020-03-05 18:51:21 -05:00
Alex Hart f9de131017 Add new contact us flow. 2020-03-05 18:42:17 -05:00
Alan Evans f1f505d41c Try getKeyStoreEntry twice on UnrecoverableKeyException.
To try to get around potentially temporary UnrecoverableKeyExceptions.
2020-03-05 18:42:17 -05:00
Alan Evans 51603be5ec Add video trimming time indication pill. 2020-03-05 18:42:17 -05:00
Alex Hart 2152b4a2cd Add warning dialog for insecure calls. 2020-03-05 18:42:17 -05:00
Alan Evans a70023a32b Use group manager to leave group. 2020-03-05 18:42:17 -05:00
Alan Evans 5038210d78 Add tap to pause to video trimming editor. 2020-03-05 18:42:17 -05:00
Alan Evans 28bbfd88b2 Group member dialog update. 2020-03-05 18:42:17 -05:00
Greyson Parrelli d05a71c8fe Update Glide to 4.11.0 2020-03-03 08:52:11 -05:00
Greyson Parrelli 245b0a7e50 Add a new buildType with Flipper. 2020-03-02 16:25:05 -05:00
Alan Evans ceb9e4aee2 Write capabilities to service. 2020-03-02 12:01:50 -04:00
Alan Evans d2e94dad7e Remove argon2 test job. 2020-03-02 11:22:37 -04:00
Alex Hart 240b2108f3 Use the image editor for avatars. 2020-03-02 11:21:57 -04:00
Greyson Parrelli f68d99d16d Clean up dangling transfer files.
Fixes #9033
2020-03-02 10:11:40 -05:00
Greyson Parrelli 44e845c875 Update emoji. 2020-03-02 10:11:40 -05:00
Alan Evans d8e2368a18 Convert UUID supported into a Capability enum. 2020-03-02 10:11:40 -05:00
Alan Evans 172a43679d Add GV2 recipient capability. 2020-03-02 10:11:40 -05:00
Greyson Parrelli 82305ce2b3 Bump version to 4.56.4 2020-03-02 09:55:04 -05:00
Greyson Parrelli eaf73edcad Updated language translations. 2020-03-02 09:54:35 -05:00
Greyson Parrelli 543a4ee177 Fix backup restore crash. 2020-03-02 09:48:18 -05:00
Greyson Parrelli fd2a464bae Use internal contact viewer for avatar clicks. 2020-03-02 08:33:25 -05:00
Alex Hart b06152ba58 Fix and simplify Y translation calculation for Conversation. 2020-03-02 08:57:11 -04:00
Greyson Parrelli c24d285cd3 Bump version to 4.56.3 2020-02-28 17:35:13 -05:00
Greyson Parrelli be39cd653e Updated language translations. 2020-02-28 17:35:13 -05:00
Greyson Parrelli 6813f47bc1 Remove feature flags that are no longer remote capable. 2020-02-28 17:35:10 -05:00
Greyson Parrelli 8e795c4177 Read the sticker length during backup import. 2020-02-28 16:58:47 -05:00
Greyson Parrelli 9c96afee09 Fix storage service crash with new users. 2020-02-28 16:58:47 -05:00
Greyson Parrelli d507be0ab0 Don't backup the KeyValueDatabase. 2020-02-28 16:58:47 -05:00
Greyson Parrelli 75a52f801a Implement storage service protocol changes. 2020-02-28 16:58:47 -05:00
Greyson Parrelli d3b123f3a9 Fix StorageSyncHelperTest. 2020-02-28 08:07:36 -05:00
Greyson Parrelli da3cdd984b Bump version to 4.56.2 2020-02-26 17:57:36 -05:00
Greyson Parrelli 6184e5f828 Update the storage service. 2020-02-26 17:11:34 -05:00
Curt Brune 133bd44b85 Update ringrtc to v1.0.2 2020-02-26 17:11:34 -05:00
Greyson Parrelli 0c254c9621 Improve debuglog submission. 2020-02-26 17:11:34 -05:00
Greyson Parrelli 1faf196f82 Implement additional message request improvements. 2020-02-26 17:11:29 -05:00
Greyson Parrelli 81c7887d47 Switch language string to 'System default.' 2020-02-26 17:08:27 -05:00
Greyson Parrelli e62e630987 Fix theming issue in CameraContactSelectionFragment. 2020-02-26 17:08:27 -05:00
Alex Hart 739e38a047 Fix reaction details bottom sheet scrolling. 2020-02-26 17:08:27 -05:00
Greyson Parrelli 8c23b17517 Fix some state issues post backup restore. 2020-02-26 17:08:27 -05:00
Greyson Parrelli fda8f3e1ce Refer to yourself as 'you' in reactions and group membership. 2020-02-26 17:08:27 -05:00
Alex Hart 9e5f64c431 Improve message requests, add megaphone. 2020-02-26 17:08:27 -05:00
Alex Hart dc689d325b Various PIN bug fixes. 2020-02-26 17:06:21 -05:00
Greyson Parrelli 0a883dc234 Fix issue with mimeType resolution in share flow. 2020-02-26 17:06:06 -05:00
Greyson Parrelli 3824e90997 Improve prekey refresh logic. 2020-02-26 17:06:06 -05:00
Greyson Parrelli 5158a15379 Disable PIN requirement for new registrations. 2020-02-26 17:06:06 -05:00
Curt Brune 1bae79af5b Check callManager reference is still valid in ListenableFutureTask callbacks. 2020-02-26 17:06:04 -05:00
Curt Brune 58b7612987 Drop requests to deny stale incoming calls.
This is not an illegal state, as the remote side could have hung-up
a microsecond before the local side tries to deny the call.
2020-02-26 17:06:02 -05:00
Curt Brune 9506da6dd3 Validate activePeer during Bluetooth and Speaker audio state transitions. 2020-02-26 17:05:54 -05:00
Greyson Parrelli 4ea886d05a Bump version to 4.56.1 2020-02-14 12:20:40 -05:00
Greyson Parrelli 7a52fccfd1 Updated language translations. 2020-02-14 12:20:40 -05:00
Greyson Parrelli f79d308a9f Update profile name megaphone. 2020-02-14 12:20:40 -05:00
Curt Brune 5aa64641d2 Convert IllegalStateException to warning log in receivedBusy()
This is not a fatal condition.  Convert to warning log message in
order to gather more information.
2020-02-14 08:13:11 -08:00
Curt Brune ae594a0400 Check activeRemote is non-null during handleScreenOffChange() 2020-02-14 08:13:11 -08:00
Curt Brune eeece55b45 Check activeRemote is non-null during handleWiredHeadsetChange() 2020-02-14 08:13:11 -08:00
Greyson Parrelli 9fbc50d26f Fix crash when cleared ShareViewModel. 2020-02-14 09:57:26 -05:00
Greyson Parrelli 16ebf0556a Bump version to 4.56.0 2020-02-13 20:53:10 -05:00
Greyson Parrelli bb104b5763 Updated language translations. 2020-02-13 20:53:10 -05:00
Greyson Parrelli 9bac88697b Support sharing multiple photos/videos into Signal. 2020-02-13 20:53:10 -05:00
Alan Evans 7ab240643e Remove build instructions. 2020-02-13 20:53:10 -05:00
Greyson Parrelli 70d5b798b2 Add some developer utils. 2020-02-13 20:53:10 -05:00
Greyson Parrelli 4e7a92637c Follow system theme on Android 10+. 2020-02-13 20:53:10 -05:00
Greyson Parrelli 2e19d0459b Fix album ordering issue.
Fixes #9381
2020-02-13 20:53:10 -05:00
Curt Brune 0970fd7040 Update ringrtc to v1.0.1
Add support for RingRTC Call Manager, a new component which provides
the control layer for all calling features.
2020-02-13 20:53:10 -05:00
Greyson Parrelli 81532cad95 Use a min length of 6 for new PIN reminders. 2020-02-13 20:53:10 -05:00
Greyson Parrelli dcb5f7b211 Update copy for PIN setting. 2020-02-13 20:53:10 -05:00
Alan Evans 40fd7ca332 Video trimming behind feature flag. 2020-02-13 20:53:10 -05:00
Alan Evans 7f867a6185 Remove KBS restore after set check. 2020-02-13 20:53:10 -05:00
Alan Evans 23e55ac5f7 Clear unidentified access mode when profile key changes. 2020-02-13 20:53:10 -05:00
Jonathan Leitschuh 455974cb05 Add official Gradle Wrapper validation action.
See: https://github.com/gradle/wrapper-validation-action
2020-02-13 20:53:10 -05:00
Greyson Parrelli 66a668f55b Add some Swoon blessed packs. 2020-02-13 20:53:10 -05:00
Alan Evans 7ecb50a3fe Versioned Profiles support (disabled). 2020-02-13 20:53:10 -05:00
Alan Evans f10d1eac61 Migrate profile key into database. 2020-02-13 20:53:10 -05:00
Alan Evans b92c389a5b Fix media rail thumbnail for in-app recorded video. 2020-02-13 20:53:10 -05:00
Alex Hart 9dfc57c462 Fix RTL icon issue in toolbar. 2020-02-13 20:53:10 -05:00
Alex Hart 3ea1492d67 Add profile names megaphone. 2020-02-13 20:53:07 -05:00
Greyson Parrelli c041614d1f Only store remote values for flags in a whitelist. 2020-02-11 12:03:34 -05:00
Greyson Parrelli d9c5907ea9 Bump version to 4.55.8 2020-02-11 12:03:12 -05:00
Greyson Parrelli 0b6a52277d Fixed PIN reminders showing too often, reminder UI improvements. 2020-02-11 12:03:12 -05:00
Greyson Parrelli 944adb5d7c Bump version to 4.55.7 2020-02-10 20:01:47 -05:00
Greyson Parrelli ac4129c1e1 Updated language translations. 2020-02-10 20:01:28 -05:00
Greyson Parrelli 0220a88ea5 Mark registration complete after PIN failure. 2020-02-10 19:56:41 -05:00
Greyson Parrelli 6d33763ec9 Bump version to 4.55.6 2020-02-08 10:58:43 -05:00
Greyson Parrelli 3cedadbb97 Fix video duration rendering issue. 2020-02-08 10:58:12 -05:00
Greyson Parrelli 5a6d339a89 Bump version to 4.55.5 2020-02-08 10:26:01 -05:00
Greyson Parrelli 905d8a4f33 Updated language translations. 2020-02-08 10:26:01 -05:00
Greyson Parrelli 917d312ea0 Reduced padding above megaphone button. 2020-02-08 10:26:01 -05:00
Greyson Parrelli ddc01b539f Use megaphones for PIN reminders. 2020-02-07 21:14:20 -05:00
Greyson Parrelli 38e4733433 Improve network reliability when setting PINs. 2020-02-07 01:06:41 -05:00
Greyson Parrelli cbd7160e23 Show profile creation before PIN creation. 2020-02-07 00:47:52 -05:00
Greyson Parrelli f2b3acb0c9 Update string for creating PIN. 2020-02-07 00:15:42 -05:00
Greyson Parrelli 49c7b5c442 Bump version to 4.55.4 2020-02-06 11:19:46 -05:00
Greyson Parrelli 7b7d180207 Updated language translations. 2020-02-06 11:19:46 -05:00
Alex Hart 21cf390d0e Clip reactions mask if the view falls below the input panel. 2020-02-06 11:19:46 -05:00
Greyson Parrelli 5e59f77f83 Update PIN feature flags. 2020-02-06 11:19:46 -05:00
Greyson Parrelli 62814490b3 Only store remote values for flags in a whitelist. 2020-02-06 11:19:46 -05:00
Alan Evans e5fedb8163 Add pull request trigger. 2020-02-06 07:52:32 -05:00
Greyson Parrelli cfc74c1080 Bump version to 4.55.3 2020-02-05 20:50:25 -05:00
Greyson Parrelli eaf53ad3b9 Updated language translations. 2020-02-05 20:49:59 -05:00
Greyson Parrelli a1f0c198a7 Auto-show keyboard for PIN entry. 2020-02-05 18:49:31 -05:00
Greyson Parrelli 6b1e48e485 Don't show the full-screen PIN megaphone. 2020-02-05 18:49:31 -05:00
Greyson Parrelli 83ea919434 Remove "(You)" from the reactions details.
This reverts commit ed2b049ad4.
2020-02-05 11:36:57 -05:00
Greyson Parrelli c94e93b916 Fix issue with reactions pill not appearing. 2020-02-05 11:32:20 -05:00
Alex Hart 49d418bb39 Fix RTL support for reaction sending. 2020-02-05 11:20:36 -05:00
Greyson Parrelli 23adda1817 Update reaction pill padding. 2020-02-05 10:40:08 -05:00
Alex Hart 7c729c2c4e Add AdaptiveActionsToolbar for better context bar controls. 2020-02-05 08:51:05 -04:00
Greyson Parrelli ecf7a416eb Bump version to 4.55.2 2020-02-04 20:10:36 -05:00
Greyson Parrelli 2f87f2bb62 Updated language translations. 2020-02-04 20:10:00 -05:00
Greyson Parrelli 39f4102e81 Fix issue where quoted replies showed a 'save' option. 2020-02-04 19:21:01 -05:00
Greyson Parrelli 4fcd6b15ed Improved feel of reaction popover. 2020-02-04 19:21:01 -05:00
Greyson Parrelli 8f9ed4bc40 Fix the shape of reaction pills. 2020-02-04 19:21:01 -05:00
Alex Hart 73dedd79d2 Fix crash in reactions overlay OnTouch. 2020-02-04 19:21:01 -05:00
Greyson Parrelli 2754b397d5 Darken the background more when selecting a message. 2020-02-04 19:21:01 -05:00
Greyson Parrelli 7d949ee8fd Prevent crash in megaphones after backup restore. 2020-02-04 19:21:01 -05:00
Greyson Parrelli 28e2f22550 Fix layout issues with reaction badges. 2020-02-04 19:21:01 -05:00
Alex Hart eaa1760511 Animate toolbar in reactions overlay. 2020-02-04 19:21:01 -05:00
Alex Hart 53dfd3f4c0 Fix wrong avatar displaying in reactions fragment. 2020-02-04 19:21:01 -05:00
Alex Hart ed2b049ad4 Add You to reactions you've sent in the bottom dialog fragment. 2020-02-04 10:35:45 -04:00
Alex Hart 092fb40333 Check if target is attached to window before trying to mask it. 2020-02-04 09:47:26 -04:00
Alan Evans 36a4225858 Always return passphrase without spaces. 2020-02-04 08:10:58 -05:00
Greyson Parrelli e551ea8bd9 Bump version to 4.55.1 2020-02-04 00:59:45 -05:00
Greyson Parrelli 5a28b1bf1c Updated language translations. 2020-02-04 00:59:15 -05:00
Greyson Parrelli 4cd1129c92 Show snooze snackbars for longer. 2020-02-04 00:55:38 -05:00
Greyson Parrelli a5d7bc4efc Force custom emoji for reactions. 2020-02-04 00:42:41 -05:00
Greyson Parrelli 1ff5b2af2a Capitalize 'PIN' in strings. 2020-02-04 00:01:07 -05:00
Greyson Parrelli 82446ce30a Hide attachment keyboard after selecting an action. 2020-02-04 00:00:06 -05:00
Alan Evans 6465248483 Fix megaphone snooze. 2020-02-03 22:44:43 -05:00
Greyson Parrelli 48e7f82466 Fix issue with view-once toggle breaking video controls. 2020-02-03 18:26:49 -05:00
Greyson Parrelli 6fef21ebc0 Update permission copy in attachment keyboard. 2020-02-03 15:32:20 -05:00
Greyson Parrelli 837e594607 Bump version to 4.55.0 2020-02-03 15:05:05 -05:00
Greyson Parrelli ab0cb55b80 Updated language translations. 2020-02-03 15:05:05 -05:00
Greyson Parrelli 2d24c8c525 Update conditions for PIN megaphone.
Handles additional corner cases.
- Shows megaphone when you register with a v1 pin.
- Show fullscreen when you fail to set a PIN during registration.
2020-02-03 15:04:53 -05:00
Alan Evans 40383f3733 Handle presenting KBS account locked cases. 2020-02-03 15:04:53 -05:00
Alex Hart e14861d79d CreatePinActivity naming update and copy fixes. 2020-02-03 15:04:53 -05:00
Alan Evans b29b3d0432 Require at least 4 digits during registration. 2020-02-03 15:04:53 -05:00
Greyson Parrelli c21d4861c0 Clear text entry after changing PIN types. 2020-02-03 15:04:53 -05:00
Greyson Parrelli a6786e5c2b Fix strings in KBS PIN flow. 2020-02-03 15:04:53 -05:00
Greyson Parrelli 77caa9e9d4 Fix crash in getIntentForPinCreate(), show 'Create' in prefs. 2020-02-03 15:04:53 -05:00
Greyson Parrelli 835ef02872 Add an 'All' tab to reaction details. 2020-02-03 15:04:53 -05:00
Alex Hart 279dcb1428 Apply KBS Lock fixes and pluralization 2020-02-03 15:04:53 -05:00
Alan Evans 4a8c312e0a Clear pin on confirm screen on submit. 2020-02-03 15:04:53 -05:00
Alan Evans c2bc376f87 Hide PIN from summary when feature flag set. 2020-02-03 15:04:37 -05:00
Greyson Parrelli 73160d4d26 Update reactions UI. 2020-02-03 14:20:08 -05:00
Alan Evans 1dd2a4e9c5 Allow backup passphrase verification. 2020-02-03 14:20:08 -05:00
Alan Evans ed0c4b8de5 Remove KBS feature flag. 2020-02-03 14:20:08 -05:00
Greyson Parrelli 4f921d761d Enable reaction sending. 2020-02-03 14:20:08 -05:00
Greyson Parrelli 37f85d6deb Delete unused megaphones from the database. 2020-02-03 14:20:08 -05:00
Alex Hart e1b75c78ab Add Pins for All Megaphone Kill Switch. 2020-02-03 14:20:08 -05:00
Alan Evans 5e83206e6e Fix group timer message. 2020-02-03 14:20:08 -05:00
Alan Evans 1ea6838db6 Bring KBS fragment source into RegistrationLockFragment and handle account locked. 2020-02-03 14:20:08 -05:00
Alex Hart fb82420376 Implement new PIN UX. 2020-02-01 12:42:29 -04:00
Greyson Parrelli 109d67956f Implement new attachment keyboard.
Such beauty. Such grace.
2020-02-01 12:38:53 -04:00
Greyson Parrelli 9f7b2e2cfd Track the first time a megaphone appeared. 2020-01-30 11:40:22 -05:00
Greyson Parrelli 22f9bfeceb Add support for creating Megaphones. Includes reactions megaphone. 2020-01-29 19:15:02 -05:00
Greyson Parrelli ef4c7e96da Bump version to 4.54.3 2020-01-29 18:31:07 -05:00
Greyson Parrelli 02865f99a9 Limit impact of crash on unexpected SMS receive. 2020-01-29 18:28:59 -05:00
Greyson Parrelli ef6019f13b Fix reddit link previews. 2020-01-29 18:26:16 -05:00
Greyson Parrelli 33d02bb7b8 Bump version to 4.54.2 2020-01-28 15:31:11 -05:00
Greyson Parrelli d34df2c1cf Updated language translations. 2020-01-28 15:30:11 -05:00
Alex Hart 7fdf540742 Implement new reaction notifications. 2020-01-28 15:48:24 -04:00
Alex Hart f916aabb98 Fix NPE when retrieving display name of unknown recipient. 2020-01-28 15:22:41 -04:00
Alex Hart 4ae7d56db4 Fix NPE when returning to profile from background.
Also generally improves saved-state management for Profile editor.
2020-01-28 14:57:17 -04:00
Alex Hart e3878ffde7 Change profile preference screen to use toolbar. 2020-01-28 13:41:06 -04:00
Alex Hart 5221b6fb43 Fix expiration timer display issue on devices with modified font sizes.
Fixes #9335
2020-01-27 14:54:04 -04:00
Alex Hart 5e0fe86858 Add SM-G920F and BLK-L09 to LegacyCameraModels. 2020-01-27 14:32:48 -04:00
Alex Hart c86ced0911 Add back arrow to profile editor. 2020-01-27 14:30:53 -04:00
Alex Hart 0aad82d3d7 Check content disposition flag in carrier config before parsing PDU.
Fixes #9081
2020-01-27 12:15:32 -04:00
Greyson Parrelli ce86adab82 Bump version to 4.54.1 2020-01-27 10:09:48 -05:00
Greyson Parrelli b543f727be Fix lint error. 2020-01-27 10:09:24 -05:00
Greyson Parrelli 8de7e0f198 Bump version to 4.54.0 2020-01-27 09:51:00 -05:00
Alan Evans 866dacf198 Updated language translations. 2020-01-27 09:51:00 -05:00
Alan Evans 3589fa381d Make better effort to delete leftover temporary backup files. 2020-01-27 09:51:00 -05:00
Greyson Parrelli 5d54ebfaa0 Fix crash when notification state is empty. 2020-01-27 09:51:00 -05:00
Alan Evans fea2b6253f KBS remote feature flag. 2020-01-27 09:51:00 -05:00
Alan Evans ba6e1ab15a Add type to KBS json. 2020-01-27 09:51:00 -05:00
Curt Brune 1d9fff3c98 Update ringrtc to 0.3.3 2020-01-27 09:51:00 -05:00
Greyson Parrelli 526adce603 Add support for sticky and hot-swappable feature flags. 2020-01-27 09:51:00 -05:00
Alan Evans e7f568e162 Trimming profile names to fit byte budget and remove leading/trailing spaces. 2020-01-27 09:51:00 -05:00
Alan Evans 7d15c602a6 Enable KBS. 2020-01-27 09:51:00 -05:00
Greyson Parrelli bdb30ebc48 Set a better User-Agent on requests. 2020-01-27 09:51:00 -05:00
Greyson Parrelli a31da7616d Rename 'userAgent' to 'signalAgent'.
This wasn't actually being used in the User-Agent header. Instead, it
was used as the value for an X-Signal-Agent header. To avoid confusion,
I'm renaming this.
2020-01-27 09:51:00 -05:00
Alex Hart f1147c10ee Disable reaction sending on update messages. 2020-01-27 09:51:00 -05:00
Alan Evans 544a5386ad Always show sticker icon in image editor.
Fixes flicker seen jumping toggling view once.
2020-01-27 09:51:00 -05:00
Greyson Parrelli 2d502213e4 Remove forced feature flag for reaction sending. 2020-01-27 09:51:00 -05:00
Greyson Parrelli 55e9f8722f Add support for remote feature flags. 2020-01-27 09:51:00 -05:00
Greyson Parrelli b8602ee004 Fix issues with Mexican phone number formatting.
Fixes #9317
2020-01-27 09:40:27 -05:00
Alan Evans e37c4b1f87 Replace pinstretcher with Argon2 and new PIN encryption. 2020-01-24 10:54:39 -05:00
Greyson Parrelli f7a3bb2ae8 Add the ability to re-order sticker packs. 2020-01-24 10:54:39 -05:00
Alan Evans 7d70ea78cd Hmac-SIV encryption/decryption. 2020-01-24 10:54:39 -05:00
Alex Hart 3907ec8b51 Add support for setting an optional last name in profiles. 2020-01-24 10:54:39 -05:00
Alan Evans f2b9bf0b8c Use SignalStore for KBS Values. 2020-01-24 10:54:38 -05:00
Greyson Parrelli fadcc606f8 Optimize uploads during media composition.
By uploading in advance (when on unmetered connections), media messages
can send almost instantly.
2020-01-24 10:54:38 -05:00
Alex Hart 92e97e61c1 Clear cached self id on successful registration. 2020-01-24 10:54:38 -05:00
Greyson Parrelli 4b5b9fbde8 Add an encrypted key-value store.
SignalStore is backed by SQLCipher and is intended to be used instead of
TextSecurePreferences moving forward.
2020-01-24 10:54:38 -05:00
Alan Evans 711d22a0ed Do not specify random provider. 2020-01-24 10:54:38 -05:00
Greyson Parrelli 06757153b3 Add support for adding jobs with existing dependencies. 2020-01-24 10:54:38 -05:00
Greyson Parrelli 38597aea00 Add support for canceling Jobs. 2020-01-24 10:54:38 -05:00
Alex Hart b10ce080a9 Consolidate Notification Ids to a centralized constants class. 2020-01-16 05:41:27 -05:00
Alan Evans 72e10ac597 Bump version to 4.53.7 2020-01-15 16:20:47 -05:00
Alan Evans 5b591364ba Updated language translations. 2020-01-15 16:18:45 -05:00
Alan Evans ace1855797 Test various Argon2 parameters. 2020-01-15 16:11:41 -05:00
Alan Evans ddedf73939 Bump version to 4.53.6 2020-01-13 14:15:48 -05:00
Alan Evans 538014935e Updated language translations. 2020-01-13 12:22:55 -05:00
Greyson Parrelli 5e9bbf1200 Reduce avatar outlines to 1px. 2020-01-13 12:13:22 -05:00
qrest 29d6d3c041 Add Xiaomi Mi A2 to echo cancellation blacklist. 2020-01-13 08:09:00 -05:00
Alan Evans 53ab303fd9 Bump version to 4.53.5 2020-01-10 16:54:40 -05:00
Alan Evans 24103ee856 Updated language translations. 2020-01-10 16:48:54 -05:00
Greyson Parrelli 20e368ab5e Update outgoing view-once message toast. 2020-01-10 16:28:30 -05:00
Greyson Parrelli 05763191ce Show the correct quoted media type for unviewed view-once messages. 2020-01-10 15:34:38 -05:00
Alan Evans f6685fb9c9 Fix content NPE. 2020-01-10 07:48:06 -05:00
Greyson Parrelli dbdf9602c2 Bump version to 4.53.4 2020-01-09 18:45:38 -05:00
Greyson Parrelli 6173f7049c Updated language translations. 2020-01-09 18:44:48 -05:00
Greyson Parrelli 4adacf4b98 Fix issue where you could send text with a view-once message. 2020-01-09 18:38:34 -05:00
Greyson Parrelli 8cb6ed26a1 Update view-once design. 2020-01-09 18:32:14 -05:00
Greyson Parrelli fd7aa9ccfa Fix view-once sync and quote descriptions. 2020-01-09 18:32:14 -05:00
Alex Hart e2a48d1714 Fix notification reply image. 2020-01-09 12:36:46 -04:00
Greyson Parrelli a5c4c1e0a6 Converted outlines from 1px to 1dp. 2020-01-08 15:27:55 -05:00
Alan Evans b29d03e872 Bump version to 4.53.3 2020-01-08 11:51:27 -05:00
Alan Evans dff11092ec Updated language translations. 2020-01-08 11:50:39 -05:00
Greyson Parrelli 5e9c4e8fa3 Remove tap-to-dismiss from view-once. 2020-01-07 21:44:41 -05:00
Greyson Parrelli c346f32762 Made view-once a non-sticky setting. 2020-01-07 16:18:07 -05:00
Greyson Parrelli d2d450aff2 Make view-once viewed bubble match the conversation background. 2020-01-07 16:14:11 -05:00
Greyson Parrelli 09af858be8 Show upload progress for view-once messages. 2020-01-07 15:50:12 -05:00
Alan Evans 9c8a99c79c Bump version to 4.53.2 2020-01-07 15:02:43 -05:00
Greyson Parrelli c6b9855198 Always show view-once video remaining time. 2020-01-07 15:02:43 -05:00
Alan Evans c142928fad Updated language translations. 2020-01-07 13:45:45 -05:00
Greyson Parrelli fc0cfd5188 Disable multiselect actions for inapplicable message types. 2020-01-07 13:09:25 -05:00
Greyson Parrelli d9c78e5c3e Fix toolbar overlap with cutouts in recipient preferences.
Fixes #9323
2020-01-07 12:17:41 -05:00
Greyson Parrelli b449fceca0 Dismiss conversation search after swipe-to-reply.
Fixes #9325
2020-01-07 11:13:16 -05:00
Greyson Parrelli f0d15c0bce Fix crash when clicking group avatars. 2020-01-07 10:51:40 -05:00
Greyson Parrelli 8f031f61ea Fix group update string when re-added to group. 2020-01-06 18:36:56 -05:00
Alan Evans 502e8559f0 Bump version to 4.53.1 2020-01-06 16:53:31 -05:00
Alan Evans 8cf6f7e936 Relocate jni libs. 2020-01-06 16:52:41 -05:00
Alan Evans 0ef01cc620 Bump version to 4.53.0 2020-01-06 11:08:30 -05:00
Alan Evans 930828ef86 Updated language translations. 2020-01-06 11:08:30 -05:00
Alan Evans 9ebe920195 Move all files to natural position. 2020-01-06 11:08:30 -05:00
Greyson Parrelli 0df36047e7 Ensure transport type gets reset in onNewIntent()
Fixes #9319
2020-01-06 11:08:27 -05:00
Greyson Parrelli 94604921f9 Fix issue with swipe-to-reply triggering incorrectly.
Fixes #9227
2020-01-06 11:08:27 -05:00
Curt Brune 284fe294ac Skip monochrome cameras when switching cameras on video calls. 2020-01-06 11:08:27 -05:00
Greyson Parrelli ffa01d491a Do not list yourself in group membership updates. 2020-01-06 11:08:27 -05:00
Kevin T. Berstene 1d9513e743 Respect local camera aspect ratio in calls.
Fixes #8615
2020-01-06 11:08:27 -05:00
Greyson Parrelli 277c9e22f1 Show long-press-magnify in sticker preview screen. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 4e7b4da941 Implement resumable downloads. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 7e72c9c33b Add view-once tooltip. 2020-01-06 11:08:27 -05:00
Greyson Parrelli e0e2c3a3f5 Fix some UI bugs in view-once sending. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 3b5e444e76 Enable view-once send support. 2020-01-06 11:08:27 -05:00
Alan Evans 02006e3ff5 Update copyright year. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 8083596f19 Use FitCenter sizing for stickers. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 6551689a0c Increase the size of stickers in the conversation. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 3fbf21a34e Don't crash on packs missing metadata. 2020-01-06 11:08:27 -05:00
Alan Evans b598431237 Separate message decryption from message processing. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 3b5d9a2cae Consider groups 'unknown' if they have no title, avatar, or members. 2020-01-06 11:08:27 -05:00
Alex Hart 3bd8aa8a86 Apply MessageStyle and fix chronology issues. 2020-01-06 11:08:27 -05:00
Greyson Parrelli fe5fca8eaf Sync thread order and archive status with linked devices. 2020-01-06 11:08:27 -05:00
Greyson Parrelli 848101a783 Refactor AvatarImageView#setAvatarClickHandler.
This triggered a call to Recipient#isGroup when rendering a view, which
itself could trigger a DB read. This delays the call to #isGroup to
lower the possibility of doing DB reads on the main thread.
2019-12-19 18:06:29 -05:00
Alex Hart d7c350f987 Fix in-call proximity lock for wireless, speaker, and bluetooth. 2019-12-19 18:06:29 -05:00
Curt Brune 4c526f0b3c Update ringrtc to 0.3.0 2019-12-19 18:06:29 -05:00
Greyson Parrelli 876ffb5b13 Bump version to 4.52.4 2019-12-19 18:00:56 -05:00
Greyson Parrelli 0b14cf3d6a Show sticker install tooltips less often. 2019-12-19 17:59:42 -05:00
Greyson Parrelli c2044b36b1 Bump version to 4.52.3 2019-12-18 20:58:07 -05:00
Greyson Parrelli f970b4acfa Revert "Remove the Pixel 4 from the CameraX blacklist."
This reverts commit 1f5a597d50.
2019-12-18 20:55:03 -05:00
Greyson Parrelli e69c0af613 Bump version to 4.52.2 2019-12-18 19:56:16 -05:00
Greyson Parrelli 42e2dd8b30 Updated language translations. 2019-12-18 18:04:42 -05:00
Greyson Parrelli 4fd6e7b033 Fix issue with stickers flickering on send. 2019-12-18 16:45:07 -05:00
Greyson Parrelli 3580816eac Bump version to 4.52.1 2019-12-18 00:50:02 -05:00
Greyson Parrelli f1c2e5f194 Updated language translations. 2019-12-18 00:49:36 -05:00
Greyson Parrelli b6d59f1d46 Fix sticker manager text wrapping issues. 2019-12-17 22:26:09 -05:00
Greyson Parrelli a3521681e7 Bump version to 4.52.0 2019-12-17 15:38:12 -05:00
Alan Evans ca10e1136c Updated language translations. 2019-12-17 15:37:11 -05:00
Greyson Parrelli 282fb2af0e Fix tinting issues on API 21. 2019-12-17 14:36:54 -05:00
Greyson Parrelli 1f5a597d50 Remove the Pixel 4 from the CameraX blacklist. 2019-12-17 14:36:54 -05:00
Greyson Parrelli a32666817c Remove old image editor stickers. 2019-12-17 14:36:54 -05:00
Greyson Parrelli b9bd3f2b4c Launch stickers. 2019-12-17 14:36:54 -05:00
Alex Hart 4173efbe5a Fix NPE on call initalization 2019-12-16 11:01:32 -04:00
yate 8121c8bd41 Remove blank MMS config overrides.
Some SIM cards include blank values for `uaProfUrl` and `userAgent`.

Passing in these blank values along with the overrides causes a 412 precondition error when downloading MMS from ATT&T.

If these values happen to be blank, then they should be removed completely from the overrides, allowing the request to use the default `uaProfUrl` and `userAgent` for the system.

Fixes #9173
2019-12-14 11:29:46 -05:00
Greyson Parrelli 6ca8218d55 Bump version to 4.51.6 2019-12-06 11:58:12 -05:00
Greyson Parrelli 653afbdd46 Updated language translations. 2019-12-06 11:57:33 -05:00
Alex Hart 4453d1752f Fix reactions scrubber positioning on vertically split multiscreen. 2019-12-06 11:43:51 -05:00
Alan Evans 5782c8a58b Make media overview view pager scrollable for long translations. 2019-12-06 11:43:51 -05:00
Greyson Parrelli 3e041befc8 Bump version to 4.51.5 2019-12-06 01:14:24 -05:00
Greyson Parrelli 6d3847c26f Updated language translations. 2019-12-06 01:10:47 -05:00
Alex Hart 4bdfc37bc1 Fix swipe to reply clobbering long press events. 2019-12-06 01:00:39 -05:00
Alan Evans 28afe0d829 Fix first media overview item selected text. 2019-12-06 01:00:39 -05:00
Greyson Parrelli 04dddd3378 Bump version to 4.51.4 2019-12-05 19:15:10 -05:00
Greyson Parrelli 23a14583fe Updated language translations. 2019-12-05 19:14:48 -05:00
Greyson Parrelli 19c83de510 Fix possible performance issues with reactions query. 2019-12-05 19:10:37 -05:00
Greyson Parrelli e94d4b64cf Bump version to 4.51.3 2019-12-05 12:21:12 -05:00
Greyson Parrelli 7380c99f3a Updated language translations. 2019-12-05 12:20:28 -05:00
Greyson Parrelli 2961a372c3 Avoid unneccessary recipient refreshes. 2019-12-05 12:20:28 -05:00
Greyson Parrelli 5e2a4fb058 Put safeguards around Recipient creation in the IdentityStore. 2019-12-05 12:20:28 -05:00
Greyson Parrelli a16242b9f8 Fix issues with RecipientDatabase#update(). 2019-12-05 12:20:28 -05:00
Alan Evans acfba9ac96 Show total file size of selected items when appropriate. 2019-12-05 12:20:28 -05:00
Alan Evans 006343460e Fix long press on image thumbnail in detail view.
Allow long press to select inside select mode.
2019-12-05 12:20:28 -05:00
Alex Hart 1cc8634cc7 Display reactions in ImageView instead of TextView on message bubbles. 2019-12-05 12:20:27 -05:00
Greyson Parrelli 9f8a110428 Bump version to 4.51.2 2019-12-05 02:59:42 -05:00
Greyson Parrelli 07b4279d0b Updated language translations. 2019-12-05 02:59:42 -05:00
Greyson Parrelli 6a33b231e3 Add a FrameRateTracker to log frame drops. 2019-12-05 02:59:42 -05:00
Greyson Parrelli b38d02061d Improve logging in RetrieveProfileJob. 2019-12-05 02:59:42 -05:00
Greyson Parrelli f832a36a5e Prevent possible UUID-only recipient creations. 2019-12-05 02:59:42 -05:00
Alan Evans 911ca7c29d Improve storage management detail view descriptions. 2019-12-05 02:59:42 -05:00
Greyson Parrelli 544b75a2a7 Bump version to 4.51.2 2019-12-04 15:30:30 -05:00
Greyson Parrelli 56e8d4fb06 Updated language translations. 2019-12-04 15:28:45 -05:00
Greyson Parrelli 36a2278aef Add sanity checks for phone number during link process. 2019-12-04 15:28:45 -05:00
Alex Hart 0c785b85b8 Fix video icon issue. 2019-12-04 15:28:45 -05:00
Alan Evans a0ecba147e Use decimal digit groups in file size pretty printing. 2019-12-04 15:28:45 -05:00
Alan Evans 977591ac82 Remove assertion error in recipient preferences. 2019-12-04 15:28:28 -05:00
Greyson Parrelli fe1838d3fe Fix issue where unlocking would dismiss MainActivity. 2019-12-04 15:25:26 -05:00
Greyson Parrelli ede06cf97d Remove unnecessary directory refreshes. 2019-12-04 15:25:26 -05:00
Greyson Parrelli 93deee6824 Attempt to fix crash in ClassicOpenHelper.
These users must have > 1.5-year-old installs that haven't been updated,
but it's still a top crash for whatever reason, so gotta try to fix it.
2019-12-04 15:25:26 -05:00
Greyson Parrelli db19077834 Fix crash when receiving a PreKey message in a new session.
We don't allow creating recipients with only a UUID at the moment
(for good reason), but the way the decrypt method was written, it
was possible to do so. Until we have CDS, we should prefer the phone
number in scenarios like these.
2019-12-04 15:25:26 -05:00
Alex Hart 6f91f62db2 Fix InviteActivity sendSmsMessage type error. 2019-12-04 15:25:26 -05:00
Greyson Parrelli 1fc63b7597 Fix crash in conversation search. 2019-12-04 15:25:26 -05:00
Alex Hart a079e479ec Skip call dialog if signal call is already active. 2019-12-04 15:25:26 -05:00
Alex Hart e90705b459 Add correct archive icon to light theme. 2019-12-04 15:25:26 -05:00
Alex Hart 244db437cb Fix reaction display issues. 2019-12-04 15:25:25 -05:00
Alan Evans 6558eae032 Fix audio color on light theme. 2019-12-04 09:10:48 -05:00
Greyson Parrelli 88b54a262b Bump version to 4.51.0 2019-12-04 00:17:03 -05:00
Alan Evans 4fa8b8a4bd Updated language translations. 2019-12-04 00:08:01 -05:00
Greyson Parrelli cc0ced9a81 Add internal pre-alpha support for storage service. 2019-12-04 00:08:01 -05:00
Alan Evans 52447f5e97 Add storage management features. 2019-12-04 00:07:49 -05:00
Alex Hart bceb69b284 Add internal pre-alpha support for receiving reactions. 2019-12-04 00:07:49 -05:00
Alex Hart a8d826020d Implement new video call experience. 2019-12-04 00:07:49 -05:00
Alan Evans 2ada7f87f2 Update lib phone number. 2019-12-04 00:07:49 -05:00
Alan Evans 7f8ca58762 Add internal pre-alpha support for Registration Lock v2. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 058c25808b Clean up website auto-update APKs.
I think previously we thought that because we always used the same
filename, that we were just overwring the same file repeatedly. However,
DownloadManager will actually append numbers to a filename instead of
overwriting, so we were generating a ton of files.

Now we just clear out any previous files before downloading a new one.

Related to #9033
2019-12-04 00:07:49 -05:00
Greyson Parrelli c1e6b6b086 Add better Loader performance logs. 2019-12-04 00:07:49 -05:00
Greyson Parrelli b0a6bb79f6 Fix issue with setting Note to Self color.
Fixes #9235
2019-12-04 00:07:49 -05:00
Greyson Parrelli de52bf50a2 Improve image and sticker notifications. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 207dd23c86 Fix performance issue with large number of notifications.
Constructing the notification would call KeyCachingService#isLocked()
many times in a loop. This call is slow, because when the device isn't
locked, it would construct the master secret each time, which can take
50+ ms. Do that twice in a loop a hundred times, and it adds up.

This simplified #isLocked to avoid the unnecessary master secret
generation.
2019-12-04 00:07:49 -05:00
Alex Hart 7e0de29dd7 Add notification policy permission and runtime check.
Fixes #9217
2019-12-04 00:07:49 -05:00
Greyson Parrelli eaa8f1ee8f Ignore expiration updates from groups you've left. 2019-12-04 00:07:49 -05:00
Alex Hart fb494c1151 Implement new invite screen. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 49ecd9ef5d Added additional logging in RetrieveProfileJob. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 8772214fd4 Increase number of log files to 7. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 5b74d0cbeb Improve activity lifecycle logging. 2019-12-04 00:07:49 -05:00
Greyson Parrelli 1e7c93007d Convert the conversation list into a real fragment.
It was a fragment before, but it's functionality was inappropriately
split between the various layers.

This also sets us up better to do tablet stuff in the future, if we
choose to do that.
2019-12-04 00:07:49 -05:00
Greyson Parrelli 608815a69b Add internal pre-alpha support for usernames. 2019-12-04 00:07:42 -05:00
Greyson Parrelli fb49efa34d Add internal pre-alpha libsignal support for reactions. 2019-12-04 00:07:13 -05:00
Android Team b20e8616ec Move libsignal-service-java into this repo.
libsignal-service-java repo commit: https://github.com/signalapp/libsignal-service-java/commit/1a01c22636ad2c4e24e911ed7c4eb95f14cc58d6
2019-12-04 00:07:13 -05:00
Alan Evans acf78b6b63 Make new top level gradle file, make app dir and move build.gradle. 2019-12-04 00:07:13 -05:00
Alex Hart d68fe928b8 Fix nav bar contrast on sticker preview. 2019-12-04 00:07:13 -05:00
Alex Hart db4d561d9e Fix nav bar color on shared contacts edit screen. 2019-12-04 00:07:07 -05:00
Alex Hart 6933ca50a7 Implement new Message Request UI. 2019-12-04 00:07:07 -05:00
Greyson Parrelli 88c0e6f8ab Fail a job when you can't instantiate it.
This will still crash, but prevent apps from getting into crash loops
when we have bad data in a job.
2019-11-25 11:32:16 -05:00
Greyson Parrelli 6cd4728e3c Update job serialized data after retry. 2019-11-25 11:32:16 -05:00
Greyson Parrelli 97cc82837c Don't backup the JobsDatabase. 2019-11-25 11:32:16 -05:00
Greyson Parrelli 5f0b0e8495 Bump version to 4.50.6 2019-11-25 11:32:07 -05:00
Greyson Parrelli c837d590ab Removed RefreshUnidentifiedDeliveryAvailabilityJob.
It's been long enough -- it's no longer necessary to check.

Also, the service is going to start returning certs no matter what, so
at this point it's just an unnecessary network call.
2019-11-25 11:32:06 -05:00
Greyson Parrelli 6108a32631 Prevent reading UUIDs from external contacts. 2019-11-25 11:29:58 -05:00
Greyson Parrelli 6d78e1760d Fix possible crash in very old database migration.
The NumberMigrator constructor is crashing. One possibility is that this
migration is running before the user registered, in which case they
wouldn't have a number. Wrapped the parts that don't need to run in this
situation in an `if` block.
2019-11-25 11:29:58 -05:00
Greyson Parrelli c7b7242eff Fix issues with blocking and MMS groups.
Fixes #9218.

Note that this removes MMS group blocking for now, just because it never
really worked, and I don't want to hotfix in a feature.
2019-11-25 11:29:19 -05:00
Greyson Parrelli 77c687efcf Bump version to 4.50.5 2019-11-21 13:28:23 -05:00
Greyson Parrelli 274af2f010 Ensure group message is being sent to a group. 2019-11-21 13:28:00 -05:00
Greyson Parrelli 6cd5100530 Ensure Recipient.self() is available. 2019-11-21 13:24:54 -05:00
Alan Evans 115a408b0b Bump version to 4.50.4 2019-11-18 09:36:14 -05:00
Alan Evans d983016137 Updated language translations. 2019-11-18 09:33:53 -05:00
Greyson Parrelli a8309b6b2b Fix attachment sizes during backup exports. 2019-11-18 09:19:11 -05:00
Alan Evans e241589c92 Bump version to 4.50.3 2019-11-15 15:43:47 -05:00
Alan Evans dcb6240a6a Updated language translations. 2019-11-15 15:39:39 -05:00
Alex Hart 599bb5ab0f Update insights copy and queries. 2019-11-15 16:33:54 -04:00
Alan Evans 739fe584c6 Bump version to 4.50.2 2019-11-14 14:49:48 -05:00
Alan Evans 920d1c6207 Updated language translations. 2019-11-14 14:49:48 -05:00
Greyson Parrelli c1d3d26a15 Removed unnecessary subtext in Insights. 2019-11-14 14:49:48 -05:00
theBoatman e3b66dc7e8 Fixing RealtimeSleepTimer to work in concurrent threads. 2019-11-14 13:56:12 -05:00
Alan Evans dfa08d1356 In dark mode, use a dark theme base for RegistrationLockDialog and RationaleDialog.
Fixes #8995
Closes #9077
2019-11-14 13:51:01 -05:00
Curt Brune 6da734c2f9 Update ringrtc to 0.2.0 2019-11-13 14:05:22 -08:00
Alan Evans a9f3141a77 Create android.yml running qa task. 2019-11-13 15:49:22 -05:00
Alan Evans ee267d4702 Bump version to 4.50.1 2019-11-13 14:40:04 -05:00
Alan Evans c39174ed8a Updated language translations. 2019-11-13 14:40:04 -05:00
Greyson Parrelli 9c872b6da6 Fix issue where SMS groups showing up as 'Unnamed Group'. 2019-11-13 11:37:49 -05:00
Greyson Parrelli 2924a09936 Don't run UuidMigration if not registered. 2019-11-13 11:37:49 -05:00
Greyson Parrelli b9da012cc4 Fix media send issues on Android 4.4.
Before lollipop, Android would ignore activity results for activities
launched with singleTask. So I switched to singleTop, since that should
still solve the problem of double-activity-opens without running into
this issue.

Fixes #9149
2019-11-13 11:37:49 -05:00
Alan Evans 8626d41787 I18N for "Unlock Signal". 2019-11-13 11:37:49 -05:00
Alex Hart c58705722a Fix Insights Adapter item text clashing. 2019-11-13 11:37:49 -05:00
Greyson Parrelli c702ff676a Support additional sync behavior for linked devices. 2019-11-13 11:37:49 -05:00
Alan Evans 995569dd50 Bump version to 4.50.0 2019-11-12 10:55:41 -05:00
Greyson Parrelli a0dfd42527 Fix possible context crash. 2019-11-12 10:55:41 -05:00
Alex Hart a7dd78cce6 Implement in-app insights. 2019-11-12 10:55:41 -05:00
Greyson Parrelli e2e9cd40b3 Blacklist additional phone models from the new camera. 2019-11-11 18:13:34 -05:00
Alan Evans e4fc6f41b8 Schedule first backup after restore for 24hrs time. 2019-11-11 13:00:01 -05:00
Alan Evans 778b2a0d27 Remove old strings file. 2019-11-10 09:23:44 -05:00
Alan Evans 64cd15ca0b Make AlbumThumbnailView_plus non-translatable. 2019-11-10 09:23:43 -05:00
Alan Evans 1e46c3c0ba Make app_name non-translatable. 2019-11-10 09:23:43 -05:00
Alan Evans 91772b4e11 Exclude non-translatable strings.
Lint exclude ExtraTranslation.
2019-11-10 09:23:42 -05:00
Alan Evans af5c7cb7ca Added dotted lines to time icons. 2019-11-09 07:45:53 -05:00
Alan Evans e760474fa9 Clear unauthorized flag on connect. 2019-11-09 07:01:08 -05:00
Greyson Parrelli 28bb88e347 Lower max number of JobScheduler IDs.
Some devices can only handle 25 job IDs, so I lowered it to 20.
2019-11-09 07:01:08 -05:00
Curt Brune 17a1fe97ca Send hangup message when terminating on call errors.
When errors occur during a call, attempt to send the remote peer a
hangup message before terminating the call.  This allows the remote
peer to shutdown their side of the call in a timely manner.
2019-11-09 07:01:08 -05:00
Alan Evans 7348237862 Lint.
- baseline update.
- eradicate ButtonOrder.
- eradicate VectorRaster.
- eradicate HardcodedText.
2019-11-09 07:01:08 -05:00
Alex Hart 4e66db5dc1 Fix timer disabled icon. 2019-11-09 07:01:08 -05:00
Alex Hart ffa6c9acd1 Utilize icon_tint color filter for UpdateItem icons. 2019-11-09 07:01:08 -05:00
Greyson Parrelli b3e66a9259 Prevent creation of UUID-only recipients. 2019-11-09 07:01:08 -05:00
Alan Evans 5c0cf8c1f0 English staging app name. 2019-11-09 07:01:08 -05:00
Alex Hart 89c716fca4 Fix layout gravity on recv long message footer. 2019-11-09 07:01:08 -05:00
Alan Evans aae9830dfa Use lib-signal-servivce 2.15.1 2019-11-09 07:01:08 -05:00
Alan Evans 48c72697c1 Add protobuf lite exception for proguard. 2019-11-09 07:01:08 -05:00
Greyson Parrelli aac7e9ea53 Fix issue with ShareActivity contact display. 2019-11-09 07:01:08 -05:00
Alan Evans a1b10b3222 Update to protobuf 3.10 lite.
Update fingerprint logic.
Update for additional validation around SignalServiceAddresses.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
2019-11-09 07:01:08 -05:00
Curt Brune 2ab8db33e3 Update ringrtc to 0.1.9 2019-11-09 07:01:08 -05:00
Curt Brune 2b1386232f Implement logging interface for ringrtc-0.1.8
Implement the org.signal.ringrtc.Log.Logger interface, using
org.thoughtcrime.securesms.logging.Log as the underlying logger.

This allows ringrtc to log in the same way as the rest of the
application.
2019-11-09 07:01:07 -05:00
Alex Hart 7bb1caa22e Remove unused pictures that were captured outside of the in-app camera. 2019-11-09 07:01:07 -05:00
Alex Hart 72c14b8651 Apply new Keyboard and Dial Pad icons. 2019-11-09 07:01:07 -05:00
Alex Hart f85c3bb1e6 Add feature flag to new Profile display. 2019-11-09 07:01:07 -05:00
Alan Evans d8714fe3b4 Delegate to library Base64. 2019-11-09 07:01:07 -05:00
Alex Hart c54c1907b6 Fix nav bar colors on reg and call screen. 2019-11-09 07:01:07 -05:00
Greyson Parrelli 55257155dd Handle ProtocolInvalidMessageExceptions with no sender. 2019-11-09 07:01:07 -05:00
alex-signal 55a8bd86de Consolidate profile display labels. 2019-11-09 07:01:07 -05:00
Greyson Parrelli c60909272b UUID migration. 2019-11-09 07:01:07 -05:00
Greyson Parrelli 3dcc2d8171 Moved DirectoryHelper. 2019-11-09 07:01:07 -05:00
Greyson Parrelli 0ccbb22e4c Unsubscribe from old recipient observers in ConversationActivity. 2019-11-09 07:01:07 -05:00
Greyson Parrelli 0ffa10eaea Bump version to 4.49.18 2019-11-08 20:27:14 -05:00
Alan Evans 9824073023 Keep line numbers within R8. 2019-11-08 16:17:32 -05:00
Alan Evans d2adc11a2d New languages.
bn,bs,ms,mr,ta,ur
2019-11-08 16:17:32 -05:00
Alan Evans 4d0b870c88 Updated language translations. 2019-11-08 16:17:32 -05:00
Alan Evans 840997cb7d Translate tasks. 2019-11-08 11:41:38 -05:00
Alex Hart e4406621ed Fix lint issues. 2019-11-08 11:41:38 -05:00
Greyson Parrelli 7f96ee0b62 Clear DATA_HASH column. 2019-11-08 11:30:48 -05:00
Alan Evans 17f850b31a Ensure that we can still navigate when we get a response from server. 2019-11-08 11:19:57 -05:00
Alan Evans 5d87ad0301 Ignore period in phone number entry.
Fixes #9168
2019-11-08 11:19:32 -05:00
Greyson Parrelli a33eeed6d3 Bump version to 4.49.17 2019-11-07 16:18:27 -05:00
Alex Hart 554ce29337 Adjust Typing Indicator margin in groups. 2019-11-07 16:03:13 -05:00
Alex Hart 6054d0b36f Nullify Data Hash when Data is Nullified 2019-11-07 16:02:56 -05:00
Greyson Parrelli 8ba15cf3b4 Bump version to 4.49.16 2019-11-06 16:42:16 -05:00
Greyson Parrelli b15e5b4867 Fix potential invalid context crash. 2019-11-06 16:38:28 -05:00
Greyson Parrelli 544511905a Fix issue where deduped media might not be kept in sync. 2019-11-06 16:37:25 -05:00
Greyson Parrelli 02e3f511bd Bump version to 4.49.15 2019-11-04 11:30:08 -05:00
Alex Hart 0539b071e7 Fix message info icon inflation error on API 19 devices.
Fixes #9148
2019-11-04 11:29:31 -05:00
Greyson Parrelli 36e400650c Fix backup restore issue.
Fixes #9133
2019-11-04 11:21:11 -05:00
Greyson Parrelli d8930daf4b Bump version to 4.49.14 2019-11-01 17:23:52 -04:00
Greyson Parrelli 17b5816d5b Skip corrupt attachments during backup restore.
Instead of aborting the entire backup restore.

Related to #8355
2019-11-01 17:11:10 -04:00
Curt Brune e574c81ea2 Update ringrtc to 0.1.7.2 2019-11-01 12:26:00 -07:00
Greyson Parrelli 6799a2f841 Bump version to 4.49.13 2019-10-29 16:10:09 -04:00
Curt Brune 61fb20f412 Update ringrtc to v0.1.7 2019-10-29 12:46:01 -07:00
Alan Evans 47f648e7bc Correct text on registration lock fragment. 2019-10-29 13:47:14 -04:00
Alan Evans 064c0ddb82 Manually restrict to 30 digits to allow pasting containing any number of spaces. 2019-10-29 09:59:53 -04:00
Greyson Parrelli b42c42007d Bump version to 4.49.12 2019-10-28 19:48:07 -04:00
Greyson Parrelli 807cdfce2e Increase backup passphrase input length to 35.
The way pasting works, it could prevent you from pasting in text if you
included the spaces in between number chunks.
2019-10-28 19:47:05 -04:00
Greyson Parrelli 43dc3aeebd Fix 'direct share' icon rendering issue.
Fixes #9138
2019-10-28 16:51:06 -04:00
Greyson Parrelli 0369c5ee16 Make avatars larger in conversations. 2019-10-28 16:46:21 -04:00
Greyson Parrelli 3df2390fe4 Bump version to 4.49.11 2019-10-28 12:28:58 -04:00
Greyson Parrelli 9ec4a7af0f Update max video duration for MMS. 2019-10-28 12:09:54 -04:00
Alex Hart 70636fb4a7 Blacklist Pixel4 from CameraX (#319)
* Blacklist Pixel4 from CameraX

* Create isSupported method
2019-10-28 13:07:28 -03:00
Alex Hart 3c4efdd8f9 Apply proper color when action mode destroyed 2019-10-28 12:00:59 -04:00
Greyson Parrelli 80deb301e5 Bump version to 4.49.10 2019-10-25 14:00:33 -07:00
Greyson Parrelli 5eabfdc34c Fix crash if you leave during log generation. 2019-10-25 14:00:33 -07:00
Greyson Parrelli 5e96832666 Disable video capture for legacy camera devices. 2019-10-25 13:54:38 -07:00
Alan Evans 1ccce24cf8 Fix drawable crash on API 19. 2019-10-25 15:33:34 -04:00
Greyson Parrelli d59e4f2da7 Bump version to 4.49.9 2019-10-24 14:20:09 -07:00
Greyson Parrelli a254c1a7b2 Updated language translations. 2019-10-24 14:17:19 -07:00
Alex Hart 82cc610938 Apply themed context to SingleRecipientNotificationBuilder 2019-10-24 17:59:34 -03:00
Greyson Parrelli becf7c40e8 Bump version to 4.49.8 2019-10-23 23:45:13 -07:00
Greyson Parrelli ed21c3ca03 Improve send button in media send flow.
Fixes #8988
2019-10-23 23:45:13 -07:00
Greyson Parrelli 631005e565 CameraX cleanup. 2019-10-23 23:45:13 -07:00
Greyson Parrelli b57fab8c75 Make MediaSendActivity singleTask. 2019-10-23 23:45:13 -07:00
Greyson Parrelli 4260a8436b Fix possible de-duping issues.
- Clean bad hashes from earlier release.
- Fix file equality comparison.
- Given our new de-duping, we don't want to run into a situation where two
simultaneous compressions could be happening on the same image.
2019-10-23 23:45:13 -07:00
Greyson Parrelli 23d478191f Don't render 'null' contact labels. 2019-10-23 23:45:13 -07:00
Greyson Parrelli d075a33d4e Fix issue where video controls may be missing.
Fixes #9121
2019-10-23 23:45:13 -07:00
4514 changed files with 264981 additions and 139348 deletions
+30
View File
@@ -0,0 +1,30 @@
name: Android CI
on:
pull_request:
push:
branches:
- 'master'
- '4.**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Install NDK
run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;20.0.5594570" --sdk_root=${ANDROID_SDK_ROOT}
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
run: ./gradlew qa
-10
View File
@@ -1,10 +0,0 @@
[main]
host = https://www.transifex.com
lang_map = da_DK:da-rDK,he:iw,id:in,kn_IN:kn-rIN,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
[signal-android.master]
file_filter = res/values-<lang>/strings.xml
source_file = res/values/strings.xml
source_lang = en
type = ANDROID
-736
View File
@@ -1,736 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2"/>
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<!-- For sending/receiving events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- So we can add a TextSecure 'Account' -->
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<!-- For conversation 'shortcuts' on the desktop -->
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- For fixing MMS -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:replace="android:allowBackup"
android:allowBackup="false"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
android:launchMode="singleTask"/>
<activity android:name=".InviteActivity"
android:theme="@style/TextSecure.HighlightTheme"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".ConversationListActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
</activity>
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tsdevice"/>
</intent-filter>
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ShareActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/plain" />
<data android:mimeType="video/*" />
<data android:mimeType="application/*"/>
<data android:mimeType="text/*"/>
<data android:mimeType="*/*"/>
</intent-filter>
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value=".service.DirectShareService" />
</activity>
<activity android:name=".stickers.StickerPackPreviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sgnl"
android:host="addstickers" />
</intent-filter>
</activity>
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true" />
<activity-alias android:name=".RoutingActivity"
android:targetActivity=".ConversationListActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
</intent-filter>
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
android:resource="@mipmap/ic_launcher" />
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
android:resource="@mipmap/ic_launcher" />
</activity-alias>
<activity android:name=".ConversationListArchiveActivity"
android:label="@string/AndroidManifest_archived_conversations"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".ConversationListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
</activity>
<activity android:name=".conversation.ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".ConversationListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
</activity>
<activity android:name=".longmessage.LongMessageActivity" />
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
android:launchMode="singleTask"
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@style/TextSecure.LightTheme.Popup"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".MessageDetailsActivity"
android:label="@string/AndroidManifest__message_details"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".GroupCreateActivity"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseMigrationActivity"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ExperienceUpgradeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".NewConversationActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".giph.ui.GiphyActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.MediaSendActivity"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".VerifyIdentityActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ApplicationPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
</intent-filter>
</activity>
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".revealable.ViewOnceMessageActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".LogSubmitActivity"
android:label="@string/AndroidManifest__log_submit"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaOverviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
android:enabled="true"
android:allowTaskReparenting="true"
android:noHistory="true"
android:excludeFromRecents="true"
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
<activity android:name=".PlayServicesProblemActivity"
android:theme="@style/TextSecure.DialogActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".SmsSendtoActivity">
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact" />
</intent-filter>
</activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:excludeFromRecents="true"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
</intent-filter>
</activity>
<activity android:name=".RecipientPreferenceActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".BlockedContactsActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.StickerSelectActivity"
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.NewStickerSelectActivity"
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/TextSecure.DarkTheme"/>
<activity android:name=".CreateProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ClearProfileAvatarActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:icon="@drawable/clear_profile_avatar"
android:label="@string/AndroidManifest_remove_photo">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name=".contactshare.ContactShareEditActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.ContactNameEditActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.SharedContactDetailsActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ShortcutLauncherActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/TextSecure.LightNoActionBar" />
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.IncomingMessageObserver$ForegroundService"/>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
</service>
<service android:name=".service.ContactsSyncAdapterService" android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service android:name=".service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
<intent-filter>
<action android:name="android.service.chooser.ChooserTargetService" />
</intent-filter>
</service>
<service android:name=".service.GenericForegroundService"/>
<service android:name=".gcm.FcmService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver android:name=".service.SmsListener"
android:permission="android.permission.BROADCAST_SMS"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
</intent-filter>
</receiver>
<receiver android:name=".service.SmsDeliveryListener"
android:exported="true">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
</intent-filter>
</receiver>
<receiver android:name=".service.MmsListener"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.CLEAR"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.RemoteReplyReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.AndroidAutoHeardReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.AndroidAutoReplyReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"/>
</intent-filter>
</receiver>
<receiver android:name=".service.ExpirationListener" />
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="org.thoughtcrime.provider.securesms" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="org.thoughtcrime.provider.securesms.mms" />
<provider android:name="androidx.core.content.FileProvider"
android:authorities="org.thoughtcrime.securesms.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" />
</provider>
<provider android:name=".database.DatabaseContentProviders$Conversation"
android:authorities="org.thoughtcrime.securesms.database.conversation"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$ConversationList"
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Attachment"
android:authorities="org.thoughtcrime.securesms.database.attachment"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Sticker"
android:authorities="org.thoughtcrime.securesms.database.sticker"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$StickerPack"
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
android:exported="false" />
<provider android:name="androidx.camera.camera2.impl.Camera2Initializer"
android:authorities="${applicationId}.camerax-init"
android:exported="false"
android:enabled="false" />
<receiver android:name=".service.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="org.thoughtcrime.securesms.RESTART"/>
</intent-filter>
</receiver>
<receiver android:name=".service.DirectoryRefreshListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSignedPreKeyListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSenderCertificateListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.LocalBackupListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.PersistentConnectionBootListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.LocaleChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.DeleteNotificationReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
</intent-filter>
</receiver>
<receiver android:name=".ExperienceUpgradeActivity$AppUpgradeReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
<data android:scheme="package" />
</intent-filter>
<intent-filter>
<action android:name="org.thoughtcrime.securesms.ExperienceUpgradeActivity.DISMISS_ACTION"/>
</intent-filter>
</receiver>
<receiver
android:name=".service.PanicResponderListener"
android:exported="true">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
</intent-filter>
</receiver>
<service
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.KeepAliveService"
android:enabled="@bool/enable_alarm_manager" />
<receiver
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
android:enabled="@bool/enable_alarm_manager" />
<!-- Probably don't need this one -->
<receiver
android:name=".jobmanager.BootReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
</application>
</manifest>
-74
View File
@@ -1,74 +0,0 @@
Building Signal
===============
Basics
------
Signal uses [Gradle](http://gradle.org) to build the project and to maintain
dependencies. However, you needn't install it yourself; the
"gradle wrapper" `gradlew`, mentioned below, will do that for you.
Building Signal
---------------
The following steps should help you (re)build Signal from the command line.
1. Checkout the Signal-Android project source with the command:
git clone https://github.com/signalapp/Signal-Android.git
2. Make sure you have the [Android SDK](https://developer.android.com/sdk/index.html) installed.
3. Ensure that the following packages are installed from the Android SDK manager:
* Android SDK Build Tools (see buildToolsVersion in build.gradle)
* SDK Platform (All API levels)
* Android Support Repository
* Google Repository
4. Create a local.properties file at the root of your source checkout and add an sdk.dir entry to it. For example:
sdk.dir=/Application/android-sdk-macosx
5. Using Java 8
6. Execute Gradle:
./gradlew build
Visual assets
----------------------
Source assets tend to be large binary blobs, which are best stored outside of git repositories. Some source files are SVGs that can be auto-colored and sized using a tool like [android-res-utils](https://github.com/sebkur/android-res-utils).
Sample command for generating our audio placeholder image:
```bash
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #000 --opacity 0.54 --suffix _light
pngs_from_svg.py ic_audio.svg /path/to/Signal/res/ 150 --color #fff --opacity 1.00 --suffix _light
```
Setting up a development environment
------------------------------------
[Android Studio](https://developer.android.com/sdk/installing/studio.html) is the recommended development environment.
1. Install Android Studio.
2. Open Android Studio. On a new installation, the Quickstart panel will appear. If you have open projects, close them using "File > Close Project" to see the Quickstart panel.
3. From the Quickstart panel, choose "Configure" then "SDK Manager".
4. In the SDK Tools tab of the SDK Manager, make sure that the "Android Support Repository" is installed, and that the latest "Android SDK build-tools" are installed. Click "OK" to return to the Quickstart panel.
5. From the Quickstart panel, choose "Checkout from Version Control" then "git".
6. Paste the URL for the Signal-Android project when prompted (https://github.com/signalapp/Signal-Android.git).
7. Android studio should detect the presence of a project file and ask you whether to open it. Click "yes".
9. Default config options should be good enough.
9. Project initialisation and build should proceed.
Contributing code
-----------------
Code contributions should be sent via github as pull requests, from feature branches [as explained here](https://help.github.com/articles/using-pull-requests).
Mailing list
------------
Development discussion happens on the whispersystems mailing list.
[To join](https://lists.riseup.net/www/info/whispersystems)
Send emails to whispersystems@lists.riseup.net
+1 -4
View File
@@ -27,7 +27,6 @@ Interested in helping to translate Signal? Contribute here:
https://www.transifex.com/projects/p/signal-android/ https://www.transifex.com/projects/p/signal-android/
## Contributing Code ## Contributing Code
Instructions on how to setup your development environment and build Signal can be found in [BUILDING.md](https://github.com/signalapp/Signal-Android/blob/master/BUILDING.md).
If you're new to the Signal codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar. Also please have a look at the [CONTRIBUTING.md](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md), that might answer some of your questions. If you're new to the Signal codebase, we recommend going through our issues and picking out a simple bug to fix (check the "easy" label in our issues) in order to get yourself familiar. Also please have a look at the [CONTRIBUTING.md](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md), that might answer some of your questions.
@@ -60,9 +59,7 @@ The form and manner of this distribution makes it eligible for export under the
## License ## License
Copyright 2011 Whisper Systems Copyright 2013-2020 Signal
Copyright 2013-2017 Open Whisper Systems
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html
+10
View File
@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = da_DK:da-rDK,fil:tl,he:iw,id:in,kn_IN:kn-rIN,pa_PK:pa-rPK,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
[signal-android.master]
file_filter = src/main/res/values-<lang>/strings.xml
source_file = src/main/res/values/strings.xml
source_lang = en
type = ANDROID
+457
View File
@@ -0,0 +1,457 @@
import org.signal.signing.ApkSignerUtil
import java.security.MessageDigest
buildscript {
repositories {
google()
maven {
url "https://repo1.maven.org/maven2"
}
jcenter {
content {
includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824'
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'witness'
apply from: 'translations.gradle'
apply from: 'witness-verifications.gradle'
repositories {
maven {
url "https://raw.github.com/signalapp/maven/master/photoview/releases/"
content {
includeGroupByRegex "com\\.github\\.chrisbanes.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/"
content {
includeGroupByRegex "me\\.leolin.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
content {
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/"
content {
includeGroupByRegex "org\\.signal.*"
}
}
maven { // textdrawable
url 'https://dl.bintray.com/amulyakhare/maven'
content {
includeGroupByRegex "com\\.amulyakhare.*"
}
}
google()
mavenCentral()
jcenter()
mavenLocal()
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.10.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
def canonicalVersionCode = 652
def canonicalVersionName = "4.62.4"
def postFixSize = 10
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
android {
flavorDimensions "none"
compileSdkVersion 28
buildToolsVersion '28.0.3'
useLibrary 'org.apache.http.legacy'
dexOptions {
javaMaxHeapSize "4g"
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
minSdkVersion 19
targetSdkVersion 28
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal");
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDS_MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\""
buildConfigField "String", "KBS_MRENCLAVE", "\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resConfigs autoResConfig()
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'LICENSE.txt'
exclude 'LICENSE'
exclude 'NOTICE'
exclude 'asm-license.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/proguard/androidx-annotations.pro'
}
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard/proguard-firebase-messaging.pro',
'proguard/proguard-google-play-services.pro',
'proguard/proguard-jackson.pro',
'proguard/proguard-sqlite.pro',
'proguard/proguard-appcompat-v7.pro',
'proguard/proguard-square-okhttp.pro',
'proguard/proguard-square-okio.pro',
'proguard/proguard-spongycastle.pro',
'proguard/proguard-rounded-image-view.pro',
'proguard/proguard-glide.pro',
'proguard/proguard-shortcutbadger.pro',
'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro',
'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro',
'proguard/proguard.cfg'
testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg'
}
staging {
initWith debug
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "CDS_MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
buildConfigField "String", "KBS_ENCLAVE_NAME", "\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdls=\""
}
flipper {
initWith debug
minifyEnabled false
}
release {
minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles
}
}
productFlavors {
play {
dimension "none"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
website {
dimension "none"
ext.websiteUpdateUrl = "https://updates.signal.org/android"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
}
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
lintOptions {
abortOnError true
baseline file("lint-baseline.xml")
disable "LintError"
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
lintChecks project(':lintchecks')
implementation('androidx.appcompat:appcompat:1.1.0-beta01') {
force = true
}
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
implementation "androidx.camera:camera-core:1.0.0-beta01"
implementation "androidx.camera:camera-camera2:1.0.0-beta01"
implementation "androidx.camera:camera-lifecycle:1.0.0-beta01"
implementation "androidx.concurrent:concurrent-futures:1.0.0"
implementation "androidx.autofill:autofill:1.0.0"
implementation "androidx.paging:paging-common:2.1.2"
implementation "androidx.paging:paging-runtime:2.1.2"
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
implementation ('com.google.firebase:firebase-messaging:20.2.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
implementation 'org.conscrypt:conscrypt-android:2.0.0'
implementation 'org.signal:aesgcmprovider:0.0.3'
implementation project(':libsignal-service')
implementation 'org.signal:zkgroup-android:0.7.0'
implementation 'org.signal:argon2:13.1@aar'
implementation 'org.signal:ringrtc-android:2.0.3'
implementation "me.leolin:ShortcutBadger:1.1.16"
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
implementation 'com.makeramen:roundedimageview:2.1.0'
implementation 'com.pnikosis:materialish-progress:1.5'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'pl.tajchert:waitingdots:0.1.0'
implementation 'com.melnykov:floatingactionbutton:1.3.0'
implementation 'com.google.zxing:android-integration:3.1.0'
implementation 'mobi.upod:time-duration-picker:1.1.3'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.zxing:core:3.2.1'
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation ('com.klinkerapps:android-smsmms:4.0.1') {
exclude group: 'com.squareup.okhttp', module: 'okhttp'
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
}
implementation 'com.annimon:stream:1.1.8'
implementation ('com.takisoft.fix:colorpicker:0.9.1') {
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
implementation 'com.airbnb.android:lottie:3.0.7'
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.freemarker'
}
implementation 'dnsjava:dnsjava:2.1.9'
flipperImplementation 'com.facebook.flipper:flipper:0.32.2'
flipperImplementation 'com.facebook.soloader:soloader:0.8.2'
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.11.1'
testImplementation 'org.mockito:mockito-core:1.9.5'
testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
testImplementation 'androidx.test:core:1.2.0'
testImplementation ('org.robolectric:robolectric:4.2') {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
testImplementation 'org.robolectric:shadows-multidex:4.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
dependencyVerification {
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
}
def assembleWebsiteDescriptor = { variant, file ->
if (file.exists()) {
MessageDigest md = MessageDigest.getInstance("SHA-256");
file.eachByte 4096, {bytes, size ->
md.update(bytes, 0, size);
}
String digest = md.digest().collect {String.format "%02x", it}.join();
String url = variant.productFlavors.get(0).ext.websiteUpdateUrl
String apkName = file.getName()
String descriptor = "{" +
"\"versionCode\" : ${canonicalVersionCode * postFixSize + abiPostFix['universal']}," +
"\"versionName\" : \"$canonicalVersionName\"," +
"\"sha256sum\" : \"$digest\"," +
"\"url\" : \"$url/$apkName\"" +
"}"
File descriptorFile = new File(file.getParent(), apkName.replace(".apk", ".json"))
descriptorFile.write(descriptor)
}
}
def signProductionRelease = { variant ->
variant.outputs.collect { output ->
String apkName = output.outputFile.name
File inputFile = new File(output.outputFile.path)
File outputFile = new File(output.outputFile.parent, apkName.replace('-unsigned', ''))
new ApkSignerUtil('sun.security.pkcs11.SunPKCS11',
'pkcs11.config',
'PKCS11',
'file:pkcs11.password').calculateSignature(inputFile.getAbsolutePath(),
outputFile.getAbsolutePath())
inputFile.delete()
outputFile
}
}
task signProductionPlayRelease {
doLast {
signProductionRelease(android.applicationVariants.find { (it.name == 'playRelease') })
}
}
task signProductionWebsiteRelease {
doLast {
def variant = android.applicationVariants.find { (it.name == 'websiteRelease') }
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
assembleWebsiteDescriptor(variant, signedRelease)
}
}
tasks.whenTaskAdded { task ->
if (task.name.equals("assemblePlayRelease")) {
task.finalizedBy signProductionPlayRelease
}
if (task.name.equals("assembleWebsiteRelease")) {
task.finalizedBy signProductionWebsiteRelease
}
}
def getLastCommitTimestamp() {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'git'
args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os
}
return os.toString() + "000"
}
}
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 3.3.2" client="gradle" variant="playRelease" version="3.3.2">
<issue
id="MissingPermission"
message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
errorLine1=" List&lt;SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java"
line="101"
column="35"/>
</issue>
<issue
id="ResourceType"
message="Expected resource of type styleable"
errorLine1=" drawables.getColor(1, 0xff000000);"
errorLine2=" ~">
<location
file="src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java"
line="187"
column="36"/>
</issue>
<issue
id="AppLinkUrlError"
message="Missing URL"
errorLine1=" &lt;intent-filter>"
errorLine2=" ^">
<location
file="AndroidManifest.xml"
line="368"
column="9"/>
</issue>
<issue
id="AppLinkUrlError"
message="Missing URL"
errorLine1=" &lt;intent-filter>"
errorLine2=" ^">
<location
file="AndroidManifest.xml"
line="381"
column="9"/>
</issue>
</issues>
+33
View File
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- L10N errors -->
<!-- This is a runtime crash so we don't want to ship with this. -->
<issue id="StringFormatMatches" severity="error" />
<!-- L10N warnings -->
<issue id="MissingTranslation" severity="warning" />
<issue id="MissingQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="ImpliedQuantity" severity="warning" />
<issue id="CanvasSize" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="VectorRaster" severity="error" />
<issue id="ButtonOrder" severity="error" />
<issue id="ExtraTranslation" severity="warning" />
<!-- Custom lints -->
<issue id="LogNotSignal" severity="error" />
<issue id="LogNotAppSignal" severity="error" />
<issue id="LogTagInlined" severity="error" />
<issue id="RestrictedApi" severity="error">
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
<ignore path="*/org/thoughtcrime/securesms/conversation/*.java" />
<ignore path="*/org/thoughtcrime/securesms/lock/v2/CreateKbsPinViewModel.java" />
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
</issue>
</lint>
+11
View File
@@ -0,0 +1,11 @@
-dontoptimize
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
+29
View File
@@ -0,0 +1,29 @@
{
"data": [
{
"name": "Ottttooooooooo Ocataaaaaaaavius",
"number": "+1 (555) 555-5555",
"label": "Mobile"
},
{
"name": "Victor Von Doom Phd",
"number": "+1 (555) 123-4567",
"label": "Home"
},
{
"name": "Flash Thompson",
"number": "+1 (555) 435-1261",
"label": "Work"
},
{
"name": "Dr. Curtis Connors",
"number": "+1 (555) 992-1567",
"label": "Mobile"
},
{
"name": "Billy Russo",
"number": "+1 (555) 234-1516",
"label": "Mobile"
}
]
}
@@ -0,0 +1,93 @@
package org.thoughtcrime.securesms.lock;
import org.junit.Test;
import org.thoughtcrime.securesms.util.Hex;
import org.whispersystems.signalservice.api.kbs.HashedPin;
import org.whispersystems.signalservice.api.kbs.KbsData;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import java.io.IOException;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password() throws IOException {
String pin = "password";
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("ab7e8499d21f80a6600b3b9ee349ac6d72c07e3359fe885a934ba7aa844429f8"), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("3f33ce58eb25b40436592a30eae2a8fabab1899095f4e2fba6e2d0dc43b4a2d9cac5a3931748522393951e0e54dec769"), kbsData.getCipherText());
assertEquals(masterKey, kbsData.getMasterKey());
String localPinHash = PinHashing.localPinHash(pin);
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
}
@Test
public void argon2_hashed_pin_another_password() throws IOException {
String pin = "anotherpassword";
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("301d9dd1e96f20ce51083f67d3298fd37b97525de8324d5e12ed2d407d3d927b"), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("9d9b05402ea39c17ff1c9298c8a0e86784a352aa02a74943bf8bcf07ec0f4b574a5b786ad0182c8d308d9eb06538b8c9"), kbsData.getCipherText());
assertEquals(masterKey, kbsData.getMasterKey());
String localPinHash = PinHashing.localPinHash(pin);
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
}
@Test
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
String pin = " Pass६örd ";
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("ab645acdccc1652a48a34b2ac6926340ff35c03034013f68760f20013f028dd8"), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("11c0ba1834db15e47c172f6c987c64bd4cfc69c6047dd67a022afeec0165a10943f204d5b8f37b3cb7bab21c6dfc39c8"), kbsData.getCipherText());
assertEquals(masterKey, kbsData.getMasterKey());
assertEquals("577939bccb2b6638c39222d5a97998a867c5e154e30b82cc120f2dd07a3de987", kbsData.getMasterKey().deriveRegistrationLock());
String localPinHash = PinHashing.localPinHash(pin);
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
}
@Test
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
String pin = " ६१८ ";
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
HashedPin hashedPin = PinHashing.hashPin(pin, () -> backupId);
KbsData kbsData = hashedPin.createNewKbsData(masterKey);
assertArrayEquals(hashedPin.getKbsAccessKey(), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("d2fedabd0d4c17a371491c9722578843a26be3b4923e28d452ab2fc5491e794b"), kbsData.getKbsAccessKey());
assertArrayEquals(Hex.fromStringCondensed("877ef871ef1fc668401c717ef21aa12e8523579fb1ff4474b76f28c2293537c80cc7569996c9e0229bea7f378e3a824e"), kbsData.getCipherText());
assertEquals(masterKey, kbsData.getMasterKey());
assertEquals("23a75cb1df1a87df45cc2ed167c2bdc85ab1220b847c88761b0005cac907fce5", kbsData.getMasterKey().deriveRegistrationLock());
String localPinHash = PinHashing.localPinHash(pin);
assertTrue(PinHashing.verifyLocalPinHash(localPinHash, pin));
}
}
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<application
android:name=".FlipperApplicationContext"
tools:replace="android:name"/>
</manifest>
@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.soloader.SoLoader;
import org.thoughtcrime.securesms.database.FlipperSqlCipherAdapter;
public class FlipperApplicationContext extends ApplicationContext {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
FlipperClient client = AndroidFlipperClient.getInstance(this);
client.addPlugin(new InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()));
client.addPlugin(new DatabasesFlipperPlugin(new FlipperSqlCipherAdapter(this)));
client.addPlugin(new SharedPreferencesFlipperPlugin(this));
client.start();
}
}
@@ -0,0 +1,245 @@
package org.thoughtcrime.securesms.database;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.flipper.plugins.databases.DatabaseDescriptor;
import com.facebook.flipper.plugins.databases.DatabaseDriver;
import net.sqlcipher.DatabaseUtils;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
* and made to work with SqlCipher. Unfortunately I couldn't use it directly, nor subclass it.
*/
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
public FlipperSqlCipherAdapter(Context context) {
super(context);
}
@Override
public List<Descriptor> getDatabases() {
return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext())));
}
@Override
public List<String> getTableNames(Descriptor descriptor) {
SQLiteDatabase db = descriptor.getReadable();
List<String> tableNames = new ArrayList<>();
try (Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type IN (?, ?)", new String[] { "table", "view" })) {
while (cursor != null && cursor.moveToNext()) {
tableNames.add(cursor.getString(0));
}
}
return tableNames;
}
@Override
public DatabaseGetTableDataResponse getTableData(Descriptor descriptor, String table, String order, boolean reverse, int start, int count) {
SQLiteDatabase db = descriptor.getReadable();
long total = DatabaseUtils.queryNumEntries(db, table);
String orderBy = order != null ? order + (reverse ? " DESC" : " ASC") : null;
String limitBy = start + ", " + count;
try (Cursor cursor = db.query(table, null, null, null, null, null, orderBy, limitBy)) {
String[] columnNames = cursor.getColumnNames();
List<List<Object>> rows = cursorToList(cursor);
return new DatabaseGetTableDataResponse(Arrays.asList(columnNames), rows, start, rows.size(), total);
}
}
@Override
public DatabaseGetTableStructureResponse getTableStructure(Descriptor descriptor, String table) {
SQLiteDatabase db = descriptor.getReadable();
Map<String, String> foreignKeyValues = new HashMap<>();
try(Cursor cursor = db.rawQuery("PRAGMA foreign_key_list(" + table + ")", null)) {
while (cursor != null && cursor.moveToNext()) {
String from = cursor.getString(cursor.getColumnIndex("from"));
String to = cursor.getString(cursor.getColumnIndex("to"));
String tableName = cursor.getString(cursor.getColumnIndex("table")) + "(" + to + ")";
foreignKeyValues.put(from, tableName);
}
}
List<String> structureColumns = Arrays.asList("column_name", "data_type", "nullable", "default", "primary_key", "foreign_key");
List<List<Object>> structureValues = new ArrayList<>();
try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + table + ")", null)) {
while (cursor != null && cursor.moveToNext()) {
String columnName = cursor.getString(cursor.getColumnIndex("name"));
String foreignKey = foreignKeyValues.containsKey(columnName) ? foreignKeyValues.get(columnName) : null;
structureValues.add(Arrays.asList(columnName,
cursor.getString(cursor.getColumnIndex("type")),
cursor.getInt(cursor.getColumnIndex("notnull")) == 0,
getObjectFromColumnIndex(cursor, cursor.getColumnIndex("dflt_value")),
cursor.getInt(cursor.getColumnIndex("pk")) == 1,
foreignKey));
}
}
List<String> indexesColumns = Arrays.asList("index_name", "unique", "indexed_column_name");
List<List<Object>> indexesValues = new ArrayList<>();
try (Cursor indexesCursor = db.rawQuery("PRAGMA index_list(" + table + ")", null)) {
List<String> indexedColumnNames = new ArrayList<>();
String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name"));
try(Cursor indexInfoCursor = db.rawQuery("PRAGMA index_info(" + indexName + ")", null)) {
while (indexInfoCursor.moveToNext()) {
indexedColumnNames.add(indexInfoCursor.getString(indexInfoCursor.getColumnIndex("name")));
}
}
indexesValues.add(Arrays.asList(indexName,
indexesCursor.getInt(indexesCursor.getColumnIndex("unique")) == 1,
TextUtils.join(",", indexedColumnNames)));
}
return new DatabaseGetTableStructureResponse(structureColumns, structureValues, indexesColumns, indexesValues);
}
@Override
public DatabaseGetTableInfoResponse getTableInfo(Descriptor databaseDescriptor, String table) {
SQLiteDatabase db = databaseDescriptor.getReadable();
try (Cursor cursor = db.rawQuery("SELECT sql FROM sqlite_master WHERE name = ?", new String[] { table })) {
cursor.moveToFirst();
return new DatabaseGetTableInfoResponse(cursor.getString(cursor.getColumnIndex("sql")));
}
}
@Override
public DatabaseExecuteSqlResponse executeSQL(Descriptor descriptor, String query) {
SQLiteDatabase db = descriptor.getWritable();
String firstWordUpperCase = getFirstWord(query).toUpperCase();
switch (firstWordUpperCase) {
case "UPDATE":
case "DELETE":
return executeUpdateDelete(db, query);
case "INSERT":
return executeInsert(db, query);
case "SELECT":
case "PRAGMA":
case "EXPLAIN":
return executeSelect(db, query);
default:
return executeRawQuery(db, query);
}
}
private static String getFirstWord(String s) {
s = s.trim();
int firstSpace = s.indexOf(' ');
return firstSpace >= 0 ? s.substring(0, firstSpace) : s;
}
private static DatabaseExecuteSqlResponse executeUpdateDelete(SQLiteDatabase database, String query) {
SQLiteStatement statement = database.compileStatement(query);
int count = statement.executeUpdateDelete();
return DatabaseExecuteSqlResponse.successfulUpdateDelete(count);
}
private static DatabaseExecuteSqlResponse executeInsert(SQLiteDatabase database, String query) {
SQLiteStatement statement = database.compileStatement(query);
long insertedId = statement.executeInsert();
return DatabaseExecuteSqlResponse.successfulInsert(insertedId);
}
private static DatabaseExecuteSqlResponse executeSelect(SQLiteDatabase database, String query) {
try (Cursor cursor = database.rawQuery(query, null)) {
String[] columnNames = cursor.getColumnNames();
List<List<Object>> rows = cursorToList(cursor);
return DatabaseExecuteSqlResponse.successfulSelect(Arrays.asList(columnNames), rows);
}
}
private static DatabaseExecuteSqlResponse executeRawQuery(SQLiteDatabase database, String query) {
database.execSQL(query);
return DatabaseExecuteSqlResponse.successfulRawQuery();
}
private static @NonNull List<List<Object>> cursorToList(Cursor cursor) {
List<List<Object>> rows = new ArrayList<>();
int numColumns = cursor.getColumnCount();
while (cursor.moveToNext()) {
List<Object> values = new ArrayList<>(numColumns);
for (int column = 0; column < numColumns; column++) {
values.add(getObjectFromColumnIndex(cursor, column));
}
rows.add(values);
}
return rows;
}
private static @Nullable Object getObjectFromColumnIndex(Cursor cursor, int column) {
switch (cursor.getType(column)) {
case Cursor.FIELD_TYPE_NULL:
return null;
case Cursor.FIELD_TYPE_INTEGER:
return cursor.getLong(column);
case Cursor.FIELD_TYPE_FLOAT:
return cursor.getDouble(column);
case Cursor.FIELD_TYPE_BLOB:
return cursor.getBlob(column);
case Cursor.FIELD_TYPE_STRING:
default:
return cursor.getString(column);
}
}
static class Descriptor implements DatabaseDescriptor {
private final SQLCipherOpenHelper sqlCipherOpenHelper;
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
}
@Override
public String name() {
return sqlCipherOpenHelper.getDatabaseName();
}
public @NonNull SQLiteDatabase getReadable() {
return sqlCipherOpenHelper.getReadableDatabase();
}
public @NonNull SQLiteDatabase getWritable() {
return sqlCipherOpenHelper.getWritableDatabase();
}
}
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Signal (Flipper)</string>
</resources>
+759
View File
@@ -0,0 +1,759 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle" />
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
android:protectionLevel="signature" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<!-- For sending/receiving events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- So we can add a TextSecure 'Account' -->
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<!-- For conversation 'shortcuts' on the desktop -->
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- For fixing MMS -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
tools:replace="android:allowBackup"
android:allowBackup="false"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTask"/>
<activity android:name=".InviteActivity"
android:theme="@style/Signal.Light.NoActionBar.Invite"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".MainActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tsdevice"/>
</intent-filter>
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".sharing.ShareActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="audio/*" />
<data android:mimeType="image/*" />
<data android:mimeType="text/plain" />
<data android:mimeType="video/*" />
<data android:mimeType="application/*"/>
<data android:mimeType="text/*"/>
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*" />
<data android:mimeType="video/*" />
</intent-filter>
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value=".service.DirectShareService" />
</activity>
<activity android:name=".stickers.StickerPackPreviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sgnl"
android:host="addstickers" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="signal.art"
android:pathPrefix="/addstickers"/>
</intent-filter>
</activity>
<activity-alias android:name=".RoutingActivity"
android:targetActivity=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
</intent-filter>
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
android:resource="@mipmap/ic_launcher" />
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
android:resource="@mipmap/ic_launcher" />
</activity-alias>
<activity android:name=".conversation.ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".longmessage.LongMessageActivity" />
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
android:launchMode="singleTask"
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@style/TextSecure.LightTheme.Popup"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".MessageDetailsActivity"
android:label="@string/AndroidManifest__message_details"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".GroupCreateActivity"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".groups.ui.pendingmemberinvites.PendingMemberInvitesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.LightNoActionBar" />
<activity android:name=".groups.ui.managegroup.ManageGroupActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseMigrationActivity"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".NewConversationActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".giph.ui.GiphyActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.MediaSendActivity"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".VerifyIdentityActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ApplicationPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
</intent-filter>
</activity>
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".revealable.ViewOnceMessageActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".logsubmit.SubmitDebugLogActivity"
android:label="@string/AndroidManifest__log_submit"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AvatarPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediaoverview.MediaOverviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
android:enabled="true"
android:allowTaskReparenting="true"
android:noHistory="true"
android:excludeFromRecents="true"
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
<activity android:name=".PlayServicesProblemActivity"
android:theme="@style/TextSecure.DialogActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".SmsSendtoActivity">
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.contact" />
</intent-filter>
</activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:excludeFromRecents="true"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
</intent-filter>
</activity>
<activity android:name=".RecipientPreferenceActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.AvatarSelectionActivity"
android:theme="@style/TextSecure.FullScreenMedia"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".BlockedContactsActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".profiles.edit.EditProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".lock.v2.CreateKbsPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".lock.v2.KbsMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ClearProfileAvatarActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:icon="@drawable/clear_profile_avatar"
android:label="@string/AndroidManifest_remove_photo">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name=".messagerequests.MessageRequestMegaphoneActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.ContactShareEditActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.ContactNameEditActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".contactshare.SharedContactDetailsActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ShortcutLauncherActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MainActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".pin.PinRestoreActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
android:theme="@style/TextSecure.LightNoActionBar" />
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
android:theme="@style/TextSecure.LightNoActionBar" />
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
android:theme="@style/TextSecure.LightNoActionBar" />
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
</service>
<service android:name=".service.ContactsSyncAdapterService" android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service android:name=".service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
<intent-filter>
<action android:name="android.service.chooser.ChooserTargetService" />
</intent-filter>
</service>
<service android:name=".service.GenericForegroundService"/>
<service android:name=".gcm.FcmFetchService" />
<service android:name=".gcm.FcmReceiveService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver android:name=".service.SmsListener"
android:permission="android.permission.BROADCAST_SMS"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
</intent-filter>
</receiver>
<receiver android:name=".service.SmsDeliveryListener"
android:exported="true">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
</intent-filter>
</receiver>
<receiver android:name=".service.MmsListener"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.CLEAR"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.RemoteReplyReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.WEAR_REPLY"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.AndroidAutoHeardReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.AndroidAutoReplyReceiver"
android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY"/>
</intent-filter>
</receiver>
<receiver android:name=".service.ExpirationListener" />
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="org.thoughtcrime.provider.securesms" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="org.thoughtcrime.provider.securesms.mms" />
<provider android:name="androidx.core.content.FileProvider"
android:authorities="org.thoughtcrime.securesms.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_provider_paths" />
</provider>
<provider android:name=".database.DatabaseContentProviders$Conversation"
android:authorities="org.thoughtcrime.securesms.database.conversation"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$ConversationList"
android:authorities="org.thoughtcrime.securesms.database.conversationlist"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Attachment"
android:authorities="org.thoughtcrime.securesms.database.attachment"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$Sticker"
android:authorities="org.thoughtcrime.securesms.database.sticker"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$StickerPack"
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
android:exported="false" />
<receiver android:name=".service.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="org.thoughtcrime.securesms.RESTART"/>
</intent-filter>
</receiver>
<receiver android:name=".service.DirectoryRefreshListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSignedPreKeyListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSenderCertificateListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.LocalBackupListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.PersistentConnectionBootListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.LocaleChangedReceiver">
<intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.DeleteNotificationReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
</intent-filter>
</receiver>
<receiver
android:name=".service.PanicResponderListener"
android:exported="true">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
</intent-filter>
</receiver>
<service
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.KeepAliveService"
android:enabled="@bool/enable_alarm_manager" />
<receiver
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
android:enabled="@bool/enable_alarm_manager" />
<!-- Probably don't need this one -->
<receiver
android:name=".jobmanager.BootReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_H" android:value="598.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_W" android:value="632.0dip" />
<meta-data android:name="com.sec.android.multiwindow.MINIMUM_SIZE_H" android:value="598.0dip" />
</application>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

@@ -0,0 +1,20 @@
package org.thoughtcrime.securesms;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
public final class AppCapabilities {
private AppCapabilities() {
}
private static final boolean UUID_CAPABLE = false;
/**
* @param storageCapable Whether or not the user can use storage service. This is another way of
* asking if the user has set a Signal PIN or not.
*/
public static SignalServiceProfile.Capabilities getCapabilities(boolean storageCapable) {
return new SignalServiceProfile.Capabilities(UUID_CAPABLE, FeatureFlags.groupsV2(), storageCapable);
}
}
@@ -0,0 +1,55 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.insights.InsightsOptOut;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
/**
* Rule of thumb: if there's something you want to do on the first app launch that involves
* persisting state to the database, you'll almost certainly *also* want to do it post backup
* restore, since a backup restore will wipe the current state of the database.
*/
public final class AppInitialization {
private static final String TAG = Log.tag(AppInitialization.class);
private AppInitialization() {}
public static void onFirstEverAppLaunch(@NonNull Context context) {
Log.i(TAG, "onFirstEverAppLaunch()");
InsightsOptOut.userRequestedOptOut(context);
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
SignalStore.onFirstEverAppLaunch();
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
}
public static void onPostBackupRestore(@NonNull Context context) {
Log.i(TAG, "onPostBackupRestore()");
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
SignalStore.onFirstEverAppLaunch();
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
}
}
@@ -17,24 +17,22 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.camera.camera2.Camera2AppConfig;
import androidx.camera.core.CameraX;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
import com.google.android.gms.security.ProviderInstaller; import com.google.android.gms.security.ProviderInstaller;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
import org.signal.aesgcmprovider.AesGcmProvider; import org.signal.aesgcmprovider.AesGcmProvider;
import org.signal.ringrtc.CallConnectionFactory; import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.components.TypingStatusRepository; import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
@@ -42,37 +40,36 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider; import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
import org.thoughtcrime.securesms.gcm.FcmJobService; import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.JobMigrator;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.FastJobStorage;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob; import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.JobManagerFactories;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob; import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.logging.AndroidLogger; import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger; import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger; import org.thoughtcrime.securesms.logging.SignalUncaughtExceptionHandler;
import org.thoughtcrime.securesms.messages.InitialMessageRetriever;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess; import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.registration.RegistrationUtil;
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener; import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver; import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener; import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener; import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener; import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.VersionTracker; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.webrtc.voiceengine.WebRtcAudioManager; import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils; import org.webrtc.voiceengine.WebRtcAudioUtils;
@@ -115,8 +112,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
initializeSecurityProvider(); initializeSecurityProvider();
initializeLogging(); initializeLogging();
initializeCrashHandling(); initializeCrashHandling();
initializeFirstEverAppLaunch();
initializeAppDependencies(); initializeAppDependencies();
initializeFirstEverAppLaunch();
initializeApplicationMigrations(); initializeApplicationMigrations();
initializeMessageRetrieval(); initializeMessageRetrieval();
initializeExpiringMessageManager(); initializeExpiringMessageManager();
@@ -129,10 +126,14 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
initializeCircumvention(); initializeCircumvention();
initializeRingRtc(); initializeRingRtc();
initializePendingMessages(); initializePendingMessages();
initializeUnidentifiedDeliveryAbilityRefresh();
initializeBlobProvider(); initializeBlobProvider();
initializeCameraX(); initializeCleanup();
FeatureFlags.init();
NotificationChannels.create(this); NotificationChannels.create(this);
RefreshPreKeysJob.scheduleIfNecessary();
StorageSyncHelper.scheduleRoutineSync();
RegistrationUtil.markRegistrationPossiblyComplete();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this); ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
if (Build.VERSION.SDK_INT < 21) { if (Build.VERSION.SDK_INT < 21) {
@@ -146,9 +147,13 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
public void onStart(@NonNull LifecycleOwner owner) { public void onStart(@NonNull LifecycleOwner owner) {
isAppVisible = true; isAppVisible = true;
Log.i(TAG, "App is now visible."); Log.i(TAG, "App is now visible.");
FeatureFlags.refreshIfNecessary();
ApplicationDependencies.getRecipientCache().warmUp(); ApplicationDependencies.getRecipientCache().warmUp();
executePendingContactSync(); executePendingContactSync();
KeyCachingService.onAppForegrounded(this); KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getFrameRateTracker().begin();
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
catchUpOnMessages();
} }
@Override @Override
@@ -156,7 +161,8 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
isAppVisible = false; isAppVisible = false;
Log.i(TAG, "App is no longer visible."); Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this); KeyCachingService.onAppBackgrounded(this);
MessageNotifier.setVisibleThread(-1); ApplicationDependencies.getMessageNotifier().clearVisibleThread();
ApplicationDependencies.getFrameRateTracker().end();
} }
public ExpiringMessageManager getExpiringMessageManager() { public ExpiringMessageManager getExpiringMessageManager() {
@@ -216,7 +222,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
private void initializeCrashHandling() { private void initializeCrashHandling() {
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler(); final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler)); Thread.setDefaultUncaughtExceptionHandler(new SignalUncaughtExceptionHandler(originalHandler));
} }
private void initializeApplicationMigrations() { private void initializeApplicationMigrations() {
@@ -235,9 +241,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) { if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
if (!SQLCipherOpenHelper.databaseFileExists(this)) { if (!SQLCipherOpenHelper.databaseFileExists(this)) {
Log.i(TAG, "First ever app launch!"); Log.i(TAG, "First ever app launch!");
AppInitialization.onFirstEverAppLaunch(this);
TextSecurePreferences.setAppMigrationVersion(this, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(this, JobManager.CURRENT_VERSION);
} }
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE); Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
@@ -298,6 +302,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
add("Moto G4"); add("Moto G4");
add("TA-1053"); add("TA-1053");
add("Mi A1"); add("Mi A1");
add("Mi A2");
add("E5823"); // Sony z5 compact add("E5823"); // Sony z5 compact
add("Redmi Note 5"); add("Redmi Note 5");
add("FP2"); // Fairphone FP2 add("FP2"); // Fairphone FP2
@@ -317,9 +322,9 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true); WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
} }
CallConnectionFactory.initialize(this); CallManager.initialize(this, new RingRtcLogger());
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
Log.w(TAG, e); throw new AssertionError("Unable to load ringrtc library", e);
} }
} }
@@ -360,29 +365,47 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
} }
} }
private void initializeUnidentifiedDeliveryAbilityRefresh() {
if (TextSecurePreferences.isMultiDevice(this) && !TextSecurePreferences.isUnidentifiedDeliveryEnabled(this)) {
ApplicationDependencies.getJobManager().add(new RefreshUnidentifiedDeliveryAbilityJob());
}
}
private void initializeBlobProvider() { private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> { SignalExecutors.BOUNDED.execute(() -> {
BlobProvider.getInstance().onSessionStart(this); BlobProvider.getInstance().onSessionStart(this);
}); });
} }
@SuppressLint("RestrictedApi") private void initializeCleanup() {
private void initializeCameraX() { SignalExecutors.BOUNDED.execute(() -> {
if (Build.VERSION.SDK_INT >= 21) { int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
new Thread(() -> { Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
try { });
CameraX.init(this, Camera2AppConfig.create(this)); }
} catch (Throwable t) {
Log.w(TAG, "Failed to initialize CameraX."); private void catchUpOnMessages() {
} InitialMessageRetriever retriever = ApplicationDependencies.getInitialMessageRetriever();
}, "signal-camerax-initialization").start();
if (retriever.isCaughtUp()) {
return;
} }
SignalExecutors.UNBOUNDED.execute(() -> {
long startTime = System.currentTimeMillis();
switch (retriever.begin(TimeUnit.SECONDS.toMillis(60))) {
case SUCCESS:
Log.i(TAG, "Successfully caught up on messages. " + (System.currentTimeMillis() - startTime) + " ms");
break;
case FAILURE_TIMEOUT:
Log.w(TAG, "Did not finish catching up due to a timeout. " + (System.currentTimeMillis() - startTime) + " ms");
break;
case FAILURE_ERROR:
Log.w(TAG, "Did not finish catching up due to an error. " + (System.currentTimeMillis() - startTime) + " ms");
break;
case SKIPPED_ALREADY_CAUGHT_UP:
Log.i(TAG, "Already caught up. " + (System.currentTimeMillis() - startTime) + " ms");
break;
case SKIPPED_ALREADY_RUNNING:
Log.i(TAG, "Already in the process of catching up. " + (System.currentTimeMillis() - startTime) + " ms");
break;
}
});
} }
@Override @Override
@@ -19,15 +19,17 @@ package org.thoughtcrime.securesms;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference; import androidx.preference.Preference;
import org.thoughtcrime.securesms.help.HelpFragment;
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment; import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment; import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment; import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
@@ -35,11 +37,14 @@ import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment; import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment; import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment; import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference; import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
/** /**
* The Activity for application preference display and management. * The Activity for application preference display and management.
@@ -60,7 +65,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection"; private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance"; private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats"; private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices"; private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
private static final String PREFERENCE_CATEGORY_HELP = "preference_category_help";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced"; private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicTheme dynamicTheme = new DynamicTheme();
@@ -105,7 +112,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
if (fragmentManager.getBackStackEntryCount() > 0) { if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack(); fragmentManager.popBackStack();
} else { } else {
Intent intent = new Intent(this, ConversationListActivity.class); // TODO [greyson] Navigation
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent); startActivity(intent);
finish(); finish();
@@ -144,10 +152,23 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE)); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_CHATS) this.findPreference(PREFERENCE_CATEGORY_CHATS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS)); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
this.findPreference(PREFERENCE_CATEGORY_DEVICES) this.findPreference(PREFERENCE_CATEGORY_DEVICES)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES)); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
this.findPreference(PREFERENCE_CATEGORY_HELP)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_HELP));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED) this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED)); .setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
tintIcons();
}
private void tintIcons() {
if (Build.VERSION.SDK_INT >= 21) return;
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
preference.getIcon().setColorFilter(ThemeUtil.getThemedColor(requireContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_IN);
} }
@Override @Override
@@ -213,6 +234,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_CHATS: case PREFERENCE_CATEGORY_CHATS:
fragment = new ChatsPreferenceFragment(); fragment = new ChatsPreferenceFragment();
break; break;
case PREFERENCE_CATEGORY_STORAGE:
fragment = new StoragePreferenceFragment();
break;
case PREFERENCE_CATEGORY_DEVICES: case PREFERENCE_CATEGORY_DEVICES:
Intent intent = new Intent(getActivity(), DeviceActivity.class); Intent intent = new Intent(getActivity(), DeviceActivity.class);
startActivity(intent); startActivity(intent);
@@ -220,6 +244,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_ADVANCED: case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment(); fragment = new AdvancedPreferenceFragment();
break; break;
case PREFERENCE_CATEGORY_HELP:
fragment = new HelpFragment();
break;
default: default:
throw new AssertionError(); throw new AssertionError();
} }
@@ -245,11 +272,12 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private class ProfileClickListener implements Preference.OnPreferenceClickListener { private class ProfileClickListener implements Preference.OnPreferenceClickListener {
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(preference.getContext(), CreateProfileActivity.class); Intent intent = new Intent(preference.getContext(), EditProfileActivity.class);
intent.putExtra(CreateProfileActivity.EXCLUDE_SYSTEM, true); intent.putExtra(EditProfileActivity.EXCLUDE_SYSTEM, true);
intent.putExtra(EditProfileActivity.DISPLAY_USERNAME, true);
intent.putExtra(EditProfileActivity.NEXT_BUTTON_TEXT, R.string.save);
getActivity().startActivity(intent); requireActivity().startActivity(intent);
// ((BaseActionBarActivity)getActivity()).startActivitySceneTransition(intent, getActivity().findViewById(R.id.avatar), "avatar");
return true; return true;
} }
} }
@@ -0,0 +1,152 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
/**
* Activity for displaying avatars full screen.
*/
public final class AvatarPreviewActivity extends PassphraseRequiredActionBarActivity {
private static final String TAG = Log.tag(AvatarPreviewActivity.class);
private static final String RECIPIENT_ID_EXTRA = "recipient_id";
public static @NonNull Intent intentFromRecipientId(@NonNull Context context,
@NonNull RecipientId recipientId)
{
Intent intent = new Intent(context, AvatarPreviewActivity.class);
intent.putExtra(RECIPIENT_ID_EXTRA, recipientId.serialize());
return intent;
}
public static Bundle createTransitionBundle(@NonNull Activity activity, @NonNull View from) {
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
}
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready);
setTheme(R.style.TextSecure_MediaPreview);
setContentView(R.layout.contact_photo_preview_activity);
Toolbar toolbar = findViewById(R.id.toolbar);
ImageView avatar = findViewById(R.id.avatar);
setSupportActionBar(toolbar);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
showSystemUI();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Context context = getApplicationContext();
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
Recipient.live(recipientId).observe(this, recipient -> {
ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar())
: recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto();
GlideApp.with(this).load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(this))
.error(fallbackPhoto.asCallCard(this))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.addListener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
Log.w(TAG, "Unable to load avatar, or avatar removed, closing");
finish();
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
})
.into(avatar);
toolbar.setTitle(recipient.toShortString(context));
});
avatar.setOnClickListener(v -> toggleUiVisibility());
showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
}
private static void showAndHideWithSystemUI(@NonNull Window window, @NonNull View... views) {
window.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
boolean hide = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
for (View view : views) {
view.animate()
.alpha(hide ? 0 : 1)
.start();
}
});
}
private void toggleUiVisibility() {
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
showSystemUI();
} else {
hideSystemUI();
}
}
private void hideSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN );
}
private void showSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN );
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
@@ -5,9 +5,10 @@ import androidx.annotation.Nullable;
import android.view.View; import android.view.View;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@@ -44,5 +45,7 @@ public interface BindableConversationItem extends Unbindable {
void onAddToContactsClicked(@NonNull Contact contact); void onAddToContactsClicked(@NonNull Contact contact);
void onMessageSharedContactClicked(@NonNull List<Recipient> choices); void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
void onInviteSharedContactClicked(@NonNull List<Recipient> choices); void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
void onReactionClicked(long messageId, boolean isMms);
void onGroupMemberAvatarClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId);
} }
} }
@@ -0,0 +1,17 @@
package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.Locale;
import java.util.Set;
public interface BindableConversationListItem extends Unbindable {
void bind(@NonNull ThreadRecord thread,
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
@NonNull Set<Long> typingThreads,
@NonNull Set<Long> selectedThreads, boolean batchMode);
}
@@ -0,0 +1,128 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.res.Resources;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.Lifecycle;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
/**
* This should be used whenever we want to prompt the user to block/unblock a recipient.
*/
public final class BlockUnblockDialog {
private BlockUnblockDialog() { }
public static void showBlockFor(@NonNull Context context,
@NonNull Lifecycle lifecycle,
@NonNull Recipient recipient,
@NonNull Runnable onBlock)
{
SimpleTask.run(lifecycle,
() -> buildBlockFor(context, recipient, onBlock, null),
AlertDialog.Builder::show);
}
public static void showBlockAndDeleteFor(@NonNull Context context,
@NonNull Lifecycle lifecycle,
@NonNull Recipient recipient,
@NonNull Runnable onBlock,
@NonNull Runnable onBlockAndDelete)
{
SimpleTask.run(lifecycle,
() -> buildBlockFor(context, recipient, onBlock, onBlockAndDelete),
AlertDialog.Builder::show);
}
public static void showUnblockFor(@NonNull Context context,
@NonNull Lifecycle lifecycle,
@NonNull Recipient recipient,
@NonNull Runnable onUnblock)
{
SimpleTask.run(lifecycle,
() -> buildUnblockFor(context, recipient, onUnblock),
AlertDialog.Builder::show);
}
@WorkerThread
private static AlertDialog.Builder buildBlockFor(@NonNull Context context,
@NonNull Recipient recipient,
@NonNull Runnable onBlock,
@Nullable Runnable onBlockAndDelete)
{
recipient = recipient.resolve();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
Resources resources = context.getResources();
if (recipient.isGroup()) {
if (DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_and_leave_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_you_will_no_longer_receive_messages_or_updates);
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_leave, ((dialog, which) -> onBlock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_group_members_wont_be_able_to_add_you);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_block, ((dialog, which) -> onBlock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages);
if (onBlockAndDelete != null) {
builder.setNeutralButton(android.R.string.cancel, null);
builder.setPositiveButton(R.string.BlockUnblockDialog_block_and_delete, (d, w) -> onBlockAndDelete.run());
builder.setNegativeButton(R.string.BlockUnblockDialog_block, (d, w) -> onBlock.run());
} else {
builder.setPositiveButton(R.string.BlockUnblockDialog_block, ((dialog, which) -> onBlock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
}
return builder;
}
@WorkerThread
private static AlertDialog.Builder buildUnblockFor(@NonNull Context context,
@NonNull Recipient recipient,
@NonNull Runnable onUnblock)
{
recipient = recipient.resolve();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
Resources resources = context.getResources();
if (recipient.isGroup()) {
if (DatabaseFactory.getGroupDatabase(context).isActive(recipient.requireGroupId())) {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_group_members_will_be_able_to_add_you);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
} else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context)));
builder.setMessage(R.string.BlockUnblockDialog_you_will_be_able_to_call_and_message_each_other);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null);
}
return builder;
}
}
@@ -1,15 +1,8 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.cursoradapter.widget.CursorAdapter;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@@ -17,6 +10,13 @@ import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.ListView; import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cursoradapter.widget.CursorAdapter;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader; import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
@@ -25,21 +25,18 @@ import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity { public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity {
private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@Override @Override
public void onPreCreate() { public void onPreCreate() {
dynamicTheme.onCreate(this); dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
} }
@Override @Override
public void onCreate(Bundle bundle, boolean ready) { public void onCreate(Bundle bundle, boolean ready) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -51,16 +48,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
dynamicTheme.onResume(this); dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onSupportNavigateUp() {
switch (item.getItemId()) { onBackPressed();
case android.R.id.home: finish(); return true; return true;
}
return false;
} }
public static class BlockedContactsFragment public static class BlockedContactsFragment
@@ -76,14 +69,14 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onCreate(Bundle bundle) { public void onCreate(Bundle bundle) {
super.onCreate(bundle); super.onCreate(bundle);
setListAdapter(new BlockedContactAdapter(getActivity(), GlideApp.with(this), null)); setListAdapter(new BlockedContactAdapter(requireActivity(), GlideApp.with(this), null));
getLoaderManager().initLoader(0, null, this); LoaderManager.getInstance(this).initLoader(0, null, this);
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
getLoaderManager().restartLoader(0, null, this); LoaderManager.getInstance(this).restartLoader(0, null, this);
} }
@Override @Override
@@ -114,10 +107,10 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Recipient recipient = ((BlockedContactListItem)view).getRecipient(); Recipient recipient = ((BlockedContactListItem)view).getRecipient();
Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class); BlockUnblockDialog.showUnblockFor(requireContext(), getLifecycle(), recipient, () -> {
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId()); RecipientUtil.unblock(requireContext(), recipient);
LoaderManager.getInstance(this).restartLoader(0, null, this);
startActivity(intent); });
} }
private static class BlockedContactAdapter extends CursorAdapter { private static class BlockedContactAdapter extends CursorAdapter {
@@ -143,7 +136,5 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
((BlockedContactListItem) view).set(glideRequests, recipient); ((BlockedContactListItem) view).set(glideRequests, recipient);
} }
} }
} }
} }
@@ -0,0 +1,49 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ThemeUtil;
public class ClearProfileAvatarActivity extends Activity {
private static final String ARG_TITLE = "arg_title";
public static Intent createForUserProfilePhoto() {
return new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
}
public static Intent createForGroupProfilePhoto() {
Intent intent = new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO");
intent.putExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_group_photo);
return intent;
}
@Override
public void onResume() {
super.onResume();
int titleId = getIntent().getIntExtra(ARG_TITLE, R.string.ClearProfileActivity_remove_profile_photo);
new AlertDialog.Builder(new ContextThemeWrapper(this, DynamicTheme.isDarkTheme(this) ? R.style.TextSecure_DarkTheme : R.style.TextSecure_LightTheme))
.setMessage(titleId)
.setNegativeButton(android.R.string.cancel, (dialog, which) -> finish())
.setPositiveButton(R.string.ClearProfileActivity_remove, (dialog, which) -> {
Intent result = new Intent();
result.putExtra("delete", true);
setResult(Activity.RESULT_OK, result);
finish();
})
.setOnCancelListener(dialog -> finish())
.show();
}
}
@@ -5,12 +5,13 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.database.Cursor; import android.database.Cursor;
import android.os.AsyncTask; import android.os.AsyncTask;
import androidx.appcompat.app.AlertDialog;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.MmsDatabase;
@@ -20,13 +21,15 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.PushDecryptJob; import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.VerifySpan; import org.thoughtcrime.securesms.util.VerifySpan;
import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
@@ -48,7 +51,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
super(context); super(context);
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context)); Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
String name = recipient.toShortString(); String name = recipient.toShortString(context);
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name); String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
SpannableString spannableString = new SpannableString(introduction + " " + SpannableString spannableString = new SpannableString(introduction + " " +
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact)); context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
@@ -95,7 +98,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) { synchronized (SESSION_LOCK) {
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireAddress().toPhoneString(), 1); SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext()); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true); identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true);
@@ -167,7 +170,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
boolean legacy = !messageRecord.isContentBundleKeyExchange(); boolean legacy = !messageRecord.isContentBundleKeyExchange();
SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
messageRecord.getIndividualRecipient().requireAddress().toPhoneString(), Optional.of(RecipientUtil.toSignalServiceAddress(getContext(), messageRecord.getIndividualRecipient())),
messageRecord.getRecipientDeviceId(), messageRecord.getRecipientDeviceId(),
messageRecord.getDateSent(), messageRecord.getDateSent(),
legacy ? Base64.decode(messageRecord.getBody()) : null, legacy ? Base64.decode(messageRecord.getBody()) : null,
@@ -176,7 +179,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
long pushId = pushDatabase.insert(envelope); long pushId = pushDatabase.insert(envelope);
ApplicationDependencies.getJobManager().add(new PushDecryptJob(getContext(), pushId, messageRecord.getId())); ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
} catch (IOException e) { } catch (IOException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
@@ -19,18 +19,19 @@ package org.thoughtcrime.securesms;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.thoughtcrime.securesms.logging.Log; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.thoughtcrime.securesms.components.ContactFilterToolbar; import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.util.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil; import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -43,12 +44,14 @@ import java.lang.ref.WeakReference;
*/ */
public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity public abstract class ContactSelectionActivity extends PassphraseRequiredActionBarActivity
implements SwipeRefreshLayout.OnRefreshListener, implements SwipeRefreshLayout.OnRefreshListener,
ContactSelectionListFragment.OnContactSelectedListener ContactSelectionListFragment.OnContactSelectedListener,
ContactSelectionListFragment.ScrollCallback
{ {
private static final String TAG = ContactSelectionActivity.class.getSimpleName(); private static final String TAG = ContactSelectionActivity.class.getSimpleName();
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); public static final String EXTRA_LAYOUT_RES_ID = "layout_res_id";
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
protected ContactSelectionListFragment contactsFragment; protected ContactSelectionListFragment contactsFragment;
@@ -57,18 +60,17 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override @Override
protected void onPreCreate() { protected void onPreCreate() {
dynamicTheme.onCreate(this); dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
} }
@Override @Override
protected void onCreate(Bundle icicle, boolean ready) { protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) { if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS; : DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode); getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
} }
setContentView(R.layout.contact_selection_activity); setContentView(getIntent().getIntExtra(EXTRA_LAYOUT_RES_ID, R.layout.contact_selection_activity));
initializeToolbar(); initializeToolbar();
initializeResources(); initializeResources();
@@ -79,7 +81,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
dynamicTheme.onResume(this); dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
} }
protected ContactFilterToolbar getToolbar() { protected ContactFilterToolbar getToolbar() {
@@ -87,10 +88,9 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
} }
private void initializeToolbar() { private void initializeToolbar() {
this.toolbar = ViewUtil.findById(this, R.id.toolbar); this.toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(false); getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setIcon(null); getSupportActionBar().setIcon(null);
@@ -113,10 +113,21 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
} }
@Override @Override
public void onContactSelected(String number) {} public void onContactSelected(Optional<RecipientId> recipientId, String number) {}
@Override @Override
public void onContactDeselected(String number) {} public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
@Override
public void onBeginScroll() {
hideKeyboard();
}
private void hideKeyboard() {
ServiceUtil.getInputMethodManager(this)
.hideSoftInputFromWindow(toolbar.getWindowToken(), 0);
toolbar.clearFocus();
}
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> { private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
@@ -128,7 +139,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override @Override
protected Void doInBackground(Context... params) { protected Void doInBackground(Context... params) {
try { try {
DirectoryHelper.refreshDirectory(params[0], true); DirectoryHelper.refreshDirectory(params[0], true);
} catch (IOException e) { } catch (IOException e) {
@@ -0,0 +1,646 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.Manifest;
import android.animation.LayoutTransition;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.CycleInterpolator;
import android.widget.Button;
import android.widget.HorizontalScrollView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.transition.AutoTransition;
import androidx.transition.TransitionManager;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;
import com.google.android.material.chip.ChipGroup;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.contacts.ContactChip;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
* Fragment for selecting a one or more contacts from a list.
*
* @author Moxie Marlinspike
*
*/
public final class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
@SuppressWarnings("unused")
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
private static final int CHIP_GROUP_EMPTY_CHILD_COUNT = 1;
private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150;
public static final int NO_LIMIT = Integer.MAX_VALUE;
public static final String DISPLAY_MODE = "display_mode";
public static final String MULTI_SELECT = "multi_select";
public static final String REFRESHABLE = "refreshable";
public static final String RECENTS = "recents";
public static final String TOTAL_CAPACITY = "total_capacity";
public static final String CURRENT_SELECTION = "current_selection";
private ConstraintLayout constraintLayout;
private TextView emptyText;
private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh;
private View showContactsLayout;
private Button showContactsButton;
private TextView showContactsDescription;
private ProgressWheel showContactsProgress;
private String cursorFilter;
private RecyclerView recyclerView;
private RecyclerViewFastScroller fastScroller;
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
private ChipGroup chipGroup;
private HorizontalScrollView chipGroupScrollContainer;
private TextView groupLimit;
@Nullable private FixedViewsAdapter headerAdapter;
@Nullable private FixedViewsAdapter footerAdapter;
@Nullable private ListCallback listCallback;
@Nullable private ScrollCallback scrollCallback;
private GlideRequests glideRequests;
private int selectionLimit;
private Set<RecipientId> currentSelection;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ListCallback) {
listCallback = (ListCallback) context;
}
if (context instanceof ScrollCallback) {
scrollCallback = (ScrollCallback) context;
}
}
@Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
initializeCursor();
}
@Override
public void onStart() {
super.onStart();
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.onAllGranted(() -> {
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
handleContactPermissionGranted();
} else {
LoaderManager.getInstance(this).initLoader(0, null, this);
}
})
.onAnyDenied(() -> {
FragmentActivity activity = requireActivity();
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
if (activity.getIntent().getBooleanExtra(RECENTS, false)) {
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
} else {
initializeNoContactsPermission();
}
})
.execute();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
emptyText = view.findViewById(android.R.id.empty);
recyclerView = view.findViewById(R.id.recycler_view);
swipeRefresh = view.findViewById(R.id.swipe_refresh);
fastScroller = view.findViewById(R.id.fast_scroller);
showContactsLayout = view.findViewById(R.id.show_contacts_container);
showContactsButton = view.findViewById(R.id.show_contacts_button);
showContactsDescription = view.findViewById(R.id.show_contacts_description);
showContactsProgress = view.findViewById(R.id.progress);
chipGroup = view.findViewById(R.id.chipGroup);
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
groupLimit = view.findViewById(R.id.group_limit);
constraintLayout = view.findViewById(R.id.container);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setItemAnimator(new DefaultItemAnimator() {
@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
return true;
}
});
swipeRefresh.setEnabled(requireActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
selectionLimit = requireActivity().getIntent().getIntExtra(TOTAL_CAPACITY, NO_LIMIT);
currentSelection = getCurrentSelection();
updateGroupLimit(getChipCount());
return view;
}
private void updateGroupLimit(int chipCount) {
if (selectionLimit != NO_LIMIT) {
groupLimit.setText(String.format(Locale.getDefault(), "%d/%d", currentSelection.size() + chipCount, selectionLimit));
groupLimit.setVisibility(View.VISIBLE);
} else {
groupLimit.setVisibility(View.GONE);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
public @NonNull List<SelectedContact> getSelectedContacts() {
if (cursorRecyclerViewAdapter == null) {
return Collections.emptyList();
}
return cursorRecyclerViewAdapter.getSelectedContacts();
}
public int getSelectedContactsCount() {
if (cursorRecyclerViewAdapter == null) {
return 0;
}
return cursorRecyclerViewAdapter.getSelectedContactsCount();
}
private Set<RecipientId> getCurrentSelection() {
List<RecipientId> currentSelection = requireActivity().getIntent().getParcelableArrayListExtra(CURRENT_SELECTION);
return currentSelection == null ? Collections.emptySet()
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet()));
}
private boolean isMulti() {
return requireActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
private void initializeCursor() {
glideRequests = GlideApp.with(this);
cursorRecyclerViewAdapter = new ContactSelectionListAdapter(requireContext(),
glideRequests,
null,
new ListClickListener(),
isMulti(),
currentSelection);
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
if (listCallback != null && FeatureFlags.newGroupUI()) {
if (FeatureFlags.groupsV2create() && FeatureFlags.groupsV2internalTest()) {
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback), createNewGroupsV1GroupItem(listCallback));
} else {
headerAdapter = new FixedViewsAdapter(createNewGroupItem(listCallback));
}
headerAdapter.hide();
concatenateAdapter.addAdapter(headerAdapter);
}
concatenateAdapter.addAdapter(cursorRecyclerViewAdapter);
if (listCallback != null) {
footerAdapter = new FixedViewsAdapter(createInviteActionView(listCallback));
footerAdapter.hide();
concatenateAdapter.addAdapter(footerAdapter);
}
recyclerView.setAdapter(concatenateAdapter);
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
if (scrollCallback != null) {
scrollCallback.onBeginScroll();
}
}
}
});
}
private View createInviteActionView(@NonNull ListCallback listCallback) {
View view = LayoutInflater.from(requireContext())
.inflate(R.layout.contact_selection_invite_action_item, (ViewGroup) requireView(), false);
view.setOnClickListener(v -> listCallback.onInvite());
return view;
}
private View createNewGroupItem(@NonNull ListCallback listCallback) {
View view = LayoutInflater.from(requireContext())
.inflate(R.layout.contact_selection_new_group_item, (ViewGroup) requireView(), false);
view.setOnClickListener(v -> listCallback.onNewGroup(false));
return view;
}
private View createNewGroupsV1GroupItem(@NonNull ListCallback listCallback) {
View view = LayoutInflater.from(requireContext())
.inflate(R.layout.contact_selection_new_group_v1_item, (ViewGroup) requireView(), false);
view.setOnClickListener(v -> listCallback.onNewGroup(true));
return view;
}
private void initializeNoContactsPermission() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsProgress.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them);
showContactsButton.setVisibility(View.VISIBLE);
showContactsButton.setOnClickListener(v -> {
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts))
.onSomeGranted(permissions -> {
if (permissions.contains(Manifest.permission.WRITE_CONTACTS)) {
handleContactPermissionGranted();
}
})
.execute();
});
}
public void setQueryFilter(String filter) {
this.cursorFilter = filter;
LoaderManager.getInstance(this).restartLoader(0, null, this);
}
public void resetQueryFilter() {
setQueryFilter(null);
swipeRefresh.setRefreshing(false);
}
public boolean hasQueryFilter() {
return !TextUtils.isEmpty(cursorFilter);
}
public void setRefreshing(boolean refreshing) {
swipeRefresh.setRefreshing(refreshing);
}
public void reset() {
cursorRecyclerViewAdapter.clearSelectedContacts();
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) {
LoaderManager.getInstance(this).restartLoader(0, null, this);
}
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
FragmentActivity activity = requireActivity();
return new ContactsCursorLoader(activity,
activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
cursorFilter, activity.getIntent().getBooleanExtra(RECENTS, false));
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, @Nullable Cursor data) {
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
cursorRecyclerViewAdapter.changeCursor(data);
if (footerAdapter != null) {
footerAdapter.show();
}
if (headerAdapter != null) {
if (TextUtils.isEmpty(cursorFilter)) {
headerAdapter.show();
} else {
headerAdapter.hide();
}
}
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
boolean useFastScroller = data != null && data.getCount() > 20;
recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
if (useFastScroller) {
fastScroller.setVisibility(View.VISIBLE);
fastScroller.setRecyclerView(recyclerView);
} else {
fastScroller.setRecyclerView(null);
fastScroller.setVisibility(View.GONE);
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
cursorRecyclerViewAdapter.changeCursor(null);
fastScroller.setVisibility(View.GONE);
}
@SuppressLint("StaticFieldLeak")
private void handleContactPermissionGranted() {
final Context context = requireContext();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsButton.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.ConversationListFragment_loading);
showContactsProgress.setVisibility(View.VISIBLE);
showContactsProgress.spin();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
DirectoryHelper.refreshDirectory(context, false);
return true;
} catch (IOException e) {
Log.w(TAG, e);
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
showContactsLayout.setVisibility(View.GONE);
swipeRefresh.setVisibility(View.VISIBLE);
reset();
} else {
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
initializeNoContactsPermission();
}
}
}.execute();
}
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override
public void onItemClick(ContactSelectionListItem contact) {
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
if (!isMulti() || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) {
if (selectionLimitReached()) {
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_the_group_is_full, Toast.LENGTH_SHORT).show();
groupLimit.animate().scaleX(1.3f).scaleY(1.3f).setInterpolator(new CycleInterpolator(0.5f)).start();
return;
}
if (contact.isUsernameType()) {
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
return UsernameUtil.fetchUuidForUsername(requireContext(), contact.getNumber());
}, uuid -> {
loadingDialog.dismiss();
if (uuid.isPresent()) {
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber());
markContactSelected(selected);
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null);
}
} else {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
.setPositiveButton(R.string.ContactSelectionListFragment_okay, (dialog, which) -> dialog.dismiss())
.show();
}
});
} else {
markContactSelected(selectedContact);
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber());
}
}
} else {
markContactUnselected(selectedContact);
cursorRecyclerViewAdapter.notifyItemChanged(recyclerView.getChildAdapterPosition(contact), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
}
}}
}
private boolean selectionLimitReached() {
return getChipCount() >= selectionLimit;
}
private void markContactSelected(@NonNull SelectedContact selectedContact) {
cursorRecyclerViewAdapter.addSelectedContact(selectedContact);
if (isMulti() && FeatureFlags.newGroupUI()) {
addChipForSelectedContact(selectedContact);
}
}
private void markContactUnselected(@NonNull SelectedContact selectedContact) {
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
removeChipForContact(selectedContact);
}
private void removeChipForContact(@NonNull SelectedContact contact) {
for (int i = chipGroup.getChildCount() - 1; i >= 0; i--) {
View v = chipGroup.getChildAt(i);
if (v instanceof ContactChip && contact.matches(((ContactChip) v).getContact())) {
chipGroup.removeView(v);
}
}
updateGroupLimit(getChipCount());
if (getChipCount() == 0) {
setChipGroupVisibility(ConstraintSet.GONE);
}
}
private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) {
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
resolved -> addChipForRecipient(resolved, selectedContact));
}
private void addChipForRecipient(@NonNull Recipient recipient, @NonNull SelectedContact selectedContact) {
final ContactChip chip = new ContactChip(requireContext());
if (getChipCount() == 0) {
setChipGroupVisibility(ConstraintSet.VISIBLE);
}
chip.setText(recipient.getShortDisplayName(requireContext()));
chip.setContact(selectedContact);
chip.setCloseIconVisible(true);
chip.setOnCloseIconClickListener(view -> {
markContactUnselected(selectedContact);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(Optional.of(recipient.getId()), recipient.getE164().orNull());
}
});
chipGroup.getLayoutTransition().addTransitionListener(new LayoutTransition.TransitionListener() {
@Override
public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
}
@Override
public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {
if (view == chip && transitionType == LayoutTransition.APPEARING) {
chipGroup.getLayoutTransition().removeTransitionListener(this);
registerChipRecipientObserver(chip, recipient.live());
chipGroup.post(ContactSelectionListFragment.this::smoothScrollChipsToEnd);
}
}
});
chip.setAvatar(glideRequests, recipient, () -> addChip(chip));
}
private void addChip(@NonNull ContactChip chip) {
chipGroup.addView(chip);
updateGroupLimit(getChipCount());
}
private int getChipCount() {
int count = chipGroup.getChildCount() - CHIP_GROUP_EMPTY_CHILD_COUNT;
if (count < 0) throw new AssertionError();
return count;
}
private void registerChipRecipientObserver(@NonNull ContactChip chip, @Nullable LiveRecipient recipient) {
if (recipient != null) {
recipient.observe(getViewLifecycleOwner(), resolved -> {
if (chip.isAttachedToWindow()) {
chip.setAvatar(glideRequests, resolved, null);
chip.setText(resolved.getShortDisplayName(chip.getContext()));
}
});
}
}
private void setChipGroupVisibility(int visibility) {
TransitionManager.beginDelayedTransition(constraintLayout, new AutoTransition().setDuration(CHIP_GROUP_REVEAL_DURATION_MS));
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.setVisibility(R.id.chipGroupScrollContainer, visibility);
constraintSet.applyTo(constraintLayout);
}
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
this.onContactSelectedListener = onContactSelectedListener;
}
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
}
private void smoothScrollChipsToEnd() {
int x = chipGroupScrollContainer.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR ? chipGroup.getWidth() : 0;
chipGroupScrollContainer.smoothScrollTo(x, 0);
}
public interface OnContactSelectedListener {
void onContactSelected(Optional<RecipientId> recipientId, String number);
void onContactDeselected(Optional<RecipientId> recipientId, String number);
}
public interface ListCallback {
void onInvite();
void onNewGroup(boolean forceV1);
}
public interface ScrollCallback {
void onBeginScroll();
}
}
@@ -149,7 +149,8 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivi
if (getIntent().hasExtra("next_intent")) { if (getIntent().hasExtra("next_intent")) {
startActivity((Intent)getIntent().getParcelableExtra("next_intent")); startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
} else { } else {
startActivity(new Intent(this, ConversationListActivity.class)); // TODO [greyson] Navigation
startActivity(new Intent(this, MainActivity.class));
} }
} }
@@ -20,6 +20,7 @@ import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.push.AccountManagerFactory;
@@ -177,7 +178,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
try { try {
Context context = DeviceActivity.this; Context context = DeviceActivity.this;
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context); SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
String verificationCode = accountManager.getNewDeviceVerificationCode(); String verificationCode = accountManager.getNewDeviceVerificationCode();
String ephemeralId = uri.getQueryParameter("uuid"); String ephemeralId = uri.getQueryParameter("uuid");
String publicKeyEncoded = uri.getQueryParameter("pub_key"); String publicKeyEncoded = uri.getQueryParameter("pub_key");
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -13,7 +14,6 @@ import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicelist.Device; import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -162,27 +162,31 @@ public class DeviceListFragment extends ListFragment
builder.show(); builder.show();
} }
@SuppressLint("StaticFieldLeak")
private void handleDisconnectDevice(final long deviceId) { private void handleDisconnectDevice(final long deviceId) {
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(), new ProgressDialogAsyncTask<Void, Void, Boolean>(getActivity(),
R.string.DeviceListActivity_unlinking_device_no_ellipsis, R.string.DeviceListActivity_unlinking_device_no_ellipsis,
R.string.DeviceListActivity_unlinking_device) R.string.DeviceListActivity_unlinking_device)
{ {
@Override @Override
protected Void doInBackground(Void... params) { protected Boolean doInBackground(Void... params) {
try { try {
accountManager.removeDevice(deviceId); accountManager.removeDevice(deviceId);
ApplicationDependencies.getJobManager().add(new RefreshUnidentifiedDeliveryAbilityJob()); return true;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show(); return false;
} }
return null;
} }
@Override @Override
protected void onPostExecute(Void result) { protected void onPostExecute(Boolean result) {
super.onPostExecute(result); super.onPostExecute(result);
getLoaderManager().restartLoader(0, null, DeviceListFragment.this); if (result) {
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
} else {
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
}
} }
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@@ -17,9 +17,6 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
@Override @Override
protected void onCreate(Bundle bundle, boolean ready) { protected void onCreate(Bundle bundle, boolean ready) {
assert getSupportActionBar() != null;
getSupportActionBar().hide();
AlertDialog dialog = new AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this)
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device)) .setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner)) .setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
@@ -18,20 +18,14 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.logging.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
@@ -42,6 +36,10 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
@@ -52,29 +50,35 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.RecipientsEditor; import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity;
import org.thoughtcrime.securesms.mediasend.AvatarSelectionBottomSheetDialogFragment;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener; import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import java.io.File; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
@@ -82,7 +86,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* Activity to create and update groups * Activity to create and update {@link GroupId.V1} groups
* *
* @author Jake McGinty * @author Jake McGinty
*/ */
@@ -93,27 +97,31 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private final static String TAG = GroupCreateActivity.class.getSimpleName(); private final static String TAG = GroupCreateActivity.class.getSimpleName();
public static final String GROUP_ADDRESS_EXTRA = "group_recipient"; private static final String GROUP_ID_EXTRA = "group_id";
public static final String GROUP_THREAD_EXTRA = "group_thread"; private static final String GROUP_THREAD_EXTRA = "group_thread";
private final DynamicTheme dynamicTheme = new DynamicTheme(); private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private static final int PICK_CONTACT = 1; private static final short REQUEST_CODE_SELECT_AVATAR = 26165;
public static final int AVATAR_SIZE = 210; private static final int PICK_CONTACT = 1;
private EditText groupName; private EditText groupName;
private ListView lv; private ListView listView;
private ImageView avatar; private ImageView avatar;
private TextView creatingText; private TextView creatingText;
private Bitmap avatarBmp; private Bitmap avatarBmp;
@NonNull private Optional<GroupData> groupToUpdate = Optional.absent(); @NonNull private Optional<GroupData> groupToUpdate = Optional.absent();
public static Intent newEditGroupIntent(@NonNull Context context, @NonNull GroupId.V1 groupId) {
Intent intent = new Intent(context, GroupCreateActivity.class);
intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, groupId.toString());
return intent;
}
@Override @Override
protected void onPreCreate() { protected void onPreCreate() {
dynamicTheme.onCreate(this); dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
} }
@Override @Override
@@ -129,7 +137,6 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
dynamicTheme.onResume(this); dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
updateViewState(); updateViewState();
} }
@@ -186,27 +193,42 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
private void initializeResources() { private void initializeResources() {
RecipientsEditor recipientsEditor = ViewUtil.findById(this, R.id.recipients_text); RecipientsEditor recipientsEditor = findViewById(R.id.recipients_text);
PushRecipientsPanel recipientsPanel = ViewUtil.findById(this, R.id.recipients); PushRecipientsPanel recipientsPanel = findViewById(R.id.recipients);
lv = ViewUtil.findById(this, R.id.selected_contacts_list);
avatar = ViewUtil.findById(this, R.id.avatar); listView = findViewById(R.id.selected_contacts_list);
groupName = ViewUtil.findById(this, R.id.group_name); avatar = findViewById(R.id.avatar);
creatingText = ViewUtil.findById(this, R.id.creating_group_text); groupName = findViewById(R.id.group_name);
creatingText = findViewById(R.id.creating_group_text);
SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this); SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this);
adapter.setOnRecipientDeletedListener(this); adapter.setOnRecipientDeletedListener(this);
lv.setAdapter(adapter); listView.setAdapter(adapter);
recipientsEditor.setHint(R.string.recipients_panel__add_members); recipientsEditor.setHint(R.string.recipients_panel__add_members);
recipientsPanel.setPanelChangeListener(this); recipientsPanel.setPanelChangeListener(this);
findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener()); findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_outline_40, R.drawable.ic_group_outline_20).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this)));
avatar.setOnClickListener(view -> AvatarSelection.startAvatarSelection(this, false, false)); avatar.setImageDrawable(getDefaultGroupAvatar());
avatar.setOnClickListener(view -> AvatarSelectionBottomSheetDialogFragment.create(avatarBmp != null, false, REQUEST_CODE_SELECT_AVATAR, true).show(getSupportFragmentManager(), null));
}
private Drawable getDefaultGroupAvatar() {
return new ResourceContactPhoto(R.drawable.ic_group_outline_34, R.drawable.ic_group_outline_20).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this));
} }
private void initializeExistingGroup() { private void initializeExistingGroup() {
final Address groupAddress = getIntent().getParcelableExtra(GROUP_ADDRESS_EXTRA); final GroupId groupId = GroupId.parseNullableOrThrow(getIntent().getStringExtra(GROUP_ID_EXTRA));
if (groupAddress != null) { if (groupId != null) {
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupAddress.toGroupString()); GroupId.V1 groupIdV1 = groupId.requireV1();
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupIdV1);
if (FeatureFlags.newGroupUI()) {
avatar.setOnClickListener(v -> startActivity(EditProfileActivity.getIntentForGroupProfile(this, groupIdV1)));
}
} }
} }
@@ -261,8 +283,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
private void handleGroupUpdate() { private void handleGroupUpdate() {
new UpdateSignalGroupTask(this, groupToUpdate.get().id, avatarBmp, new UpdateSignalGroupV1Task(this, groupToUpdate.get().id, avatarBmp,
getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); getGroupName(), getAdapter().getRecipients()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void handleOpenConversation(long threadId, Recipient recipient) { private void handleOpenConversation(long threadId, Recipient recipient) {
@@ -275,7 +297,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
private SelectedRecipientsAdapter getAdapter() { private SelectedRecipientsAdapter getAdapter() {
return (SelectedRecipientsAdapter)lv.getAdapter(); return (SelectedRecipientsAdapter) listView.getAdapter();
} }
private @Nullable String getGroupName() { private @Nullable String getGroupName() {
@@ -285,37 +307,41 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override @Override
public void onActivityResult(int reqCode, int resultCode, final Intent data) { public void onActivityResult(int reqCode, int resultCode, final Intent data) {
super.onActivityResult(reqCode, resultCode, data); super.onActivityResult(reqCode, resultCode, data);
Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped"));
if (data == null || resultCode != Activity.RESULT_OK) if (data == null || resultCode != Activity.RESULT_OK)
return; return;
switch (reqCode) { switch (reqCode) {
case PICK_CONTACT: case PICK_CONTACT:
List<String> selected = data.getStringArrayListExtra("contacts"); List<RecipientId> selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
for (String contact : selected) { for (RecipientId contact : selected) {
Recipient recipient = Recipient.external(this, contact); Recipient recipient = Recipient.resolved(contact);
addSelectedContacts(recipient); addSelectedContacts(recipient);
} }
break;
case AvatarSelection.REQUEST_CODE_AVATAR:
AvatarSelection.circularCropImage(this, data.getData(), outputFile, R.string.CropImageActivity_group_avatar);
break; break;
case AvatarSelection.REQUEST_CODE_CROP_IMAGE: case REQUEST_CODE_SELECT_AVATAR:
final Uri resultUri = AvatarSelection.getResultUri(data); if (data.getBooleanExtra("delete", false)) {
avatarBmp = null;
avatar.setImageDrawable(getDefaultGroupAvatar());
return;
}
final Media result = data.getParcelableExtra(AvatarSelectionActivity.EXTRA_MEDIA);
final DecryptableUri decryptableUri = new DecryptableUri(result.getUri());
GlideApp.with(this) GlideApp.with(this)
.asBitmap() .asBitmap()
.load(resultUri) .load(decryptableUri)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop() .centerCrop()
.override(AVATAR_SIZE, AVATAR_SIZE) .override(AvatarHelper.AVATAR_DIMENSIONS, AvatarHelper.AVATAR_DIMENSIONS)
.into(new SimpleTarget<Bitmap>() { .into(new SimpleTarget<Bitmap>() {
@Override @Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) { public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
setAvatar(resultUri, resource); setAvatar(decryptableUri, resource);
} }
}); });
} }
@@ -352,7 +378,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
memberAddresses.add(Recipient.self().getId()); memberAddresses.add(Recipient.self().getId());
String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true); GroupId.Mms groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateMmsGroupForMembers(memberAddresses);
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId); RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId); Recipient groupRecipient = Recipient.resolved(groupRecipientId);
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT); long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
@@ -416,7 +442,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Optional<GroupActionResult> doInBackground(Void... aVoid) { protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
return Optional.of(GroupManager.createGroup(activity, members, avatar, name, false)); return Optional.of(GroupManager.createGroupV1(activity, members, BitmapUtil.toByteArray(avatar), name, false));
} }
@Override @Override
@@ -433,11 +459,11 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
} }
private static class UpdateSignalGroupTask extends SignalGroupTask { private static class UpdateSignalGroupV1Task extends SignalGroupTask {
private String groupId; private final GroupId.V1 groupId;
public UpdateSignalGroupTask(GroupCreateActivity activity, String groupId, UpdateSignalGroupV1Task(GroupCreateActivity activity, GroupId.V1 groupId,
Bitmap avatar, String name, Set<Recipient> members) Bitmap avatar, String name, Set<Recipient> members)
{ {
super(activity, avatar, name, members); super(activity, avatar, name, members);
this.groupId = groupId; this.groupId = groupId;
@@ -445,11 +471,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override @Override
protected Optional<GroupActionResult> doInBackground(Void... aVoid) { protected Optional<GroupActionResult> doInBackground(Void... aVoid) {
try { return Optional.fromNullable(GroupManager.updateGroup(activity, groupId, members, BitmapUtil.toByteArray(avatar), name));
return Optional.of(GroupManager.updateGroup(activity, groupId, members, avatar, name));
} catch (InvalidNumberException e) {
return Optional.absent();
}
} }
@Override @Override
@@ -458,7 +480,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (!activity.isFinishing()) { if (!activity.isFinishing()) {
Intent intent = activity.getIntent(); Intent intent = activity.getIntent();
intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId()); intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId());
intent.putExtra(GROUP_ADDRESS_EXTRA, result.get().getGroupRecipient().requireAddress()); intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId().toString());
activity.setResult(RESULT_OK, intent); activity.setResult(RESULT_OK, intent);
activity.finish(); activity.finish();
} }
@@ -500,8 +522,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (failIfNotPush && !isPush) { if (failIfNotPush && !isPush) {
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group, results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group,
recipient.toShortString()))); recipient.toShortString(activity))));
} else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.requireAddress().serialize())) { } else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getE164().or(""))) {
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_youre_already_in_the_group))); results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_youre_already_in_the_group)));
} else { } else {
results.add(new Result(recipient, isPush, null)); results.add(new Result(recipient, isPush, null));
@@ -525,7 +547,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
} }
private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<String,Void,Optional<GroupData>> { private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask<GroupId.V1, Void, Optional<GroupData>> {
private GroupCreateActivity activity; private GroupCreateActivity activity;
public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) { public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) {
@@ -536,18 +558,24 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
@Override @Override
protected Optional<GroupData> doInBackground(String... groupIds) { protected Optional<GroupData> doInBackground(GroupId.V1... groupIds) {
final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity); final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity);
final List<Recipient> recipients = db.getGroupMembers(groupIds[0], false); final List<Recipient> recipients = db.getGroupMembers(groupIds[0], GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
final Optional<GroupRecord> group = db.getGroup(groupIds[0]); final Optional<GroupRecord> group = db.getGroup(groupIds[0]);
final Set<Recipient> existingContacts = new HashSet<>(recipients.size()); final Set<Recipient> existingContacts = new HashSet<>(recipients.size());
existingContacts.addAll(recipients); existingContacts.addAll(recipients);
if (group.isPresent()) { if (group.isPresent()) {
Bitmap avatar = null;
try {
avatar = BitmapFactory.decodeStream(AvatarHelper.getAvatar(getContext(), group.get().getRecipientId()));
} catch (IOException e) {
Log.w(TAG, "Failed to read avatar.");
}
return Optional.of(new GroupData(groupIds[0], return Optional.of(new GroupData(groupIds[0],
existingContacts, existingContacts,
BitmapUtil.fromByteArray(group.get().getAvatar()), avatar,
group.get().getAvatar(), BitmapUtil.toByteArray(avatar),
group.get().getTitle())); group.get().getTitle()));
} else { } else {
return Optional.absent(); return Optional.absent();
@@ -567,7 +595,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(activity, group.get().recipients); SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(activity, group.get().recipients);
adapter.setOnRecipientDeletedListener(activity); adapter.setOnRecipientDeletedListener(activity);
activity.lv.setAdapter(adapter); activity.listView.setAdapter(adapter);
activity.updateViewState(); activity.updateViewState();
} }
} }
@@ -584,13 +612,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
private static class GroupData { private static class GroupData {
String id; GroupId.V1 id;
Set<Recipient> recipients; Set<Recipient> recipients;
Bitmap avatarBmp; Bitmap avatarBmp;
byte[] avatarBytes; byte[] avatarBytes;
String name; String name;
public GroupData(String id, Set<Recipient> recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) { GroupData(GroupId.V1 id, Set<Recipient> recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) {
this.id = id; this.id = id;
this.recipients = recipients; this.recipients = recipients;
this.avatarBmp = avatarBmp; this.avatarBmp = avatarBmp;
@@ -0,0 +1,57 @@
package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import java.util.List;
public final class GroupMembersDialog {
private final FragmentActivity fragmentActivity;
private final Recipient groupRecipient;
public GroupMembersDialog(@NonNull FragmentActivity activity,
@NonNull Recipient groupRecipient)
{
this.fragmentActivity = activity;
this.groupRecipient = groupRecipient;
}
public void display() {
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
.setTitle(R.string.ConversationActivity_group_members)
.setIconAttribute(R.attr.group_members_dialog_icon)
.setCancelable(true)
.setView(R.layout.dialog_group_members)
.setPositiveButton(android.R.string.ok, null)
.show();
GroupMemberListView memberListView = dialog.findViewById(R.id.list_members);
LiveGroup liveGroup = new LiveGroup(groupRecipient.requireGroupId());
LiveData<List<GroupMemberEntry.FullMember>> fullMembers = liveGroup.getFullMembers();
//noinspection ConstantConditions
fullMembers.observe(fragmentActivity, memberListView::setMembers);
dialog.setOnDismissListener(d -> fullMembers.removeObservers(fragmentActivity));
memberListView.setRecipientClickListener(recipient -> {
dialog.dismiss();
contactClick(recipient);
});
}
private void contactClick(@NonNull Recipient recipient) {
RecipientBottomSheetDialogFragment.create(recipient.getId(), groupRecipient.requireGroupId())
.show(fragmentActivity.getSupportFragmentManager(), "BOTTOM");
}
}
@@ -0,0 +1,291 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.AnimRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener {
private ContactSelectionListFragment contactsFragment;
private EditText inviteText;
private ViewGroup smsSendFrame;
private Button smsSendButton;
private Animation slideInAnimation;
private Animation slideOutAnimation;
private DynamicTheme dynamicTheme = new DynamicNoActionBarInviteTheme();
private Toolbar primaryToolbar;
@Override
protected void onPreCreate() {
super.onPreCreate();
dynamicTheme.onCreate(this);
}
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
setContentView(R.layout.invite_activity);
initializeAppBar();
initializeResources();
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
private void initializeAppBar() {
primaryToolbar = findViewById(R.id.toolbar);
setSupportActionBar(primaryToolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__invite_friends);
}
private void initializeResources() {
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
View shareButton = ViewUtil.findById(this, R.id.share_button);
View smsButton = ViewUtil.findById(this, R.id.sms_button);
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
inviteText = ViewUtil.findById(this, R.id.invite_text);
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
updateSmsButtonText();
contactsFragment.setOnContactSelectedListener(this);
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
}
private Animation loadAnimation(@AnimRes int animResId) {
final Animation animation = AnimationUtils.loadAnimation(this, animResId);
animation.setInterpolator(new FastOutSlowInInterpolator());
return animation;
}
@Override
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
updateSmsButtonText();
}
@Override
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
updateSmsButtonText();
}
private void sendSmsInvites() {
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
contactsFragment.getSelectedContacts()
.toArray(new SelectedContact[0]));
}
private void updateSmsButtonText() {
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
selectedContacts.size(),
selectedContacts.size()));
smsSendButton.setEnabled(!selectedContacts.isEmpty());
}
@Override public void onBackPressed() {
if (smsSendFrame.getVisibility() == View.VISIBLE) {
cancelSmsSelection();
} else {
super.onBackPressed();
}
}
private void cancelSmsSelection() {
setPrimaryColorsToolbarNormal();
contactsFragment.reset();
updateSmsButtonText();
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
}
private void setPrimaryColorsToolbarNormal() {
primaryToolbar.setBackgroundColor(0);
primaryToolbar.getNavigationIcon().setColorFilter(null);
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.title_text_color_primary));
if (Build.VERSION.SDK_INT >= 23) {
getWindow().setStatusBarColor(ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
WindowUtil.setLightStatusBarFromTheme(this);
}
WindowUtil.setLightNavigationBarFromTheme(this);
}
private void setPrimaryColorsToolbarForSms() {
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.core_ultramarine));
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
if (Build.VERSION.SDK_INT >= 23) {
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
WindowUtil.clearLightStatusBar(getWindow());
}
if (Build.VERSION.SDK_INT >= 27) {
getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.core_ultramarine));
WindowUtil.clearLightNavigationBar(getWindow());
}
}
private class ShareClickListener implements OnClickListener {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, inviteText.getText().toString());
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, getString(R.string.InviteActivity_invite_to_signal)));
} else {
Toast.makeText(InviteActivity.this, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
}
private class SmsClickListener implements OnClickListener {
@Override
public void onClick(View v) {
setPrimaryColorsToolbarForSms();
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
}
}
private class SmsCancelClickListener implements OnClickListener {
@Override
public void onClick(View v) {
cancelSmsSelection();
}
}
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new AlertDialog.Builder(InviteActivity.this)
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()))
.setMessage(inviteText.getText().toString())
.setPositiveButton(R.string.yes, (dialog, which) -> sendSmsInvites())
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
.show();
}
}
private class ContactFilterChangedListener implements OnFilterChangedListener {
@Override
public void onFilterChanged(String filter) {
contactsFragment.setQueryFilter(filter);
}
}
@SuppressLint("StaticFieldLeak")
private class SendSmsInvitesAsyncTask extends ProgressDialogAsyncTask<SelectedContact,Void,Void> {
private final String message;
SendSmsInvitesAsyncTask(Context context, String message) {
super(context, R.string.InviteActivity_sending, R.string.InviteActivity_sending);
this.message = message;
}
@Override
protected Void doInBackground(SelectedContact... contacts) {
final Context context = getContext();
if (context == null) return null;
for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context);
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null);
if (recipient.getContactUri() != null) {
DatabaseFactory.getRecipientDatabase(context).setHasSentInvite(recipient.getId());
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
final Context context = getContext();
if (context == null) return;
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE).addListener(new Listener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
contactsFragment.reset();
}
@Override
public void onFailure(ExecutionException e) {}
});
Toast.makeText(context, R.string.InviteActivity_invitations_sent, Toast.LENGTH_LONG).show();
}
}
}
@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class MainActivity extends PassphraseRequiredActionBarActivity {
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final MainNavigator navigator = new MainNavigator(this);
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready);
setContentView(R.layout.main_activity);
navigator.onCreate(savedInstanceState);
}
@Override
protected void onPreCreate() {
super.onPreCreate();
dynamicTheme.onCreate(this);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onBackPressed() {
if (!navigator.onBackPressed()) {
super.onBackPressed();
}
}
public @NonNull MainNavigator getNavigator() {
return navigator;
}
}
@@ -0,0 +1,22 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
public class MainFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (!(requireActivity() instanceof MainActivity)) {
throw new IllegalStateException("Can only be used inside of MainActivity!");
}
}
protected @NonNull MainNavigator getNavigator() {
return MainNavigator.get(requireActivity());
}
}
@@ -0,0 +1,104 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId;
public class MainNavigator {
private final MainActivity activity;
public MainNavigator(@NonNull MainActivity activity) {
this.activity = activity;
}
public static MainNavigator get(@NonNull Activity activity) {
if (!(activity instanceof MainActivity)) {
throw new IllegalArgumentException("Activity must be an instance of MainActivity!");
}
return ((MainActivity) activity).getNavigator();
}
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
return;
}
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, ConversationListFragment.newInstance())
.commit();
}
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
*/
public boolean onBackPressed() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof BackHandler) {
return ((BackHandler) fragment).onBackPressed();
}
return false;
}
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, startingPosition);
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
}
public void goToAppSettings() {
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
activity.startActivity(intent);
}
public void goToArchiveList() {
getFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
.replace(R.id.fragment_container, ConversationListArchiveFragment.newInstance())
.addToBackStack(null)
.commit();
}
public void goToGroupCreation() {
activity.startActivity(CreateGroupActivity.newIntent(activity));
}
public void goToInvite() {
Intent intent = new Intent(activity, InviteActivity.class);
activity.startActivity(intent);
}
public void goToInsights() {
InsightsLauncher.showInsightsDashboard(activity.getSupportFragmentManager());
}
private @NonNull FragmentManager getFragmentManager() {
return activity.getSupportFragmentManager();
}
public interface BackHandler {
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
*/
boolean onBackPressed();
}
}
@@ -52,11 +52,11 @@ import androidx.viewpager.widget.ViewPager;
import org.thoughtcrime.securesms.animation.DepthPageTransformer; import org.thoughtcrime.securesms.animation.DepthPageTransformer;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener; import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.MediaDatabase; import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader; import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment; import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel; import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter; import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
@@ -64,11 +64,11 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sharing.ShareActivity;
import org.thoughtcrime.securesms.util.AttachmentUtil; import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.SaveAttachmentTask; import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment; import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@@ -85,12 +85,16 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
private final static String TAG = MediaPreviewActivity.class.getSimpleName(); private final static String TAG = MediaPreviewActivity.class.getSimpleName();
public static final String RECIPIENT_EXTRA = "recipient_id"; private static final int NOT_IN_A_THREAD = -2;
public static final String THREAD_ID_EXTRA = "thread_id";
public static final String DATE_EXTRA = "date"; public static final String DATE_EXTRA = "date";
public static final String SIZE_EXTRA = "size"; public static final String SIZE_EXTRA = "size";
public static final String CAPTION_EXTRA = "caption"; public static final String CAPTION_EXTRA = "caption";
public static final String OUTGOING_EXTRA = "outgoing";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent"; public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
public static final String HIDE_ALL_MEDIA_EXTRA = "came_from_all_media";
public static final String SHOW_THREAD_EXTRA = "show_thread";
public static final String SORTING_EXTRA = "sorting";
private ViewPager mediaPager; private ViewPager mediaPager;
private View detailsContainer; private View detailsContainer;
@@ -103,12 +107,29 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
private String initialMediaType; private String initialMediaType;
private long initialMediaSize; private long initialMediaSize;
private String initialCaption; private String initialCaption;
private Recipient conversationRecipient;
private boolean leftIsRecent; private boolean leftIsRecent;
private MediaPreviewViewModel viewModel; private MediaPreviewViewModel viewModel;
private ViewPagerListener viewPagerListener; private ViewPagerListener viewPagerListener;
private int restartItem = -1; private int restartItem = -1;
private long threadId = NOT_IN_A_THREAD;
private boolean cameFromAllMedia;
private boolean showThread;
private MediaDatabase.Sorting sorting;
public static @NonNull Intent intentFromMediaRecord(@NonNull Context context,
@NonNull MediaRecord mediaRecord,
boolean leftIsRecent)
{
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, mediaRecord.getThreadId());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize());
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, mediaRecord.getAttachment().getCaption());
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent);
intent.setDataAndType(mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType());
return intent;
}
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@Override @Override
@@ -152,19 +173,45 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
MediaItem mediaItem = getCurrentMediaItem(); MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem != null) { if (mediaItem != null) {
CharSequence relativeTimeSpan; getSupportActionBar().setTitle(getTitleText(mediaItem));
getSupportActionBar().setSubtitle(getSubTitleText(mediaItem));
}
}
if (mediaItem.date > 0) { private @NonNull String getTitleText(@NonNull MediaItem mediaItem) {
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date); String from;
} else { if (mediaItem.outgoing) from = getString(R.string.MediaPreviewActivity_you);
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft); else if (mediaItem.recipient != null) from = mediaItem.recipient.toShortString(this);
else from = "";
if (showThread) {
String to = null;
Recipient threadRecipient = mediaItem.threadRecipient;
if (threadRecipient != null) {
if (mediaItem.outgoing || threadRecipient.isGroup()) {
if (threadRecipient.isLocalNumber()) {
from = getString(R.string.note_to_self);
} else {
to = threadRecipient.toShortString(this);
}
} else {
to = getString(R.string.MediaPreviewActivity_you);
}
} }
if (mediaItem.outgoing) getSupportActionBar().setTitle(getString(R.string.MediaPreviewActivity_you)); return to != null ? getString(R.string.MediaPreviewActivity_s_to_s, from, to)
else if (mediaItem.recipient != null) getSupportActionBar().setTitle(mediaItem.recipient.toShortString()); : from;
else getSupportActionBar().setTitle(""); } else {
return from;
}
}
getSupportActionBar().setSubtitle(relativeTimeSpan); private @NonNull String getSubTitleText(@NonNull MediaItem mediaItem) {
if (mediaItem.date > 0) {
return DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
} else {
return getString(R.string.MediaPreviewActivity_draft);
} }
} }
@@ -217,20 +264,19 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
} }
private void initializeResources() { private void initializeResources() {
RecipientId recipientId = getIntent().getParcelableExtra(RECIPIENT_EXTRA); Intent intent = getIntent();
initialMediaUri = getIntent().getData(); threadId = intent.getLongExtra(THREAD_ID_EXTRA, NOT_IN_A_THREAD);
initialMediaType = getIntent().getType(); cameFromAllMedia = intent.getBooleanExtra(HIDE_ALL_MEDIA_EXTRA, false);
initialMediaSize = getIntent().getLongExtra(SIZE_EXTRA, 0); showThread = intent.getBooleanExtra(SHOW_THREAD_EXTRA, false);
initialCaption = getIntent().getStringExtra(CAPTION_EXTRA); sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(SORTING_EXTRA, 0)];
leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
initialMediaUri = intent.getData();
initialMediaType = intent.getType();
initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0);
initialCaption = intent.getStringExtra(CAPTION_EXTRA);
leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
restartItem = -1; restartItem = -1;
if (recipientId != null) {
conversationRecipient = Recipient.live(recipientId).get();
} else {
conversationRecipient = null;
}
} }
private void initializeObservers() { private void initializeObservers() {
@@ -239,6 +285,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
return; return;
} }
if (!((MediaItemAdapter) mediaPager.getAdapter()).hasFragmentFor(mediaPager.getCurrentItem())) {
Log.d(TAG, "MediaItemAdapter wasn't ready. Posting again...");
viewModel.resubmitPreviewData();
}
View playbackControls = ((MediaItemAdapter) mediaPager.getAdapter()).getPlaybackControls(mediaPager.getCurrentItem()); View playbackControls = ((MediaItemAdapter) mediaPager.getAdapter()).getPlaybackControls(mediaPager.getCurrentItem());
if (previewData.getAlbumThumbnails().isEmpty() && previewData.getCaption() == null && playbackControls == null) { if (previewData.getAlbumThumbnails().isEmpty() && previewData.getCaption() == null && playbackControls == null) {
@@ -275,7 +326,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
Log.i(TAG, "Loading Part URI: " + initialMediaUri); Log.i(TAG, "Loading Part URI: " + initialMediaUri);
if (conversationRecipient != null) { if (isMediaInDb()) {
LoaderManager.getInstance(this).restartLoader(0, null, this); LoaderManager.getInstance(this).restartLoader(0, null, this);
} else { } else {
mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize)); mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize));
@@ -298,9 +349,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
} }
private void showOverview() { private void showOverview() {
Intent intent = new Intent(this, MediaOverviewActivity.class); startActivity(MediaOverviewActivity.forThread(this, threadId));
intent.putExtra(MediaOverviewActivity.RECIPIENT_EXTRA, conversationRecipient.getId());
startActivity(intent);
} }
private void forward() { private void forward() {
@@ -378,6 +427,10 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
menu.findItem(R.id.delete).setVisible(false); menu.findItem(R.id.delete).setVisible(false);
} }
if (cameFromAllMedia) {
menu.findItem(R.id.media_preview__overview).setVisible(false);
}
return true; return true;
} }
@@ -397,7 +450,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
} }
private boolean isMediaInDb() { private boolean isMediaInDb() {
return conversationRecipient != null; return threadId != NOT_IN_A_THREAD;
} }
private @Nullable MediaItem getCurrentMediaItem() { private @Nullable MediaItem getCurrentMediaItem() {
@@ -416,7 +469,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
@Override @Override
public @NonNull Loader<Pair<Cursor, Integer>> onCreateLoader(int id, Bundle args) { public @NonNull Loader<Pair<Cursor, Integer>> onCreateLoader(int id, Bundle args) {
return new PagingMediaLoader(this, conversationRecipient, initialMediaUri, leftIsRecent); return new PagingMediaLoader(this, threadId, initialMediaUri, leftIsRecent, sorting);
} }
@Override @Override
@@ -427,16 +480,14 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
mediaPager.setAdapter(adapter); mediaPager.setAdapter(adapter);
adapter.setActive(true); adapter.setActive(true);
Util.postToMain(() -> { viewModel.setCursor(this, data.first, leftIsRecent);
viewModel.setCursor(this, data.first, leftIsRecent);
int item = restartItem >= 0 ? restartItem : data.second; int item = restartItem >= 0 ? restartItem : data.second;
mediaPager.setCurrentItem(item); mediaPager.setCurrentItem(item);
if (item == 0) { if (item == 0) {
viewPagerListener.onPageSelected(0); viewPagerListener.onPageSelected(0);
} }
});
} }
} }
@@ -548,7 +599,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
@Override @Override
public MediaItem getMediaItemFor(int position) { public MediaItem getMediaItemFor(int position) {
return new MediaItem(null, null, uri, mediaType, -1, true); return new MediaItem(null, null, null, uri, mediaType, -1, true);
} }
@Override @Override
@@ -565,6 +616,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
} }
return null; return null;
} }
@Override
public boolean hasFragmentFor(int position) {
return mediaPreviewFragment != null;
}
} }
private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) { private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) {
@@ -677,12 +733,15 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
public MediaItem getMediaItemFor(int position) { public MediaItem getMediaItemFor(int position) {
cursor.moveToPosition(getCursorPosition(position)); cursor.moveToPosition(getCursorPosition(position));
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
RecipientId recipientId = mediaRecord.getRecipientId(); MediaRecord mediaRecord = MediaRecord.from(context, cursor);
RecipientId recipientId = mediaRecord.getRecipientId();
RecipientId threadRecipientId = mediaRecord.getThreadRecipientId();
if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError(); if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError();
return new MediaItem(Recipient.live(recipientId).get(), return new MediaItem(Recipient.live(recipientId).get(),
Recipient.live(threadRecipientId).get(),
mediaRecord.getAttachment(), mediaRecord.getAttachment(),
mediaRecord.getAttachment().getDataUri(), mediaRecord.getAttachment().getDataUri(),
mediaRecord.getContentType(), mediaRecord.getContentType(),
@@ -703,6 +762,11 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
return null; return null;
} }
@Override
public boolean hasFragmentFor(int position) {
return mediaFragments.containsKey(position);
}
private int getCursorPosition(int position) { private int getCursorPosition(int position) {
if (leftIsRecent) return position; if (leftIsRecent) return position;
else return cursor.getCount() - 1 - position; else return cursor.getCount() - 1 - position;
@@ -711,6 +775,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
private static class MediaItem { private static class MediaItem {
private final @Nullable Recipient recipient; private final @Nullable Recipient recipient;
private final @Nullable Recipient threadRecipient;
private final @Nullable DatabaseAttachment attachment; private final @Nullable DatabaseAttachment attachment;
private final @NonNull Uri uri; private final @NonNull Uri uri;
private final @NonNull String type; private final @NonNull String type;
@@ -718,18 +783,20 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
private final boolean outgoing; private final boolean outgoing;
private MediaItem(@Nullable Recipient recipient, private MediaItem(@Nullable Recipient recipient,
@Nullable Recipient threadRecipient,
@Nullable DatabaseAttachment attachment, @Nullable DatabaseAttachment attachment,
@NonNull Uri uri, @NonNull Uri uri,
@NonNull String type, @NonNull String type,
long date, long date,
boolean outgoing) boolean outgoing)
{ {
this.recipient = recipient; this.recipient = recipient;
this.attachment = attachment; this.threadRecipient = threadRecipient;
this.uri = uri; this.attachment = attachment;
this.type = type; this.uri = uri;
this.date = date; this.type = type;
this.outgoing = outgoing; this.date = date;
this.outgoing = outgoing;
} }
} }
@@ -737,5 +804,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActionBarActiv
MediaItem getMediaItemFor(int position); MediaItem getMediaItemFor(int position);
void pause(int position); void pause(int position);
@Nullable View getPlaybackControls(int position); @Nullable View getPlaybackControls(int position);
boolean hasFragmentFor(int position);
} }
} }
@@ -31,7 +31,11 @@ import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import org.thoughtcrime.securesms.conversation.ConversationItem; import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.os.Parcelable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@@ -51,7 +55,6 @@ import org.thoughtcrime.securesms.database.loaders.MessageDetailsLoader;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
@@ -61,6 +64,7 @@ import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ExpirationUtil; import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Optional;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@@ -131,13 +135,13 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
assert getSupportActionBar() != null; assert getSupportActionBar() != null;
getSupportActionBar().setTitle(R.string.AndroidManifest__message_details); getSupportActionBar().setTitle(R.string.AndroidManifest__message_details);
MessageNotifier.setVisibleThread(threadId); ApplicationDependencies.getMessageNotifier().setVisibleThread(threadId);
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
MessageNotifier.setVisibleThread(-1L); ApplicationDependencies.getMessageNotifier().clearVisibleThread();
} }
@Override @Override
@@ -268,7 +272,9 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
toFrom.setText(toFromRes); toFrom.setText(toFromRes);
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, null, false); conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, null, false);
Parcelable state = recipientsList.onSaveInstanceState();
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup)); recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
recipientsList.onRestoreInstanceState(state);
} }
private void inflateMessageViewIfAbsent(MessageRecord messageRecord) { private void inflateMessageViewIfAbsent(MessageRecord messageRecord) {
@@ -276,9 +282,9 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
if (messageRecord.isGroupAction()) { if (messageRecord.isGroupAction()) {
conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_update, itemParent, false); conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_update, itemParent, false);
} else if (messageRecord.isOutgoing()) { } else if (messageRecord.isOutgoing()) {
conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_sent, itemParent, false); conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_sent_multimedia, itemParent, false);
} else { } else {
conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_received, itemParent, false); conversationItem = (ConversationItem) inflater.inflate(R.layout.conversation_item_received_multimedia, itemParent, false);
} }
itemParent.addView(conversationItem); itemParent.addView(conversationItem);
} }
@@ -368,7 +374,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId()); List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
if (receiptInfoList.isEmpty()) { if (receiptInfoList.isEmpty()) {
List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireAddress().toGroupString(), false); List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
for (Recipient recipient : group) { for (Recipient recipient : group) {
recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1)); recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1));
@@ -438,8 +444,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
} }
private void onResendClicked(View v) { private void onResendClicked(View v) {
MessageSender.resend(MessageDetailsActivity.this, messageRecord);
resendButton.setVisibility(View.GONE); resendButton.setVisibility(View.GONE);
SignalExecutors.BOUNDED.execute(() -> MessageSender.resend(MessageDetailsActivity.this, messageRecord));
} }
} }
} }
@@ -11,7 +11,9 @@ import android.widget.BaseAdapter;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Conversions; import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.adapter.StableIdGenerator;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -19,11 +21,12 @@ import java.util.List;
class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener { class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
private final Context context; private final Context context;
private final GlideRequests glideRequests; private final GlideRequests glideRequests;
private final MessageRecord record; private final MessageRecord record;
private final List<RecipientDeliveryStatus> members; private final List<RecipientDeliveryStatus> members;
private final boolean isPushGroup; private final boolean isPushGroup;
private final StableIdGenerator<RecipientId> idGenerator;
MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests, MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
@NonNull MessageRecord record, @NonNull List<RecipientDeliveryStatus> members, @NonNull MessageRecord record, @NonNull List<RecipientDeliveryStatus> members,
@@ -34,6 +37,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
this.record = record; this.record = record;
this.isPushGroup = isPushGroup; this.isPushGroup = isPushGroup;
this.members = members; this.members = members;
this.idGenerator = new StableIdGenerator<>();
} }
@Override @Override
@@ -48,11 +52,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
try { return idGenerator.getId(members.get(position).recipient.getId());
return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.requireAddress().serialize().getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
} }
@Override @Override
@@ -81,23 +81,45 @@ public class MessageRecipientListItem extends RelativeLayout
this.deliveryStatusView = findViewById(R.id.delivery_status); this.deliveryStatusView = findViewById(R.id.delivery_status);
} }
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
observeMember();
}
@Override
protected void onDetachedFromWindow() {
unsubscribeFromMember();
super.onDetachedFromWindow();
}
public void set(final GlideRequests glideRequests, public void set(final GlideRequests glideRequests,
final MessageRecord record, final MessageRecord record,
final RecipientDeliveryStatus member, final RecipientDeliveryStatus member,
final boolean isPushGroup) final boolean isPushGroup)
{ {
if (this.member != null) this.member.getRecipient().live().removeForeverObserver(this); unsubscribeFromMember();
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
this.member = member; this.member = member;
observeMember();
member.getRecipient().live().observeForever(this);
fromView.setText(member.getRecipient()); fromView.setText(member.getRecipient());
contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false); contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false);
setIssueIndicators(record, isPushGroup); setIssueIndicators(record, isPushGroup);
unidentifiedDeliveryIcon.setVisibility(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()) && member.isUnidentified() ? VISIBLE : GONE); unidentifiedDeliveryIcon.setVisibility(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()) && member.isUnidentified() ? VISIBLE : GONE);
} }
private void observeMember() {
if (isAttachedToWindow() && member != null && member.getRecipient() != null) {
member.getRecipient().live().observeForever(this);
}
}
private void unsubscribeFromMember() {
if (member != null && member.getRecipient() != null) member.getRecipient().live().removeForeverObserver(this);
}
private void setIssueIndicators(final MessageRecord record, private void setIssueIndicators(final MessageRecord record,
final boolean isPushGroup) final boolean isPushGroup)
{ {
@@ -162,7 +184,7 @@ public class MessageRecipientListItem extends RelativeLayout
} }
public void unbind() { public void unbind() {
if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().live().removeForeverObserver(this); unsubscribeFromMember();
} }
@Override @Override
@@ -1,8 +1,10 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -23,6 +25,10 @@ public class MuteDialog extends AlertDialog {
} }
public static void show(final Context context, final @NonNull MuteSelectionListener listener) { public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
show(context, listener, null);
}
public static void show(final Context context, final @NonNull MuteSelectionListener listener, @Nullable Runnable cancelListener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context); AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.MuteDialog_mute_notifications); builder.setTitle(R.string.MuteDialog_mute_notifications);
builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() { builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
@@ -43,6 +49,13 @@ public class MuteDialog extends AlertDialog {
} }
}); });
if (cancelListener != null) {
builder.setOnCancelListener(dialog -> {
cancelListener.run();
dialog.dismiss();
});
}
builder.show(); builder.show();
} }
@@ -19,14 +19,16 @@ package org.thoughtcrime.securesms;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.util.guava.Optional;
/** /**
* Activity container for starting a new conversation. * Activity container for starting a new conversation.
@@ -35,7 +37,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
* *
*/ */
public class NewConversationActivity extends ContactSelectionActivity public class NewConversationActivity extends ContactSelectionActivity
implements ContactSelectionListFragment.InviteCallback implements ContactSelectionListFragment.ListCallback
{ {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -49,9 +51,18 @@ public class NewConversationActivity extends ContactSelectionActivity
} }
@Override @Override
public void onContactSelected(String number) { public void onContactSelected(Optional<RecipientId> recipientId, String number) {
Recipient recipient = Recipient.external(this, number); Recipient recipient;
if (recipientId.isPresent()) {
recipient = Recipient.resolved(recipientId.get());
} else {
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
recipient = Recipient.external(this, number);
}
launch(recipient);
}
private void launch(Recipient recipient) {
Intent intent = new Intent(this, ConversationActivity.class); Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId()); intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA)); intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
@@ -85,7 +96,7 @@ public class NewConversationActivity extends ContactSelectionActivity
} }
private void handleCreateGroup() { private void handleCreateGroup() {
startActivity(new Intent(this, GroupCreateActivity.class)); startActivity(CreateGroupActivity.newIntent(this));
} }
private void handleInvite() { private void handleInvite() {
@@ -93,10 +104,10 @@ public class NewConversationActivity extends ContactSelectionActivity
} }
@Override @Override
protected boolean onPrepareOptionsPanel(View view, Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
MenuInflater inflater = this.getMenuInflater();
menu.clear(); menu.clear();
inflater.inflate(R.menu.new_conversation_activity, menu); getMenuInflater().inflate(R.menu.new_conversation_activity, menu);
super.onPrepareOptionsMenu(menu); super.onPrepareOptionsMenu(menu);
return true; return true;
} }
@@ -104,5 +115,12 @@ public class NewConversationActivity extends ContactSelectionActivity
@Override @Override
public void onInvite() { public void onInvite() {
handleInvite(); handleInvite();
finish();
}
@Override
public void onNewGroup(boolean forceV1) {
handleCreateGroup();
finish();
} }
} }
@@ -41,6 +41,8 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class PassphraseChangeActivity extends PassphraseActivity { public class PassphraseChangeActivity extends PassphraseActivity {
private static final String TAG = Log.tag(PassphraseChangeActivity.class);
private DynamicTheme dynamicTheme = new DynamicTheme(); private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage(); private DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -145,7 +147,7 @@ public class PassphraseChangeActivity extends PassphraseActivity {
return masterSecret; return masterSecret;
} catch (InvalidPassphraseException e) { } catch (InvalidPassphraseException e) {
Log.w(PassphraseChangeActivity.class.getSimpleName(), e); Log.w(TAG, e);
return null; return null;
} }
} }
@@ -55,6 +55,7 @@ import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException; import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil; import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
import org.thoughtcrime.securesms.util.DynamicIntroTheme; import org.thoughtcrime.securesms.util.DynamicIntroTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -164,7 +165,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
} }
private void handleLogSubmit() { private void handleLogSubmit() {
Intent intent = new Intent(this, LogSubmitActivity.class); Intent intent = new Intent(this, SubmitDebugLogActivity.class);
startActivity(intent); startActivity(intent);
} }
@@ -237,7 +238,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
EditorInfo.IME_ACTION_DONE); EditorInfo.IME_ACTION_DONE);
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp); fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN); fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
lockScreenButton.setOnClickListener(v -> resumeScreenLock()); lockScreenButton.setOnClickListener(v -> resumeScreenLock());
} }
@@ -277,7 +278,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null); fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
} else if (Build.VERSION.SDK_INT >= 21){ } else if (Build.VERSION.SDK_INT >= 21){
Log.i(TAG, "firing intent..."); Log.i(TAG, "firing intent...");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Signal", ""); Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
startActivityForResult(intent, 1); startActivityForResult(intent, 1);
} else { } else {
Log.w(TAG, "Not compatible..."); Log.w(TAG, "Not compatible...");
@@ -357,7 +358,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
handleAuthenticated(); handleAuthenticated();
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp); fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN); fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
} }
}).start(); }).start();
} }
@@ -380,7 +381,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
@Override @Override
public void onAnimationEnd(Animation animation) { public void onAnimationEnd(Animation animation) {
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp); fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN); fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.core_ultramarine), PorterDuff.Mode.SRC_IN);
} }
@Override @Override
@@ -0,0 +1,272 @@
package org.thoughtcrime.securesms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity implements MasterSecretListener {
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
public static final String LOCALE_EXTRA = "locale_extra";
public static final String NEXT_INTENT_EXTRA = "next_intent";
private static final int STATE_NORMAL = 0;
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
private static final int STATE_WELCOME_PUSH_SCREEN = 4;
private static final int STATE_ENTER_SIGNAL_PIN = 5;
private static final int STATE_CREATE_PROFILE_NAME = 6;
private static final int STATE_CREATE_SIGNAL_PIN = 7;
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
@Override
protected final void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onCreate()");
this.networkAccess = new SignalServiceNetworkAccess(this);
onPreCreate();
final boolean locked = KeyCachingService.isLocked(this);
routeApplicationState(locked);
super.onCreate(savedInstanceState);
if (!isFinishing()) {
initializeClearKeyReceiver();
onCreate(savedInstanceState, true);
}
}
protected void onPreCreate() {}
protected void onCreate(Bundle savedInstanceState, boolean ready) {}
@Override
protected void onResume() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onResume()");
super.onResume();
if (networkAccess.isCensored(this)) {
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
}
}
@Override
protected void onStart() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onStart()");
super.onStart();
}
@Override
protected void onPause() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onPause()");
super.onPause();
}
@Override
protected void onStop() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onStop()");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onDestroy()");
super.onDestroy();
removeClearKeyReceiver(this);
}
@Override
public void onMasterSecretCleared() {
Log.d(TAG, "onMasterSecretCleared()");
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
else finish();
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment)
{
return initFragment(target, fragment, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@Nullable Locale locale)
{
return initFragment(target, fragment, locale, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@Nullable Locale locale,
@Nullable Bundle extras)
{
Bundle args = new Bundle();
args.putSerializable(LOCALE_EXTRA, locale);
if (extras != null) {
args.putAll(extras);
}
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(target, fragment)
.commitAllowingStateLoss();
return fragment;
}
private void routeApplicationState(boolean locked) {
Intent intent = getIntentForState(getApplicationState(locked));
if (intent != null) {
startActivity(intent);
finish();
}
}
private Intent getIntentForState(int state) {
Log.d(TAG, "routeApplicationState(), state: " + state);
switch (state) {
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
default: return null;
}
}
private int getApplicationState(boolean locked) {
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
return STATE_CREATE_PASSPHRASE;
} else if (locked) {
return STATE_PROMPT_PASSPHRASE;
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
return STATE_UI_BLOCKING_UPGRADE;
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
return STATE_WELCOME_PUSH_SCREEN;
} else if (SignalStore.storageServiceValues().needsAccountRestore()) {
return STATE_ENTER_SIGNAL_PIN;
} else if (userMustSetProfileName()) {
return STATE_CREATE_PROFILE_NAME;
} else if (userMustCreateSignalPin()) {
return STATE_CREATE_SIGNAL_PIN;
} else {
return STATE_NORMAL;
}
}
private boolean userMustCreateSignalPin() {
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed();
}
private boolean userMustSetProfileName() {
return !SignalStore.registrationValues().isRegistrationComplete() && Recipient.self().getProfileName().isEmpty();
}
private Intent getCreatePassphraseIntent() {
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
}
private Intent getPromptPassphraseIntent() {
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
}
private Intent getUiBlockingUpgradeIntent() {
return getRoutedIntent(ApplicationMigrationActivity.class,
TextSecurePreferences.hasPromptedPushRegistration(this)
? getConversationListIntent()
: getPushRegistrationIntent());
}
private Intent getPushRegistrationIntent() {
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
}
private Intent getEnterSignalPinIntent() {
return getRoutedIntent(PinRestoreActivity.class, getIntent());
}
private Intent getCreateSignalPinIntent() {
final Intent intent;
if (userMustSetProfileName()) {
intent = getCreateProfileNameIntent();
} else {
intent = getIntent();
}
return getRoutedIntent(CreateKbsPinActivity.class, intent);
}
private Intent getCreateProfileNameIntent() {
return getRoutedIntent(EditProfileActivity.class, getIntent());
}
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
final Intent intent = new Intent(this, destination);
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
return intent;
}
private Intent getConversationListIntent() {
// TODO [greyson] Navigation
return new Intent(this, MainActivity.class);
}
private void initializeClearKeyReceiver() {
this.clearKeyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive() for clear key event");
onMasterSecretCleared();
}
};
IntentFilter filter = new IntentFilter(KeyCachingService.CLEAR_KEY_EVENT);
registerReceiver(clearKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
}
private void removeClearKeyReceiver(Context context) {
if (clearKeyReceiver != null) {
context.unregisterReceiver(clearKeyReceiver);
clearKeyReceiver = null;
}
}
/**
* Puts an extra in {@code intent} so that {@code nextIntent} will be shown after it.
*/
public static @NonNull Intent chainIntent(@NonNull Intent intent, @NonNull Intent nextIntent) {
intent.putExtra(NEXT_INTENT_EXTRA, nextIntent);
return intent;
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.ArrayList;
import java.util.List;
/**
* Activity container for selecting a list of contacts.
*
* @author Moxie Marlinspike
*
*/
public class PushContactSelectionActivity extends ContactSelectionActivity {
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
@SuppressWarnings("unused")
private final static String TAG = PushContactSelectionActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle icicle, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
super.onCreate(icicle, ready);
initializeToolbar();
}
protected void initializeToolbar() {
getToolbar().setNavigationIcon(R.drawable.ic_check_24);
getToolbar().setNavigationOnClickListener(v -> {
onFinishedSelection();
});
}
protected final void onFinishedSelection() {
Intent resultIntent = getIntent();
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
List<RecipientId> recipients = Stream.of(selectedContacts).map(sc -> sc.getOrCreateRecipientId(this)).toList();
resultIntent.putParcelableArrayListExtra(KEY_SELECTED_RECIPIENTS, new ArrayList<>(recipients));
setResult(RESULT_OK, resultIntent);
finish();
}
}

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