Compare commits

..

2639 Commits

Author SHA1 Message Date
Cody Henthorne e37d3be73a Bump version to 6.13.2 2023-02-23 20:20:50 -05:00
Cody Henthorne 16c2609dab Updated language translations. 2023-02-23 20:01:05 -05:00
Nicholas e4d4a5d9e0 Adapt change number flow to use V2 API. 2023-02-23 19:56:32 -05:00
Greyson Parrelli a552a5a5bc Updated in-app language selector to always use native language name. 2023-02-23 19:11:46 -05:00
Greyson Parrelli 13d48b880b Add language support for Cantonese. 2023-02-23 18:58:39 -05:00
Cody Henthorne 6cb8c7a8a9 Ensure attributes are updated with latest properties. 2023-02-23 16:51:25 -05:00
Chris Eager ae3ff21689 Update spam reporting token JSON field name. 2023-02-23 16:27:14 -05:00
Cody Henthorne d3c3986100 Bump version to 6.13.1 2023-02-23 12:38:57 -05:00
Cody Henthorne 6f3c095a95 Updated language translations. 2023-02-23 12:30:21 -05:00
Cody Henthorne 6ee04f6574 Fix crash after entering incorrect pin for registration lock. 2023-02-23 12:24:39 -05:00
Nicholas f3922c4156 Fix bottom sheet behavior and design. 2023-02-23 12:24:39 -05:00
Cody Henthorne 2ffc576387 Fix verification file for windows builders. 2023-02-23 12:24:39 -05:00
Cody Henthorne 583f7db554 Clear old group rings on startup. 2023-02-23 12:24:39 -05:00
Cody Henthorne 1cffd88af2 Fix crashes during skip SMS flow. 2023-02-23 12:24:39 -05:00
Cody Henthorne 01351125f1 Fix reporting token data bug. 2023-02-23 08:08:21 -05:00
Greyson Parrelli 19d67d1111 Bump version to 6.13.0 2023-02-22 22:35:00 -05:00
Greyson Parrelli 8cec6a8b0c Updated language translations. 2023-02-22 22:34:30 -05:00
Greyson Parrelli e8b3d2c7aa Improve logging around message processing. 2023-02-22 22:26:14 -05:00
Cody Henthorne 62414e72b5 Add support for pin entry sad paths. 2023-02-22 22:26:14 -05:00
Nicholas afb9b76208 Bug fixes for the new registration flow. 2023-02-22 22:26:14 -05:00
Cody Henthorne 4f458a022f Add skip SMS flow. 2023-02-22 22:26:14 -05:00
Nicholas a47e3900c1 Implement session-based account registration API. 2023-02-22 22:11:58 -05:00
Jim Gustafson 3de17fa2d0 Update to RingRTC v2.25.0 2023-02-22 19:09:55 -05:00
Clark 7abf358ac4 Pre-cache conversation_list_item_view to speed up cold start. 2023-02-22 16:50:08 -05:00
Greyson Parrelli 64d5cbce3d Fix bug where you could choose to add someone already in a group. 2023-02-22 16:08:39 -05:00
Greyson Parrelli 691ab353da Fix possible dlist sync crash, improved debugging.
Fixes #12795
2023-02-22 14:18:41 -05:00
Greyson Parrelli b689ea62a6 Fix using system emoji in condensed message mode. 2023-02-22 13:14:53 -05:00
Greyson Parrelli 17aa0365d6 Handle keepMutedChatsArchived more generically, also apply it to sends.
Fixes #12788
2023-02-22 13:14:44 -05:00
Greyson Parrelli 3f93d4b9fc Change ReportSpamJob lifespan to 1 day. 2023-02-22 10:59:09 -05:00
Greyson Parrelli 263fb9fc04 Use null when submitting empty reporting tokens.
Cleaned up a few things too, just spacing and stuff.
2023-02-22 10:59:04 -05:00
Greyson Parrelli 3ebafca297 Validate that reporting token is non-null and non-empty. 2023-02-22 10:33:56 -05:00
Greyson Parrelli 316df00287 Save username when applying update to AccountRecord. 2023-02-22 09:23:49 -05:00
Greyson Parrelli b92346d4ae Make our FABs rounded rects again. 2023-02-21 14:42:04 -05:00
Greyson Parrelli 7bdb5fd76c Update material library to 1.8.0
Fixes #12792
2023-02-21 11:32:24 -05:00
Greyson Parrelli dad9980a80 Add Observable for LiveRecipient. 2023-02-21 11:32:24 -05:00
Greyson Parrelli 21df032b04 Mark Recipient.self() as needing sync after change PNP setting. 2023-02-21 11:32:24 -05:00
Alex Hart 7edebe9fa1 Clean up a couple warnings in MediaPreviewV2Fragment. 2023-02-21 11:32:24 -05:00
Alex Hart a398745740 Implement username is out of sync banner. 2023-02-21 11:32:24 -05:00
Greyson Parrelli 4954be109c Bump version to 6.12.5 2023-02-21 09:59:42 -05:00
Greyson Parrelli 7380d4b11e Updated language translations. 2023-02-21 09:59:19 -05:00
Greyson Parrelli d8a6f9c324 Fix possible crash when finishing animation. 2023-02-21 09:45:55 -05:00
Greyson Parrelli ab9057cb25 Fix possible crash during backup restore. 2023-02-21 09:45:44 -05:00
Greyson Parrelli ba8ea3b54b Bump version to 6.12.4 2023-02-17 15:33:51 -05:00
Greyson Parrelli d9aca34eee Updated language translations. 2023-02-17 15:33:09 -05:00
Greyson Parrelli 05d232beec Fix possible crash in conversation fragment startup. 2023-02-17 15:25:45 -05:00
Greyson Parrelli fde0726500 Fix bug where you couldn't add new raw phone numbers to groups. 2023-02-17 15:17:18 -05:00
Greyson Parrelli bb8b987833 Ignore lastProfileFetchTime when determining Recipient equality.
Was resulting in some unnecessary Recipient re-renders during
conversation open.
2023-02-17 10:12:42 -05:00
Alex Hart f066fb8ea2 Tweak media transition fade. 2023-02-16 17:26:53 -04:00
Greyson Parrelli 7738c286c2 Bump version to 6.12.3 2023-02-16 16:08:47 -05:00
Greyson Parrelli 697670b334 Updated language translations. 2023-02-16 16:08:47 -05:00
Greyson Parrelli 4cfba86cb1 Fix bug where username wasn't synced to ContactRecord. 2023-02-16 16:08:47 -05:00
Greyson Parrelli ce4e84aadc Remove crash for group remap updates. 2023-02-16 16:08:47 -05:00
Cody Henthorne 23d0152767 Use newer APIs for wave form generation. 2023-02-16 16:08:47 -05:00
Greyson Parrelli d714590d3f Address bioauth issues on API 28. 2023-02-16 16:08:47 -05:00
Alex Hart 65bc1263f3 Add final copy for username deletion snackbar. 2023-02-16 16:08:47 -05:00
Alex Hart 730065fc76 Add support URL for usernames. 2023-02-16 16:08:47 -05:00
Alex Hart 1e10b82769 Fix gif sizing in conversation. 2023-02-16 16:08:47 -05:00
Cody Henthorne 01f477a587 Fix voice note UX issues. 2023-02-16 16:08:47 -05:00
Alex Hart 76383fe1bc Fix RTL corners in AlbumThumbnailView. 2023-02-16 16:08:47 -05:00
Greyson Parrelli c61f45b88b Remove old private certificate authority. 2023-02-16 16:08:47 -05:00
Greyson Parrelli e559198495 Fix crash when creating backup. 2023-02-16 16:08:47 -05:00
Greyson Parrelli 8676cb27ae Don't show add contact button if the user has no e164.
Fixes #12570
2023-02-16 16:08:46 -05:00
Alex Hart 7215ca6a28 Fix issue where view padding would not properly update on rotation. 2023-02-16 16:08:46 -05:00
Alex Hart ef11a8d98d Fix navbar flashing on transform. 2023-02-16 16:08:46 -05:00
Greyson Parrelli 6efd501f1c Don't send MR accept for unblocking.
That's handled by storage service now.
2023-02-15 18:07:40 -05:00
Greyson Parrelli 66c650e859 Bump version to 6.12.2 2023-02-15 17:35:23 -05:00
Greyson Parrelli 8141f7a5cf Updated language translations. 2023-02-15 17:35:23 -05:00
Greyson Parrelli 0fcdf61e76 Revert "Don't run FTS optimize job (for now)."
This reverts commit f26b2c0b2a.
2023-02-15 17:35:23 -05:00
Greyson Parrelli fa571f14e6 Update SQLCipher to 4.5.3-FTS-S2 2023-02-15 17:35:23 -05:00
Cody Henthorne 583860053b Cancel scheduled message alarm if no messages are scheduled. 2023-02-15 17:35:23 -05:00
Alex Hart ad70baf557 Add documentation to DisplaySecondaryInformation. 2023-02-15 17:35:23 -05:00
Cody Henthorne 2b0e9783a7 Fix invalid attachment data during sms export. 2023-02-15 17:35:23 -05:00
Alex Hart c75a9b577d Prefer about over phone number. 2023-02-15 17:35:23 -05:00
Cody Henthorne e8ff1a04ed Fix scrolling issue in transfer lock dialog for small displays. 2023-02-15 17:35:22 -05:00
Cody Henthorne a22a696722 Fix missing padding in schedule message time picker. 2023-02-15 17:35:22 -05:00
Alex Hart 66494fa418 Fix transition for very tall images. 2023-02-15 17:35:22 -05:00
Greyson Parrelli 9fd763fe83 Bump version to 6.12.1 2023-02-15 13:32:37 -05:00
Greyson Parrelli 1d508ad5cc Updated language translations. 2023-02-15 13:31:58 -05:00
Alex Hart c1c7f57ec0 Fix sticker scaling. 2023-02-15 13:31:58 -05:00
Cody Henthorne 6100160e18 Address various issues with dark theme registration flow. 2023-02-15 13:24:15 -05:00
Alex Hart e2c3db3eda Fix miscalculation of groups in common. 2023-02-15 13:24:15 -05:00
Alex Hart 6759b59507 Fix toolbar height and fade in. 2023-02-15 13:24:15 -05:00
Greyson Parrelli e36844fe78 Improve logging around unblocking. 2023-02-15 13:24:15 -05:00
Alex Hart 5cf937215a Fix stub of TransferControlsView. 2023-02-15 13:24:15 -05:00
Alex Hart 1b49b9bffb Hide system UI until the shared element transition completes. 2023-02-15 13:24:15 -05:00
Alex Hart a3a29d5cb2 Prevent shared element animation when we're not on the initial media. 2023-02-15 13:24:15 -05:00
Nicholas 6fbfb87bd6 Restore entered phone number post-captcha. 2023-02-15 13:24:15 -05:00
Alex Hart 2bff2d3a30 Disable shared element transitions from bubble. 2023-02-15 13:24:15 -05:00
Cody Henthorne a88410faaf Fix incorrect quick react emojis for story replies. 2023-02-15 13:24:15 -05:00
Greyson Parrelli f26b2c0b2a Don't run FTS optimize job (for now). 2023-02-15 13:24:15 -05:00
Alex Hart 6f1b03eac6 Utilize fade instead of just setting alpha to 0. 2023-02-15 10:57:54 -04:00
Cody Henthorne 9610339f38 Improve UX around seeing audio wave forms.
- Attempts to generate the wave form on download instead on display
- Allows multi-threaded generation of wave forms instead of serial
  executor
2023-02-15 09:43:16 -05:00
Alex Hart d4ce8458a4 Ensure e164s are pretty-printed. 2023-02-15 10:10:25 -04:00
Greyson Parrelli 384cdf8610 Bump version to 6.12.0 2023-02-14 22:47:48 -05:00
Greyson Parrelli 3ee30808de Updated language translations. 2023-02-14 22:47:20 -05:00
Greyson Parrelli 78c64880f7 Fix instance where PNI may be accessed too early. 2023-02-14 14:51:29 -05:00
Greyson Parrelli b99ce9cc1d Fix typo in string. 2023-02-14 14:30:25 -05:00
Greyson Parrelli 41f796d809 Update CDSI_MRENCLAVE. 2023-02-14 14:28:22 -05:00
Alex Hart ec504af593 Improve conversation open speed. 2023-02-14 15:10:08 -04:00
Greyson Parrelli 60874ba57b Fix contact name syncing to storage service. 2023-02-14 14:03:09 -05:00
Greyson Parrelli 4397b5af25 Add support for storing systemNickname from storage service. 2023-02-14 14:03:09 -05:00
Rashad Sookram 07234443c6 Update verification metadata for MacOS. 2023-02-14 14:03:09 -05:00
Alex Hart c027203e8c Polish thumbnail animation. 2023-02-14 14:03:09 -05:00
Alex Hart 417db2341b Utilize drawable instead of bitmap for transition. 2023-02-14 14:03:09 -05:00
Bernie Dolan 6aa4ef95b5 Update payments to 4.0.0.1 2023-02-14 14:03:09 -05:00
Greyson Parrelli 6145fa213e Move common gradle config into convention plugins. 2023-02-14 14:03:09 -05:00
Greyson Parrelli 9fa4741e49 Update ContactRecord.hidden field to value 20. 2023-02-14 14:03:09 -05:00
Greyson Parrelli b9d5fb54c3 Allow using the location picker with approximate location. 2023-02-14 14:03:09 -05:00
Greyson Parrelli c0fe156897 Do not fail message inserts on bad quote attachments.
Fixes #12721
2023-02-14 14:03:09 -05:00
Alex Hart 22cad64089 Clean up ThumbnailView warnings. 2023-02-14 14:03:09 -05:00
Alex Hart 702cf6ef71 Remove unused layout class. 2023-02-14 14:03:09 -05:00
Alex Hart d7c3112602 Speed up thumbnail transition. 2023-02-14 14:03:09 -05:00
Greyson Parrelli d9c31a6cd6 Update AGP to 7.4.0 2023-02-14 14:03:09 -05:00
Greyson Parrelli 408c288936 Convert MediaTable to kotlin. 2023-02-14 14:03:09 -05:00
Greyson Parrelli af6f16bdb6 Move Backups.proto to Wire. 2023-02-14 14:03:09 -05:00
Cody Henthorne 055ceba398 Add 'AnyAddressPorts' calling field trial flag. 2023-02-14 14:03:08 -05:00
Greyson Parrelli 3f81a94176 Fix case where we were performing remote inserts. 2023-02-14 14:02:23 -05:00
Greyson Parrelli a02d2e467b Revert "Remove the unknown insert validation."
This reverts commit 320669c54e.
2023-02-14 14:02:23 -05:00
Greyson Parrelli 414550861e Prevent recursive early content processing. 2023-02-14 14:02:23 -05:00
Greyson Parrelli afbce6f800 Re-enable FTS optimization after deletes. 2023-02-14 14:02:23 -05:00
Alex Hart dda5037429 Add stubbing to ConversationThumbnailView and caching to a typeface. 2023-02-14 14:02:23 -05:00
Greyson Parrelli ffbebe0670 Update SQLCipher to 4.5.3-FTS-S1 2023-02-14 14:02:23 -05:00
Alex Hart cf250b4b32 Add catch for candidate generation error to be treated the same as username unavailable. 2023-02-14 14:02:23 -05:00
Nicholas Tinsley b14aea0922 Support dark mode in verification code keyboard. 2023-02-14 14:02:23 -05:00
Alex Hart d0de43a6b2 Add thumbnail shared element animation. 2023-02-14 14:02:23 -05:00
Alex Hart 2c48d40375 Update API endpoints and integration for usernames. 2023-02-14 14:02:23 -05:00
Greyson Parrelli 803154c544 Add a new PNP build flavor. 2023-02-14 14:02:23 -05:00
Greyson Parrelli 684150dc1e Handle split contacts in storage service when in PNP mode. 2023-02-14 14:02:22 -05:00
Greyson Parrelli fdcf0a76e8 Split unregistered contacts when in PNP mode. 2023-02-14 14:02:22 -05:00
Greyson Parrelli 9e056e5dd0 Add support for rendering session switchover events. 2023-02-14 14:02:22 -05:00
Cody Henthorne 03c68375db Fix bad group state when requesting to rejoin a group. 2023-02-14 14:02:22 -05:00
Alex Hart 5d328857aa Upgrade libsignal to 0.22.0 2023-02-14 14:02:22 -05:00
Cody Henthorne 3a0dbe6e67 Use alarm clock for scheduling message sends. 2023-02-14 14:02:22 -05:00
Cody Henthorne 56b35f3767 Fix quoted links from rendering as clickable. 2023-02-14 14:02:22 -05:00
Greyson Parrelli 7f0221c5c6 Prefer MessageRecord mismatches when updating SN's. 2023-02-14 14:02:22 -05:00
Cody Henthorne 23050152de Replace time duration picker dialog for screen lock timeout. 2023-02-14 14:02:22 -05:00
Alex Hart db65edb7df Mark DSL api discouraged. 2023-02-14 14:02:22 -05:00
Alex Hart 605289aca4 Upgrade ktlint and add twitter compose rules. 2023-02-14 14:02:22 -05:00
Jim Gustafson 52e9b31554 Update to RingRTC v2.24.0 2023-02-14 14:02:22 -05:00
Alex Hart c8e6ccc0c0 Add extended colors to SignalTheme. 2023-02-14 14:02:22 -05:00
Alex Hart f20d929292 Add Buttons object for properly themed compose buttons. 2023-02-14 14:02:22 -05:00
Greyson Parrelli a9accfb074 Bump version to 6.11.7 2023-02-14 14:01:40 -05:00
Greyson Parrelli 8f2d1a2d12 Updated language translations. 2023-02-14 14:01:40 -05:00
Greyson Parrelli ca8755c6ad Inline the scheduled message feature flag. 2023-02-14 14:01:40 -05:00
Cody Henthorne dc4eb7911d Bump version to 6.11.6 2023-02-13 13:43:23 -05:00
Cody Henthorne eb2e0205ae Updated language translations. 2023-02-13 13:31:23 -05:00
Cody Henthorne 7a72a9a0d7 Fix memory leak in conversation fragment. 2023-02-13 13:07:32 -05:00
Alex Hart 805ccc4f7a Bump version to 6.11.5 2023-02-10 16:13:23 -04:00
Alex Hart 499b186b68 Updated language translations. 2023-02-10 16:12:21 -04:00
Cody Henthorne c741e32824 Fix stale thread id when a conversation is deleted. 2023-02-10 13:08:51 -05:00
Alex Hart fba4c882cb Bump version to 6.11.4 2023-02-09 16:16:41 -04:00
Alex Hart 24ef853f24 Updated language translations. 2023-02-09 16:16:04 -04:00
Nicholas 9f22ba68ea Set PIN entry text to use dynamic theme colors. 2023-02-09 13:45:26 -05:00
Greyson Parrelli d8eac87219 Cleanup dangling MSL rows. 2023-02-09 13:41:32 -05:00
Greyson Parrelli cf71e2cfa8 Bump version to 6.11.3 2023-02-08 21:21:36 -05:00
Greyson Parrelli 7f16d0653c Updated language translations. 2023-02-08 21:20:43 -05:00
Greyson Parrelli 61e127fabf Fix method to find MMS group. 2023-02-08 21:13:14 -05:00
Alex Hart 7ffdf91ce5 Bump version to 6.11.2 2023-02-07 15:06:25 -04:00
Alex Hart 4c26fe432e Updated language translations. 2023-02-07 15:06:25 -04:00
Cody Henthorne 6c78a405bb Fix backup scheduling looping bug. 2023-02-07 15:06:25 -04:00
Cody Henthorne 89b0167fd2 Ensure backup job verification can be cancelled. 2023-02-07 11:19:26 -05:00
Alex Hart e25133fa29 Bump version to 6.11.1 2023-02-06 17:15:26 -04:00
Alex Hart 4ba77c0f9f Updated language translations. 2023-02-06 17:09:45 -04:00
Nicholas Tinsley 10f376e402 Catch new audio recording error states. 2023-02-06 16:53:08 -04:00
Cody Henthorne 7bae8b6e1b Fix scheduled message sends changing thread disappearing message timer. 2023-02-06 16:53:08 -04:00
Cody Henthorne 67fb9d09d4 Fix scheduled send in note to self with no linked devices. 2023-02-06 16:53:08 -04:00
Nicholas 3b40b10a77 Try to check group mute status for keeping archived. 2023-02-06 16:53:08 -04:00
Cody Henthorne 418b486776 Fix crash when scheduling a message in an empty thread. 2023-02-06 16:53:08 -04:00
Cody Henthorne 1f31f4a50a Adjust SMS phases and show Phase 3 start date. 2023-02-06 16:53:08 -04:00
Cody Henthorne 9b08ebcc1d Update QR code and send symbols. 2023-02-06 16:53:08 -04:00
Nicholas aec4944c56 Allow V1 groups to be deleted by clearing app data. 2023-02-06 16:53:08 -04:00
Nicholas Tinsley 9a1f8af703 Add Material3 SVG assets to Registration. 2023-02-06 16:53:08 -04:00
Greyson Parrelli 268b11c4e1 Fix bug in MSL table definition. 2023-02-06 16:53:08 -04:00
Nicholas Tinsley 2e3d73f44b Media preview design tweaks. 2023-02-06 16:53:08 -04:00
Alex Hart f477a4dae9 Add compose bottom-sheet handle. 2023-02-06 16:53:08 -04:00
Greyson Parrelli a41aed20e1 Fix issue where group stories weren't syncing to linked devices. 2023-02-03 12:08:21 -05:00
Alex Hart 1ed3dbb147 Add svg asset for username megaphone. 2023-02-03 09:46:42 -04:00
Alex Hart fcfb9fad01 Add svg assets to username education screen. 2023-02-03 09:45:13 -04:00
Alex Hart 25c96a6be6 Resolve crashing when trying to get the header letters for the contacts section of search. 2023-02-03 09:33:18 -04:00
Nicholas Tinsley 90695182f3 Bump version to 6.11.0 2023-02-02 17:55:33 -05:00
Nicholas Tinsley 1c38ab18b8 Updated language translations. 2023-02-02 17:55:12 -05:00
Alex Hart 7c716e5525 Fix slow kotlin build. 2023-02-02 17:22:40 -05:00
Cody Henthorne 56a44ae65c Enforce expected ordering when scheduling text and media messages. 2023-02-02 17:22:40 -05:00
Nicholas d33aa247db Fix composer voice memo cancellation due to focus loss. 2023-02-02 17:22:40 -05:00
Alex Hart 63a153571d Add generic Compose fragment. 2023-02-02 17:22:40 -05:00
Alex Hart fb07e897d0 Mark username megaphone completion after hitting continue. 2023-02-02 17:22:40 -05:00
Alex Hart 93387ec79a Add deletion snackbar for usernames with temporary copy. 2023-02-02 17:22:40 -05:00
Alex Hart cd79dbbb82 Add Username UI updates. 2023-02-02 17:22:40 -05:00
Alex Hart 7fbfc09a89 Refactor ContactSelectionListFragment to use ContactSearch infrastructure. 2023-02-02 17:22:40 -05:00
Alex Hart 0f6bc0471c Add core-ui module and Jetpack Compose. 2023-02-02 17:22:40 -05:00
Alex Hart ba919d4ecc Add proper styling for text inputs. 2023-02-02 17:22:40 -05:00
Alex Hart 73722297cf Fix membership query to account for active state and mms state. 2023-02-02 17:22:40 -05:00
Greyson Parrelli 6050a9f585 Update feature flag constant. 2023-02-02 17:22:40 -05:00
Cody Henthorne b243eee4ce Fix incorrect unread count after sending scheduled messages. 2023-02-02 17:22:40 -05:00
Greyson Parrelli a91a13cead Introduce Wire for proto codegen. 2023-02-02 17:22:40 -05:00
Nicholas 72449fd73e Store & submit spam reporting token from server. 2023-02-02 17:22:40 -05:00
Cody Henthorne 6a8e82ef91 Prevent scheduling of sends when alarm permission is denied. 2023-02-02 17:22:40 -05:00
Greyson Parrelli 987fafff92 Remove is_mms field from MSL tables. 2023-02-02 17:22:40 -05:00
Cody Henthorne 35ff977df9 Fix position calculation for conversations with scheduled messages. 2023-02-01 19:23:12 -05:00
Cody Henthorne fe2d71fca0 Delete pending scheduled messages when leaving a group. 2023-02-01 19:04:29 -05:00
Greyson Parrelli a12a246e87 Add foreign key constraint details to Spinner. 2023-02-01 17:41:28 -05:00
Alex Hart 4f387cf8d9 Add username education screen. 2023-02-01 17:41:28 -05:00
Cody Henthorne dae69744c2 Prevent schedule send UI from showing in story send flow. 2023-02-01 17:41:28 -05:00
Cody Henthorne 4ad233c6d1 Show will send immediately warning if scheduled send is in the past. 2023-02-01 17:41:28 -05:00
Cody Henthorne b4c572678c Fix incorrect ripple effect in search box.
Fixes #12641
2023-02-01 17:41:28 -05:00
Cody Henthorne 5024998a6f Improve clarity of screen lock timeout duration. 2023-02-01 17:41:28 -05:00
Jim Gustafson 43cde19071 Remove redundant logging.
And move the backup stun server to the end of the list.
2023-02-01 17:41:28 -05:00
Greyson Parrelli 62a2f3d8ba Fix bug when highlighting search results. 2023-02-01 17:41:28 -05:00
Greyson Parrelli 5bc44fa586 Improve network reliability. 2023-02-01 17:41:28 -05:00
Greyson Parrelli f0b3aa66f7 Revert "Enable gradle configuration cache."
This reverts commit 6e5b4bbc15.
2023-02-01 17:41:28 -05:00
Clark ef9cd2515e Add new story reaction bar. 2023-02-01 17:41:28 -05:00
Greyson Parrelli 4677f207e7 Rotate scheduled message feature flag. 2023-02-01 17:41:28 -05:00
Bernie Dolan 4c26f3258d Update Mobilecoin testnet enclave values. 2023-02-01 17:41:28 -05:00
Cody Henthorne 77a3037614 Update icons in popup/context menus. 2023-02-01 17:41:28 -05:00
Alex Hart 9600d6f6a9 Add different menu copy for clearing the enabled chat filter. 2023-02-01 17:41:28 -05:00
Alex Hart 36dfa19aec Add "contacts without threads" section to Conversation List Search. 2023-02-01 17:41:28 -05:00
Alex Hart 09902e5d11 Add "Group Members" section to ConversationList search results. 2023-02-01 17:41:27 -05:00
Nicholas Tinsley e84c6187b9 Bump version to 6.10.5 2023-02-01 16:59:54 -05:00
Nicholas Tinsley b5d52db57c Add logging for audio recorder exceptions. 2023-02-01 11:41:30 -05:00
Alex Hart f320cf8833 Add factory for story privacy view model. 2023-02-01 12:22:55 -04:00
Cody Henthorne c76ca957e1 Prevent keyboard from closing immediately after opening. 2023-02-01 10:35:42 -05:00
Cody Henthorne 56354f6aae Fix memory leak of schedule message observers. 2023-02-01 10:22:17 -05:00
Nicholas Tinsley 2bf84a5f77 Fix country code dropdown during registration. 2023-02-01 09:54:44 -05:00
Nicholas Tinsley 8b23d9a6c4 Bump version to 6.10.4 2023-01-31 17:20:05 -05:00
Nicholas Tinsley 2cae3ddf04 Updated language translations. 2023-01-31 17:20:05 -05:00
Greyson Parrelli 670b6c4c56 Revert "Upgrade to Glide 4.14.2"
This reverts commit 3ee889cb79.
2023-01-31 16:15:33 -05:00
Alex Hart eceed641bf Fix leak in recording session. 2023-01-31 16:15:33 -05:00
Nicholas 5febe6490c Null check Media URI in save task. 2023-01-31 16:15:33 -05:00
Nicholas dca47e4cb5 Strip mention Spans out of media captions. 2023-01-31 16:15:33 -05:00
Nicholas c3bcba6380 Design improvements for registration flow. 2023-01-31 16:15:33 -05:00
Alex Hart cb01692a50 Fix localization of note to self string in search. 2023-01-31 10:07:50 -04:00
Alex Hart e90074ffef Fix issue with bottom sheets. 2023-01-31 10:00:25 -04:00
Alex Hart 691520bc75 Clean out dead code from contact search. 2023-01-30 12:52:27 -04:00
Greyson Parrelli e6de06be6f Bump version to 6.10.3 2023-01-30 10:40:13 -05:00
Greyson Parrelli a77079ac81 Updated language translations. 2023-01-30 10:39:52 -05:00
Greyson Parrelli 30b58fe5f4 Don't run FTS optimize job (for now). 2023-01-30 10:39:41 -05:00
Greyson Parrelli 7275b95b58 Bump version to 6.10.2 2023-01-27 17:42:22 -05:00
Greyson Parrelli f04d46b4ed Updated language translations. 2023-01-27 17:42:22 -05:00
Greyson Parrelli e12bbe943b Restore the 3-dot menu when creating a PIN. 2023-01-27 17:42:22 -05:00
Greyson Parrelli 7348224dc2 Prevent thread trimming from gumming up the database. 2023-01-27 17:42:22 -05:00
Cody Henthorne 30c33fdd77 Fix issues with scheduled messages and quotes.
- Tapping quote in schedule view will jump to message in chat
- Scheduling a quote will not make the quoted message render as "isQuoted"
- Scheduled quotes will not appear in the quoted message's sheet of replies
- Fixes an off-by-N where N = # of scheduled messages when calculating location for jumping to a message
2023-01-27 17:42:22 -05:00
Alex Hart e7339af119 Add fix for group membership query. 2023-01-27 17:42:22 -05:00
Cody Henthorne 661fff7a0e Fix scheduled messages being sent out of order. 2023-01-27 17:42:22 -05:00
Alex Hart c37bad0f7a Fix opening filter when swiping from within collapsingtoolbar. 2023-01-27 17:42:22 -05:00
Alex Hart 7f228fc0fd Do not display add to story if stories are disabled. 2023-01-27 17:42:22 -05:00
Cody Henthorne 14cd216668 Fix crash when delete scheduled message dialog is open and message sends. 2023-01-27 17:42:22 -05:00
Cody Henthorne 0cb0ef977c Add calendar icon to pick date and time item.
Co-authored-by: Sgn-32 <49990901+Sgn-32@users.noreply.github.com>
2023-01-27 17:42:22 -05:00
Cody Henthorne 1761529ce9 Disable schedule message for SMS. 2023-01-27 17:42:22 -05:00
Clark a14fc82e83 Fix scheduled view once message looking weird. 2023-01-27 17:42:22 -05:00
Clark b94f5501d9 Disable scheduling of voice note messages. 2023-01-27 17:42:21 -05:00
Clark 834283ba9b Hide scheduled messages bar with input panel. 2023-01-27 17:42:21 -05:00
Nicholas 23190a2f6e Fix NumericKeyboardView in RTL. 2023-01-27 11:06:04 -05:00
Alex Hart 04f4cd8edc Fix V172 Migration. 2023-01-27 10:20:44 -04:00
Greyson Parrelli 8f02e4e1f5 Bump version to 6.10.1 2023-01-26 20:25:28 -05:00
Greyson Parrelli db81a5be04 Updated language translations. 2023-01-26 20:25:28 -05:00
Greyson Parrelli fe40e37da4 Fix full text search migration after table name change. 2023-01-26 20:25:28 -05:00
Alex Hart 22a4271dfb Rotate paypal recurring donations flag. 2023-01-26 20:25:28 -05:00
Greyson Parrelli 1263b51e03 Patch random place where we forgot to update minSdk. 2023-01-26 20:25:28 -05:00
Nicholas ca468047ef Adjust media caption height, fix rail visibility. 2023-01-26 20:25:28 -05:00
Clark 958c52a5b8 Force indexes for scheduled message queries. 2023-01-26 20:25:28 -05:00
Greyson Parrelli 9b28585c59 Add foreign key dependency between reactions and messages. 2023-01-26 20:25:27 -05:00
Clark c5c60b7214 Add permissions dialogs for scheduled messages. 2023-01-26 20:25:27 -05:00
Nicholas 31bcc2e2eb Finish MediaPreviewV2Activity when jumping to a message. 2023-01-26 20:25:27 -05:00
Cody Henthorne 71ecba17fc Fix crash when saving empty formatted text drafts. 2023-01-26 20:25:27 -05:00
Greyson Parrelli afa5c68312 Periodically optimize the FTS index. 2023-01-26 20:25:27 -05:00
Clark f3e715e069 Add support for scheduled message sends. 2023-01-26 20:25:27 -05:00
Alex Hart df695f7611 Fix crash when trying to create new group story.
Adds INNER JOIN to threads table to allow access to date in ORDER BY
2023-01-26 20:25:27 -05:00
Greyson Parrelli 27e1bc0854 Bump version to 6.10.0 2023-01-25 17:11:34 -05:00
Greyson Parrelli f4371b9e96 Updated language translations. 2023-01-25 17:08:53 -05:00
Cody Henthorne e0633180ef Fix crash when trying to update a group call without an era id. 2023-01-25 17:02:41 -05:00
Alex Hart 32dd227ab6 Utilize left join instead of inner join when querying groups. 2023-01-25 17:02:41 -05:00
Cody Henthorne 0deed9d4d2 Fix notification sound not respecting notification volume.
Fix is immediate for general messages channel and for future custom channel creation
2023-01-25 17:02:41 -05:00
Cody Henthorne cc490f4b73 Add text formatting send and receive support for conversations. 2023-01-25 17:02:41 -05:00
Cody Henthorne aa2075c78f Attempt to fix view jitter when switching keyboards. 2023-01-25 17:02:41 -05:00
Alex Hart b4a34599d7 Add support for message and thread results. 2023-01-25 17:02:41 -05:00
Nicholas 8dd1d3bdeb Allow user-selected backup time. 2023-01-25 17:02:41 -05:00
Greyson Parrelli a7d9bd944b Insert session switchover events when appropriate. 2023-01-25 17:02:41 -05:00
Clark 7745ae62ea Add logging for voice note recording events. 2023-01-25 17:02:41 -05:00
Greyson Parrelli 6e5b4bbc15 Enable gradle configuration cache.
Android Studio told me to do this and that it would save me over 7
seconds. We'll see if it breaks anything :p
2023-01-25 17:02:41 -05:00
Clark e3b38e6d38 Fix some thumbnail images not showing up in MediaGallery. 2023-01-25 17:02:41 -05:00
Clark 25aa4f39a3 Close input streams on failed resource decryption. 2023-01-25 17:02:41 -05:00
Cody Henthorne 17849e20bd Update group receipt table when sync'ing story sends to distribution lists. 2023-01-25 17:02:41 -05:00
Alex Hart c022172ace Add group member results to contact search. 2023-01-25 17:02:41 -05:00
Nicholas eaeeb08987 Display message text in Media Preview. 2023-01-25 17:02:41 -05:00
Alex Hart 1b7e4e047c Introduce ManyToMany table for group membership. 2023-01-24 14:18:28 -05:00
Clark d635683303 Fix share intent not being cleared from recents. 2023-01-24 14:18:28 -05:00
Clark 4dcbbfdd63 Fix voice note draft not being generated on audio focus loss. 2023-01-24 14:18:28 -05:00
Nicholas 150bbf181d Redesign FTUX to use Material Design 3. 2023-01-24 14:18:28 -05:00
Alex Hart 0303467c91 Add PayPal decline code errors. 2023-01-24 14:18:28 -05:00
Nicholas 88da382a6f For calling purposes, categorize hearing aids as Bluetooth headsets. 2023-01-24 14:18:28 -05:00
Alex Hart 5d14166a27 Add support for arbitrary rows in contact search. 2023-01-24 14:18:28 -05:00
Jim Gustafson d76d13f76c Update to RingRTC v2.23.1 2023-01-24 14:18:28 -05:00
Greyson Parrelli ad4ec23875 Bump version to 6.9.2 2023-01-24 14:15:52 -05:00
Greyson Parrelli 61df2afc32 Updated language translations. 2023-01-24 14:15:52 -05:00
Nicholas Tinsley 1c6d2f7198 If enabled, don't unarchive muted group chats.
Fixes #12732
2023-01-24 14:13:42 -05:00
Cody Henthorne df8f9761b2 Fix incorrect total sms export count. 2023-01-24 13:49:25 -05:00
Greyson Parrelli 657c5d2bce Bump version to 6.9.1 2023-01-20 18:12:37 -05:00
Greyson Parrelli 81324c6923 Updated language translations. 2023-01-20 18:12:17 -05:00
Greyson Parrelli 269a2e2990 Improve timer event generation from GV2 sync messages. 2023-01-20 17:58:17 -05:00
clark-signal 73b453b0d4 Fix re-used share intent when restarting task from recent activities. 2023-01-20 13:42:13 -05:00
Cody Henthorne 97604dc4c5 Bump version to 6.9.0 2023-01-19 13:44:21 -05:00
Cody Henthorne 8e1c05ed64 Updated language translations. 2023-01-19 13:38:09 -05:00
Nicholas 231b55a956 Don't show media controls on new pages. 2023-01-19 13:33:07 -05:00
Alex Hart 4fcdee9fa5 Rotate PayPal recurring donations feature flag. 2023-01-19 13:33:07 -05:00
Cody Henthorne 6e2e5e21cc Update copy and behavior of SMS phased removal flow. 2023-01-19 13:33:07 -05:00
Alex Hart 8f49323648 Extract adapter creation from ContactSearchMediator. 2023-01-19 13:33:07 -05:00
Greyson Parrelli 13f969b622 Fix an app migration.
Fixes #12730
2023-01-19 13:33:07 -05:00
Greyson Parrelli 518d9b3984 Update SQLCipher to 4.5.1-S1
This reverts commit d1894caea6.
2023-01-19 13:33:07 -05:00
Alex Hart 8e313f8387 Collapse KnownRecipient / Story into single model. 2023-01-19 13:33:07 -05:00
Nicholas 70c6e9e60f Store additional data that will allow us to reduce the number of verification SMSs. 2023-01-19 13:32:35 -05:00
Cody Henthorne dcf8a82c37 Fix no snippet being shown for threads.
Snippet query wasn't updated to exclude SMS types after the conjuction
of the tables.
2023-01-19 13:32:35 -05:00
Alex Hart f368e5b133 Suppress deselection error when opening gallery from chat. 2023-01-19 13:32:35 -05:00
Alex Hart 3ee889cb79 Upgrade to Glide 4.14.2 2023-01-17 14:30:48 -05:00
Greyson Parrelli 3e7dc79fe8 Remove unnecessary code now that minSdk is 21. 2023-01-17 14:30:48 -05:00
Greyson Parrelli 8cfd02aff2 Bump minSdk to 21. 2023-01-17 14:30:48 -05:00
Alex Hart f36efc562e Fix improper filtering of unread conversations. 2023-01-17 14:30:48 -05:00
Sgn-32 67b6b109de Use ic_save_24_tinted instead of ic_download_24_tinted. 2023-01-17 14:30:48 -05:00
Alex Hart 8fd378db4e Fix issue where links do not render in stories if previews are off. 2023-01-17 14:30:48 -05:00
Cody Henthorne 760ace93d4 Enable PNI group invite processing. 2023-01-17 14:30:48 -05:00
Nicholas 125fd83afa Programmatically dismiss logged out notification on registration. 2023-01-17 14:30:48 -05:00
Alex Hart 7dcb598b66 Inline gift badge flag. 2023-01-17 14:30:48 -05:00
Alex Hart 4917e93d9f Update CameraX to 1.2.0 2023-01-17 14:30:48 -05:00
Alex Hart 1e9115a917 Increment compileSdkVersion to 33. 2023-01-17 14:30:48 -05:00
clark-signal 7af94f60ae Fix story linking from private story reply. 2023-01-17 14:30:48 -05:00
clark-signal c3c8f8e7e6 Update icons across home screen and settings screen. 2023-01-17 14:30:48 -05:00
clark-signal 011c85c75b Fix "My subscription" dark mode overlapping background bubbles. 2023-01-17 14:30:47 -05:00
Cody Henthorne 90112bec31 Bump version to 6.8.3 2023-01-17 14:15:45 -05:00
Cody Henthorne bca43fb93b Updated language translations. 2023-01-17 14:00:36 -05:00
Alex Voloshyn b6ee69d346 Add proguard config for payments. 2023-01-17 09:37:45 -05:00
Alex Hart b2a4dc303b Bump version to 6.8.2 2023-01-13 16:47:35 -04:00
Alex Hart 8553cf6b96 Updated language translations. 2023-01-13 16:47:23 -04:00
Alex Hart 276f485b49 Fix media review screen view translation when animations are disabled.
Fixes #12698
2023-01-13 10:28:14 -04:00
Greyson Parrelli 54ffb4ad7b Improve network reliability. 2023-01-12 17:54:08 -05:00
Alex Hart 28531bb415 Bump version to 6.8.1 2023-01-12 15:46:19 -04:00
Alex Hart d0c4cefaad Updated language translations. 2023-01-12 15:41:46 -04:00
Nicholas Tinsley d765fb1d5d Fix androidTests for registration changes. 2023-01-12 15:36:21 -04:00
Greyson Parrelli bce2dd1d1b Fix v171 migration.
Copy-pasted from the old one, but forgot that the sms table no longer
exists...
2023-01-12 15:36:21 -04:00
Sgn-32 c099ad0aa7 Replace the old SimpleProgressDialog with the new ProgressCard in WallpaperCropActivity. 2023-01-12 15:36:21 -04:00
Alex Hart 3738daf4a8 Pre-cache max unreserved player count on process startup.
This call was taking up to 20ms to run on the first time we would
load a conversation. Adding a post-render call to app launch reduces
this time to 0ms, as the data has been cached by the system. Doing
it on a background thread means we do not pay for it in TTFF.
2023-01-12 15:36:21 -04:00
Greyson Parrelli 564b9f47ee Add banner warning about API 19 deprecation. 2023-01-12 15:36:21 -04:00
Alex Hart 7617a164fc Bump version to 6.8.0 2023-01-11 14:25:59 -04:00
Alex Hart 46c98f4e0b Updated language translations. 2023-01-11 14:22:49 -04:00
Greyson Parrelli 522346479c Improve network reliability. 2023-01-11 14:18:25 -04:00
Greyson Parrelli df86d1b4ba Re-run migration to add foreign key to thread table.
Unfortunately the table schema wasn't fixed for new installs, so we need
to run it again.
2023-01-11 14:18:25 -04:00
clark-signal 74760a4a64 Add alternate junit-bom 5.8.1 to fix CI. 2023-01-11 14:18:25 -04:00
clark-signal 9e903a023f Cleanup media rail crossfade animation. 2023-01-11 14:18:25 -04:00
clark-signal 6120902ff5 Prevent system UI from being hidden incorrectly while paging media. 2023-01-11 14:18:25 -04:00
Cody Henthorne 1d2fbf0ebf Make adding properties to media send result easier. 2023-01-11 14:18:25 -04:00
Greyson Parrelli 40f9a25b87 Set Accept-Language header when requesting SMS code. 2023-01-11 14:18:25 -04:00
clark-signal f2881843db Fix MediaRailAdapter request counting. 2023-01-11 14:18:25 -04:00
Greyson Parrelli c53b090b76 Fix formatting in LiveRecipient. 2023-01-11 14:18:24 -04:00
Alex Voloshyn 1ba2712375 Update MobileCoin SDK to v4.0.0 2023-01-11 14:18:24 -04:00
Alex Hart 71ff31e91f Fix Stripe json body error handling. 2023-01-11 14:18:24 -04:00
Nicholas aa9a530e59 Upload PIN to KBS on successful register. 2023-01-11 14:18:24 -04:00
Nicholas e4cc7f5181 Attempt to Skip PIN Entry on Re-Registration. 2023-01-11 14:18:24 -04:00
Cody Henthorne 13f43799d6 Unify registration response models and processors. 2023-01-11 14:18:24 -04:00
Alex Hart 3543a5ea24 Update donations strings. 2023-01-11 14:18:24 -04:00
Greyson Parrelli 429f89cba1 Add some logging for early delivery receipts. 2023-01-11 14:18:24 -04:00
clark-signal 5b0084a5e2 Fix group story selection crash. 2023-01-11 14:18:24 -04:00
clark-signal a3bc831346 Fix mentions in drafts being uneditable. 2023-01-11 14:18:24 -04:00
Alex Hart 2fd6b7c49e Fix donation payment update issue. 2023-01-11 14:18:24 -04:00
clark-signal fa613557e8 Hide "Add as contact" option when no phone number present. 2023-01-11 14:18:20 -04:00
clark-signal 87c366223a Load drafts from DB when opening conversation from notifications. 2023-01-11 14:18:15 -04:00
Alex Hart aac1d0cedb Utilize switchMapSingle instead of flatMapSingle. 2023-01-11 13:17:26 -04:00
Jim Gustafson ea12cde1d8 Update to RingRTC v2.23.0 2023-01-11 13:17:25 -04:00
Alex Hart 9e2a5002bc Update paypal payment method endpoint and enable subs in staging. 2023-01-11 13:16:43 -04:00
Alex Hart 396742f3ad Add new chat bubble pulse. 2023-01-11 13:16:43 -04:00
Alex Hart af0fbdd2b2 Fix story splitting in multishare flow. 2023-01-11 13:16:43 -04:00
Cody Henthorne bdbeefe08e Update MobileCoin measurements and configuration. 2023-01-11 13:16:43 -04:00
Nicholas 9a763bd726 Upgrade Lottie to 5.2.0. 2023-01-11 13:16:43 -04:00
Alex Hart 9636aa4d37 Bump version to 6.7.6 2023-01-11 11:00:13 -04:00
Alex Hart 410e6abba9 Updated language translations. 2023-01-11 11:00:00 -04:00
Greyson Parrelli d1894caea6 Revert "Update SQLCipher to 4.5.1-S1"
This reverts commit f1d204b834.
2023-01-11 08:57:56 -05:00
Alex Hart af335a447f Bump version to 6.7.5 2023-01-10 13:05:34 -04:00
Alex Hart 711eec4bf2 Updated language translations. 2023-01-10 13:03:00 -04:00
Alex Hart 80c5cbe0da Add padding around clear-filter tip. 2023-01-10 12:57:28 -04:00
Alex Hart 84d0283719 Ensure capability check occurs for gift recipients. 2023-01-10 12:57:03 -04:00
Alex Hart 53e347a67d Bump version to 6.7.4 2023-01-09 13:32:28 -04:00
Alex Hart 3d6220737a Updated language translations. 2023-01-09 13:29:12 -04:00
Alex Hart 383525e7b7 Update chat filter behaviour after round of feedback. 2023-01-06 16:40:51 -04:00
Cody Henthorne 3869de414f Bump version to 6.7.3 2023-01-06 14:15:29 -05:00
Cody Henthorne 89bbfd3ded Updated language translations. 2023-01-06 13:06:33 -05:00
Greyson Parrelli e2fb65920c Ensure SMS and MMS messages are sent appropriately. 2023-01-06 11:27:33 -05:00
Greyson Parrelli 5537039e46 Bump version to 6.7.2 2023-01-05 15:03:41 -05:00
Greyson Parrelli 0a40432ed4 Fix crash that occurs during thread trims by date. 2023-01-05 14:52:51 -05:00
Cody Henthorne 73e46053f0 Fix voice note crash when future failed. 2023-01-05 12:12:03 -05:00
Greyson Parrelli a835e5d143 Bump version to 6.7.1 2023-01-05 10:14:04 -05:00
Greyson Parrelli 073d5dfe8c Don't mark unauthorized unless we're registered. 2023-01-05 10:05:58 -05:00
Greyson Parrelli bfba60b6b6 Improve reliability of SqlUtil.getNextAutoIncrementId() 2023-01-05 10:01:33 -05:00
Alex Hart 84d9e1d28e Drop proxy sheet events if fragment state has been saved. 2023-01-05 09:44:26 -04:00
Alex Hart a14c61c370 Rotate chat-filters flag. 2023-01-05 09:38:54 -04:00
Greyson Parrelli 0db4630f58 Bump version to 6.7.0 2023-01-04 16:57:33 -05:00
Greyson Parrelli 6d2e51def6 Updated language translations. 2023-01-04 16:57:05 -05:00
Nicholas 091eb0aa2b Fix chaining of Stories FTUX for small screens. 2023-01-04 16:40:36 -05:00
Greyson Parrelli 59f05e0815 Improve performance of marking chats read.
SQLite isn't always smart enough to use the best index for a query.
The main improvement here was to force it to use a better index than the
one it was using (which, on my device, happened to by the story index,
which was only minimally useful here).
2023-01-04 16:40:36 -05:00
Greyson Parrelli a513e93d18 Fix log around sending SKDM's. 2023-01-04 16:40:36 -05:00
Alex Hart 91f6cff4df Do not display recovery card if entropy is not set. 2023-01-04 16:40:36 -05:00
Alex Hart ec3ec969eb Add updated search hint text when filter is enabled. 2023-01-04 16:40:36 -05:00
Alex Hart 54d0df9a05 Ensure only a single story is displayed above the fold in bottom sheets. 2023-01-04 16:40:36 -05:00
Greyson Parrelli 320669c54e Remove the unknown insert validation.
There's actually a legitimate case where this is ok: right after a
backup restore.

Restoring a backup means that you have possibly carried over some
unknownIds, and if you don't remember your PIN, those items wouldn't be
there remotely. And you _should_ insert them. Otherwise they're lost.

I don't think this validation is worth the trouble of carving out lots
of conditions to allow this usecase.
2023-01-04 16:40:36 -05:00
Alex Hart bf491c25f7 Update view badge bottom sheet button copy. 2023-01-04 16:40:36 -05:00
Alex Hart 1e153e129c Ensure ShareViewModel#resolve is performed on the background thread.
Fixes #12696
2023-01-04 16:40:36 -05:00
Cody Henthorne b546d661ba Update 1:1 call event copy. 2023-01-04 16:40:36 -05:00
Greyson Parrelli 6a1a657451 Fix story database observer updates to ensure rings are updated live. 2023-01-04 16:40:36 -05:00
Greyson Parrelli ece087eaae Require non-null uri in UriAttachment.
Part of an effort to track down a different crash. This should make it
more obvious where the error is being made.
2023-01-04 16:40:36 -05:00
Alex Hart a04590b658 Ensure pre-upload media is properly fanned out to group stories. 2023-01-04 16:40:36 -05:00
Alex Hart eb6a14e686 Fix bad background on long text posts. 2023-01-04 16:40:36 -05:00
Alex Hart c7bb0eadc2 Fix restore-state of filter pull view. 2023-01-04 16:40:36 -05:00
Nicholas d70fe8f2cd Only notify for unauthorized response to messages endpoint. 2023-01-04 16:40:36 -05:00
Greyson Parrelli 0fd8f73cca Show username if appropriate in group chips. 2023-01-04 16:40:36 -05:00
Greyson Parrelli b9fc36be5a Prevent crash when adding group member by username. 2023-01-04 16:40:36 -05:00
Alex Hart 4de27482bb Update blocking get call to safeBlockingGet. 2023-01-04 16:40:36 -05:00
Nicholas ba347301cf Keep media preview image order even for RTL locales.
Addresses #12574.
2023-01-04 16:40:36 -05:00
Alex Hart 296a113c65 Add "You can pull to filter" tip. 2023-01-04 16:40:36 -05:00
Cody Henthorne 43fe789807 Add support for general media attachments to release notes channel messages. 2023-01-04 16:40:36 -05:00
Alex Hart 98dfd5bfbf Lower fade out duration of chat pill to 150ms. 2023-01-04 16:40:36 -05:00
Alex Hart f387785a46 Add chat filter pill fade out on slide close. 2023-01-04 16:40:36 -05:00
Cody Henthorne 7b3d8d01ae Prevent dismissal of notification.
Fixes #12681
2023-01-03 11:03:06 -05:00
Alex Hart 1712442560 Add chat filter pill color lerp at close apex. 2023-01-03 10:47:53 -04:00
Nicholas e4ddedcc48 Log type of connected headsets. 2023-01-03 09:33:08 -05:00
Alex Hart 14503b952a Add helper text when dragging filter at a low velocity. 2023-01-03 10:31:31 -04:00
Nicholas 5cb3e1cd02 Launch "Keep Muted Chats Archived". 2023-01-03 09:05:57 -05:00
Nicholas 7959343661 Add local notification when client receives HTTP 403
Also corrects typo in method name.
2023-01-02 17:20:42 -05:00
Alex Hart 52062679d4 Update filter chip padding to match spec. 2023-01-02 15:05:07 -04:00
Alex Hart 9e8350e8c2 Implement chat filter design feedback. 2023-01-02 12:19:04 -04:00
Sgn-32 495c91ba86 Use the correct delivery time in the message details
CLoses #11655
2023-01-02 09:51:20 -05:00
Greyson Parrelli 92b9fda6c7 Convert GroupTable to kotlin.
Also required converting some tests to mockk.
2023-01-01 23:05:02 -05:00
Greyson Parrelli fecfd7cd78 Remove the rest of MmsSmsTable. 2022-12-31 13:43:12 -05:00
Greyson Parrelli 6cd6073bc7 Migrate most of MmsSmsTable. 2022-12-30 18:24:00 -05:00
Greyson Parrelli f149c0adb9 Remove MmsSmsColumns.
All the columns got moved to MessageTable.
I kept the types though and renamed the class to MessageTypes because
it's a lot of boring domain-specific code.
2022-12-30 16:54:49 -05:00
Greyson Parrelli 3708cc5583 Add additional protections around recipientIds and threadIds matching. 2022-12-30 16:54:49 -05:00
Greyson Parrelli 4dd8e81db7 Fix some situations where MessageTable actions were doubled. 2022-12-30 16:54:49 -05:00
Cody Henthorne 06b414f4ef Add call disposition syncing. 2022-12-30 16:54:49 -05:00
Nicholas d471647e12 Animate swapping of play/pause buttons. 2022-12-30 16:54:49 -05:00
Greyson Parrelli dd3bad858d Prevent scrolling when context menu is showing on story landing page. 2022-12-30 15:11:57 -05:00
Cody Henthorne 0fe6538ce4 Fix media viewer rail items jumping around while paging. 2022-12-30 15:11:57 -05:00
Alex Hart 1e2f7f0775 Add currency selection logic update. 2022-12-30 15:11:57 -05:00
Cody Henthorne 055b4691d7 Fix video playback starting when off screen in media viewer. 2022-12-30 15:11:57 -05:00
Cody Henthorne ebdfa88882 Schedule ExpireStoriesManager when viewing Stories tab. 2022-12-30 15:11:57 -05:00
Alex Hart d79c4775b6 Add chat filter animation. 2022-12-30 15:11:56 -05:00
Cody Henthorne a13599ae2a Add payment activation capability. 2022-12-30 15:11:56 -05:00
Alex Hart 96b2051400 Rotate chat-filters flag for internal testing. 2022-12-30 15:11:56 -05:00
Nicholas ad6d1a2e8d Update styling of the media rail selection states. 2022-12-30 15:11:56 -05:00
Greyson Parrelli eada1e96ee Improve emoji search rankings. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 91fbc236ce Rename pnp capability to pni. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 202f20893c Add internal setting for manually initializing PNP mode. 2022-12-30 15:11:56 -05:00
Greyson Parrelli f1d204b834 Update SQLCipher to 4.5.1-S1 2022-12-30 15:11:56 -05:00
Greyson Parrelli 73e19209ff Fix some issues with projections result from MessageTable migration. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 835bf3998f Perform large inserts in batches during MessageTable migration. 2022-12-30 15:11:56 -05:00
Greyson Parrelli d83ef56ab1 Remove SmsMessageRecord. 2022-12-30 15:11:56 -05:00
Greyson Parrelli a84a9c5381 Fetch isQuoted status in bulk during conversation load.
Improves overall time to load a page of messages by ~50%.
2022-12-30 15:11:56 -05:00
Greyson Parrelli c6f29fc950 Migrate queued jobs during SMS migration. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 4d9dc42868 Improve the performance of the migration by ~4x. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 32b66643c5 Rename PushMediaSendJob -> IndividualSendJob. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 3850c9c89d Remove isMms from MessageId. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 60ae883df6 Rename SignalDatabase.sms/mms to SignalDatabase.messages 2022-12-30 15:11:56 -05:00
Greyson Parrelli a7e3bdc892 Rename OutgoingMediaMessage -> OutgoingMessage. 2022-12-30 15:11:56 -05:00
Greyson Parrelli 9b60bd9a4b Remove OutgoingTextMessage and PushTextSendJob. 2022-12-30 15:11:56 -05:00
Greyson Parrelli e9d98b7d39 Migrate SMS messages into the MMS table. 2022-12-30 15:11:56 -05:00
Greyson Parrelli cb0e7ade14 Bump version to 6.6.3 2022-12-24 11:13:04 -05:00
Greyson Parrelli 268f5c807d Ensure that remapped records are valid.
Fixes #12691
2022-12-24 11:13:04 -05:00
Greyson Parrelli f6003023bf Bump version to 6.6.2 2022-12-23 12:23:48 -05:00
Nicholas Tinsley 3f160f256a Prevent crash on pause for Media Preview with no fragments. 2022-12-23 12:20:56 -05:00
Greyson Parrelli 9846517075 Bump version to 6.6.1 2022-12-21 13:40:10 -05:00
Greyson Parrelli 0f1cc03dc0 Updated language translations. 2022-12-21 13:40:10 -05:00
Nicholas Tinsley 0e5031ab45 Revert "Switch to BT mic if available for voice memo recording."
This reverts commit 9f6eb142d2.
2022-12-21 13:25:56 -05:00
Greyson Parrelli 0e4926b5ec Bump version to 6.6.0 2022-12-19 18:42:05 -05:00
Greyson Parrelli a25e7c6d3e Updated language translations. 2022-12-19 18:38:42 -05:00
Cody Henthorne 4081ac2a83 Fix video controls becoming unresponsive after quickly paging. 2022-12-19 14:30:37 -05:00
Alex Hart 98a528f595 Fix recording progress bar when animations are scaled. 2022-12-19 12:46:13 -04:00
Nicholas 680325b5ee Increase MediaPreviewV2 lifecycle logging. 2022-12-16 16:12:11 -05:00
Nicholas 16668574a9 Separate message for media decode failure. 2022-12-16 15:32:59 -05:00
Rashad Sookram 0d8f6de4c1 Refactor bandwidth mode setting. 2022-12-16 15:22:04 -05:00
Alex Hart 4c0a98d526 Add nullability check to video capture callback.
Fixes #12666
2022-12-16 15:22:04 -05:00
Greyson Parrelli 10f78d5daa Change spinner to lazily read database stuff.
Otherwise you get into situations where Spinner will force DB accesses
super early during Application#onCreate on the main thread, which can be
bad when testing large DB migrations.
2022-12-16 15:22:04 -05:00
Cody Henthorne 3ce5a7da67 Fix emoji toggle behavior when in emoji search mode.
When in emoji search, toggle would be set to "emoji" state or
act like in "emoji" state. Fix is to show "keyboard" state still
when in emoji search.
2022-12-16 15:22:04 -05:00
Greyson Parrelli 4d47b9c594 Round Spinner timings to 3 decimal places. 2022-12-16 15:22:04 -05:00
Nicholas 9f6eb142d2 Switch to BT mic if available for voice memo recording.
Addresses #12016.
2022-12-16 15:22:04 -05:00
Nicholas 0e08b4ee26 Correctly animate deletion when attaching multiple media. 2022-12-16 15:22:04 -05:00
Cody Henthorne 9b85907918 Fix flicker of local avatar in call view. 2022-12-16 15:22:04 -05:00
Cody Henthorne 6463dca2c6 Fix media selection dismissing when deselecting last item. 2022-12-16 15:22:04 -05:00
Alex Hart 498b7fee69 Remove SingleLiveEvent from EditAboutViewModel. 2022-12-16 15:22:04 -05:00
Cody Henthorne 3478e13d38 Fix progress dialog deprecation warnings.
Moves everything under our own class and ignores the deprecation. Also
gives us future ability to re-style all blocking UI dialogs in the
future for mat3 compat.
2022-12-16 15:22:04 -05:00
Alex Hart 5f0d37739a Remove SLE from EditProxyViewModel. 2022-12-16 15:22:04 -05:00
Cody Henthorne c5b4f44ab8 Fix various compiler warnings. 2022-12-16 15:22:04 -05:00
Alex Hart 819c9f61dc Remove SingleLiveEvent from BlockedUsersActivity. 2022-12-16 15:22:04 -05:00
Alex Hart 4f167feaf5 Handle deprecated connectivity intent filter. 2022-12-16 15:22:04 -05:00
Alex Hart de558bc87c Remove SingleLiveEvent from ConversationSettingsViewModel. 2022-12-16 15:22:04 -05:00
Alex Hart 4a5a65ff6c Remove usage of SingleLiveEvent from MediaCaptureViewModel. 2022-12-16 15:22:04 -05:00
Cody Henthorne c56e63d62f Convert OutgoingMediaMessage and it's couterparts to kotlin. 2022-12-16 15:22:04 -05:00
Nicholas 8cd9a3cabe Map platform WIRED_HEADPHONES to our WIRED_HEADSET.
Fixes #12622.
2022-12-16 15:22:04 -05:00
Alex Hart 3a8c324c12 Clean up a bunch of warnings. 2022-12-16 15:22:04 -05:00
Cody Henthorne ff882edeae Enable kotlin for libsignal-service project and convert SignalServiceDataMessage. 2022-12-16 15:22:04 -05:00
Cody Henthorne fb0aa55cbb Fix instrumentation tests by forcing channel id usage to init channels. 2022-12-16 15:22:04 -05:00
Alex Hart 51015dc898 Clean up warnings in Gradle file. 2022-12-16 15:22:04 -05:00
Cody Henthorne 4af40e7861 Bump version to 6.5.6 2022-12-16 12:47:55 -05:00
Cody Henthorne 24fcc0c3b0 Updated language translations. 2022-12-16 12:40:19 -05:00
Nicholas Tinsley 993fc24dd3 Change inheritance of MediaPreviewV2Activity. 2022-12-16 12:03:01 -05:00
Greyson Parrelli fddc6bcd5f Update maven endpoint for sqlcipher. 2022-12-16 12:02:42 -05:00
Cody Henthorne 558051086e Bump version to 6.5.5 2022-12-14 13:33:43 -05:00
Cody Henthorne 2c187bc55d Updated language translations. 2022-12-14 12:59:41 -05:00
Cody Henthorne e947979169 Revert "Fix view flicker when switching between keyboard and attachment/emoji keyboards."
This reverts commit 1618141342.
2022-12-14 12:53:01 -05:00
Greyson Parrelli 08f1ddb212 Guard against potentially double-running a migration. 2022-12-14 11:15:23 -05:00
Cody Henthorne 4c318d8d82 Bump version to 6.5.4 2022-12-13 16:52:10 -05:00
Cody Henthorne 3e6ebfabb0 Updated language translations. 2022-12-13 16:41:01 -05:00
Alex Hart 55f4692d99 Add logging for response fields when an error happens. 2022-12-13 16:36:36 -05:00
Greyson Parrelli ebe82cf3e6 Add back missing reaction triggers. 2022-12-13 16:36:36 -05:00
Greyson Parrelli 21a8434e4d Attempt to fix SQLite crash in migration. 2022-12-13 10:59:27 -05:00
Greyson Parrelli 4990778a97 Fix recipient remapping of sms/mms records. 2022-12-13 09:54:53 -05:00
Alex Hart 303e5c7996 Remove PayPal order complete sheet. 2022-12-12 16:05:54 -04:00
Alex Hart 599caee229 Add error handling to re-throw Stripe POST errors. 2022-12-12 15:59:34 -04:00
Cody Henthorne e6f28c6cdd Bump version to 6.5.3 2022-12-12 12:48:59 -05:00
Cody Henthorne fd3b0ee375 Updated language translations. 2022-12-12 12:41:54 -05:00
Greyson Parrelli bd11ed9f17 Fix table drop order during backup import.
Fixes #12671
2022-12-12 11:54:05 -05:00
Alex Hart a6a185004d Only brighten screen when flash is ON and camera is FRONT. 2022-12-12 12:53:25 -04:00
Alex Hart 3cc556d803 Fix issue with cache entry access. 2022-12-12 12:51:57 -04:00
Alex Hart c3f9984346 Update error handling to include customized action when user cancels PayPal flow. 2022-12-12 11:54:56 -04:00
Cody Henthorne 10df4ee0d1 Add additional info when backup verification fails. 2022-12-12 10:49:55 -05:00
Greyson Parrelli c03a183904 Fix transaction issue on backup restore. 2022-12-12 10:03:15 -05:00
Greyson Parrelli a2893fbec7 Fix possible null column crash in V166 migration.
Fixes #12672
2022-12-12 09:30:49 -05:00
Alex Hart 19cbace33d Fix group search predicate causing crashing when creating group story. 2022-12-12 10:26:22 -04:00
Alex Hart 8a78481cca Bump version to 6.5.2 2022-12-09 14:34:51 -04:00
Alex Hart e1fd254d15 Updated language translations. 2022-12-09 14:16:58 -04:00
Alex Hart 019219f1e1 Rotate paypal one-time flag. 2022-12-09 14:02:27 -04:00
Greyson Parrelli ad3c04cb52 Fix ambiguous column in query. 2022-12-09 11:10:30 -05:00
Greyson Parrelli 61f9dc7498 Fix possible issue with reproducible builds.
- Needed to update apkdiff.py to ignore some new app-signing-related
  files.
- While I was in there, I cleaned up the script a lot to make it easier
  to read as well as extract files that didn't match.
- We also need to guarantee git hashes are the same length -- the script
  we were calling might provide hashes of different length depending on
  how you checked out the code.

Co-authored-by: inthewaves<26474149+inthewaves@users.noreply.github.com>
2022-12-09 08:53:17 -05:00
Alex Hart 4deb16a37a Bump version to 6.5.1 2022-12-08 14:20:33 -04:00
Alex Hart 4129151bd2 Updated language translations. 2022-12-08 14:17:26 -04:00
Cody Henthorne 10cf431537 Revert " Enable kotlin for libsignal-service project and convert SignalServiceDataMessage."
This reverts commit fc2b67aa0f.
2022-12-08 13:07:24 -05:00
Alex Hart 011dd2d973 Fix issue where gift receipt showed boost badge. 2022-12-08 13:45:44 -04:00
Alex Hart c85c4c5020 Bump version to 6.5.0 2022-12-08 12:20:36 -04:00
Alex Hart 5f1439df00 Updated language translations. 2022-12-08 12:11:48 -04:00
Cody Henthorne e76bec63a3 Remote ring small groups feature flag. 2022-12-08 12:07:02 -04:00
Cody Henthorne fc2b67aa0f Enable kotlin for libsignal-service project and convert SignalServiceDataMessage. 2022-12-08 12:07:02 -04:00
Alex Hart bcd0360dd0 Remove obselete unused dexOptions. 2022-12-08 12:07:02 -04:00
Cody Henthorne 04bf2cd0c2 Ignore decomissioned KBS enclaves when encountered during getToken. 2022-12-08 12:07:02 -04:00
Nicholas aba51da932 Ensure view binding is valid after Media Preview animations. 2022-12-08 12:07:02 -04:00
Nicholas f8520d83be Add null checks for FABs in conversation list.
Fixes #12651.
2022-12-08 12:07:02 -04:00
Greyson Parrelli 69003dfbe2 Convert IdentityTable to kotlin. 2022-12-08 12:07:02 -04:00
Alex Hart 380b377ed8 Ensure we rotate storage id when applying hidden story state or username. 2022-12-08 12:07:02 -04:00
fm-sys 4c5db983e3 Make voice messages long-clickable.
Fixes #12658
2022-12-08 12:07:02 -04:00
Greyson Parrelli 48c887ac03 Add gradle test devices. 2022-12-08 12:07:02 -04:00
Greyson Parrelli f207a82d2f Show smaller quote chains within larger quote chains. 2022-12-08 12:07:02 -04:00
Cody Henthorne 56f6888d49 Update kotlin to 1.7.20 2022-12-08 12:07:02 -04:00
Alex Hart 66ece479f6 Update access modifiers. 2022-12-08 12:07:02 -04:00
Greyson Parrelli c1cc2b064c Convert SenderKeyTable to kotlin. 2022-12-08 12:07:02 -04:00
Greyson Parrelli 98980b8192 Convert SenderKeySharedTable to kotlin. 2022-12-08 12:07:02 -04:00
Alex Hart 79ec76f11f Update tooltip to behave better when content is at edge of screen. 2022-12-08 12:07:02 -04:00
Cody Henthorne 45a1c5c369 Fix mention crash with overlapping ranges. 2022-12-08 12:07:02 -04:00
Greyson Parrelli 2dc41f319c Convert RemappedRecordTables to kotlin. 2022-12-08 12:07:02 -04:00
Alex Hart 2cdb1b8300 Fix issue where story thumb could show as a chat image preview. 2022-12-08 12:07:02 -04:00
Alex Hart e846b4e20a Add better onBack handling for donations webviews. 2022-12-08 12:07:02 -04:00
Alex Hart 961057f620 Implement PayPal confirm donation sheet. 2022-12-08 12:07:02 -04:00
Greyson Parrelli e686a09ce4 Convert GroupReceiptTable to kotlin. 2022-12-08 12:07:02 -04:00
Greyson Parrelli fc8cf2957f Convert DraftTable to kotlin. 2022-12-08 12:07:02 -04:00
Alex Hart 0bef37bfc1 Add minimum amount error for boosts. 2022-12-07 13:03:02 -05:00
Cody Henthorne 1618141342 Fix view flicker when switching between keyboard and attachment/emoji keyboards. 2022-12-07 13:03:02 -05:00
Alex Hart d7fb05f596 Fix integration tests. 2022-12-07 13:03:02 -05:00
Greyson Parrelli 2eb15cc8e3 Convert SearchTable to kotlin. 2022-12-07 13:03:02 -05:00
Alex Hart 424a0233c2 Implement refactor to utilize new donation configuration endpoint. 2022-12-07 13:03:02 -05:00
Alex Hart 40cf87307a Add improved handling for credit card errors. 2022-12-07 13:03:02 -05:00
Sgn-32 643206b946 SubmitDebugLogActivity progress dialog make-over.
Fixes #12656
2022-12-07 13:03:02 -05:00
Varsha cc95041519 Fix navigation after sending payment from conversation. 2022-12-07 13:03:02 -05:00
Cody Henthorne 45b498f62f Remove unused resources. 2022-12-07 13:03:02 -05:00
Sgn-32 9e6d78ba5f Enable hyphenation on conversation settings buttons.
Closes #12609
2022-12-07 13:03:02 -05:00
Greyson Parrelli 95eba78d9c Improve constraints on thread and message tables. 2022-12-07 13:03:02 -05:00
Alex Hart 5d9f00b268 Fix issue when copying attachment data. 2022-12-07 13:03:02 -05:00
Alex Hart 6a01388e82 Ignore start/end clipping when directed to do so by transform properties. 2022-12-07 13:03:02 -05:00
Ehren Kret 2ef6f78d39 Remove some unused code in ConversationAdapter. 2022-12-07 13:03:02 -05:00
Alex Hart a754c39599 Bump version to 6.4.2 2022-12-07 10:29:19 -04:00
Alex Hart 14622cd06c Updated language translations. 2022-12-07 10:29:02 -04:00
Cody Henthorne 3132cd1198 Drop group call rings for large groups. 2022-12-06 22:21:14 -05:00
Cody Henthorne 94c35d86e2 Update post translation qa tasks. 2022-12-06 15:11:20 -05:00
Cody Henthorne 3c2c6d782a Revert "Clear formatting when pasting text."
This reverts commit 77be721f5a.

If pasting an image will crash the application, does not handle pasting
via multiple other methods like quick suggestion or via a clipboard
manager like provided by Samsung via their keyboard.
2022-12-06 13:38:39 -05:00
Cody Henthorne 1764b21214 Fix crash when opening notification settings. 2022-12-06 13:11:22 -05:00
Greyson Parrelli 260e572071 Fix bug where disappearing timer was applied to sent group stories. 2022-12-05 17:36:57 -05:00
Greyson Parrelli 54251a27a8 Do not show stories for inactive groups. 2022-12-05 17:20:58 -05:00
Alex Hart 88a8430c31 Bump version to 6.4.1 2022-12-02 13:54:13 -04:00
Alex Hart 678b653873 Updated language translations. 2022-12-02 13:50:44 -04:00
Greyson Parrelli 21592ca5c0 Do not include archived messages in unread count. 2022-12-02 12:38:23 -05:00
gitstart 1bca2f06bd Pause voice memos when you open a video.
Fixes #11156.

Signed-off-by: Nicholas Tinsley <nicholas@signal.org>
2022-12-02 10:47:38 -05:00
Alex Hart 9f166105a6 Remove tinting when forwarding content. 2022-12-02 11:04:31 -04:00
Alex Hart ea08b59e6b Fix error routing for credit cards. 2022-12-02 11:00:22 -04:00
Alex Hart 9aca0af22c Fix issue with poor sent video viewing behavior. 2022-12-02 10:43:31 -04:00
Alex Hart 591d8c3d1a Separate PayPal flags into one-time and recurring. 2022-12-02 09:13:58 -04:00
Nicholas 22b73494a7 Rename *Database androidTest classes to *Table. 2022-12-01 18:15:37 -05:00
Nicholas 9bb80077c6 Fix jumping from media to message in group converstations. 2022-12-01 18:15:09 -05:00
Cody Henthorne 646f41663f Fix in-chat payment message rendering with long note. 2022-12-01 10:20:27 -05:00
Cody Henthorne 63cca2de66 Bump version to 6.4.0 2022-11-30 20:16:51 -05:00
Cody Henthorne 16361ac489 Updated language translations. 2022-11-30 20:05:03 -05:00
Cody Henthorne e8f39e8f71 Fix in-chat payment view not updating properly. 2022-11-30 19:58:47 -05:00
Alex Hart 7945b3c971 Fix story sync message behaviour between iOS and Android. 2022-11-30 17:10:36 -05:00
Cody Henthorne e5d196c642 Log backup verify failure independently from file not found. 2022-11-30 17:10:36 -05:00
Alex Hart 979f87db78 Add initial PayPal implementation behind a feature flag. 2022-11-30 17:10:36 -05:00
Alex Hart b70b4fac91 Inline gift receive flag. 2022-11-30 17:10:36 -05:00
Nicholas 031d7b9cb0 Remove shrinking animation from opening media preview bottom bar. 2022-11-30 17:10:36 -05:00
Nicholas c68859c606 Convert registration button to Tonal colorway. 2022-11-30 17:10:36 -05:00
Greyson Parrelli 23804046c6 Always use new foreground service utils. 2022-11-30 17:10:36 -05:00
Alex Hart 7b13550086 Add entry points for adding to a group story. 2022-11-30 17:10:36 -05:00
Greyson Parrelli 7949996c5c Renamed database classes to table classes.
Because they're not databases. They're tables.
2022-11-30 17:10:36 -05:00
Nicholas b190f9495a Only show "Delete Everywhere" with linked devices.
This applies to Note To Self.
2022-11-30 17:10:36 -05:00
Jim Gustafson b4c0635a63 Update to RingRTC v2.22.0
Co-authored-by: Jordan Rose <jrose@signal.org>
2022-11-30 17:10:36 -05:00
Nicholas 21bd8a308b Add jump to message shortcut for media viewer. 2022-11-30 17:10:36 -05:00
Nicholas 800405fc3e Add background drawable for play/pause buttons. 2022-11-30 17:10:36 -05:00
Alex bf18db354c Explicitly declare permissions in Github workflows.
Closes #12476

Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-11-30 17:10:36 -05:00
Greyson Parrelli e0b89bedd4 Removed some unused log classes. 2022-11-30 17:10:36 -05:00
Greyson Parrelli 504b7ad5b3 Remove unsupported languages. 2022-11-30 17:10:36 -05:00
Nicholas 0558808370 Unmute Stories when ringer mode changed. 2022-11-30 17:10:36 -05:00
Nicholas cff3840c51 Show AlertDialogs for registration errors. 2022-11-30 17:10:36 -05:00
Nicholas a46fc96ff1 Improve media album rail entrance animation. 2022-11-30 17:10:36 -05:00
gitstart 77be721f5a Clear formatting when pasting text.
Fixes #8058
Closes #12614
2022-11-30 17:10:36 -05:00
Greyson Parrelli 023b181917 Update backup passphrase layout spacing.
Fixes #12623
2022-11-30 17:10:36 -05:00
Sgn-32 311ef0d65b Fix video call icons in ConversationListItem.
Closes #12618
2022-11-30 17:10:36 -05:00
Cody Henthorne 74314e08ac Show notification when mentioned in a group story reply. 2022-11-30 17:10:36 -05:00
Rashad Sookram 81df9fcddb Default to staging SFU on staging builds. 2022-11-30 17:10:36 -05:00
Greyson Parrelli ff64c2a911 Add more locking around attachment deletions. 2022-11-30 17:10:36 -05:00
Cody Henthorne 8a9605ade8 Fix crash when handling expired call offers. 2022-11-30 17:10:36 -05:00
Greyson Parrelli 7a449a971f Update rate limit handling for CDS. 2022-11-30 17:10:36 -05:00
Cody Henthorne 258951dea8 Show excluded count in Story privacy settings overview. 2022-11-30 17:10:36 -05:00
Greyson Parrelli cdff0a61f2 Change chat badge to show total unread message count. 2022-11-30 17:10:36 -05:00
Alex Hart 2200af9c31 Remove background highlighting from empty lines in image editor.
Co-Authored-By: GitStart <1501599+gitstart@users.noreply.github.com>

Fixes #12612
2022-11-30 17:10:36 -05:00
Cody Henthorne dfb913cb98 Fix thread update with drafts bugs.
* Fix thread not updating correctly when drafts are present.
* Fix thread delete bug during first message drafting.
2022-11-30 17:10:36 -05:00
Varsha 9ee10512fb Update enclave measurements to v3.0.0 for testnet. 2022-11-30 17:10:36 -05:00
Greyson Parrelli 81c10a1eae Lazily initialize NotificationChannels. 2022-11-30 17:10:36 -05:00
Nicholas Tinsley 3e8b5ca91d Allow remote delete from Media Preview menu. 2022-11-30 17:10:35 -05:00
Cody Henthorne ba0b0cdefa Bump version to 6.3.6 2022-11-30 17:00:07 -05:00
Alex Hart f00ee0a226 Fix issue preventing subscriptions from processing. 2022-11-30 16:48:22 -05:00
Cody Henthorne bd4a69eddc Bump version to 6.3.5 2022-11-29 14:08:09 -05:00
Cody Henthorne 8c95b37826 Updated language translations. 2022-11-29 14:05:17 -05:00
Alex Hart 133d3145d1 Fix error with syncing of remote deletion of stories. 2022-11-29 14:48:08 -04:00
Cody Henthorne 4a0db31103 Bump version to 6.3.4 2022-11-29 11:49:32 -05:00
Cody Henthorne ce85bb1575 Updated language translations. 2022-11-29 11:39:38 -05:00
Alex Hart eee4ff3f87 Add new error strings for credit cards. 2022-11-29 11:01:07 -04:00
Greyson Parrelli f6356c9720 Never show stories from blocked users. 2022-11-28 20:40:50 -05:00
Alex Hart 42d2d415d6 Clean up keyboard fragment when view is detached from window. 2022-11-28 13:01:58 -04:00
Alex Hart 683247bf98 Cleanly exit on KeepAlive 409. 2022-11-28 12:47:25 -04:00
Alex Hart d7404cf32f Prevent empty or all-whitespace string from being sent as a gift message. 2022-11-28 12:02:25 -04:00
Greyson Parrelli ec1f771364 Bump version to 6.3.3 2022-11-24 22:35:51 -05:00
Greyson Parrelli 95ac9628fb Updated language translations. 2022-11-24 22:35:30 -05:00
Cody Henthorne ba68d795af Fix megaphone donate crash. 2022-11-24 22:33:14 -05:00
Alex Hart 245f7d3e03 Bump version to 6.3.2 2022-11-18 17:02:44 -04:00
Alex Hart 972ce41689 Updated language translations. 2022-11-18 16:54:16 -04:00
Alex Hart be12a17ff7 Add handling for payment_intent with missing status. 2022-11-18 13:22:30 -04:00
Alex Hart 0c615e2fc2 Bump version to 6.3.1 2022-11-17 16:43:49 -04:00
Alex Hart 6829257a83 Updated language translations. 2022-11-17 16:39:38 -04:00
Nicholas b7b7a04fad Improve animations for video seekbar. 2022-11-17 15:33:15 -05:00
Cody Henthorne 50084f8f73 Fix debuglog system info formatting bug. 2022-11-17 11:54:51 -05:00
Alex Hart 04e8235cfc Add group stories education sheet. 2022-11-17 12:35:17 -04:00
Alex Hart 0df3096241 Fix issue where gallery image was overlapped by count. 2022-11-17 12:18:32 -04:00
Alex Hart 29f22d515a Set story image post minimum duration to 5s. 2022-11-17 12:13:07 -04:00
Alex Hart 9931496b0f Fix crash when toggling pills. 2022-11-17 12:06:36 -04:00
Alex Hart 950363a4e9 Don't wrap donation errors. 2022-11-17 11:07:20 -04:00
Alex Hart 3469e8d0e0 Set brightness to 66% when taking a selfie. 2022-11-17 10:02:02 -04:00
Alex Hart 586339575f Fix menu visibility for chat filters. 2022-11-16 16:53:27 -04:00
Varsha 807a0e02a2 Fix memory leak in payment transfer fragment. 2022-11-16 15:11:09 -05:00
Cody Henthorne afb2b1a1a2 Do not include self in exported SMS threads. 2022-11-16 14:18:57 -05:00
Alex Hart a8946961d5 Bump version to 6.3.0 2022-11-16 15:14:49 -04:00
Alex Hart 026aaac451 Updated language translations. 2022-11-16 15:10:26 -04:00
Alex Hart 159f319d77 Update caption bar readability in stories. 2022-11-16 15:05:47 -04:00
Greyson Parrelli cf00995b6f Guarantee table export order is valid. 2022-11-16 15:05:47 -04:00
Cody Henthorne 7c60c32918 Add re-export SMS support and hard code Phase 0. 2022-11-16 15:05:47 -04:00
Cody Henthorne fd1d2ec8fc Ignore group ring requests if we are already in the call. 2022-11-16 15:05:47 -04:00
Alex Hart a11c40e4fe Add credit card support to badge gifting. 2022-11-16 15:05:47 -04:00
Greyson Parrelli 1eb2f51398 Convert AVIF files to jpegs. 2022-11-16 15:05:47 -04:00
Nicholas 13ed122c3e Null check RecyclerView references in search bar callbacks. 2022-11-16 15:05:47 -04:00
Alex Hart fa02ee1d3d Skip re-emission of duplicate StoryPosts. 2022-11-16 15:05:47 -04:00
Alex Hart 4908e39308 Skip prefetch call if no stories need to be cached. 2022-11-16 15:05:47 -04:00
Alex Hart ad001d585e Utilize center-inside transform to ensure proper downsampling of cached images. 2022-11-16 15:05:47 -04:00
Greyson Parrelli 3fd5e55363 Improve RecipientDatabase tests. 2022-11-16 15:05:47 -04:00
Greyson Parrelli ebc1bc3f7f Fix issue where non-ascii characters didn't show inline emoji suggestions.
Fixes #12579
2022-11-16 15:05:47 -04:00
Cody Henthorne c51e13fd30 Ignore rings from non-admins in announcement only groups and rev feature flag. 2022-11-16 15:05:47 -04:00
Nicholas fd37613f2f Don't fade in media preview controls if hidden. 2022-11-16 15:05:47 -04:00
Greyson Parrelli eb921f3103 Don't show megaphones in landscape. 2022-11-16 15:05:47 -04:00
Varsha d5b6c47670 Fix memory leak in payments home. 2022-11-16 15:05:47 -04:00
Varsha a4494b58f0 Fix memory leaks in payments home and confirm payment view models. 2022-11-16 15:05:47 -04:00
Varsha b0c68b12ed Fix memory leak in create payment fragment. 2022-11-16 15:05:47 -04:00
Varsha b47e5f2fa9 Fix memory leak in contact selection list. 2022-11-16 15:05:47 -04:00
Alex Hart bba1315906 Add chat filter support behind a flag. 2022-11-16 15:05:47 -04:00
Alex Hart 3e2ecdaaa9 Add blur hashes behind videos. 2022-11-15 16:26:19 -04:00
Nicholas fb8e81cf50 Center selected item in media rail.
Fixes #12582
2022-11-15 16:26:19 -04:00
Cody Henthorne 52a5fb8ea2 Fix crash when showing a message with a button without media. 2022-11-15 16:26:19 -04:00
Alex Hart b2f3867b0b Add dynamic duration to stories with captions. 2022-11-15 16:26:19 -04:00
Alex Hart 45ca3bd7cf Show default gallery icon if permissions is disabled or media is not available. 2022-11-15 16:26:19 -04:00
Alex Hart 74b7057608 Brighten camera screen if under 66%. 2022-11-15 16:26:19 -04:00
Robotwombat 3a060c7a79 Update some info on the README.
* Removed the mention of SMS/MMS support.
* Replaced the Signal description with some direct text from either Signal's Play Store listing or from signal.org
* Fixed some capitalization errors
* Replaced "Open Whisper Systems" with "Signal" in the 'Contributing Ideas' section

Closes #12597
2022-11-15 16:26:19 -04:00
Jim Gustafson de426d22bf Update to RingRTC v2.21.5 2022-11-15 16:26:19 -04:00
Alex Hart 14549fd401 Fix issue where SystemWindwInsetsSetter didn't respect type on older API levels. 2022-11-15 16:26:19 -04:00
Alex Hart 1ff16a2c18 Bump version to 6.2.3 2022-11-15 16:08:06 -04:00
Alex Hart 0174af7b9b Updated language translations. 2022-11-15 16:06:48 -04:00
Alex Hart e7f1d3fc1a Add JsonCreator annotation to data class constructors. 2022-11-15 15:14:55 -04:00
Alex Hart 09afb1be41 Bump version to 6.2.2 2022-11-14 12:48:03 -04:00
Alex Hart ad2ebfb389 Updated language translations. 2022-11-14 12:45:03 -04:00
Alex Hart 85d7a5c6cc Rotate Credit Cards flag for Beta. 2022-11-14 12:23:10 -04:00
Nicholas Tinsley 4fbbc9d395 Show thumbnail rail when viewing a thread's media. 2022-11-14 11:22:34 -05:00
Alex Hart e3954ab5e8 Utilize logic from lottie to determine animation scale. 2022-11-14 10:49:55 -04:00
Alex Hart c1b19390a2 Add 48dp padding to end of gift add message input. 2022-11-14 10:04:02 -04:00
Alex Hart f7e4e9c855 Fix crash when user does not have a subscription. 2022-11-14 09:59:09 -04:00
Alex Hart 5c6f709faa Do not pre-select my story privacy state. 2022-11-14 09:52:46 -04:00
Alex Hart 47f1d3f594 Add default values to global duration scale resolution. 2022-11-11 13:57:24 -04:00
Greyson Parrelli 2b10f93718 Update hint text for group story replies. 2022-11-11 12:29:36 -05:00
Greyson Parrelli ccee7577f7 Do not double-insert change number events. 2022-11-11 12:14:13 -05:00
Greyson Parrelli 4e871e2dd8 Bump version to 6.2.1 2022-11-11 10:42:24 -05:00
Greyson Parrelli 455da6649b Updated language translations. 2022-11-11 10:41:52 -05:00
Greyson Parrelli dc4acd83e8 Fix typo in string. 2022-11-11 10:41:14 -05:00
Alex Hart 0e3a9a3130 Finalize credit card copy. 2022-11-11 10:35:55 -05:00
Greyson Parrelli ed2edc1ebb Do no double-process the CDSI response. 2022-11-11 10:34:40 -05:00
Greyson Parrelli 6f4de36c6f Bump version to 6.2.0 2022-11-10 17:01:32 -05:00
Greyson Parrelli e10696b44e Updated language translations. 2022-11-10 17:00:51 -05:00
Alex Hart 6ed1c21a66 Add in new donations strings for credit card support. 2022-11-10 16:58:25 -05:00
Alex Hart 263f7ebac5 Trim zeroes in subscription row items. 2022-11-10 16:58:25 -05:00
Alex Hart c3063b721d Allow restricted users to update or cancel their subscription. 2022-11-10 16:58:25 -05:00
Cody Henthorne 1dc29fda12 Add in-chat payment messages. 2022-11-10 16:58:25 -05:00
Cody Henthorne 28193c2f61 Allow all notifications to be cancelled when bubbles are disabled. 2022-11-10 16:58:25 -05:00
Alex Hart 9d71c4df81 Refactor a large portion of the payments code to prep it for PayPal support. 2022-11-10 16:58:25 -05:00
Greyson Parrelli c563ef27da Add UX for handling CDS rate limits. 2022-11-10 16:58:25 -05:00
Cody Henthorne 8eb3a1906e Fully delete remotely deleted stories after sending or on receive. 2022-11-10 10:47:14 -05:00
Cody Henthorne 0309f9ea89 Change destination for remote donation megaphones. 2022-11-10 10:46:57 -05:00
Nicholas d678341399 Wrap DefaultAudioSink to tolerate init errors. 2022-11-10 10:15:10 -05:00
Nicholas 99f8ba5e0c Enable icons in overflow menu. 2022-11-10 09:37:32 -05:00
Alex Hart c69b91c4db Add blocked regions from global config for donations payments. 2022-11-10 10:17:13 -04:00
Alex Hart 8f56c1baa5 Add new CC icon for dark mode. 2022-11-09 19:26:48 -05:00
Alex Hart a0d4026e40 Enable screenshot security for CC fragment. 2022-11-09 19:26:48 -05:00
Cody Henthorne 975b242a08 Fix notification not showing after thread with custom notification is deleted. 2022-11-09 19:26:48 -05:00
Nicholas f1fafa6516 Gain temporary audio focus during voice memo recording. 2022-11-09 19:26:48 -05:00
Nicholas fca412b47d Pause videos/GIFs when sharing or forwarding. 2022-11-09 19:26:48 -05:00
Cody Henthorne 18c32a7a80 Only allow active groups to start ringing. 2022-11-09 19:26:48 -05:00
Nicholas f96c31b38f Always allow remote delete in note to self. 2022-11-09 19:26:48 -05:00
Alex Hart 65a4ef2f70 Update donation strings. 2022-11-09 19:26:48 -05:00
Alex Hart 2b685ea89f Inline the stories flag. 2022-11-09 19:26:48 -05:00
Cody Henthorne b55954380d Bump various Google Play Services dependencies. 2022-11-09 19:26:48 -05:00
Greyson Parrelli 739a8e9451 Add PNP change number insert events and tests. 2022-11-09 19:26:48 -05:00
Alex Hart 433b5ebc13 Flip case for donation method availability. 2022-11-09 19:26:48 -05:00
Alex Hart 018bb49a03 Cancel boost animations in onStop. 2022-11-09 19:26:48 -05:00
Alex Hart fc145d7367 Increase height of boost items to align with subscriptions. 2022-11-09 19:26:48 -05:00
Alex Hart c5f05f322f Rotate Credit Card payments flag. 2022-11-09 19:26:48 -05:00
Greyson Parrelli b419eb4cd5 Inline internal-only strings. 2022-11-09 19:26:48 -05:00
Alex Hart 9bdf65c4e4 Add androidTest for inserting a direct reply via MessageContentProcessor. 2022-11-09 19:26:48 -05:00
Alex Hart dbbae7f13f Fix a few flaky instrumentation tests to ensure suite passes. 2022-11-09 19:26:48 -05:00
Alex Hart 513228b366 Update text spacing on donations page. 2022-11-09 19:26:48 -05:00
Greyson Parrelli a2415261bd Pair usernames flag with the PNP flag. 2022-11-09 19:26:48 -05:00
Alex Hart 8f06381239 Centralize where we make decisions about donations availability. 2022-11-09 19:26:48 -05:00
Alex Hart f6f1fdb87d Mark unexpected cancellation when silenced so we do not keep hammering the logs. 2022-11-09 19:26:48 -05:00
Alex Hart b8e16353ab Donations credit card formatting. 2022-11-09 19:26:48 -05:00
Alex Hart 16cbc971a5 Add small amount of unit testing for MessageContentProcessor. 2022-11-09 19:26:47 -05:00
Alex Hart d1df069669 Add support for Credit Card 3DS during subscriptions. 2022-11-09 19:26:47 -05:00
Greyson Parrelli 844480786e Bump version to 6.1.3 2022-11-09 18:15:01 -05:00
Greyson Parrelli 77aa0424fd Updated language translations. 2022-11-09 18:15:01 -05:00
Alex Hart 4d94d9d968 Utilize areAnimatorsEnabled on API levels that support it. 2022-11-09 18:15:01 -05:00
Greyson Parrelli 89fca76327 Bump version to 6.1.2 2022-11-08 17:38:55 -05:00
Greyson Parrelli 14b9518a48 Updated language translations. 2022-11-08 17:38:55 -05:00
Greyson Parrelli 512ba2b0a8 Show bottom sheet when you tap an avatar in the story viewer. 2022-11-08 17:38:55 -05:00
Cody Henthorne 9851bc300e Fix mentions with share to single group story flow. 2022-11-08 17:38:55 -05:00
Nicholas a81a4cdb53 Adjust stories view receipts button destination. 2022-11-08 17:38:55 -05:00
Alex Hart b1d1aee373 Fix infinite animation loop when system animations are disabled. 2022-11-08 17:38:55 -05:00
Cody Henthorne 2cfa31a9b0 Fix crash when typing @ in story add message. 2022-11-07 22:39:54 -05:00
Nicholas 67b6cd164e Manually dismiss keyboard on forwarding messages. 2022-11-07 12:23:26 -05:00
Greyson Parrelli f241a51fe1 Hopeful fix for crash on API 19. 2022-11-07 11:22:31 -05:00
Nicholas 74c542099a Autoplay all videos. 2022-11-07 09:15:39 -05:00
Nicholas 5d76f13c51 Increase touch target height of seekbar. 2022-11-07 09:15:24 -05:00
Nicholas Tinsley c6d38600ec Restore wrap_content for album rail. 2022-11-04 17:39:33 -04:00
Cody Henthorne fc3db538bc Bump version to 6.1.1 2022-11-04 16:38:01 -04:00
Cody Henthorne acbccc32a6 Updated language translations. 2022-11-04 16:12:29 -04:00
Cody Henthorne 97a502c8c7 Restrict max threads used for large group profile fetching. 2022-11-04 16:08:31 -04:00
Greyson Parrelli bdba048bc4 Remove possible transaction from identity cache read. 2022-11-04 16:08:30 -04:00
Greyson Parrelli f7adf2ee5a Fix a typo in a group string. 2022-11-04 16:08:30 -04:00
Alex Hart dcc9b8ca66 Fix issue with window insets in API30.
Fixes #12525
2022-11-04 16:08:30 -04:00
Nicholas 7ad6d95b27 Fade in media detail view. 2022-11-04 16:08:30 -04:00
Greyson Parrelli 2856697109 Fix string apostrophe. 2022-11-04 16:08:30 -04:00
Nicholas af89d85696 Fade out video player controls on playback.
2 second delay, cancelable if the video is paused or finished.
2022-11-04 16:08:30 -04:00
Alex Voloshyn c218e22566 Update MobileCoin enclave measurements for v3.0.0 2022-11-04 16:08:30 -04:00
Varsha b38ac44d0f Prompt update on MobileCoin enclave failure. 2022-11-03 12:04:51 -04:00
Cody Henthorne 2709f0ee0d Bump version to 6.1.0 2022-11-02 15:51:37 -04:00
Cody Henthorne c1f84adb2f Updated language translations. 2022-11-02 15:45:33 -04:00
Greyson Parrelli 8c6b7ecc4c Rotate stories feature flags. 2022-11-02 15:40:44 -04:00
Nicholas 5e25e8d0a2 Keep muted chats archived option. 2022-11-02 15:40:44 -04:00
Greyson Parrelli c674d5b674 Fix bad compose box state if you leave while recording a voice note. 2022-11-02 15:31:52 -04:00
Alex Hart 377841db26 Update some keyboard action handling. 2022-11-02 15:31:52 -04:00
Alex Hart 5da7052da3 Fix blocked refresh and format argument. 2022-11-02 15:31:52 -04:00
Alex Hart ffeb60fcdd Update tooltip to Material3 spec. 2022-11-02 15:31:52 -04:00
Cody Henthorne e610ee419f Add internal user remote megaphone conditional. 2022-11-02 15:31:52 -04:00
Greyson Parrelli d61a35b118 Fix layering issue with action buttons in the contact list. 2022-11-02 15:31:52 -04:00
Alex Hart ac189865b9 Update pluralization of payments recovery dialog. 2022-11-02 15:31:52 -04:00
Alex Hart 8056aafc9d Pluralize gateway string. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 8ab16164eb Fix PNP issue around thread merging. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 473c8b199e Fix PNP CDS sync bug. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 3692d87531 Add timeout for registering SMS listener during registration. 2022-11-02 15:31:52 -04:00
Cody Henthorne 99a516f8e5 Fix gv1 migration reminder bug.
Fixes #12554
2022-11-02 15:31:52 -04:00
Alex Hart 0ff4175538 Update design for the donation thanks dialog. 2022-11-02 15:31:52 -04:00
Nicholas Tinsley 4e8208c468 Restore LTR ordering for media preview control icons. 2022-11-02 15:31:52 -04:00
Alex Hart 84f0548966 Center story viewport on tall devices. 2022-11-02 15:31:52 -04:00
Cody Henthorne 77beeda62a Add in-chat payment activation requests.
Co-authored-by: Varsha <varsha@mobilecoin.com>
2022-11-02 15:31:52 -04:00
Greyson Parrelli 8c915572fb Fetch own username from the whoami endpoint. 2022-11-02 15:31:52 -04:00
Nicholas 53883ee3d3 Update MediaPreviewV2 design values. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 40ca16bd06 Don't use ordinals when persisting PNP enum. 2022-11-02 15:31:52 -04:00
Jim Gustafson 60dcfb2fe6 Update to RingRTC v2.21.3 2022-11-02 15:31:52 -04:00
Alex Hart 123fb95916 Allow stripe error codes to be upgraded to decline codes. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 1a657a7a19 Put info about data saver in the logs. 2022-11-02 15:31:52 -04:00
elena f119496da4 Fix back button behaviour in bubbles.
Fixes #12563
2022-11-02 15:31:52 -04:00
Alex Hart 6999d1fbf1 Enforce max gif playback using unreserved count from exo pool. 2022-11-02 15:31:52 -04:00
Alex Hart c1ff2aeeff Print pool stats whenever we fail to get an ExoPlayer instance. 2022-11-02 15:31:52 -04:00
Alex Hart 4220395649 Tie into dispatcher instead of popBackStack() 2022-11-02 15:31:52 -04:00
Alex Hart 806409b329 Fix crash when entering avatar picker on kitkat. 2022-11-02 15:31:52 -04:00
Greyson Parrelli 3e3296da5b Convert ThreadDatabase to kotlin. 2022-11-02 15:31:52 -04:00
Alex Hart 4bbe01cbc3 Hide stories file size header for text stories. 2022-10-31 13:47:33 -04:00
Cody Henthorne c357c35303 Add remote megaphone snooze capabilities. 2022-10-31 13:47:33 -04:00
Alex Hart 2ea5c7e3bc Update google pay button to match new styling. 2022-10-31 13:39:33 -04:00
Alex Hart 5b8a729afc Add credit card icon. 2022-10-31 13:39:33 -04:00
Greyson Parrelli 4077dc829a Improve contact pull-to-refresh performance. 2022-10-31 13:39:33 -04:00
Alex Hart 2cfa685ae2 Add basic 3DS support for credit cards. 2022-10-31 13:39:33 -04:00
Cody Henthorne c686d33a46 Bump version to 6.0.6 2022-10-31 13:16:27 -04:00
Cody Henthorne e00ed81e7c Fix bad unread mentions database migration. 2022-10-31 12:52:50 -04:00
Cody Henthorne 06c9dbe6ec Bump version to 6.0.5 2022-10-31 11:38:48 -04:00
Cody Henthorne 05377d26de Updated language translations. 2022-10-31 11:27:05 -04:00
Cody Henthorne b781de2c17 Fix sms megaphone bug. 2022-10-31 10:49:48 -04:00
Nicholas 9f2c7a65ac Fix lifecycle state for media preview.
After a fragment is destroyed, the media remains loaded in the view model, and it is up to the re-created fragment to take that loaded data and make it ready to be viewed.
2022-10-31 10:08:14 -04:00
Nicholas bae070e60e Remove old MediaPreviewActivity. 2022-10-31 09:23:11 -04:00
Nicholas 34f6d52758 Finish media preview activity if no media present. 2022-10-31 09:07:25 -04:00
Alex Hart 72aac0732c Bump version to 6.0.4 2022-10-28 17:54:15 -03:00
Alex Hart 5da6321c67 Updated language translations. 2022-10-28 17:53:22 -03:00
Alex Hart 4b9e4d739f Ignore warning for androidx transition. 2022-10-28 17:49:50 -03:00
Alex Hart 5d4d6db197 Fix story viewed state retention. 2022-10-28 17:49:50 -03:00
Cody Henthorne 4e3bfadfbe Fix media preview launched from conversation settings crash. 2022-10-28 17:49:50 -03:00
Alex Hart abb0a25b81 Fix crash with disposable lifecycle. 2022-10-28 17:49:50 -03:00
Alex Hart e369f56eab Fix various bugs with KitKat preventing stories from launching. 2022-10-28 17:49:50 -03:00
Alex Hart a066271766 Bump version to 6.0.3 2022-10-27 18:03:36 -03:00
Alex Hart c6eb241261 Updated language translations. 2022-10-27 18:00:31 -03:00
Greyson Parrelli 906441c90c Revert "Convert ThreadDatabase to kotlin."
This reverts commit 1e88fb428d.
2022-10-27 16:54:06 -04:00
Alex Hart 6f46e9000b Permanent attachment failure.
Co-authored-by: Cody Henthorne <cody@signal.org>
2022-10-27 16:33:33 -04:00
Alex Hart 9ef58516e2 Ensure donation error dialogs are shown from main thread. 2022-10-27 15:50:39 -03:00
Alex Hart 10950756d3 Add proper fallback photo for mystory. 2022-10-27 14:39:58 -03:00
Nicholas 7c4c146189 Add edit button for media preview. 2022-10-27 13:30:54 -04:00
Alex Hart 2f0f4f94a2 Set onboarding duration to 10s per story. 2022-10-27 14:18:10 -03:00
Alex Hart 3600a4818c Update first time navigation screen. 2022-10-27 13:43:52 -03:00
Nicholas d003dc435a Design and animation updates for Media Preview. 2022-10-27 10:54:14 -04:00
Alex Hart 8e1ec5ab5b Bump version to 6.0.2 2022-10-26 17:01:03 -03:00
Alex Hart 14781c3aed Updated language translations. 2022-10-26 16:55:41 -03:00
Cody Henthorne 490e29f758 Remember scroll position for internal settings. 2022-10-26 16:50:54 -03:00
Alex Hart a0c48bed6e Fix issue where story video does not stop playback when app is backgrounded. 2022-10-26 13:05:02 -03:00
Alex Hart 1011e4b7f5 Fix tinting of story privacy toolbar action in dark mode. 2022-10-26 12:13:13 -03:00
Nicholas 9602084125 Add auto-mirror param to vector assets. 2022-10-26 11:00:44 -04:00
Alex Hart 36fddbb79a Fix comparison causing hot loop on API25. 2022-10-26 11:09:47 -03:00
Alex Hart 85d5ea0382 Fix story reaction notification summary. 2022-10-26 10:32:28 -03:00
Alex Hart b4d3690d3a Fix issue where incognito mode was not enabled in text story creation. 2022-10-26 10:23:52 -03:00
Nicholas Tinsley 529211c3a5 Fix bottom bar judder when hiding UI. 2022-10-25 17:06:35 -04:00
Nicholas Tinsley 2b4c01c106 Fix autoplay for videos in Media Preview. 2022-10-25 17:06:08 -04:00
Greyson Parrelli 168832c138 Fix stories index migration. 2022-10-25 16:42:16 -04:00
Alex Hart 07915db7bc Bump version to 6.0.1 2022-10-25 16:59:08 -03:00
Alex Hart 3cc1c39f81 Updated language translations. 2022-10-25 16:53:35 -03:00
Cody Henthorne 59de56439a Fix backup fails when running in background. 2022-10-25 16:48:42 -03:00
Nicholas 7759ad283d Media Preview V2 Visual Redesign. 2022-10-25 16:48:39 -03:00
Alex Hart b8174c5e00 Remove duplicate key from FeatureFlags set. 2022-10-25 16:48:39 -03:00
Greyson Parrelli 9de6c44b16 Fix an issue with sharing file attachments into the app. 2022-10-25 16:48:39 -03:00
Rashad Sookram 738676ea5f Add calling dev server URL. 2022-10-25 16:48:37 -03:00
Alex Hart 09361b2d40 Fix crash when viewing views of a group story. 2022-10-25 09:46:56 -03:00
Alex Hart 6055515be9 Bump version to 6.0.0 2022-10-24 21:41:27 -03:00
Alex Hart 37ff750261 Updated language translations. 2022-10-24 21:38:02 -03:00
Alex Hart bc97058ced Rotate story feature flags. 2022-10-24 21:22:50 -03:00
Greyson Parrelli 1e88fb428d Convert ThreadDatabase to kotlin. 2022-10-24 21:22:50 -03:00
Greyson Parrelli d2b72fc8b7 Stop checking the change number capability.
It's been out for a year, no need to check at this point.
2022-10-24 21:03:12 -03:00
Nicholas 469cab284e Media Preview V2 Visual Redesign 2022-10-24 21:03:12 -03:00
Jordan Rose 6c0b63d72c Update libsignal-client to 0.21.1 2022-10-24 21:02:22 -03:00
Greyson Parrelli 1007b4d635 Reduce flakiness of our dependencies. 2022-10-24 21:02:22 -03:00
Nicholas fb8b230442 Add scrolling to rationale prompt. 2022-10-24 21:02:22 -03:00
Nicholas 371267a1d3 Do not close gallery picker once 0 items selected.
This change only takes effect if the user navigates directly to the gallery picker.
2022-10-24 21:02:22 -03:00
Nicholas 084e806c25 Disable pager scrolling during 2 finger gestures. 2022-10-24 21:02:22 -03:00
Nicholas 32fbbf2b55 Add seek buttons for videos longer than 30s. 2022-10-24 21:02:22 -03:00
Nicholas 7f4e964ec8 Enable Media Preview V2. 2022-10-24 21:02:17 -03:00
Alex Hart 3fefc17582 Add new fade color for expiration sheet. 2022-10-24 21:01:41 -03:00
Greyson Parrelli 62d5777c39 Inline the RecipientMergeV2 flag. 2022-10-24 21:01:41 -03:00
Greyson Parrelli 367ff7c75c Always use CDSI. 2022-10-24 21:01:37 -03:00
Alex Hart 1174bc8e07 Credit card validator implementations and spec tests. 2022-10-24 21:00:18 -03:00
Nicholas 27c3607099 Ktformat QrMainActivity. 2022-10-24 21:00:18 -03:00
Nicholas 7088b1a302 Fix last media preview V2 UI glitches. 2022-10-24 21:00:18 -03:00
Greyson Parrelli 3826ac553d Remove the unused/deprecated WRITE_PROFILE permission. 2022-10-24 21:00:17 -03:00
Cody Henthorne 0819c8d2b9 Add inline selected emojis to the recently used list.
Fixes #12514
2022-10-24 21:00:17 -03:00
Cody Henthorne 341b8effcf Add unread mention badging to conversation list. 2022-10-24 21:00:17 -03:00
Cody Henthorne ea9bf0ccd5 Fix QR processing resolution and allow front camera use for device linking. 2022-10-24 21:00:17 -03:00
Nicholas 3d14c05114 Surround phone numbers with LTR unicode mark.
This also removes the previous TextView LTR flag that I had initially added for a one-off.
2022-10-24 21:00:17 -03:00
Nicholas 3a78031a71 Show album rail when entering media preview from All Media. 2022-10-24 21:00:17 -03:00
Alex Hart daa3721145 Add new joined donations screen. 2022-10-24 21:00:17 -03:00
Nicholas c829fba332 Only show album rail on album messages. 2022-10-24 21:00:17 -03:00
Cody Henthorne 7fafa4d5e6 Ensure network call resources are closed. 2022-10-24 21:00:17 -03:00
Nicholas 1f581c074d Close Cursor in Media Preview V2. 2022-10-24 21:00:17 -03:00
Nicholas 556d267084 Fix Z-ordering of preview media. 2022-10-24 21:00:17 -03:00
Alex Hart 3a7be812eb Bump version to 5.53.6 2022-10-24 20:57:19 -03:00
Alex Hart 807e6d4e71 Updated language translations. 2022-10-24 20:52:52 -03:00
Alex Hart c1c138ce49 Rotate story flags. 2022-10-24 20:48:22 -03:00
Alex Hart a15e97cc06 Filter story info to just the relevant people in that specific dlist.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-10-24 20:48:20 -03:00
Alex Hart 48e0a00a8a Stop observing state updates after post sequence is completed. 2022-10-24 20:06:02 -03:00
Greyson Parrelli b46e129c23 Fix issue with sending story viewed receipts. 2022-10-24 18:47:10 -04:00
Alex Hart 064f7abd92 Remove stories beta dialog. 2022-10-24 16:19:52 -03:00
Alex Hart 428ab65d8a Prevent expiry timers from being sent to distribution lists. 2022-10-24 14:07:44 -03:00
Alex Hart 94f072c5aa Ensure content is stopped during video player cleanup. 2022-10-24 12:58:53 -03:00
Cody Henthorne 91f0b75a80 Fix incorrect Phase 0 implementations. 2022-10-24 10:17:12 -04:00
Cody Henthorne cb65347bb3 Bump version to 5.53.5 2022-10-20 22:06:17 -04:00
Cody Henthorne 41aad39c62 Updated language translations. 2022-10-20 21:58:46 -04:00
Cody Henthorne 390f6c2462 Fix lint issue with ViewBinderDelegate. 2022-10-20 21:53:45 -04:00
Cody Henthorne 163c7de327 Fix blur width for landscape oriented stories. 2022-10-20 21:52:55 -04:00
Cody Henthorne 08b7dcb1ee Add check and request for SMS read permission to perform export. 2022-10-20 21:52:55 -04:00
Alex Hart dfdf68b7b5 Add support for separate story view receipt control.
This reverts commit 1046265d23.
2022-10-20 21:52:55 -04:00
Greyson Parrelli 9941ffe79c Updated KBS settings. 2022-10-20 21:52:54 -04:00
Greyson Parrelli 418083d0c7 Fix delete-for-everyone issue with stories. 2022-10-20 21:52:54 -04:00
Alex Hart 25ac462921 Update story explanation text. 2022-10-20 21:52:54 -04:00
Alex Hart f401ee00a1 Update tap to add text. 2022-10-20 21:52:54 -04:00
Alex Hart 94bd3101c9 Add support for stories "seen" state. 2022-10-20 21:52:54 -04:00
Alex Hart 995a4ad6ec Add new name field design. 2022-10-20 21:52:54 -04:00
Alex Hart 36206dfa9a Remove Stories header. 2022-10-20 21:52:54 -04:00
Alex Hart a176188c7d Add new iconography for custom stories. 2022-10-20 21:52:54 -04:00
Alex Hart 44f551acc5 Request layout after text changes to ensure content is properly sized. 2022-10-20 21:52:54 -04:00
Alex Hart 39c1939470 Rename private story as custom story. 2022-10-20 21:52:54 -04:00
Cody Henthorne ba2d84005d Fix wrong hint flashing into the compose box. 2022-10-20 21:52:54 -04:00
Greyson Parrelli f54f9b7011 Update libsignal-client to 0.21.0 2022-10-20 21:52:54 -04:00
Alex Hart 6600857259 Add new bottom bar icons. 2022-10-20 21:52:54 -04:00
Alex Hart e465f35e50 Add new stories icon to settings. 2022-10-20 21:52:54 -04:00
Alex Hart 20eda03a5a Swap add to to just new. 2022-10-20 21:52:54 -04:00
Alex Hart 2315a1c632 Remove isFeatureAvailable and remove compatibility check. 2022-10-20 21:52:54 -04:00
Alex Hart ca36eaacce Add support for separate story view receipt control.
This reverts commit 1046265d23.
2022-10-20 21:52:54 -04:00
Cody Henthorne 2f2711c9a3 Require valid link url for story test posts. 2022-10-20 17:50:09 -04:00
Greyson Parrelli 94f135ac38 Include emoji rendering info in debug log. 2022-10-20 17:50:09 -04:00
Greyson Parrelli 3687021051 Update logging to match desktop and iOS. 2022-10-20 17:50:09 -04:00
Cody Henthorne a535b4f97c Reimplement Phase 0 for SMS removal. 2022-10-20 17:50:09 -04:00
Cody Henthorne 690e1e60ba Improve error reporting for SMS export. 2022-10-19 22:11:31 -04:00
Cody Henthorne 262f762d7f Bump version to 5.53.4 2022-10-18 17:07:00 -04:00
Cody Henthorne 59fe196fe0 Updated language translations. 2022-10-18 17:02:09 -04:00
Cody Henthorne 7fccbd44c0 Fix infinite export loop and improve general error handling. 2022-10-18 16:06:37 -04:00
Cody Henthorne 0d715d2c18 Cherry pick mentions badge database migration. 2022-10-18 14:26:55 -04:00
Cody Henthorne f0e94ebbad Fix sms export crash with missing sms thread recipient. 2022-10-18 12:34:30 -04:00
Greyson Parrelli b324db53d3 Fix crash listening to PSTN pickups on some devices.
We do this as a helpful thing to end a signal call if the user gets a
PSTN call, but some Xiaomi devices are crashing because they require
unique permissions. But we can just do it optimistically.
2022-10-18 09:59:36 -04:00
Cody Henthorne a456c3fa32 Fix invite banner background color in wallpaper conversations. 2022-10-17 21:08:48 -04:00
Cody Henthorne 57151145d3 Update sms export copy to clarify intent in a few places. 2022-10-17 20:41:35 -04:00
Alex Hart ff7dcd26c8 Fix issue where forwarded link preview would overwrite original message id. 2022-10-17 13:41:18 -03:00
Alex Hart 87c024e968 Add lifecycle observation to group replies state observer. 2022-10-17 13:41:18 -03:00
Alex Hart b8665e41e8 Fix re-launch bug with ViewAllConnections dialog fragment. 2022-10-17 13:41:18 -03:00
Alex Hart 1d5a83668b Fix story name display in removal dialog. 2022-10-17 13:41:18 -03:00
Greyson Parrelli ea6f6bf47d Bump version to 5.53.3 2022-10-17 11:46:18 -04:00
Greyson Parrelli da4c9926cf Updated language translations. 2022-10-17 11:40:12 -04:00
Cody Henthorne 3b3dcdcb14 Fix crash from treating mms groups like Signal groups after sms is disabled.
Fixes #12534
2022-10-17 11:39:00 -04:00
Alex Hart ba3dd79d4e Fix possible crash where tooltip is dismissed after fragment is detached. 2022-10-17 11:39:00 -04:00
Cody Henthorne 1006af7d8a Fix sms export crash with missing thread recipients. 2022-10-17 11:39:00 -04:00
fm-sys 0daed8f7d7 Disable scrolling in contact list while context menu is shown.
Fixes #12530
Closes #12531
2022-10-17 11:39:00 -04:00
Greyson Parrelli 741eb55562 Fix some SMS strings. 2022-10-17 11:39:00 -04:00
Alex Hart 2b0bf032d7 Do not autoplay videos. 2022-10-17 11:39:00 -04:00
Greyson Parrelli 00e70212c5 Catch possible phone permission exception on specific devices. 2022-10-17 11:39:00 -04:00
Greyson Parrelli 14a9e22b5e Recreate a storageId for self if one doesn't exist. 2022-10-17 11:39:00 -04:00
Alex Hart 7ce1f9463e Fix stories icon in dark mode.
Closes #12532

Co-authored-by: fm-sys <64581222+fm-sys@users.noreply.github.com>
2022-10-17 11:38:48 -04:00
Alex Hart 9bb834e9f5 Remove RTL swap of tapping edges in story viewer. 2022-10-14 10:40:26 -03:00
Alex Hart 957f8754e1 Fix poor handling of single tap touches. 2022-10-14 10:31:55 -03:00
Greyson Parrelli 6673df2514 Fix translation comments. 2022-10-13 20:51:59 -04:00
Greyson Parrelli cd619833d1 Bump version to 5.53.2 2022-10-13 16:32:52 -04:00
Greyson Parrelli ba7bfd7171 Updated language translations. 2022-10-13 16:32:51 -04:00
Cody Henthorne 033004719a Fix bug when scheudling backups. 2022-10-13 16:32:51 -04:00
Alex Hart a0172ddb2f Autoscale story text so all content fits in viewer. 2022-10-13 16:32:51 -04:00
Cody Henthorne b6db7e7af6 Add phased SMS removal UX. 2022-10-13 16:32:51 -04:00
Greyson Parrelli 8a238a66e7 Do not flag SKDMs for group stories as stories. 2022-10-13 16:32:51 -04:00
Alex Hart de29fc047e Fix issue where last item in contact selection collection would not display. 2022-10-13 16:32:51 -04:00
Alex Hart 7cdaf988f2 Allow users to save text stories. 2022-10-13 10:21:00 -03:00
Alex Hart 43caec69e3 Update new story button. 2022-10-13 09:57:40 -03:00
Alex Hart f533219bad Move stories setting to top level. 2022-10-13 09:27:34 -03:00
Alex Hart 2bbce6ad47 Revert "Do not remove onboarding story when disabling stories."
This reverts commit 8c76cead58.
2022-10-13 09:17:34 -03:00
Greyson Parrelli a04c2c30b9 Bump version to 5.53.1 2022-10-12 15:46:19 -04:00
Greyson Parrelli 68c5f8e9ae Updated language translations. 2022-10-12 15:46:19 -04:00
Alex Hart 246fbc4ee9 Fix My Story row overlap. 2022-10-12 15:46:19 -04:00
Alex Hart 9480cd1b7b Add ability to hide sms tag in contact search config. 2022-10-12 15:46:19 -04:00
Alex Hart 220931d3df Pass through clip information to video player. 2022-10-12 15:46:19 -04:00
Alex Hart 8c76cead58 Do not remove onboarding story when disabling stories. 2022-10-12 15:46:19 -04:00
Alex Hart da3623d7e6 Fix fast story tapping. 2022-10-12 15:46:18 -04:00
Alex Hart f72c44c7c3 Do not unregister typeface result which can lead to a crash. 2022-10-12 15:46:18 -04:00
Alex Hart d2523c2661 Disallow view-once if share selection is stories. 2022-10-12 15:46:18 -04:00
Cody Henthorne 7139f91997 Fix unread count separator and mark read when viewing behavior.
Fixes #12510
2022-10-12 10:40:50 -04:00
Greyson Parrelli 371d9e8f01 Update to targetSdk 31. 2022-10-12 10:03:38 -04:00
Cody Henthorne a8e03e9bf2 Fix backup job background start restricitions with API31+. 2022-10-12 09:48:40 -04:00
Alex Hart e1c6dfb73b Move story post display logic into a single fragment. 2022-10-12 10:02:27 -03:00
Cody Henthorne 96d60e11b0 Fix call participants button showing under status bar after returning from PIP. 2022-10-11 16:59:02 -04:00
Cody Henthorne 5662473c18 Fix bubble color wallpaper preview not updating for individuals. 2022-10-11 16:10:58 -04:00
Cody Henthorne 14dd71bf78 Prevent crash from poor implementation of camera apis. 2022-10-11 15:20:55 -04:00
Cody Henthorne 55d437e54b Make group updates not mark a thread as unread. 2022-10-11 15:13:17 -04:00
Greyson Parrelli 3d8f62ce9d Bump version to 5.53.0 2022-10-11 14:48:29 -04:00
Greyson Parrelli 19d029a643 Updated language translations. 2022-10-11 14:47:44 -04:00
Greyson Parrelli e85ba03756 Rotate the stories flag. 2022-10-11 14:46:35 -04:00
Alex Hart 7315c991d5 Fix IndexOutOfBoundsException when rapidly tapping through stories. 2022-10-11 14:46:35 -04:00
Alex Hart 83d1ab2eb5 Fix crash in processing of distribution list recipients. 2022-10-11 14:46:35 -04:00
Alex Hart 1e491d0b51 Fix story link previews for broken urls. 2022-10-11 14:46:35 -04:00
Alex Hart 50a7c2ba5c Disable link preview button with empty input. 2022-10-11 14:46:35 -04:00
Alex Hart 4cc6bb4fbe Do not allow forwarding of content more than 13 lines long to stories. 2022-10-11 14:46:35 -04:00
Alex Hart 7477f3c319 Increse bottom gradient height to improve readability. 2022-10-11 14:46:35 -04:00
Alex Hart 1046265d23 Fix receipt handling issue for stories. 2022-10-11 14:46:35 -04:00
Alex Hart 7cc2029cd3 Finish the activity instead of delegating to onback dispatcher. 2022-10-11 14:46:35 -04:00
Alex Hart 71ca39fd4a Fix crash when calling dismissNow in onDestroy. 2022-10-11 14:46:35 -04:00
Greyson Parrelli bfd2686610 Fix issue where some threads were invisibly unread.
Problem 1: We weren't marking threads read when we shared into them.
Problem 2: We hid the unread status of threads whose last message was
outgoing.

This addresses both. It's possible that 'fixing' problem 2 could result
in more threads being marked as read, but really that should just make
us aware so we can properly mark the thread as read.
2022-10-11 14:46:35 -04:00
Veniamin Vynohradov c131fb500d Add 'detailed' conversation style to show full file names.
Fixes #12442
Closes #12463
2022-10-11 14:46:35 -04:00
Sgn-32 cdb7f07368 Add ability to clear the proxy address.
Closes #12499
2022-10-11 14:46:35 -04:00
Cody Henthorne de329166d2 Always show remote participant when entering PIP mode. 2022-10-11 14:46:35 -04:00
Sgn-32 83ae613e9a Use MaterialAlertDialogBuilder in RatingManager.
Closes #12501
2022-10-11 14:46:35 -04:00
Nicholas b342ce6874 Load media in preview earlier than target attachment. 2022-10-11 14:46:35 -04:00
Cody Henthorne bef83e4c0c Remove unused context arguments in RecipientUtil. 2022-10-11 14:46:35 -04:00
Greyson Parrelli db0bca00ec Bump version to 5.52.5 2022-10-11 14:30:45 -04:00
Greyson Parrelli e3c38e635a Updated language translations. 2022-10-11 14:30:07 -04:00
Cody Henthorne 10d4063ecf Remove unused story string. 2022-10-11 14:23:47 -04:00
Cody Henthorne c6e3c9dd35 Fix thread not updating after group creation. 2022-10-10 15:43:36 -04:00
Cody Henthorne 02d9cbe01b Fix export flow on small screens. 2022-10-10 14:37:18 -04:00
Cody Henthorne 68237df321 Fix ongoing call notification bug. 2022-10-10 13:25:10 -04:00
Greyson Parrelli c82bf826e0 Bump version to 5.52.4 2022-10-07 18:45:12 -04:00
Greyson Parrelli 8fb404a492 Updated language translations. 2022-10-07 18:45:12 -04:00
Greyson Parrelli 437d6c7a52 Flag deletes and replies to group stories as stories. 2022-10-07 18:45:12 -04:00
Greyson Parrelli 30b635cca2 Allow SKDM's if story=true. 2022-10-07 18:45:12 -04:00
Greyson Parrelli 5fb0956c16 Improve an registration error log. 2022-10-07 18:45:12 -04:00
Greyson Parrelli a9f654a520 Disable okhttp automatic retries for CDSI. 2022-10-07 18:45:12 -04:00
Alex Hart 4b10ec8f02 Allow story search in forward fragment to be case insensitive. 2022-10-07 18:45:12 -04:00
Alex Hart 02db5f74e9 Allow non-default emoji to animate in group replies. 2022-10-07 18:45:12 -04:00
Alex Hart 842626e96c Add viewer count and list to 'All Signal Connections'. 2022-10-07 18:45:12 -04:00
Alex Hart c239ba1e35 Fix crash after replying to a group story. 2022-10-07 18:45:11 -04:00
Alex Hart 9aa7543f2f Do not display stories as valid selections when sending view-once media. 2022-10-07 18:45:11 -04:00
Alex Hart 5c77c33dff Fix flow colors. 2022-10-07 18:45:11 -04:00
Alex Hart 3dd31432c8 Allow getMessageDestination to handle Story messages. 2022-10-07 18:45:11 -04:00
Alex Hart 3de75f48cf Add padding to bottom of selection recycler. 2022-10-07 18:45:11 -04:00
Greyson Parrelli be98ff3508 Fix bottom bar color in group story selector. 2022-10-07 18:45:11 -04:00
Greyson Parrelli 04b0c01015 Catch a foreground service start exception. 2022-10-07 18:45:11 -04:00
Cody Henthorne 50ded5c92a Rotate SMS exporter flag. 2022-10-07 18:45:11 -04:00
Alex Hart 2041756513 Story info page should mirror message details. 2022-10-07 18:45:11 -04:00
Greyson Parrelli 742d1bece0 Bump version to 5.52.3 2022-10-06 16:38:16 -04:00
Greyson Parrelli 4ee8218194 Updated language translations. 2022-10-06 16:38:16 -04:00
AsamK 22e97457a3 Fix sending normal group messages when falling back to socket.
In the sendGroupMessage message the socket fallback for sending normal
group messages always set the story parameter to true.
This causes the message to be discarded by the receivers, because it has
a story envelope, but no story content
> Envelope was flagged as a story, but it did not have any story-related content! Dropping.

Issue was introduced in 3895578d51

Closes #12496
2022-10-06 16:38:16 -04:00
Alex Hart 9d469db7ae Move stories above app security section. 2022-10-06 16:38:16 -04:00
Alex Hart 72347af967 Disassociate direct replies when remote-deleting a story. 2022-10-06 16:38:16 -04:00
Greyson Parrelli e3dff46136 Rotate AccountRecord.storiesDisabled
iOS had a bug and we need to try again.
2022-10-06 16:38:16 -04:00
Alex Hart 891c99a148 Do not allow users to attempt to send story replies to an inactive group. 2022-10-06 16:38:16 -04:00
Greyson Parrelli 8a452ddf11 Allow remote deletes to be tagged with story=true. 2022-10-06 16:38:16 -04:00
Alex Hart aef0ed828c Add proper colorization to send button in stories flow. 2022-10-06 16:38:16 -04:00
Alex Hart 9ad55e2360 Fix issue where images were not properly rendered for previews. 2022-10-06 13:45:17 -03:00
Alex Hart f687840891 Do not display story media in settings media rail. 2022-10-06 13:22:37 -03:00
Greyson Parrelli bb323dc575 Bump version to 5.52.2 2022-10-06 11:58:56 -04:00
Greyson Parrelli c0e11fbd23 Updated language translations. 2022-10-06 11:58:23 -04:00
Alex Hart 0d94794ece Fix issue where quote view would display base64 encoded text story. 2022-10-06 11:56:09 -04:00
Greyson Parrelli 14e8f5cf98 Fix sending group stories when you're the only group member. 2022-10-06 11:56:09 -04:00
Alex Hart b78f06f064 Update colors on create button in private story creation flow. 2022-10-06 11:56:09 -04:00
Alex Hart 0b978dd9d7 Update private story creation screens to match material3 spec. 2022-10-06 11:56:09 -04:00
Alex Hart da9dcc794f Close search if open on back pressed in stories landing page. 2022-10-06 11:56:09 -04:00
Alex Hart f3fabcbe6a Fix issue where onboarding text could be cut off. 2022-10-06 11:56:09 -04:00
Alex Hart 95801dbdc7 Remove long-press action from my story items. 2022-10-06 11:56:09 -04:00
Alex Hart 0a33574f1d Do not unarchive threads when story is received. 2022-10-06 11:56:09 -04:00
Alex Hart 35f1baf965 Add group story removal dialog. 2022-10-06 11:56:09 -04:00
Alex Hart cc5aab6be3 Fix display of story disable dialog. 2022-10-06 10:04:14 -03:00
Alex Hart 486e172aee Fix crash when naturally finishing story set. 2022-10-06 10:02:19 -03:00
Alex Hart ec46d6039d Fix crash when trying to share a text story. 2022-10-06 09:55:49 -03:00
Greyson Parrelli 1fe4c45c44 Bump version to 5.52.1 2022-10-05 19:49:51 -04:00
Greyson Parrelli 9946da2cec Fix crash when fetching messages. 2022-10-05 19:49:29 -04:00
Greyson Parrelli f9a4b7cf12 Bump version to 5.52.0 2022-10-05 18:15:10 -04:00
Greyson Parrelli 1fc119e027 Fix lifespan of RefreshAttributesJob. 2022-10-05 18:15:10 -04:00
Greyson Parrelli 293bc2da47 Rotate the stories feature flag. 2022-10-05 18:15:10 -04:00
Jim Gustafson 44d4075636 Update to RingRTC v2.21.2 2022-10-05 18:15:10 -04:00
Greyson Parrelli 23ba5c874a Improve styling of ChooseGroupStoryBottomSheet. 2022-10-05 18:15:10 -04:00
Cody Henthorne 26709177d2 Fix out-of-sync local state after rejoining a group via invite link. 2022-10-05 18:15:10 -04:00
Greyson Parrelli 3895578d51 Always use sealed sender when sending stories. 2022-10-05 18:15:10 -04:00
Nicholas a9a64a3f60 Update MediaPreviewV2 to use thumbnail rail & menu items. 2022-10-05 18:15:10 -04:00
Alex Hart 2edb9eeb52 Add stories beta dialog. 2022-10-05 18:15:10 -04:00
Alex Hart 4b94509a7a Add dialog protection and remote deletion to disabling stories and deleting lists. 2022-10-05 15:04:54 -03:00
Greyson Parrelli ad1801108d Fix issues with story thread when processing a sync message. 2022-10-05 11:52:57 -04:00
Alex Hart ee00e931eb Fix possible RxStore memory leak. 2022-10-05 12:06:47 -03:00
Nicholas 4f3910e3ae Add toolbar to MediaPreviewV2 implementation. 2022-10-04 17:32:57 -04:00
Alex Hart 79b3b9190a Add blocklist for mixed-mode capture. 2022-10-04 17:32:57 -04:00
Greyson Parrelli afedbf40e3 Prepare the websocket keepalive for API 31. 2022-10-04 17:32:57 -04:00
Alex Hart 437c3ffd66 Add logging to forward fragment closes. 2022-10-04 17:32:57 -04:00
Alex Hart 083219888c Add logging around attachment id update. 2022-10-04 17:32:57 -04:00
Cody Henthorne c1f3e27101 Fix missed call notification when busy on another device. 2022-10-04 17:32:57 -04:00
Greyson Parrelli 52965da8a5 Stop checking very old capabilities. 2022-10-04 17:32:57 -04:00
Varsha afe36b982f Prompt to setup payment bioauth, require to disable payment lock. 2022-10-04 17:32:57 -04:00
Nicholas f63ce79f16 Create new Media Preview infrastructure, behind feature flag. 2022-10-04 17:32:57 -04:00
Alex Hart 1af576c157 Always display the date in story viewer. 2022-10-04 17:32:57 -04:00
Alex Hart 86a345a4f3 Add proper treatment for story pager sending state bar. 2022-10-04 17:32:57 -04:00
Greyson Parrelli 13bd003564 Improve quote model generation. 2022-10-04 17:32:57 -04:00
Greyson Parrelli b3672273e8 Update BodyRange to use unsigned ints. 2022-10-04 17:32:57 -04:00
Alex Hart e2a842b440 Fix inability to forward videos to stories. 2022-10-04 17:32:57 -04:00
Greyson Parrelli 1999db97f2 Add support for system names on the ContactRecord. 2022-10-04 17:32:57 -04:00
Greyson Parrelli 6e5f28339d Bump version to 5.51.7 2022-10-04 17:32:15 -04:00
Greyson Parrelli ce55f6d1c2 Updated language translations. 2022-10-04 17:32:15 -04:00
Alex Hart b8ec43f466 Add correct icon to message action item. 2022-10-04 17:32:15 -04:00
Nicholas Tinsley 5b7875b763 Set default audio to speaker on incoming ring.
This comes from FullSignalAudioManager.
2022-10-04 17:32:15 -04:00
Alex Hart dfcc14963d Fix issue with insets on API < 30. 2022-10-04 17:32:15 -04:00
Greyson Parrelli e1c3583702 Bump version to 5.51.6 2022-10-03 11:33:18 -04:00
Greyson Parrelli 9cea4931d4 Updated language translations. 2022-10-03 11:32:51 -04:00
Nicholas 6c56ef470f Nullability safety for getCommunicationDevice(). 2022-10-03 10:50:55 -04:00
Greyson Parrelli 04822bacdc Use tryOnError in CdsiSocket. 2022-10-03 10:50:12 -04:00
Cody Henthorne 3b1ecc7015 Bump version to 5.51.5 2022-09-29 19:30:15 -04:00
Cody Henthorne 36bd7dae60 Updated language translations. 2022-09-29 19:25:51 -04:00
Cody Henthorne 1b784d6522 Fix incorrect emoji style from being used on some devices. 2022-09-29 19:21:43 -04:00
Nicholas 063f4d2994 Refactor API31 impl to match FullSignalAudioManager. 2022-09-29 19:21:43 -04:00
Cody Henthorne 4325d96a5a Fix crash when checking phone call state. 2022-09-29 11:19:41 -04:00
Greyson Parrelli 88c36e1ff6 Bump version to 5.51.4 2022-09-29 11:09:08 -04:00
Greyson Parrelli c86b34bb46 Update build tools version to 32.0.0 2022-09-29 10:55:14 -04:00
Cody Henthorne 6708089777 Bump version to 5.51.3 2022-09-29 10:44:14 -04:00
Cody Henthorne 33d108cde3 Updated language translations. 2022-09-29 10:36:53 -04:00
Nicholas 612ce5d0a8 Set UpdateApkReadyListener receivers to not exported. 2022-09-29 10:17:39 -04:00
Alex Hart 0d8ff0ead0 Update window insets logic for gallery and review screens. 2022-09-29 10:17:07 -04:00
Nicholas Tinsley d413f0041b Revert "Update to targetSdkVersion 32."
This reverts commit 7451ee1403.
2022-09-29 10:15:23 -04:00
Cody Henthorne 678d1c9549 Bump version to 5.51.2 2022-09-28 16:28:40 -04:00
Cody Henthorne 7dc149ddbc Fix non-fcm web socket monitor crash loop. 2022-09-28 16:28:16 -04:00
Cody Henthorne 09b9349f6c Bump version to 5.51.1 2022-09-28 15:49:31 -04:00
Nicholas aeb5a9cf57 Better handling of Bluetooth connections/disconnections during calls. 2022-09-28 15:44:38 -04:00
Greyson Parrelli aaf8bf3280 Fix crash with delayed foreground service. 2022-09-28 15:16:52 -04:00
Greyson Parrelli b6d7271858 Fix a PNP-related contact merge scenario. 2022-09-28 14:40:44 -04:00
Alex Hart 9498a34293 Add onWillBeDestroyed callback to ViewBinderDelegate 2022-09-28 14:45:27 -03:00
Cody Henthorne 0cae15b7fd Bump version to 5.51.0 2022-09-28 11:41:10 -04:00
Cody Henthorne 11e4fd7f34 Updated language translations. 2022-09-28 11:34:13 -04:00
Cody Henthorne 31f31534ce Round out sms/mms export process. 2022-09-28 11:34:13 -04:00
Greyson Parrelli 0e4bec3977 Clean up some unused feature flags. 2022-09-28 11:34:13 -04:00
Greyson Parrelli 7fef1b060f Add proxy support for CDSv2. 2022-09-28 11:34:13 -04:00
Alex Hart 0312dfcfcd Allow autofocus of name field. 2022-09-28 11:34:13 -04:00
Alex Hart b05f4430f6 Ensure my story is always at the top of the list. 2022-09-28 11:34:13 -04:00
Alex Hart 8703707d62 Add registration check for Stories flag check. 2022-09-28 11:34:13 -04:00
Alex Hart 04eeb434c9 Add ability to hide contacts behind a feature flag. 2022-09-28 11:34:12 -04:00
Alex Hart a8a773db43 Fix StoryLinkPreviewView touch targets. 2022-09-28 11:33:36 -04:00
Alex Hart daf78b31b5 Fix hide dialog dismissal. 2022-09-28 11:33:36 -04:00
Alex Hart 20ce3e68f8 Move snackbar positioning. 2022-09-28 11:33:36 -04:00
Nicholas 92d065050f Fix Headset Switching (Especially Bluetooth) on Android 12+. 2022-09-28 11:33:36 -04:00
Nicholas 1b53f09687 Force LTR formatting for the phone number in AppSettingsFragment. 2022-09-28 11:33:36 -04:00
Alex Hart f4d0bf900c Add polish to story crossfader when exiting viewer. 2022-09-28 11:33:36 -04:00
Sgn-32 c652d83f81 Use MaterialAlertDialogBuilder in EditProxyFragment.
Closes #12479
2022-09-28 11:33:36 -04:00
Nicholas 7167ad331f Hide megaphone view in archived list. 2022-09-28 11:33:36 -04:00
Greyson Parrelli 9bb089d198 Add interfaces for tables that reference RecipientIds or thread IDs. 2022-09-28 11:33:36 -04:00
Cody Henthorne 866853ff99 Fix qr scanner for camerax blacklisted devices. 2022-09-28 11:33:36 -04:00
Alex Hart 931b9f8831 Update stories jump logic to match spec. 2022-09-28 11:33:36 -04:00
Alex Hart e8c10cd550 Add basic story search support. 2022-09-28 11:33:35 -04:00
Alex Hart 1049f8bd2f Update to Material Design 1.6.1 2022-09-28 11:33:35 -04:00
Jim Gustafson 9929e6549e Update to RingRTC v2.21.1 2022-09-28 11:33:35 -04:00
Cody Henthorne ff28ff0e6b Fix too many pending intents crashes. 2022-09-28 11:33:35 -04:00
Alex Hart 2a82db2b02 Update bad calculation of content size for stories collection. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 457c3c0526 Don't start disallowed foreground service on API 31+. 2022-09-28 11:33:35 -04:00
Cody Henthorne 4f803c695b Fix crash when unable to decode notification image preview. 2022-09-28 11:33:35 -04:00
Alex Hart bdbdcccaff Fix potential crash when searching contacts in forward sheet. 2022-09-28 11:33:35 -04:00
Cody Henthorne 8d7393e4b5 Fix controlls showing in call PIP. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 533dcfb828 Improve handling of SSLExceptions.
Current theory is that some Samsung devices a doing something funky with SSLExceptions, causing them to not be caught as IOExceptions.
2022-09-28 11:33:35 -04:00
Isira Seneviratne e67ac95890 Use AlarmManagerCompat.
Fixes #12468
2022-09-28 11:33:35 -04:00
Alex Hart 1b63ed0b20 Remove redundant text from story landing screen empty state. 2022-09-28 11:33:35 -04:00
Alex Hart 07d9e29e7c Update new story text to be a small button. 2022-09-28 11:33:35 -04:00
Alex Hart c47a724654 Add support for new group story display states. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 8ca94eb3d5 Fix issue where link previews wouldn't finish if we couldn't fetch the thumbnail. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 11b1c9655c Fix image banding that can sometimes show in high-res images. 2022-09-28 11:33:35 -04:00
Nicholas cf3dd70600 Prevent Chats icon from animating when returning from other activity. 2022-09-28 11:33:35 -04:00
Alex Hart 0bf5f15cf9 Enqueue downloads for stories we view on other devices. 2022-09-28 11:33:35 -04:00
Alex Hart ea3fb774f8 Display failure state in story info and other places. 2022-09-28 11:33:35 -04:00
Alex Hart 25c0dc801f Display group story notifications if user has reacted or replied. 2022-09-28 11:33:35 -04:00
Alex Hart c29922a575 Add check to load thumbnail if it comes in late. 2022-09-28 11:33:35 -04:00
Varsha e676f324f1 Add new handling to encourage the user to save their wallet recovery phrase.
This only effects those who have opted in to payments and have a non-zero balance.
2022-09-28 11:33:35 -04:00
Nicholas c6bfdeb4b0 Track tab buttons' selected state in the ViewModel. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 80a6e0f781 Show a chat event when two threads are merged.
* Add internal button to split contacts for debugging.
* Show a chat event when two threads are merged.
2022-09-28 11:33:35 -04:00
Varsha bc7b0b40b0 Update payment keyboard insets and colors. 2022-09-28 11:33:35 -04:00
Alex Hart 1cea615675 Reimplement contact search collection to support group access predicate. 2022-09-28 11:33:35 -04:00
Alex Hart 9dd96148d1 Add story boolean to envelope proto. 2022-09-28 11:33:35 -04:00
Alex Hart 9e094dfc2b Add internal prefs page for launching stories dialogs. 2022-09-28 11:33:35 -04:00
Alex Hart a39b09c314 Add correct tinting to send button in multiforward activity. 2022-09-28 11:33:35 -04:00
Alex Hart 6c4c299b28 Support enabling stories access by country. 2022-09-28 11:33:35 -04:00
Nicholas a98cc5706f Use ViewCompat to get window insets on Android 5.0+.
On devices running API 20 and below, getRootWindowInsets() always returns null.
2022-09-28 11:33:35 -04:00
Nicholas Tinsley 7451ee1403 Update to targetSdkVersion 32. 2022-09-28 11:33:35 -04:00
Nicholas Tinsley b9f4dc3fe9 Specify exported status and PendingIntent mutability.
Also reduce shake sampling frequency, add coarse location permission.
Random things for targetSdk 32.
2022-09-28 11:33:35 -04:00
Alex Hart 2566d6f61f Fix story unit test compilation. 2022-09-28 11:33:35 -04:00
Alex Hart 8eebdaf451 Set max story video duration to 30999ms. 2022-09-28 11:33:35 -04:00
Alex Hart b1dacf4acd Fix story reply synchronization. 2022-09-28 11:33:35 -04:00
Alex Hart 9326c1726a Increase stories caption limit to 1500 grapheme clusters. 2022-09-28 11:33:35 -04:00
Alex Hart 654b602cef Fix bounds clipping in pinch-to-zoom story gesture. 2022-09-28 11:33:35 -04:00
Alex Hart a642876bda Fix issue where crossfader has wrong story on shared element animation start. 2022-09-28 11:33:35 -04:00
Nicholas 2b8041d779 Make VerificationCodeView lay out properly on tiny screens.
Chain together the views inside VerificationCodeView so that they don't get collapsed to 0dp width.
2022-09-28 11:33:35 -04:00
Alex Hart 8141b53c15 Display dialog to confirm hiding story in story viewer. 2022-09-28 11:33:35 -04:00
Greyson Parrelli 115d1fcf63 Improve handling of unregistered users in storage service. 2022-09-28 11:33:31 -04:00
Alex Hart ffa249885e Add scale gesture to stories. 2022-09-23 14:30:58 -04:00
Alex Hart 9a21f5abca Add stories link treatment for devices with link previews disabled. 2022-09-23 14:30:58 -04:00
Alex Hart 552592db39 Fix unread story nav. 2022-09-23 14:30:58 -04:00
Alex Hart 75af1b69e8 Update payment toolbars to match M3 specification. 2022-09-23 14:30:58 -04:00
Alex Hart c96fec9537 Update username to use . as delimiter. 2022-09-23 14:30:58 -04:00
Greyson Parrelli a457d1f569 Bump version to 5.50.4 2022-09-23 14:19:01 -04:00
Greyson Parrelli 87f206fdc4 Ensure websockets are restarted after changing proxy. 2022-09-23 14:18:05 -04:00
Greyson Parrelli e845860c7c Bump version to 5.50.3 2022-09-22 12:46:38 -04:00
Greyson Parrelli e351c74ddb Fix issue with bioauth on API 29. 2022-09-22 12:44:49 -04:00
Greyson Parrelli aeeaef567f Bump version to 5.50.2 2022-09-19 15:25:02 -04:00
Greyson Parrelli 78a9206898 Updated language translations. 2022-09-19 15:24:45 -04:00
Greyson Parrelli aab8bd1261 Filter badly-formatted numbers from one-off CDS requests. 2022-09-19 11:18:54 -04:00
Greyson Parrelli db16155b0d Use proper log tag. 2022-09-19 11:17:42 -04:00
Greyson Parrelli 1b254ca185 Bump version to 5.50.1 2022-09-14 16:42:40 -04:00
Greyson Parrelli 9a6ed9bcb3 Update Dockerfile to build with compileSdk 32. 2022-09-14 16:42:11 -04:00
Greyson Parrelli c8f0bd7b82 Fix lint. 2022-09-14 16:41:45 -04:00
Greyson Parrelli f6b7b9e913 Bump version to 5.50.0 2022-09-14 15:31:42 -04:00
Greyson Parrelli 840a56cbb4 Updated language translations. 2022-09-14 15:30:44 -04:00
Nicholas aa268fc3ba Only show "Note To Self" as Voice Memo author if both sender and receiver are self. 2022-09-14 15:30:44 -04:00
Alex Hart 889d1183b2 Allow the STORIES feature flag to be hot-swappable. 2022-09-14 15:30:44 -04:00
Alex Hart a8706f65d5 Clean out witness verification metadata. 2022-09-14 15:30:44 -04:00
Alex Hart 26bebb9811 Upgrade several AndroidX Libraries.
AppCompat 1.2.0 to 1.5.1
Lifecycle 2.3.1 to 2.5.1
Navigation 2.3.5 to 2.5.2
Fragment 1.3.5 to 1.5.2
Annotations 1.2.0 to 1.4.0
Window 1.0.0-alpha09 to 1.0.0
AAPT2 to 7.0.4
Fragment-Testing 1.3.5 to 1.5.2 (matching Fragment)
2022-09-14 15:30:43 -04:00
Alex Hart 9331e9ce89 Add deprecation notice to SingleLiveEvent. 2022-09-14 15:30:43 -04:00
Greyson Parrelli 6417f5cce0 Improve logging around attachment compression failures. 2022-09-14 15:30:43 -04:00
Alex Hart a340ebf74a Add espresso test for usernames. 2022-09-14 15:30:43 -04:00
Alex Hart 4882a4d11c Add new story-based AccountRecord fields and wiring. 2022-09-13 13:07:42 -04:00
Greyson Parrelli b5300c877c Fix issue with contact share editing.
Fixes #12446
2022-09-13 13:07:42 -04:00
Nicholas Tinsley c2b94274b0 Cancel Send if we return to fragment.
This plugs a lifecycle hole: previously if you leave this fragment (SelectionConfirmed), you get stuck in that state even if you return.
2022-09-13 13:07:42 -04:00
Nicholas Tinsley 46ec45b985 Update ReminderView to Material Design 3. 2022-09-13 13:07:42 -04:00
Cody Henthorne beee3b7dc3 Add PNP linked device initialization job.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-09-13 13:07:42 -04:00
Cody Henthorne e2a7ed86e4 Promote ongoing call notification to high priority. 2022-09-13 13:07:42 -04:00
Alex Hart 95b0639ab4 Fix issue where video player is not released by preview fragment. 2022-09-13 13:07:42 -04:00
Nicholas Tinsley d7f9582bc4 Update Megaphone text styling to match Material Design 3. 2022-09-13 13:07:42 -04:00
Alex Hart 176a705079 Add PNP Listing Wiring. 2022-09-13 13:07:42 -04:00
Greyson Parrelli 8e9f311fca Refresh your own profile when the stories flag changes. 2022-09-13 13:07:42 -04:00
Alex Hart 977af2c2f3 Add capability to request username creation during registration. 2022-09-13 13:07:42 -04:00
Alex Hart 7e45fc4a3e Update URL formatting for username links. 2022-09-13 13:07:42 -04:00
Nicholas Tinsley 58489bab61 Update Basic Megaphone to Material Design 3. 2022-09-13 13:07:42 -04:00
Cody Henthorne 0685cf4e51 Add signal.me username support. 2022-09-13 13:07:42 -04:00
Alex Hart 9b9453734c Implement new API endpoints for Usernames. 2022-09-13 13:07:42 -04:00
Cody Henthorne ca0e52e141 Fix bug with stale linked devices when changing number. 2022-09-13 13:07:42 -04:00
Alex Hart 24b7593178 Update camera layout for better support across different screen sizes. 2022-09-13 13:07:42 -04:00
Alex Hart 993e49db48 Username search UI tweak. 2022-09-13 13:07:42 -04:00
Nicholas Tinsley d458ddba55 Schedule TrimThreadsByDateManager on app startup.
If enabled, this reschedules the alarm on every startup to make sure that the system never loses track of it.
2022-09-13 13:07:42 -04:00
Cody Henthorne bd5747b7f6 Add more logging around failed backups. 2022-09-13 13:07:42 -04:00
Nicholas a335130ad4 Clear Selection on ACTION_UP if longClickCopySpan is not found. 2022-09-13 13:07:42 -04:00
Cody Henthorne 9558513190 Prevent empty call screen after missed calls. 2022-09-13 13:07:42 -04:00
Alex Hart 27a3015d4f Set reply icon size to 20dp. 2022-09-13 13:07:42 -04:00
Alex Hart f751f9afa8 Add support for new story gradient fields and fallback. 2022-09-13 13:07:42 -04:00
Alex Hart 2e2b31aa79 Start call after granting permissions.
Fixes #12419
2022-09-13 13:07:42 -04:00
Greyson Parrelli 135d002f02 Fix possible crash with CDSv2 compat. 2022-09-13 13:07:42 -04:00
Alex Hart a45ede9348 Update AudioView in Attachment keyboard stub. 2022-09-13 13:07:42 -04:00
Greyson Parrelli e4b2e5022f Remove some outdated internal settings. 2022-09-13 13:07:42 -04:00
Alex Hart 286010ce90 Fix clickable area around link previews. 2022-09-13 13:07:41 -04:00
Alex Hart 13eb89746b Add unit testing to story download enqueuer. 2022-09-13 13:07:41 -04:00
Cody Henthorne d2f639c57f Bump version to 5.49.3 2022-09-13 10:52:16 -04:00
Cody Henthorne ad587606b7 Updated language translations. 2022-09-13 10:42:51 -04:00
Cody Henthorne 9fd5e2057d Fix reply messages for android auto. 2022-09-13 10:38:31 -04:00
Cody Henthorne 8f63b850fc Bump version to 5.49.2 2022-09-07 14:33:54 -04:00
Cody Henthorne 199d04b663 Updated language translations. 2022-09-07 14:28:53 -04:00
Greyson Parrelli 658741be52 Fix token mismatch issues when using CDSv2. 2022-09-07 14:25:03 -04:00
Alex Hart f1bcc756d3 Remove animation from flash helper. 2022-09-06 10:26:45 -03:00
Alex Hart cdcb1de3d4 Bump version to 5.49.1 2022-09-01 17:17:03 -03:00
Alex Hart 7d11a6207a Updated language translations. 2022-09-01 17:16:26 -03:00
Alex Hart e608ad24c2 Hide keyboard when closing the bubble activity. 2022-09-01 17:06:51 -03:00
Alex Hart 4fe382398e Adjust alpha and duration of selfie flash animation. 2022-09-01 17:06:51 -03:00
Alex Hart b6546f3ae3 Fix single tap on video previews. 2022-09-01 17:06:51 -03:00
Alex Hart 4620eade58 Implement better state management and recoverability for donation badge jobs. 2022-09-01 17:06:51 -03:00
Alex Hart 23a328f12d Add screen to set Signal as default SMS. 2022-09-01 13:17:53 -03:00
Alex Hart 83905dd6a6 Bump version to 5.49.0 2022-08-31 15:58:41 -04:00
Alex Hart 3eb4eb3c09 Updated language translations. 2022-08-31 15:58:41 -04:00
Greyson Parrelli 2eba9a8d72 Add support for doing normal CDS queries on CDSv2. 2022-08-31 15:58:41 -04:00
Alex Hart 9b17e7a7e2 Fix story launching from settings. 2022-08-31 15:58:41 -04:00
Alex Hart 3eb9e4a035 Upgrade Glide to 4.13.2 and upgrade ExifInterface to 1.3.3 2022-08-31 15:58:41 -04:00
Alex Hart 3edc97eb38 Fix NPE when the attachment for a link preview is null. 2022-08-31 15:58:41 -04:00
Jim Gustafson cb0208af4d Update to RingRTC v2.21.0 2022-08-31 15:58:41 -04:00
Greyson Parrelli cdd311f741 Fix for possible issue in search. 2022-08-31 15:58:41 -04:00
Greyson Parrelli 8543325d59 Update database migrations to be in their own files. 2022-08-31 15:58:41 -04:00
Greyson Parrelli a1a677a3e2 Apply network interceptors to CDSv2 websocket client. 2022-08-31 15:58:41 -04:00
Alex Hart 3705465ef2 Update translation strings for story privacy modes. 2022-08-31 15:58:41 -04:00
Alex Voloshyn c80999839b Use AccountSnapshot to avoid unnecessary network calls. 2022-08-31 15:58:41 -04:00
Alex Hart 936212e684 Add initial sms exporter integration behind a feature flag. 2022-08-31 15:58:41 -04:00
Alex Hart 1cc39fb89b Fix launching of story from chat ring. 2022-08-31 15:58:41 -04:00
Alex Hart 37d3a953c8 Do not display icons in my stories row. 2022-08-31 15:58:41 -04:00
Alex Hart 5a1a23d9ac Fix view-based selfie flash. 2022-08-31 15:58:41 -04:00
Alex Hart 6cb359b2d0 Prevent header decoration from passing NO_POSITION to getHeaderId. 2022-08-31 15:58:41 -04:00
Alex Hart 8bd89d1e63 Fix camera zoom issue on some devices. 2022-08-31 15:58:40 -04:00
gram-signal f111ac7cf2 Return empty from CDSv2 refresh if current recipient list is empty.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-08-31 15:58:40 -04:00
Greyson Parrelli f6e000ab97 Fix some PNI-related issues around change number. 2022-08-31 15:58:40 -04:00
Alex Hart 29869c93b2 Adjust placement of elements in first time nav. 2022-08-31 15:58:40 -04:00
Alex Hart 3aae5ce1de Fix hide text header. 2022-08-29 14:23:28 -03:00
Alex Hart e379cf6127 Bump version to 5.48.3 2022-08-29 14:08:10 -03:00
Alex Hart 0c23cb5ca8 Updated language translations. 2022-08-29 14:07:38 -03:00
Greyson Parrelli d26ba27069 Look at new v2 remote announcement manifest. 2022-08-29 12:38:38 -04:00
Greyson Parrelli e918178694 Update launcher assets. 2022-08-29 11:16:49 -04:00
Alex Hart 3d075bdd65 Check for EXTRA_TEXT if we cannot parse EXTRA_STREAM. 2022-08-29 10:47:47 -03:00
Alex Hart 4a3b8af6af Utilize proper control color for text cursor in gift badge message. 2022-08-29 10:35:30 -03:00
Alex Hart 2743492076 Fix ISE when utilizing the ear piece for voice notes. 2022-08-29 10:09:28 -03:00
Alex Hart 6ebc453e4b Bump version to 5.48.2 2022-08-26 15:20:26 -03:00
Alex Hart 75bd950b9b Updated language translations. 2022-08-26 15:20:26 -03:00
Alex Hart 0b0c4eb8c0 Utilize themed colors in fallback resource photos. 2022-08-26 15:20:26 -03:00
Alex Hart e7dbc874bb Utilize lock icon instead of group icon for distribution lists. 2022-08-26 15:20:26 -03:00
Alex Hart 17426f1dbb Add long-press action to copy sent timestamp to clipboard. 2022-08-26 15:20:26 -03:00
Alex Hart e00ce48517 Add proper title to text story sender. 2022-08-26 15:20:26 -03:00
Alex Hart cba1caa5be Add audio focus handling to voice note playback. 2022-08-26 15:20:26 -03:00
Alex Hart 5f6b073cb6 Do not invoke reveal animation when editing a group. 2022-08-26 15:20:26 -03:00
Alex Hart 51647a5017 Enable both use-cases if available. 2022-08-26 15:20:26 -03:00
Johan fae2ceab39 Set correct variable for password timeout.
Fixes #12415
2022-08-26 15:20:23 -03:00
Greyson Parrelli 553346629a Update libphonenumber to 8.12.54 2022-08-25 16:30:05 -04:00
Greyson Parrelli 726f48bc33 Clear search toolbar upon opening. 2022-08-25 16:27:42 -04:00
Greyson Parrelli 397793064d Bump version to 5.48.1 2022-08-25 14:26:51 -04:00
Greyson Parrelli 534af3c1a0 Updated language translations. 2022-08-25 14:26:27 -04:00
Greyson Parrelli f551a700fe Fix crash when searching groups for a large number of members. 2022-08-25 14:26:27 -04:00
Greyson Parrelli d0c737779a Update profile strings. 2022-08-25 13:41:49 -04:00
Greyson Parrelli 497b38ddbf Improve the ordering of conversation search results. 2022-08-25 12:15:02 -04:00
Greyson Parrelli cdad45096b Fix bug with back navigation during payment lock. 2022-08-25 08:51:31 -04:00
Greyson Parrelli f8aedca08e Improve navigation to fingerprint settings. 2022-08-25 08:26:09 -04:00
Greyson Parrelli 490ca1d74c Bump version to 5.48.0 2022-08-24 18:29:19 -04:00
Greyson Parrelli cf9ddf3960 Updated language translations. 2022-08-24 18:26:34 -04:00
Greyson Parrelli 61498037f3 Add support for PniSignatureMessages. 2022-08-24 18:16:42 -04:00
Cody Henthorne 1e499fd12f Refactor notification thumbnails to reduce chances for ANR. 2022-08-24 17:09:01 -04:00
Cody Henthorne a9fc5622cd Add search by group membership. 2022-08-24 17:09:01 -04:00
Alex Hart 777a91abc7 SMS Exporter unit testing. 2022-08-24 17:09:01 -04:00
Varsha 372f939a67 Add support for biometric auth for payments. 2022-08-24 17:09:01 -04:00
Greyson Parrelli 716229719a Add migration to new KBS enclave. 2022-08-24 17:09:01 -04:00
Cody Henthorne b57b160660 Add error toasts to multiforward sheet. 2022-08-24 17:09:01 -04:00
Cody Henthorne 40c52a31c9 Fix race condition when joining a group call. 2022-08-24 17:09:01 -04:00
Cody Henthorne 05c16e4c70 Retry backup verify and rename with delay. 2022-08-24 17:09:01 -04:00
Greyson Parrelli 7a7c4c28c2 Update verification-metadata to remove outdated entry. 2022-08-24 17:09:01 -04:00
Greyson Parrelli 8e2ab40b4c Update string for profile creation. 2022-08-24 17:09:01 -04:00
Greyson Parrelli bcef73c2e0 Update some donation error strings. 2022-08-24 17:09:01 -04:00
Cody Henthorne f0a109245b Only fallback to unidentified socket when a auth error occurs.
Fixes #12395
2022-08-24 17:09:01 -04:00
Cody Henthorne c6c30f25a2 Attempt automated SMS verification in change number flow. 2022-08-24 17:09:01 -04:00
Cody Henthorne 8036aaa985 Reduce verbosity of phone number parse errors. 2022-08-24 17:09:01 -04:00
Greyson Parrelli a56dd5ca87 Avoid a false positive in DeadlockDetector. 2022-08-24 17:09:01 -04:00
Greyson Parrelli 40ac0f4e89 Log media quality setting. 2022-08-24 17:09:01 -04:00
Greyson Parrelli 1aa9aa97ac Only include the first photo in quoteAttachments.
Otherwise you spend a bunch of time compressing stuff people will never
see.
2022-08-24 17:09:01 -04:00
Greyson Parrelli 96a75a7f7f Always use inferred PIN state.
Saving the PIN state could lead to it being stale or mismanaged, and tbh
we were using the inferred state to _set_ the value anyway.
2022-08-24 17:09:01 -04:00
Greyson Parrelli 5009bd4e6a Prevent usage of null itemAnimator in chat list.
Fixes #12393
2022-08-24 17:09:01 -04:00
Greyson Parrelli 62ea82a2ba Do not include pending downloads in storage usage.
Fixes #12231
2022-08-24 17:09:01 -04:00
Greyson Parrelli fa55062510 Update ExoPlayer to 2.18.1 2022-08-24 17:09:01 -04:00
Greyson Parrelli 4b195c67cb Bump version to 5.47.3 2022-08-24 16:10:53 -04:00
Greyson Parrelli f36aa09a81 Revert "Ensure main database is updated before opening secondary ones."
This reverts commit e0e3f7dfec.
2022-08-24 16:08:38 -04:00
Greyson Parrelli e0f16548cf Bump version to 5.47.2 2022-08-23 14:46:30 -04:00
Greyson Parrelli 577971c7a9 Updated language translations. 2022-08-23 14:46:11 -04:00
Greyson Parrelli 6bbd941158 Fix back navigation issues when creating an initial profile. 2022-08-23 13:49:21 -04:00
Victor Ding b92dd19a4c Use StandardCharsets in OkHttpUtil.
okhttp3.internal.Util.UTF_8 was never meant to be used outside of
okhttp3 library; and it has been deleted in later versions.
Signal should use java.nio.charset.StandardCharsets instead.
No functional change.

Closes #12413
2022-08-23 10:56:01 -04:00
Greyson Parrelli 13f3a8cf8a Fix navigation bug when deactivating payments. 2022-08-23 10:45:04 -04:00
Greyson Parrelli 60da8116be Update MobileCoin SDK to 1.2.2.2 2022-08-23 10:27:28 -04:00
Greyson Parrelli 1b7c873ea5 Bump version to 5.47.1 2022-08-22 21:02:15 -04:00
Greyson Parrelli b18ecfdffd Updated language translations. 2022-08-22 21:01:27 -04:00
Greyson Parrelli da286329f7 Update libsignal-client to 0.20.0 2022-08-22 20:55:24 -04:00
Greyson Parrelli db69603b5d Fix CDS flag name. 2022-08-22 19:18:37 -04:00
Cody Henthorne a2b73bf979 Make single badge appear selected. 2022-08-22 12:03:43 -04:00
Cody Henthorne dc503e3406 Prevent video thumbnail creation from crashing the app. 2022-08-22 11:52:37 -04:00
Cody Henthorne ab3e0b87c6 Bump version to 5.47.0 2022-08-18 16:14:08 -04:00
Cody Henthorne 7751ce3ae0 Updated language translations. 2022-08-18 16:05:30 -04:00
Alex Hart 796e98be10 Utilize proper intent creation when launching profile creator from PassphraseRequiredActivity. 2022-08-18 16:01:05 -04:00
Greyson Parrelli 9c266e7995 Remove legacy fields from the Envelope. 2022-08-18 16:01:05 -04:00
Alex Hart b4ae13fe8a Catch IAE when video thumbnail extractor cannot instantiate a decoder. 2022-08-18 16:01:05 -04:00
Alex Hart 8ffad4cc6f Upgrade gradle version to v7.5.1
Fixes #12399

Co-Authored-By: Patryk Miś <foss@patrykmis.com>
2022-08-18 16:01:05 -04:00
Alex Hart f341e02fb7 Story privacy screen updates. 2022-08-18 16:01:05 -04:00
Greyson Parrelli 15e52a8b88 Add ability to do unused reads from CDSv2 to test server load. 2022-08-18 16:01:05 -04:00
Cody Henthorne 84717b95f7 Add logging around how call activity is started. 2022-08-18 16:01:05 -04:00
Cody Henthorne b1d1e92dbb Fix group call remote video not rendering. 2022-08-18 16:01:05 -04:00
Cody Henthorne cca35ec687 Dust off remote megaphone for upcoming donate megaphone. 2022-08-18 16:01:05 -04:00
Greyson Parrelli 95fc9d6c3c Add support for PNIs in storage service. 2022-08-18 16:01:05 -04:00
Alex Hart cb057968ee Move gift header into recycler. 2022-08-18 16:01:05 -04:00
Thilo deed8ac6c9 Add monochrome entry to support Themed App Icons.
Fixes #12385
2022-08-18 16:01:05 -04:00
Alex Hart fe44f8e369 Add hard-code of colors in numeric keyboard to light mode. 2022-08-18 16:01:05 -04:00
Alex Hart e517232172 Sort "new group story" entries by recency. 2022-08-18 16:01:05 -04:00
Alex Hart 28310a88f5 Username UX refresh. 2022-08-18 16:01:05 -04:00
Cody Henthorne 3252871ed5 Replace prekey jobs with one overall sync job. 2022-08-18 16:01:05 -04:00
Cody Henthorne 2740b5e300 Fix emoji completion over newlines bug. 2022-08-18 09:27:20 -03:00
Alex Hart a46faebb67 Add check before trying to launch contact add intent. 2022-08-18 09:27:20 -03:00
Alex Hart 16a4c321c4 Add additional logging for diagnosing shares with null EXTRA_STREAM. 2022-08-18 09:27:20 -03:00
Alex Hart 056ef84817 Change initial my story privacy fragment peek size to 1. 2022-08-18 09:27:20 -03:00
Alex Hart 820d76990a Add click handler to prevent tap propagation. 2022-08-18 09:27:20 -03:00
Alex Hart 01e4a7fd79 Add My Story row polish. 2022-08-18 09:27:20 -03:00
Alex Hart 8d4f87641d Update stories send preview scroll mode to none. 2022-08-18 09:27:20 -03:00
Alex Hart afb248c57c Set link preview max width to 280dp. 2022-08-18 09:27:20 -03:00
Greyson Parrelli 62871c1bdd Update keepalive interval to ping every 30sec. 2022-08-18 09:27:20 -03:00
Greyson Parrelli c6be427883 Add support for resending badly-encrypted stories. 2022-08-18 09:27:20 -03:00
Cody Henthorne 7873ec2b67 Bump version to 5.46.6 2022-08-17 20:28:08 -04:00
Cody Henthorne 64e6b492ab Updated language translations. 2022-08-17 20:14:03 -04:00
Alex Hart c1cd893a4a Bump version to 5.46.5 2022-08-16 15:27:33 -03:00
Alex Hart 8a1e033efa Updated language translations. 2022-08-16 15:27:33 -03:00
Alex Hart b2c974a684 Add distinctUntilChanged operator to security info flowable. 2022-08-16 15:27:33 -03:00
Cody Henthorne 57e476988e Fix release channel donation bug. 2022-08-16 12:18:18 -04:00
Cody Henthorne 6262f775d5 Bump version to 5.46.4 2022-08-15 16:17:23 -04:00
Cody Henthorne 18ae665cd1 Updated language translations. 2022-08-15 15:46:48 -04:00
Greyson Parrelli 029a76f8a2 Add additional verifications and logging around My Story db entry. 2022-08-15 13:34:35 -04:00
Greyson Parrelli e0e3f7dfec Ensure main database is updated before opening secondary ones. 2022-08-15 11:56:17 -04:00
Greyson Parrelli 2220ceb9d9 Attempt to self-repair some types of profile issues. 2022-08-15 10:39:03 -04:00
Cody Henthorne a28698da36 Bump version to 5.46.3 2022-08-12 15:25:47 -04:00
Cody Henthorne 9307835d2d Updated language translations. 2022-08-12 15:22:35 -04:00
Cody Henthorne cfe167b639 Fix crash when submitting debuglog via registration flow. 2022-08-12 15:18:10 -04:00
Greyson Parrelli 64d3b36b28 Attempt repair if you have an invalid credential during invite accept. 2022-08-12 12:27:30 -04:00
Cody Henthorne b433a7b816 Add control for inserting boost message in release notes channel. 2022-08-12 12:27:01 -04:00
Cody Henthorne 17643bf13b Add gift badge call to action for release notes. 2022-08-12 11:46:40 -04:00
Cody Henthorne a444a96dc9 Fix drafts not loading in bubbled conversations. 2022-08-12 11:39:02 -04:00
Cody Henthorne 41a7560e76 Do not fail backup when missing attachments. 2022-08-12 11:21:21 -04:00
Cody Henthorne 90be2a0e53 Bump version to 5.46.2 2022-08-11 17:03:37 -04:00
Cody Henthorne 6c7bb85aa3 Updated language translations. 2022-08-11 16:57:02 -04:00
Alex Voloshyn fe898d824b Upgrade payments to use 2.0.0 enclaves.
* Updated MrEnclave values for 2.0.0 support
* Updated MrEnclave values for TestNet
2022-08-11 16:52:30 -04:00
Cody Henthorne ac8a972c6e Do not fail backup creation when sticker files are missing. 2022-08-11 11:25:36 -04:00
Cody Henthorne 1e691005c7 Fix QR scanning issues, again. 2022-08-11 11:06:08 -04:00
Cody Henthorne 0b0e25b121 Bump version to 5.46.1 2022-08-10 16:58:23 -04:00
Cody Henthorne afff792ecc Fix drafts not working when typing indicators are disabled. 2022-08-10 16:45:43 -04:00
Alex Hart 7f47e50674 Bump version to 5.46.0 2022-08-10 16:15:24 -03:00
Alex Hart bca886cfb9 Updated language translations. 2022-08-10 16:14:45 -03:00
Greyson Parrelli 55216f5583 Consider ContactRecords with the local user's PNI to be invalid. 2022-08-10 16:14:45 -03:00
Greyson Parrelli f8220ca554 Add logs to detect negative changes in system clock. 2022-08-10 16:14:45 -03:00
Alex Hart 3cb674f095 Correct send button colors. 2022-08-10 16:14:45 -03:00
Alex Hart 5977016075 Add toolbar to media selection contact select. 2022-08-10 16:14:45 -03:00
Cody Henthorne eefd7bd37a Fix QR scanning issues. 2022-08-10 16:14:45 -03:00
Cody Henthorne 019025ab8a Improve backup creation exception messaging to user. 2022-08-10 16:14:45 -03:00
Cody Henthorne 36f1183d6c Update libsignal-client for CDSv2. 2022-08-10 11:06:21 -04:00
Alex Hart caf87def13 Rotate gift badge sending flag. 2022-08-10 10:00:19 -03:00
Alex Hart 729a9c0864 Log out share timestamp. 2022-08-10 09:50:32 -03:00
Greyson Parrelli f004b72ba2 Use the PNP merging function for everything. 2022-08-09 18:36:04 -04:00
Cody Henthorne e83a4692c5 Change calling rotation behavior for 1:1 calls. 2022-08-09 16:23:45 -04:00
Alex Hart acf811c79a Introduce android view-bindings. 2022-08-09 16:23:45 -04:00
Greyson Parrelli 733b4ff805 Give story sends an IMPLICIT content hint. 2022-08-09 16:23:44 -04:00
Cody Henthorne 756b926f6f Color nav bar to match unmute in release note channel. 2022-08-09 16:23:44 -04:00
Alex Hart 5164a44ee8 Fix alignment of small arabic names in LTR languages. 2022-08-09 16:23:44 -04:00
Cody Henthorne cfebd0eeb9 Verify backup can be decrypted as part of creation flow. 2022-08-09 16:23:44 -04:00
Alex Hart 5212b33b47 Add sms export library and sample app. 2022-08-09 16:23:44 -04:00
Greyson Parrelli 6120f90dcb Update CDS enclave. 2022-08-09 16:23:44 -04:00
Cody Henthorne 0a76eb81e6 Add save-as-you-compose drafts. 2022-08-09 16:23:44 -04:00
Alex Hart 192509f762 Fix action bar layout insets. 2022-08-09 16:23:44 -04:00
Brandon de09571077 Fix reproducible builds README apk path.
Closes #12349
2022-08-09 16:23:44 -04:00
Sgn-32 8f5c326758 Remove seconds from screen lock timeout summary.
Closes #11241
2022-08-09 16:23:44 -04:00
Greyson Parrelli 91d3f331e5 Make CameraX blocklist remote configurable. 2022-08-09 16:23:44 -04:00
Cody Henthorne ace4157a14 Make maps key externally configurable. 2022-08-09 16:23:44 -04:00
Cody Henthorne 83b97d274f Add support for PNI registration ids and PNP change number. 2022-08-09 16:23:44 -04:00
Greyson Parrelli 0d3ea22641 Fix accuracy of ConversationItemTest_linkifyUrlLinks. 2022-08-09 16:23:44 -04:00
Alex Hart eb634d62ce Bump version to 5.45.6 2022-08-09 16:24:50 -03:00
Alex Hart 6353e7b1be Updated language translations. 2022-08-09 16:23:54 -03:00
Cody Henthorne 286c340f01 Do not start service is non-urgent push. 2022-08-09 15:11:10 -04:00
Greyson Parrelli 055b79c9f2 Prevent setting a null profile key during account restore. 2022-08-09 11:16:00 -04:00
Alex Hart 29a9297452 Bump version to 5.45.5 2022-08-08 13:40:23 -03:00
Alex Hart 929200d53d Updated language translations. 2022-08-08 13:40:22 -03:00
Greyson Parrelli 6e9b1551e7 Fix duplicate emoji results. 2022-08-08 13:40:22 -03:00
Cody Henthorne 1547ec2067 Fix illegal state exception during backup restore of unamed groups. 2022-08-08 13:40:22 -03:00
Alex Hart f7dce21246 Rotate gift badge sending flag. 2022-08-08 13:40:22 -03:00
Greyson Parrelli 3d0634de8d Bump version to 5.45.4 2022-08-05 18:03:19 -04:00
Greyson Parrelli 64396c1de6 Updated language translations. 2022-08-05 18:03:19 -04:00
Greyson Parrelli 9f5b822e33 Do not show :query completion for possible time entries. 2022-08-05 18:03:19 -04:00
Greyson Parrelli eac9f78dfa Fix issues around all-zero UUIDs. 2022-08-05 18:03:19 -04:00
Greyson Parrelli b9879e7210 Bump version to 5.45.3 2022-08-05 13:59:54 -04:00
Greyson Parrelli ea76ce9b87 Updated language translations. 2022-08-05 13:59:54 -04:00
Greyson Parrelli 4b6ff55779 Fix crash around unknown storage enums. 2022-08-05 13:59:54 -04:00
Greyson Parrelli 718eedcb34 Allow saving debuglogs to disk. 2022-08-05 13:16:41 -04:00
Alex Hart 999314255c Add further distribution sync logging. 2022-08-05 14:14:46 -03:00
Greyson Parrelli 886c4b64f2 Bump version to 5.45.2 2022-08-04 17:12:30 -04:00
Greyson Parrelli 887221fccf Updated language translations. 2022-08-04 17:12:30 -04:00
Cody Henthorne d4c633a0f2 Include Signal release notes channel in backups. 2022-08-04 17:12:30 -04:00
Cody Henthorne 0c7a8a63b5 Use Mat3 menu and dialog in Media Preview toolbar/save. 2022-08-04 17:12:30 -04:00
Alex Hart 1b053a2613 Add explicit exceptions and group_type correction. 2022-08-04 17:12:30 -04:00
Cody Henthorne 539cd4059d Fix inline emoji search for media first flow. 2022-08-04 17:12:30 -04:00
Alex Hart c21b0cd145 Fix camera initialization error for disabled hardware. 2022-08-04 17:12:30 -04:00
Alex Hart 0a2696113c Allow long form messages if stories aren't enabled.
Fixes #12369
2022-08-04 17:12:24 -04:00
Alex Hart 710bb386e2 Fail getRecipientIdForSyncRecord immediately if identifier is invalid. 2022-08-04 17:10:41 -04:00
Greyson Parrelli 2495781055 Bump version to 5.45.1 2022-08-03 17:34:06 -04:00
Greyson Parrelli f9b29cd044 Updated language translations. 2022-08-03 17:33:03 -04:00
Alex Hart a0cc2ff90a Add new my story migration. 2022-08-03 17:17:35 -04:00
Cody Henthorne b002235ef7 Keep web socket open during calling to improve message delivery. 2022-08-03 17:17:35 -04:00
Greyson Parrelli 120dda6e68 Schedule a migration to fetch the latest search index. 2022-08-03 17:17:35 -04:00
Greyson Parrelli 907abf72d3 Improve emoji search results. 2022-08-03 17:17:35 -04:00
Cody Henthorne 18eac51576 Migrate all QR scanning to new scanner. 2022-08-03 17:17:35 -04:00
Alex Hart caf1329005 Lock CameraX fragment to portrait. 2022-08-03 17:17:35 -04:00
Alex Hart 5f7b07147f Add proper media review send tint. 2022-08-03 17:17:35 -04:00
Cody Henthorne d7d923c820 Tweak emoji suggestions UX. 2022-08-03 17:17:35 -04:00
Greyson Parrelli 440d041402 Bump version to 5.45.0 2022-08-02 14:37:06 -04:00
Greyson Parrelli 11211ee205 Updated language translations. 2022-08-02 14:37:06 -04:00
Greyson Parrelli 692006dcd8 Be more defensive when starting the FCM foreground service. 2022-08-02 14:37:06 -04:00
Alex Hart c4632dc4a3 Add new section to help diagnose story issues. 2022-08-02 14:37:06 -04:00
Greyson Parrelli a42c3d7ce8 Fix handling of early receipts.
We were storing the early content under the wrong recipient.
2022-08-02 14:37:06 -04:00
Alex Hart 370c2b941c Remove unnecessary logging. 2022-08-02 14:37:06 -04:00
Alex Hart 8be7fa8655 Improve accessibility of SMS code keyboard. 2022-08-02 14:36:30 -04:00
Cody Henthorne c2b5407911 Change batch identity check timing behavior. 2022-08-02 14:36:30 -04:00
Cody Henthorne dc04c8ed98 Add urgency flag to message sends. 2022-08-02 14:36:30 -04:00
Alex Hart c7cd261641 Add polish to stories link previews. 2022-08-02 14:36:30 -04:00
Cody Henthorne 19af68a27c Add inline emoji search. 2022-08-02 14:36:30 -04:00
Alex Hart ba7319e215 Respect proper media upload requirements for stories. 2022-08-02 14:36:30 -04:00
Greyson Parrelli 92201dcd90 Properly set the isRecipientUpdate flag on story sends. 2022-08-02 14:36:30 -04:00
Alex Hart 855d74bbbf Drop state update for unattached fragment. 2022-08-02 14:36:30 -04:00
Jim Gustafson 201f314cfb Update to RingRTC v2.20.13 2022-08-02 14:36:30 -04:00
Alex Hart 2eef2e1636 Mark internal preferences string as non-translatable. 2022-08-02 14:36:30 -04:00
Alex Hart f05f9287c1 Update LibMobileCoin to 1.2.2.1
Fixes #12354

Co-authored-by: Bernie Dolan <bernie@mobilecoin.com>
Co-authored-by: Varsha <varsha@mobilecoin.com>
2022-08-02 14:36:30 -04:00
Alex Hart 49cc962bde Fix bug where share intent data would be redisplayed. 2022-08-02 14:36:30 -04:00
Greyson Parrelli d0420ba51d Add support for the changeSelf param in getAndPossiblyMergePnp. 2022-08-02 14:36:30 -04:00
Greyson Parrelli 0e7cffedc9 Fix compilation issue with androidTests. 2022-08-02 14:36:30 -04:00
Greyson Parrelli 22688789d2 Fix multidex issue with image editor sample app. 2022-08-02 14:36:30 -04:00
Greyson Parrelli 4eb2f16ef1 Keep logs concerning decryption errors longer. 2022-08-02 14:36:30 -04:00
Alex Hart ef950bdbb5 Stick buttons to bottom of subscription page. 2022-08-02 14:36:30 -04:00
Greyson Parrelli cb9a219c4b Re-enable the 'read more' text in see replies mode. 2022-08-02 14:36:30 -04:00
Greyson Parrelli 9cd1971329 Fix issue where conversations started on linked devices didn't show a phone number. 2022-08-02 14:36:30 -04:00
Cody Henthorne a51754e207 Fix premature call termination during safety number change. 2022-08-02 14:36:30 -04:00
Greyson Parrelli df3399bde5 Remove processing of inbound GV1 messages. 2022-08-02 14:36:29 -04:00
Greyson Parrelli 5140353722 Fix situation where two keyboards could be showing in media editor.
Fixes #11618
2022-08-02 14:36:29 -04:00
Greyson Parrelli 26bb52fd60 Prevent popup menu from covering bottom items in the media overview screen. 2022-08-02 14:36:29 -04:00
Alex Hart f50bf3e9c2 Remove blocking get from donation jobs. 2022-08-02 14:36:29 -04:00
Alex Hart 8f12b2041a Allow users to remove viewers directly from stories. 2022-08-02 14:36:29 -04:00
Alex Hart 2674fd2df4 Fix issue where postponed transition would not start at the right time. 2022-08-02 14:36:29 -04:00
Evan Hahn 39d07c0081 Change default to disabled for contact joined notifications. 2022-08-02 14:36:29 -04:00
Alex Hart 1eb253562b Re-enable CameraX for Pixel 4 devices. 2022-08-02 14:36:29 -04:00
Alex Hart bc7908a4a5 Add blockingGet linter. 2022-08-02 14:36:29 -04:00
Alex Hart a52b64281c Upgrade CameraX to 1.1.0 and fork removal. 2022-08-02 14:36:29 -04:00
Alex Hart e3e9f90094 Use db as SSOT for unread counter. 2022-07-27 13:26:28 -04:00
Cody Henthorne a7a5f2e8c6 Add batch identity checks to stories and share/forward flows. 2022-07-27 13:26:28 -04:00
Alex Hart 87cb2d6bf8 Add new story send final screen. 2022-07-27 13:26:28 -04:00
Alex Hart 3c78d8619a Add sending state to story viewer. 2022-07-27 13:26:28 -04:00
Alex Hart 60e9763f7a Fix story caption protection. 2022-07-27 13:26:28 -04:00
Alex Hart ab897953bf Add padding to bottom of stories landing recycler. 2022-07-27 13:26:28 -04:00
Alex Hart d2c2952ccf Fix bug when sending to a single contact and single dlist at the same time. 2022-07-27 13:26:28 -04:00
Cody Henthorne 36c882e318 Bump version to 5.44.3 2022-07-27 13:22:21 -04:00
Cody Henthorne 18106c1eab Updated language translations. 2022-07-27 13:15:56 -04:00
Cody Henthorne 9f4d8ac12c Fix contact discovery refresh crash. 2022-07-27 13:12:58 -04:00
Alex Hart 8cb4034c80 Invert media flow button colors. 2022-07-27 13:12:58 -04:00
Alex Hart ad0acc640b Handle multishare of text. 2022-07-27 13:12:58 -04:00
Alex Hart c907a01077 Fix null pointer exception when presenting latest media thumbnail. 2022-07-27 13:12:58 -04:00
Alex Hart 053b0eabde Fix bad argument for multiselect full screen dialog. 2022-07-27 12:30:07 -03:00
Cody Henthorne 5b7ac84e7c Bump version to 5.44.2 2022-07-26 09:39:44 -04:00
Cody Henthorne fee3af42af Updated language translations. 2022-07-26 09:36:44 -04:00
Cody Henthorne eaa2d58518 Partitialy revert read more fix for See Replies. 2022-07-26 09:33:45 -04:00
Alex Hart 6c42ded2b1 Update recyclerview dependency version to 1.2.1 2022-07-26 09:33:45 -04:00
Alex Hart cb7b2d90d5 Only display outgoing messages when entering viewer through my stories. 2022-07-26 09:33:45 -04:00
Alex Hart d40be0abf8 Maintain send button tinting in media preview. 2022-07-26 09:33:45 -04:00
Alex Hart d6cc4acf5c Set send foreground to white if using a custom color. 2022-07-26 09:33:45 -04:00
Alex Hart fa2d3e93ae Remove 0 items toast. 2022-07-26 09:33:45 -04:00
Alex Hart 7511a9ae8c Remove low profile mode. 2022-07-26 09:33:45 -04:00
Alex Hart b20658c829 Allow media selection recipient selection fragment to display in user's chosen app theme. 2022-07-26 09:33:45 -04:00
Alex Hart 09b92a6559 Add logging to share activity. 2022-07-26 09:33:45 -04:00
Alex Hart b0d75a8a5a Disallow opening archived chats if in multiselect. 2022-07-26 09:33:45 -04:00
Alex Hart 234f4b4b41 Update x asset with tint. 2022-07-26 09:33:45 -04:00
Alex Hart 3f59425579 Add subscribeOn call for getSecurityInfo. 2022-07-26 09:33:44 -04:00
Jim Gustafson a50597445a Update to RingRTC v2.20.12 2022-07-26 09:33:44 -04:00
Alex Hart a49e781c8d Respect autodownload settings when opening stories. 2022-07-26 09:33:44 -04:00
Alex Hart 570b143582 Update base stories recipient selection fragment with material 3 spec. 2022-07-26 09:33:44 -04:00
Alex Hart e6829a1b7a Add add to story handling and icon in my story row. 2022-07-26 09:33:44 -04:00
Alex Hart 14f9a3c155 Ensure sent group stories are included in the My Stories item. 2022-07-26 09:33:44 -04:00
Alex Hart b32fe003b2 Update group name display in stories landing page. 2022-07-26 09:33:44 -04:00
Alex Hart c77718f4c7 Make next/continue buttons in send flow more consistent. 2022-07-26 09:33:44 -04:00
Alex Hart a50e49e4e6 Update tooltip to a more material look. 2022-07-26 09:33:44 -04:00
Alex Hart ffd60af3ff Add new background for tooltip and always display. 2022-07-26 09:33:44 -04:00
Alex Hart d62ff6ca06 Add new chevron asset to story reply bar. 2022-07-26 09:33:44 -04:00
Alex Hart 277cfe2d6f Set story reaction bar height to 56dp. 2022-07-26 09:33:44 -04:00
Alex Hart 9b669009df Reduce story direct reply composer corner radius to 18dp. 2022-07-26 09:33:44 -04:00
Alex Hart 9f069bea7b Add proper background color to group replies. 2022-07-26 09:33:44 -04:00
Alex Hart c0f00eff25 Add reactions overlay to reply bottom sheets. 2022-07-26 09:33:44 -04:00
Alex Hart b183a38f3c Add proper thread summary for reactions to stories. 2022-07-26 09:33:44 -04:00
Alex Hart d64aa3bc43 Apply 150ms delay to story chrome fadeout. 2022-07-26 09:33:44 -04:00
Alex Hart 28e10dbb43 Disable user input during state based page jump. 2022-07-22 14:39:47 -03:00
Cody Henthorne 36b1f2816c Bump version to 5.44.1 2022-07-22 13:23:41 -04:00
Cody Henthorne 931693f5fa Updated language translations. 2022-07-22 13:19:01 -04:00
Cody Henthorne 9bade7ed4b Fix telecom system freeze in poor network. 2022-07-22 13:14:42 -04:00
Alex Hart 1d6b62d8ca Stop storing state in ConversationParentFragment. 2022-07-22 13:14:42 -04:00
Alex Hart b9a225f6c6 Fix blank screen issue when entering through a quote. 2022-07-22 13:14:42 -04:00
Alex Hart c8612d5502 Fix several conversation fragment issues. 2022-07-22 13:14:42 -04:00
Alex Hart 837f86bdd3 Fix NPE when launching conversation bubble. 2022-07-22 13:14:42 -04:00
Alex Hart 6801b5a1a3 Fix gallery item aspect ratio in avatar picker. 2022-07-22 13:14:42 -04:00
Alex Hart c9b6287702 Adjust media gallery folder overlay. 2022-07-21 15:42:31 -03:00
Cody Henthorne 6cce9ed00f Bump version to 5.44.0 2022-07-21 14:08:16 -04:00
Cody Henthorne cc1a65952b Updated language translations. 2022-07-21 14:02:59 -04:00
Alex Hart 0b44935ae2 Utilize database-backed unread message count in thread. 2022-07-21 14:57:51 -03:00
Cody Henthorne fe6058e0df Improve cold start performance. 2022-07-21 13:18:20 -04:00
Alex Hart d159a0482a Apply new wallpaper bubble color. 2022-07-21 13:18:20 -04:00
Alex Hart b046eca0fb Do not allow loading state to prevent crossfader from transitioning. 2022-07-21 13:18:20 -04:00
Alex Hart c27ca9ad52 Fix nav bar color on replies bottom sheet. 2022-07-21 13:18:20 -04:00
Alex Hart 0f2afa814d Fix bad context use for pin verification toast.
Fixes #11353
2022-07-21 13:18:20 -04:00
Alex Hart 561c1a883f Add proper scaling for badge images. 2022-07-21 13:18:20 -04:00
Evan Hahn 0e8a598985 Remove call for public translations. 2022-07-21 13:18:20 -04:00
Alex Hart 6bd8bc08d8 Add new gift opening animation and confirmation haptic. 2022-07-21 13:18:20 -04:00
Alex Hart d49c8d5184 Localization tweaks for stories and gift badges. 2022-07-21 13:18:20 -04:00
Alex Hart bcd2763c34 Rotate gifting flag. 2022-07-21 13:18:20 -04:00
Alex Hart b696a0f758 Move mms and security checks into ViewModel/Repository. 2022-07-21 13:18:20 -04:00
Alex Hart c5f4a9c89e Implement feedback for Material3 Gallery refresh. 2022-07-21 13:18:20 -04:00
Alex Hart 8767f775e9 Recreate fragment whenever we handle onNewIntent instead of restarting whole activity. 2022-07-21 13:18:20 -04:00
Rashad Sookram 88b895f5ea Notify when calls start to be routed over cellular data.
Only when the device thinks that it's also connected to a WiFi network.
2022-07-21 13:18:20 -04:00
Cody Henthorne e024541b8a Add telecom integration allow list and change processing for outgoing audio calls. 2022-07-21 13:18:20 -04:00
Alex Hart e69d944f11 Add logging for unread thread ids. 2022-07-21 13:18:20 -04:00
Alex Hart 359a39ddaf Material 3 media gallery refresh. 2022-07-21 13:18:20 -04:00
Alex Hart b78633f9a7 Fix several issues with stories. 2022-07-21 13:18:20 -04:00
Alex Hart aa75f1f8a7 Fix story touch interception which prevented moving between stories. 2022-07-21 13:18:20 -04:00
Alex Hart eb18c073c6 Set contentIsReady flag if story attachment failed to download. 2022-07-21 13:18:20 -04:00
Alex Hart 3c09655949 Fix camera rotation for newer API levels. 2022-07-21 13:18:20 -04:00
Alex Hart 17b00734ac Update contact name editor. 2022-07-21 13:18:20 -04:00
Alex Hart 00d5724cec Move androidTest into instrumentation build variant. 2022-07-21 13:18:20 -04:00
Alex Hart 4c5a88c6ca Add logging around wakelock usage for voice notes player. 2022-07-21 13:18:20 -04:00
Alex Hart 2e8ebe8b74 Add info sheet for stories. 2022-07-21 13:18:20 -04:00
Alex Hart caab91cdc3 Update UI elements of contact share activity. 2022-07-21 13:18:20 -04:00
Cody Henthorne 9c914ab715 Reduce disk hits when accessing shared preferences.
While the same instance of SharedPreferences is returned each time, in
order to get it, the system has to do a file check each time it's with
a new context. We can safely cache the instance instead of paying that
file check each time and pay it only once.
2022-07-21 13:18:20 -04:00
Cody Henthorne 819f7a170f Reduce profile avatar disk reads. 2022-07-21 13:18:20 -04:00
Alex Hart 2f17963b2b Fix hot loop when trying to delete stories but only onboarding exists. 2022-07-21 13:18:20 -04:00
Alex Hart 15111b2792 Omit blocked contacts from recents. 2022-07-21 13:18:20 -04:00
Alex Hart ecbc2d30ca Float onboarding story to top of the list. 2022-07-21 13:18:20 -04:00
Alex Hart 34379b8d3a Add internal setting item to clear onboarding state. 2022-07-21 13:18:19 -04:00
Alex Hart b18542a839 Ensure images sent to stories respect media quality settings.
Stories should always use "Standard" quality, not L3 (high quality). This change ensures that we:

1. Always send stories at the appropriate quality
2. Do not corrupt or overwrite pre-existing image attachments
3. Close several streams when done (thanks StrictMode!)
2022-07-21 13:18:19 -04:00
Cody Henthorne c4bef8099f Add GV2 accept by PNI invite. 2022-07-21 13:18:19 -04:00
Alex Hart b223ebe95e Prevent remote deletion of gift badges. 2022-07-21 13:18:19 -04:00
Alex Hart 02ea5ac806 Prevent overlay from opening for unopened gifts. 2022-07-21 13:18:19 -04:00
Cody Henthorne e03b54ac0f Bump version to 5.43.7 2022-07-21 13:18:00 -04:00
Cody Henthorne 9daa57675d Updated language translations. 2022-07-21 13:12:21 -04:00
Cody Henthorne e113973358 Fix decline code infinite loop. 2022-07-21 12:13:09 -04:00
Cody Henthorne a845a020d6 Prevent crash on clients with bad data. 2022-07-21 12:10:52 -04:00
Alex Hart 041bde3fd9 Bump version to 5.43.6 2022-07-18 16:06:43 -03:00
Alex Hart 5927ba9843 Updated language translations. 2022-07-18 16:06:00 -03:00
Alex Hart 2e7e165f8a Always relaunch conversation activity. 2022-07-18 16:00:55 -03:00
Alex Hart 4bed90fa37 Bump version to 5.43.5 2022-07-18 14:11:08 -03:00
Alex Hart 408a6f662d Updated language translations. 2022-07-18 14:11:08 -03:00
Alex Hart c9e1607987 Ensure share intents are not re-used for draft data. 2022-07-18 14:11:08 -03:00
Alex Hart f9c0156757 Fix crash when outcomeReason is null. 2022-07-18 09:28:57 -03:00
Alex Hart 43f4bc5abe Bump version to 5.43.4 2022-07-15 16:48:41 -03:00
Alex Hart 0ea6ddfe80 Updated language translations. 2022-07-15 16:47:53 -03:00
Alex Hart e9cff68e0d Add support for kk and ka language codes. 2022-07-15 16:47:52 -03:00
Cody Henthorne 64b78117c1 Use mat3 dialog for save attachments. 2022-07-15 16:47:52 -03:00
Alex Hart c1ed8bc37b Fix RTL bug in message quote headers. 2022-07-15 16:47:52 -03:00
Cody Henthorne 93d370146e Revert "Fix url trailing symbol."
This reverts commit 86227fbd67.
2022-07-13 20:30:19 -04:00
Alex Hart 96539d70df Bump version to 5.43.3 2022-07-13 15:59:28 -03:00
Alex Hart 07570bbfec Updated language translations. 2022-07-13 15:59:28 -03:00
Alex Hart 71a54ae278 Add proper copy for safety number bottom sheet when completed check. 2022-07-13 15:59:28 -03:00
Alex Hart 2d29298ec4 Fix row selection in new bottom sheet. 2022-07-13 15:59:28 -03:00
Alex Hart 42d2799264 Bump version to 5.43.2 2022-07-12 16:24:19 -03:00
Alex Hart c1f3e6351c Updated language translations. 2022-07-12 16:23:20 -03:00
Cody Henthorne 40386c910c Fix bug with SMS and disappearing messages. 2022-07-12 16:17:42 -03:00
Cody Henthorne c95fd7cf0c Fix stale send type when reloading a conversation. 2022-07-12 16:17:41 -03:00
Alex Hart 453affbe28 Remove unnecessary character. 2022-07-12 09:31:44 -03:00
Alex Hart 02b8b4a295 Bump version to 5.43.1 2022-07-11 15:27:09 -03:00
Alex Hart 870d024cbf Updated language translations. 2022-07-11 15:25:17 -03:00
Alex Hart 05bcfcc43f Fix untrusted records check. 2022-07-11 15:20:26 -03:00
Alex Hart efb82369b6 Bump version to 5.43.0 2022-07-11 14:04:57 -03:00
Alex Hart 088ce0077b Updated language translations. 2022-07-11 13:56:04 -03:00
Alex Hart c169dd308d Add support for AF. 2022-07-11 13:56:04 -03:00
Alex Hart 631958e1a6 Update callee text color. 2022-07-11 13:35:53 -03:00
Alex Hart 7a0f4fafe2 Implement new Safety Number Changes bottom sheeet. 2022-07-11 13:35:53 -03:00
Cody Henthorne b0dc7fe6df Add batch identity key check call for improved safety number change performance. 2022-07-11 13:35:53 -03:00
Alex Hart 524adcb6a4 Configure pooled players for video playback by default. 2022-07-11 13:35:53 -03:00
Cody Henthorne 748dbc2ba5 Fix incorrect notification sound when channel is set to silent.
Fixes #12317
2022-07-11 13:35:53 -03:00
Cody Henthorne e80df64698 Fix block box hiding conversation search navigation.
Fixes #12329
2022-07-11 13:35:53 -03:00
Cody Henthorne 65965e8ac5 Add Moto G20 to camerax blacklist. 2022-07-11 13:35:53 -03:00
Cody Henthorne 60e366e98a Fix delete group from message request state bug.
Fixes #12193
2022-07-11 13:35:53 -03:00
Cody Henthorne 1a80cb7c42 Fix not unarchiving on sent message sync bug. 2022-07-11 13:35:53 -03:00
Alex Hart a20c2ec63f Fix stories check to account for registration. 2022-07-11 13:35:53 -03:00
Sgn-32 4656cf4bef Shorten disappearing countdown description in message details.
Fixes #10217
Closes #11265
2022-07-11 13:35:53 -03:00
Cody Henthorne d01df9f053 Fix message details expires in countdown. 2022-07-11 13:35:53 -03:00
Greyson Parrelli 5af9872806 Add a simple PNP-backed implementation of getAndPossiblyMerge. 2022-07-11 13:35:53 -03:00
Greyson Parrelli 3beb730edb Prefer ServiceIds over SignalServiceAddresses. 2022-07-11 13:35:53 -03:00
Alex Hart 6d4dadea48 Fix devices activity crash on KitKat.
Fixes #12338
2022-07-11 13:35:53 -03:00
Greyson Parrelli f08521ab55 Removed unused test scaffolding. 2022-07-11 13:35:53 -03:00
Greyson Parrelli 04cf8676cc Remove concept of 'highTrust' that is no longer necessary. 2022-07-11 13:35:53 -03:00
Alex Hart d17896ea09 Reuse video preupload for unclipped media. 2022-07-11 13:35:53 -03:00
Cody Henthorne 7dfebdca32 Fix keyboard auto-close bug. 2022-07-11 13:35:53 -03:00
Alex Hart 5b781c45f3 Abort story send if any of the messages do not have an attachment. 2022-07-11 13:35:53 -03:00
Alex Hart c906abdb37 Prevent crash when subscriber is invoked after view is destroyed. 2022-07-11 13:35:53 -03:00
Cody Henthorne 78d4d9a3dd Add first time My Story privacy configuration. 2022-07-11 13:35:53 -03:00
Greyson Parrelli 3eac397263 Basic implementation of writing a PnpChangeSet to disk. 2022-07-11 13:35:53 -03:00
Alex Hart 32312da384 Implement several caching improvements for the Story Viewer. 2022-07-11 13:35:53 -03:00
Alex Hart 8f85b58612 Utilize debouncer instead of animator timeout for video capture end time. 2022-07-11 13:35:53 -03:00
Alex Hart 6aa4706e9b Fix bad behaviour for long group replies. 2022-07-05 15:46:06 -04:00
Alex Hart adbdb97a28 Fix crash when trying to reply when there is no post to reply to. 2022-07-05 15:46:06 -04:00
Sgn-32 a51dfa1470 Use MaterialAlertDialogBuilder in RegistrationLockV2Dialog.
Closes #12326
2022-07-05 15:46:06 -04:00
Alex Hart 36ccf9ca54 Implement Story onboarding download job and message insertion. 2022-07-05 15:46:06 -04:00
Alex Hart 2270dfaf21 Update story notifications to match spec. 2022-07-05 15:46:06 -04:00
Alex Hart bd5907ea04 Do not notify for reactions if not the group story sender. 2022-07-05 15:46:06 -04:00
Alex Hart 6d24c342d2 Fix link preview issue with text stories. 2022-07-05 15:46:06 -04:00
Alex Hart 4a3fe771d1 Display views off in my stories fragment when receipts are disabled. 2022-07-05 15:46:06 -04:00
Alex Hart ed063b4b95 Prevent pre-upload for videos that require clipping if stories is enabled. 2022-07-05 15:46:06 -04:00
Alex Hart 370640eaef Force voice note player to always be LTR. 2022-07-05 15:46:06 -04:00
Alex Hart 2a5d385152 Fix audio view in overview in RTL languages. 2022-07-05 15:46:06 -04:00
Greyson Parrelli be2ed8989f Fix possible crash in ProfileKeySendJob if given an invalid threadId. 2022-07-05 15:46:06 -04:00
Jim Gustafson e413ee4ed9 Update to RingRTC v2.20.11 2022-07-05 15:46:06 -04:00
Alex Hart 3913166461 Add minheight to media count indicator. 2022-07-05 15:46:06 -04:00
Greyson Parrelli 314ef3452f Improving logging of 401 errors. 2022-07-05 15:46:06 -04:00
Alex Hart e412cac419 Implement Stories read receipt off state. 2022-07-05 15:46:06 -04:00
Sgn-32 f3873c8a7c Prevent various operations on blocked users from conversation.
Fix #10973
Closes #11979
2022-07-05 15:46:05 -04:00
Sgn-32 f8d459829e Fix one more place where Note to Self should be used.
Closes #12321
2022-07-05 15:46:05 -04:00
Greyson Parrelli 9d8e9a3a14 Bump version to 5.42.7 2022-07-05 14:06:05 -04:00
Greyson Parrelli abb4f33299 Updated language translations. 2022-07-05 13:01:39 -04:00
Greyson Parrelli a1d444fc19 Improve resiliance of FCM fetch. 2022-07-05 11:32:42 -04:00
Greyson Parrelli a3802d0af0 Avoid potential false positive in DeadlockDetector. 2022-07-05 10:38:21 -04:00
Greyson Parrelli f441b3d0f1 Use more performant method to check if message is quoted. 2022-07-04 12:46:18 -04:00
Greyson Parrelli 99f1c9fd65 Do not show the quoted indicator in multiselect mode. 2022-07-04 12:35:48 -04:00
Greyson Parrelli 041a019439 Bump version to 5.42.6 2022-07-02 15:54:23 -04:00
Greyson Parrelli 7a34c6ee80 Updated language translations. 2022-07-02 15:54:00 -04:00
Greyson Parrelli 50701dd292 Add support for GIF playback in 'see replies' bottom sheet. 2022-07-02 15:48:36 -04:00
Greyson Parrelli 3336d92cb1 Hide 'Add to Contacts' option for the Note to Self chat. 2022-07-02 15:11:29 -04:00
Greyson Parrelli 66886dfd7b Make the 'see replies' bottom sheet respond to new/deleted messages. 2022-07-02 14:55:31 -04:00
Cody Henthorne 358d9ca58c Bump version to 5.42.5 2022-07-01 15:58:52 -04:00
Cody Henthorne 85ce85de07 Updated language translations. 2022-07-01 15:54:42 -04:00
Cody Henthorne cce0a5e820 Fix clickable state bug with CircularProgressMaterialButton. 2022-07-01 15:20:36 -04:00
Cody Henthorne 0318c4f080 Fix line wrap on Request to Join bottom sheet dialog. 2022-07-01 14:54:24 -04:00
Greyson Parrelli 39288dbcbf Only condense images in the original message in 'see replies' bottom sheet. 2022-07-01 13:39:08 -04:00
Greyson Parrelli daab296172 Show the full reply chain in the 'see replies' bottom sheet. 2022-07-01 13:36:54 -04:00
Cody Henthorne a44c3c5c2f Fix disable state bug with CircularProgressMaterialButton. 2022-07-01 12:56:08 -04:00
Greyson Parrelli 089a3d386f Fix inconsistent message bubble padding in RTL. 2022-07-01 12:01:25 -04:00
Greyson Parrelli f523529338 Fix 'see replies' indicator animation in RTL. 2022-07-01 11:30:25 -04:00
Greyson Parrelli b5a99a6b3f Bump version to 5.42.4 2022-06-30 18:58:27 -04:00
Greyson Parrelli 159d0109b9 Fix crash when long-pressing a non-media message. 2022-06-30 18:58:20 -04:00
Cody Henthorne 4eddeb74c5 Bump version to 5.42.3 2022-06-30 17:10:25 -04:00
Cody Henthorne 28de1f5c3d Updated language translations. 2022-06-30 17:02:45 -04:00
Greyson Parrelli 85f38bdea8 Fix corners of images in quote bottom sheet. 2022-06-30 16:58:40 -04:00
Cody Henthorne 12a7f36bec Update copy and icon for release channel boost button. 2022-06-30 16:58:40 -04:00
Cody Henthorne 7b805e4041 Remove use of PNI Credential. 2022-06-30 15:51:59 -04:00
Greyson Parrelli fc55b5d1ea Hide 'see replies' button during long press. 2022-06-30 13:03:29 -04:00
Cody Henthorne 1b58164bf3 Bump version to 5.42.2 2022-06-30 12:09:18 -04:00
Cody Henthorne 42c32adf8c Updated language translations. 2022-06-30 12:04:58 -04:00
Greyson Parrelli 53663b5ebd Don't open images directly from the quote bottom sheet. 2022-06-30 12:01:47 -04:00
Greyson Parrelli a87fe78c33 Show reactions in quote bottom sheet. 2022-06-30 12:01:47 -04:00
Cody Henthorne 3e3ccd4b96 Fix distribution list sync crash. 2022-06-30 12:01:47 -04:00
Cody Henthorne 0e9344c8e3 Bump version to 5.42.1 2022-06-29 19:34:53 -04:00
Cody Henthorne d562ba090e Updated language translations. 2022-06-29 19:32:21 -04:00
Jim Gustafson 9c665d3a71 Update to RingRTC v2.20.10.1 2022-06-29 19:28:21 -04:00
Cody Henthorne f2e919f39f Bump version to 5.42.0 2022-06-29 15:43:53 -04:00
Cody Henthorne 19080a8a5e Updated language translations. 2022-06-29 15:36:18 -04:00
Greyson Parrelli 61ce39b5b6 Improve implementation and testing on PNP contact merging. 2022-06-29 15:32:26 -04:00
Alex Hart c64be82710 Add context menus to story contacts in contact selection. 2022-06-29 15:32:25 -04:00
Alex Hart 7bd34d2b99 Reimplement contact chips with a recyclerview. 2022-06-29 15:32:25 -04:00
Cody Henthorne 4215b0391d Fix leak in Message Details for disappearing messages. 2022-06-29 15:32:25 -04:00
Cody Henthorne 96ea4c0cc2 Fix gift plurals resource. 2022-06-29 15:32:25 -04:00
Cody Henthorne 1129ca28fb Revert "Disable voice note proximity sensor when using bluetooth headset. (#2448)"
This reverts commit 9c7a5e3cc8.
2022-06-29 15:32:25 -04:00
Alex Hart ba6e1b5dd5 Fix attachment deduplication issue with Stories. 2022-06-29 15:32:25 -04:00
Cody Henthorne ed25be2e23 Fix couple more places where Note to Self should be used. 2022-06-29 15:32:25 -04:00
Cody Henthorne 7a0bd3315b Update release channel with material 3 changes. 2022-06-29 15:32:25 -04:00
Alex Hart 8b806a8ac5 Isolate and add unit testing to new link logic.
Co-Authored-By: ylpoonlg <56300571+ylpoonlg@users.noreply.github.com>
2022-06-29 15:32:25 -04:00
Alex Hart 0ac5782f1f Ensure stub is never resolved if not needed. 2022-06-29 15:32:25 -04:00
Alex Hart e10c20ffd7 Fix issue with getUnreadStories query. 2022-06-29 15:32:25 -04:00
ylpoonlg 86227fbd67 Fix url trailing symbol.
Fixes #12309
Fixes #10898
Fixes #11310
2022-06-29 15:32:25 -04:00
Alex Hart 1cfa5c31f2 Implement correct video story sound behaviour. 2022-06-29 15:32:25 -04:00
Alex Hart 521bd2cce4 Implement first-time-nav screen for stories. 2022-06-29 15:32:25 -04:00
Alex Hart 858c7a7f2e Implement "unviewed only" mode for story viewer. 2022-06-29 15:32:25 -04:00
Cody Henthorne 89a6730efe Add Storage Service plugin to Spinner. 2022-06-29 15:32:25 -04:00
Cody Henthorne 9bc25132c3 Add new My Story privacy settings. 2022-06-29 15:32:25 -04:00
Alex Hart ebc556801e Ensure story media is only uploaded once. 2022-06-29 15:32:25 -04:00
Alex Hart 6b745ba58a Allow swipe up to close viewer when viewing last story. 2022-06-29 15:32:25 -04:00
Alex Hart 6ddb5b983f Implement proper error handling for charge failure on initial subscription attempt. 2022-06-29 15:32:25 -04:00
Alex Hart 8efd07b3e2 Fix diplay issue with note to self banner. 2022-06-29 15:32:25 -04:00
Alex Hart e85adad2b4 Add safety net for when the user has disabled their contacts app. 2022-06-29 15:32:25 -04:00
Alex Hart 678a6f86ab Change several creations of alertdialogs to use materialalertdialogbuilder. 2022-06-29 15:32:25 -04:00
Jim Gustafson 9dc061e64f Update to RingRTC v2.20.10 2022-06-29 15:32:25 -04:00
Frazer Smith 2fed3f7e90 Update github actions with latest versions.
Closes #12294
2022-06-29 15:32:25 -04:00
Alex Hart af362736de Update help categories. 2022-06-29 15:32:25 -04:00
Cody Henthorne d39a4b14e7 Only add one sustainer request message per release notes update. 2022-06-29 15:32:25 -04:00
Alex Hart 6a385c7a22 Implement video length enforcement for Stories. 2022-06-28 15:42:15 -04:00
Alex Hart 2c3d8337c3 Include self in recents section. 2022-06-28 15:42:15 -04:00
Alex Hart 28feba6a6c Add proper catch for ISE in video thumb extractor. 2022-06-28 15:42:15 -04:00
Greyson Parrelli 6ec7834046 Add the ability to see replies. 2022-06-28 15:42:15 -04:00
Alex Hart ee4f3abf22 Add unit testing for pinned last message deletion fix. 2022-06-28 15:42:14 -04:00
Alex Hart dc66583ef1 Update camera UX to match Material3 Spec. 2022-06-28 15:42:14 -04:00
Alex Hart d30714bfd4 Update coloring of capture first flow toggle.: 2022-06-28 15:42:14 -04:00
Alex Hart d04d2f7e93 Fix bad centering of emoji button in add message fragment. 2022-06-28 15:42:14 -04:00
Alex Hart 1328aab939 Add material3 coloring to story reply dialog. 2022-06-28 15:42:14 -04:00
Alex Hart 2a9d2cf580 Remove bottomsheet elevation tinting. 2022-06-28 15:42:14 -04:00
Jim Gustafson a316650aee Update to RingRTC v2.20.9 2022-06-28 15:42:14 -04:00
Alex Hart 4d1e8b8f75 Update several story ui elements for Material3. 2022-06-28 15:42:14 -04:00
Alex Hart 9c7a5e3cc8 Disable voice note proximity sensor when using bluetooth headset. (#2448) 2022-06-28 15:42:14 -04:00
Alex Hart 2022dae37a Draw pulse outliner in onDrawForeground instead of in onDraw. 2022-06-28 15:42:14 -04:00
Chris Eager 05b7055678 Update device-transfer app build to work with the latest libsignal 2022-06-28 15:42:14 -04:00
Alex Hart 53c60e1f6d Add proper coloring to send buttons. 2022-06-28 15:42:14 -04:00
Alex Hart cd8fa58d7e Fix voice note playback bar for RTL regions. 2022-06-28 15:42:14 -04:00
Cody Henthorne c2ffc8332d Bump version to 5.41.11 2022-06-28 15:41:43 -04:00
Cody Henthorne 343a49fa26 Updated language translations. 2022-06-28 15:41:20 -04:00
Cody Henthorne 2c700c7e0e Fix broken Material3 changes on Android 6. 2022-06-28 15:20:19 -04:00
Alex Hart 105d0c778c Bump version to 5.41.10 2022-06-23 17:10:08 -03:00
Alex Hart d8bf2392ae Updated language translations. 2022-06-23 17:09:32 -03:00
Cody Henthorne 4585b439d5 Remove notification creation in WebRtcCallSerivce onCreate. 2022-06-23 15:55:57 -04:00
Alex Hart 587aa49db8 Bump version to 5.41.9 2022-06-22 14:51:18 -03:00
Alex Hart 1ef576f6f8 Updated language translations. 2022-06-22 14:50:37 -03:00
Greyson Parrelli d070ebcd2f Fix splash screen when app theme mismatches system theme. 2022-06-22 09:02:15 -04:00
Alex Hart 2c779e700d Adjust message request padding for better localization support. 2022-06-22 09:52:01 -03:00
Alex Hart feadde8737 Bump version to 5.41.8 2022-06-21 19:16:38 -03:00
Alex Hart 64dca6f60b Updated language translations. 2022-06-21 19:16:17 -03:00
Greyson Parrelli 4c4cfe917d Always ensure the send type matches the send button. 2022-06-21 19:11:27 -03:00
Alex Hart 852989ce48 Manually set nav bar background to 50 transparent black with wallpaper. 2022-06-21 19:11:27 -03:00
Alex Hart 3cecd503ab Bump version to 5.41.7 2022-06-21 15:45:16 -03:00
Alex Hart d8b97d8f87 Updated language translations. 2022-06-21 15:44:50 -03:00
Alex Hart 611950a589 Utilize translucent navigation bar for now. 2022-06-21 14:40:35 -03:00
Alex Hart 73be74dac1 Bump version to 5.41.6 2022-06-20 16:37:30 -03:00
Alex Hart 94fc7ad3c0 Updated language translations. 2022-06-20 16:36:37 -03:00
Greyson Parrelli 290fbbb9ee Update backoff logic of ClearFallbackKbsEnclaveJob. 2022-06-20 15:30:50 -04:00
Alex Hart c0735c8119 Clear snippet when the last message in a pinned thread is deleted. 2022-06-20 16:15:24 -03:00
Greyson Parrelli 8f5fc83529 Remove inactive KBS fallback. 2022-06-20 12:20:23 -04:00
Alex Hart ac2cbba067 Fix pin reminder dialog submit button. 2022-06-20 10:15:27 -03:00
Alex Hart 1bcfbaf16e Fix bottom nav overlay issue with react-with-any sheet. 2022-06-20 10:10:51 -03:00
Greyson Parrelli c950c2bdd2 Fix reaction overlay issue in dark theme. 2022-06-19 11:18:53 -04:00
Greyson Parrelli 38cecf68b5 Bump version to 5.41.5 2022-06-17 19:27:34 -04:00
Greyson Parrelli f932ed6c6a Updated language translations. 2022-06-17 19:21:40 -04:00
Greyson Parrelli 0209db4531 Show/hide attachment keyboard with the reaction overlay. 2022-06-17 19:13:14 -04:00
Greyson Parrelli 2e9f43cf94 Fix gap in reaction overlay.
I use the term 'fix' lightly. Used a stupid hack that we should revisit.
2022-06-17 18:57:52 -04:00
Alex Hart 897e176f0d Revert shade removal and add nav bar coloring. 2022-06-17 14:22:14 -03:00
Greyson Parrelli fcb4c627e4 Bump version to 5.41.4 2022-06-17 11:06:19 -04:00
Greyson Parrelli c85076138a Updated language translations. 2022-06-17 11:05:44 -04:00
Greyson Parrelli 8877603e13 Fix another possible crash with available message types. 2022-06-17 11:05:44 -04:00
Alex Hart ae6ca49e4e Fix toolbar overlap in all media screen. 2022-06-17 11:05:44 -04:00
Greyson Parrelli 2620a8fc51 Address corner case where contact details may not be synced.
Relates to #12293
2022-06-17 11:05:44 -04:00
Alex Hart 008f153b66 Adjust wallpaper preview. 2022-06-17 11:05:44 -04:00
Alex Hart 539a0182e0 Fix navigation bar color issues. 2022-06-17 11:05:44 -04:00
Alex Hart ff64f7368b Update background color for attachment keyboard. 2022-06-17 11:05:44 -04:00
Greyson Parrelli 211361684d Bump version to 5.41.3 2022-06-16 13:22:39 -04:00
Greyson Parrelli 268b00bbf9 Updated language translations. 2022-06-16 13:22:39 -04:00
Alex Hart a593bc0b7a Implement composer tweaks to allow for better contrast. 2022-06-16 13:22:39 -04:00
Greyson Parrelli b6d1af3760 Add possible fix for weird send button state. 2022-06-16 12:02:36 -04:00
Alex Hart 3acbcf54db Fix wrong color flashing when scrolling conversation settings. 2022-06-16 12:02:36 -04:00
Greyson Parrelli 673a8f540b Fix some lifecycle-related crashes. 2022-06-16 12:02:36 -04:00
Alex Hart 69e2a138d9 Fix in-call audio output picker dialog. 2022-06-16 12:02:36 -04:00
Sgn-32 11c6e748f7 Use MaterialAlertDialogBuilder in AddToGroupsActivity.
Closes #12291
2022-06-16 10:59:42 -04:00
Greyson Parrelli 33187ea12f Update the color preview to tint the send button. 2022-06-16 10:47:19 -04:00
Greyson Parrelli 6c5ceab4e5 Bump version to 5.41.2 2022-06-15 11:57:02 -04:00
Greyson Parrelli f2dc454727 Updated language translations. 2022-06-15 11:57:02 -04:00
Greyson Parrelli 8cb0898f1f Capitalize log field. 2022-06-15 11:57:02 -04:00
Greyson Parrelli 2a2809c17c Update send button color after chat color change. 2022-06-15 11:57:02 -04:00
Greyson Parrelli 9eeecaa73d Initialize WAL mode earlier. 2022-06-15 11:57:02 -04:00
Alex Hart c83a888ed0 Fix banner input overlap in some situations. 2022-06-15 11:57:02 -04:00
Alex Hart 6854632fec Allow separate specification of status and toolbar active/inactive coloring. 2022-06-15 09:45:37 -03:00
Greyson Parrelli e6cc49368e Update some dialogs to MaterialAlertDialog. 2022-06-15 08:32:20 -04:00
Greyson Parrelli 18bf00eb7a Bump version to 5.41.1 2022-06-14 17:47:08 -04:00
Greyson Parrelli fcef6f965d Fix crash that can occur when using non-standard font sizes. 2022-06-14 17:32:26 -04:00
Greyson Parrelli fb9a9b7c96 Bump version to 5.41.0 2022-06-14 15:20:43 -04:00
Greyson Parrelli d662bddeb1 Updated language translations. 2022-06-14 15:20:43 -04:00
Greyson Parrelli c5afeb6d71 Update contact photo syncing for linked devices. 2022-06-14 15:20:43 -04:00
Greyson Parrelli c66a2b8c61 Add autoVerify to some intent filters. 2022-06-14 15:20:43 -04:00
Alex Hart 88a66b49ff Apply new story list ordering rules.
Co-authored-by: Cody Henthorne <cody@signal.org>
2022-06-14 15:20:43 -04:00
Alex Hart 3b07f4a8ca Do not wait on content to launch story viewer. 2022-06-14 15:20:43 -04:00
Alex Hart f6fd1e1c91 Fix strange scale behaviour on long press of conversation item. 2022-06-14 15:20:42 -04:00
Alex Hart 2412f6f63a Fix outgoing quote over media. 2022-06-14 15:20:42 -04:00
Greyson Parrelli ce1983a3b1 Updated libphonenumber to 8.12.50 2022-06-14 15:20:42 -04:00
Greyson Parrelli 523f9c7409 Be more resistent to android disallowing service starts. 2022-06-14 15:20:42 -04:00
Cody Henthorne d5d7c73ebf Remove bad quantity strings. 2022-06-14 15:20:42 -04:00
Cody Henthorne ce93537fee Update incoming call handling.
* Fix crash with incoming ringer when custom ringtone isn't found.
* Stop notification profiles from terminating calls on linked devices.
2022-06-14 15:20:42 -04:00
Cody Henthorne 5df20d755a Fix FCM not initialized crash. 2022-06-14 15:20:42 -04:00
Alex Hart 2eb933c2d4 Implement animated color lerp for material toolbars. 2022-06-14 15:20:42 -04:00
Alex Hart ef3c776b4b Fix reaction pill background color. 2022-06-14 15:20:42 -04:00
Alex Hart bf156ad7d2 Apply Material3 spec to dialogs. 2022-06-14 15:20:42 -04:00
Alex Hart 56a2b27745 Refactor reactions dialog to match Material3 spec. 2022-06-14 15:20:42 -04:00
Rashad Sookram 0e7ace0da4 Remove unused libsignal files from APK. 2022-06-09 12:00:24 -04:00
Alex Hart 6743861630 Account for archival and meaningful message status in unread count query. 2022-06-09 12:40:35 -03:00
Alex Hart 92c6a84075 Ensure shared background for all generated text stories in a set. 2022-06-09 09:20:04 -03:00
Alex Hart b8a7748dc1 Update verify safety number display fragment. 2022-06-08 17:00:51 -03:00
Alex Hart 8b5c630303 Adjust padding below indicator. 2022-06-08 16:42:08 -03:00
Alex Hart 9ac5db2f0c Remove more solid icons. 2022-06-08 16:37:56 -03:00
Alex Hart a7380b33c7 Add proper padding and outline to invite sheet. 2022-06-08 16:30:55 -03:00
Alex Hart 4779096ac5 Update reaction pill colors. 2022-06-08 16:26:05 -03:00
Alex Hart 43be54ec42 Fix padding on expired messages save button. 2022-06-08 15:41:08 -03:00
Alex Hart 7010985be8 Update colors for scroll-to buttons to match material3 spec. 2022-06-06 13:20:19 -03:00
Alex Hart 5080dd4c4b Update emoji keyboard to be aligned with Material3 spec. 2022-06-06 13:08:17 -03:00
Alex Hart 1b1acf0aa5 Modify colorOnSurfaceVariant for dark themes. 2022-06-06 12:23:19 -03:00
Alex Hart 5527269283 Update quoteview background colors. 2022-06-06 12:21:39 -03:00
Alex Hart af32e156c2 Update message details fragment with material3 spec. 2022-06-06 12:12:13 -03:00
Felix Nüsse 9c7c94b2d4 Allow camera to rotate even when screen is locked
Fixes #8611
Closes #12247

Signed-off-by: Felix Nüsse <felix.nuesse@t-online.de>
2022-06-06 08:51:03 -04:00
Alex Hart 796e5f6f86 Add proper tinting to typing indicator. 2022-06-06 09:45:31 -03:00
Sgn-32 b282b775d0 Add LogSectionSMS to debug log.
Closes #12273
2022-06-05 12:25:26 -04:00
Greyson Parrelli 4da422fd3c Refactor how message send types are selected. 2022-06-03 18:07:29 -04:00
Jim Gustafson bf90909496 Update to RingRTC v2.20.8 2022-06-03 08:22:23 -07:00
Alex Hart 7aa99ce9a7 Remove a few more unnecessary styles. 2022-06-02 11:52:10 -04:00
Alex Hart b6767b02ed Remove several old and unnecessary styles. 2022-06-02 11:52:10 -04:00
Alex Hart cf5f7ef634 Update styles on several group bottom sheets. 2022-06-02 11:52:10 -04:00
Alex Hart 3ca4ff9a94 Update tonal buttons to utilize primaryContainer. 2022-06-02 11:52:10 -04:00
Alex Hart 28edd18e55 Fix quote text sizing. 2022-06-02 11:52:10 -04:00
Alex Hart 7bf2ae3d5e Fix-up iconography in recipient bottom sheet. 2022-06-02 11:52:10 -04:00
Alex Hart 7896a525f2 Fix devicelistfragment and remove two dependencies. 2022-06-02 11:52:10 -04:00
Alex Hart f2d5bfe51d Fix overlap of multiselect in toolbar. 2022-06-02 11:52:10 -04:00
Alex Hart b2b6f98294 Fix launch responsiveness of story viewer. 2022-06-02 11:52:10 -04:00
Alex Hart 4758369f79 Fix background of sticker management row item. 2022-06-02 11:52:10 -04:00
Alex Hart e10e629d13 Add proper text size to LabelMedium. 2022-06-02 11:52:10 -04:00
Alex Hart 0e9e39a4eb Bump ConversationListItem avatar down by 4dp. 2022-06-02 11:52:10 -04:00
Alex Hart 93e5052d6b Fix bad donor badge input behaviour. 2022-06-02 11:52:10 -04:00
Alex Hart 1b471e163d Implement new Material3 spec. 2022-06-02 11:52:10 -04:00
Greyson Parrelli 556e480b06 Bump version to 5.40.4 2022-06-02 11:50:22 -04:00
Greyson Parrelli d08bee3413 Updated language translations. 2022-06-02 11:49:57 -04:00
Cody Henthorne e83cb6fa8b Fix QR scanning bug when using camerax. 2022-06-02 11:42:25 -04:00
Greyson Parrelli 499cdd9f29 Make Github action build a specific variant. 2022-06-02 08:50:21 -04:00
Greyson Parrelli 13aa150206 Bump version to 5.40.3 2022-06-02 00:32:17 -04:00
Greyson Parrelli 63d6bab7d6 Updated language translations. 2022-06-02 00:31:39 -04:00
Cody Henthorne d6108fbbf3 Add force legacy QR scanning switch. 2022-06-01 16:38:15 -04:00
Cody Henthorne 4c44f1ee02 Tweak new QR processing some more. 2022-06-01 14:39:42 -04:00
Greyson Parrelli f4c728f57c Bump version to 5.40.2 2022-05-31 10:16:30 -04:00
Greyson Parrelli 58cebf7346 Updated language translations. 2022-05-31 10:15:40 -04:00
Cody Henthorne 2446792c62 Tweak QR code capture configuration. 2022-05-30 15:37:01 -04:00
Cody Henthorne 259a86b605 Fix lost scroll position in conversation list bug. 2022-05-30 14:33:04 -04:00
Jim Gustafson fafe795f39 Update to RingRTC v2.20.7 2022-05-27 13:50:00 -07:00
Alex Hart 9a9636b58f Bump version to 5.40.1 2022-05-27 16:57:05 -03:00
Alex Hart d48a686d98 Updated language translations. 2022-05-27 16:56:13 -03:00
Cody Henthorne d69d1c8967 Fix IAE crash in link device transition. 2022-05-27 09:51:20 -04:00
Alex Hart 60b6a9ff3f Prevent crash when ConversationListFragment list is nullified. 2022-05-27 09:55:12 -03:00
Alex Hart f85803c1fe Bump version to 5.40.0 2022-05-26 14:24:57 -03:00
Alex Hart 9dea815fce Updated language translations. 2022-05-26 14:24:57 -03:00
Cody Henthorne 4e01336b2f Fix unclosed streams during backup export. 2022-05-26 14:24:57 -03:00
Cody Henthorne 9fbc7c0f65 Fix stories viewed not updating in UI. 2022-05-26 14:24:57 -03:00
Cody Henthorne 4d028d1867 Improve messaging around story send failures. 2022-05-26 14:24:57 -03:00
Cody Henthorne 95a46f1ce5 Show user a toast when an unexpected send text story fails. 2022-05-26 14:24:57 -03:00
Cody Henthorne 26a84c5546 Show calling service notification immediately. 2022-05-26 14:24:57 -03:00
Cody Henthorne 652d0d46ed Add foreground service type to WebRtcCallService. 2022-05-26 14:24:57 -03:00
Cody Henthorne e0f3e34899 Attempt to get service name in start foreground exception stack trace. 2022-05-26 14:24:57 -03:00
Cody Henthorne 4a8083f7b1 Fix Vivo NPE quirk. 2022-05-26 14:24:57 -03:00
Cody Henthorne 08556b111b Fix ISE crash. 2022-05-26 14:24:57 -03:00
Cody Henthorne 7e7bc13b62 Swallow too many pending intents exception. 2022-05-26 14:24:56 -03:00
Cody Henthorne 5115eb125d Fix conversation search not showing after entering via settings. 2022-05-26 14:24:56 -03:00
Ahmad Alturki 3ec55b24f8 Fix swapped group title & avatar on rtl layout.
Fixes #12612
2022-05-26 14:24:56 -03:00
Cody Henthorne 5dba1067d6 Fix conversation list memory leak. 2022-05-26 14:24:56 -03:00
Cody Henthorne 2a91c67c51 Add sending and error states for story group replies. 2022-05-26 14:24:56 -03:00
Alex Hart a29bc1da8c Add proper hyphenation break to badge name. 2022-05-26 14:24:56 -03:00
Alex Hart 32b4d11a82 Fix crash in onPlaying if fragment is detached. 2022-05-26 14:24:56 -03:00
Jim Gustafson f013f7357f Update to RingRTC v2.20.6 2022-05-26 14:24:56 -03:00
Alex Hart e37150e98a Update capabilities logging. 2022-05-26 14:24:56 -03:00
Alex Hart eaa7262b2f Add debug log entry for video player pool usage. 2022-05-26 14:24:56 -03:00
Alex Hart 63f4f0bcec Fix note to self label in conversation settings. 2022-05-26 14:24:56 -03:00
Alex Hart 6dec6cef27 Add decline code messages into expiration sheet. 2022-05-24 15:03:54 -03:00
Greyson Parrelli 4d8faffb75 Notify recipient changes after bulk registration update. 2022-05-24 15:03:54 -03:00
Alex Hart fa6bb07e8a Update strings for unclear translations. 2022-05-24 15:03:54 -03:00
Cody Henthorne d260c48393 Fix device linking issues on newer devices. 2022-05-24 15:03:54 -03:00
Cody Henthorne cc31417c97 Fix desugar crash on spinner builds. 2022-05-24 15:03:54 -03:00
Alex Hart 4d2af5b536 Rotate gift badges flag. 2022-05-24 15:03:54 -03:00
Alex Hart 6029c8ae4a Bump version to 5.38.3 2022-05-24 14:31:18 -03:00
Alex Hart 3bc18c3300 Updated language translations. 2022-05-24 14:30:15 -03:00
Cody Henthorne a652bc65cc Fix font version check timeout. 2022-05-23 22:06:18 -04:00
Cody Henthorne 53252aa797 Bump version to 5.39.2 2022-05-19 16:36:06 -04:00
Cody Henthorne 956c1d96af Updated language translations. 2022-05-19 16:31:46 -04:00
Greyson Parrelli d0ecbda962 Hide attachment keyboard if system keyboard shows. 2022-05-19 08:53:12 -04:00
Greyson Parrelli 5d880e2b2a Fix bug where searching emoji would dismiss the view. 2022-05-19 08:50:40 -04:00
Greyson Parrelli bb13be1e7a Bump version to 5.39.1 2022-05-18 17:57:56 -04:00
Greyson Parrelli 05975a0068 Fix scrolling to last seen. 2022-05-18 17:51:00 -04:00
Greyson Parrelli 153feb002e Update R8 to 3.3.28 2022-05-18 17:34:04 -04:00
Cody Henthorne f63ed8f269 Bump version to 5.39.0 2022-05-18 14:18:31 -04:00
Cody Henthorne 139a503403 Updated language translations. 2022-05-18 14:11:46 -04:00
Cody Henthorne db4d072bd9 Upgrade kotlin to 1.6.21
Also fix a collection of warnings.
2022-05-18 14:05:17 -04:00
Cody Henthorne 42b0842aab Fix ANR when showing media in notifications. 2022-05-18 11:54:17 -04:00
Greyson Parrelli 9ab275195f Add support for CDSI. 2022-05-18 11:54:17 -04:00
Alex Hart 8407f2ff69 Add guard against out of bounds indices in story viewer. 2022-05-18 11:54:17 -04:00
Greyson Parrelli 9d518879dd Update libsignal-client to 0.17.0 2022-05-18 11:54:17 -04:00
Alex Hart 588663b3c2 Add better handling for unexpected cancellations. 2022-05-18 11:54:17 -04:00
Alex Hart 77f8489e51 Scroll to top on chat press when already on that tab. 2022-05-18 11:54:17 -04:00
Cody Henthorne 3c08b070fc Fetch PNI Credential during own profile refresh. 2022-05-18 11:54:17 -04:00
Greyson Parrelli dda5ce4809 Add basic CDSv2 database writes and unit tests. 2022-05-18 11:54:17 -04:00
Alex Hart 307be5c75e Ensure callback is registered for shaking gifts. 2022-05-18 11:54:17 -04:00
Alex Hart a0b89051cf Add duration info to gift row item. 2022-05-18 11:54:17 -04:00
Alex Hart a1025a8e9a Add expiry information to gift conversation items. 2022-05-18 11:54:17 -04:00
Alex Hart ce2418ce9f Consolidate local badge writes. 2022-05-18 11:54:17 -04:00
Cody Henthorne ec3540e200 Fix long text in Safety Number Change dialog. 2022-05-18 11:54:17 -04:00
Alex Hart ff4311d114 Add outline around sent gift reply thumb. 2022-05-18 11:54:17 -04:00
Alex Hart 425a13e68c Mark sent gift viewed when opened. 2022-05-18 11:54:17 -04:00
Alex Hart 15af1d3bd1 Add default animations to gift flow. 2022-05-18 11:54:17 -04:00
Alex Hart 2d57cb4ed0 Enqueue profile refresh sync after badge redemption. 2022-05-18 11:54:17 -04:00
Alex Hart 25788ef751 Do not include self in recents list for gift badging. 2022-05-18 11:54:17 -04:00
Greyson Parrelli b3086e595f Fix abbreviations with some emoji.
Fixes #12212
2022-05-18 11:54:17 -04:00
Greyson Parrelli 57e233413a Update string for group invite. 2022-05-18 11:54:17 -04:00
Greyson Parrelli f5777d58fc Fix situation where two keyboards could be showing. 2022-05-18 11:54:17 -04:00
Alex Hart 6b55cd0128 Always pop open keyboard when opening group reply sheet. 2022-05-18 11:54:17 -04:00
Alex Hart a03c49e12c Implement group story notifications. 2022-05-18 11:54:17 -04:00
Alex Hart 01543dd52b Utilize round outline for deleted messages. 2022-05-18 11:54:17 -04:00
Alex Hart 987f69227a Add polish to story replies button and direct reply sheet. 2022-05-18 11:54:17 -04:00
Alex Hart e51841a28b Fix freeze of first story first post. 2022-05-18 11:54:17 -04:00
Alex Hart bcfe2fef72 Hide gift badge row if user does not have capability set and rotate flag. 2022-05-18 11:54:17 -04:00
Alex Hart 9ed3f95ab8 Ignore duplicate stories in sync messages. 2022-05-18 11:54:17 -04:00
Cody Henthorne 0fe0765e63 Bump version to 5.38.5 2022-05-17 14:04:33 -04:00
Cody Henthorne 6e6752cfed Updated language translations. 2022-05-17 14:04:23 -04:00
Cody Henthorne 0107e8e6eb Reduce minimum translation requirement. 2022-05-17 13:41:30 -04:00
Cody Henthorne 7fe5376772 Bump version to 5.38.4 2022-05-17 11:14:42 -04:00
Cody Henthorne 30d2d12f89 Updated language translations. 2022-05-17 11:05:31 -04:00
Cody Henthorne 98ab48f0eb Revert "Fix system UI freeze with image notifications."
This reverts commit 8f2c5d43df.
2022-05-17 11:00:55 -04:00
Cody Henthorne a181ed0420 Revert "Always try to close the PartProvider open file pipe."
This reverts commit d97184ef60.
2022-05-17 11:00:54 -04:00
Cody Henthorne dbddb274db Revert "Fix NPE in PartProvider."
This reverts commit 3b16a1d28c.
2022-05-17 11:00:52 -04:00
Cody Henthorne 8502badb6d Bump version to 5.38.3 2022-05-16 12:16:39 -04:00
Cody Henthorne cb4ba1ccfe Updated language translations. 2022-05-16 12:06:36 -04:00
Cody Henthorne 3b16a1d28c Fix NPE in PartProvider. 2022-05-16 11:32:23 -04:00
Cody Henthorne ba1473acb9 Revert "Fix Google Camera social share."
This reverts commit c078d08df7.
2022-05-16 11:02:02 -04:00
Cody Henthorne 709c866786 Revert "Fix direct sharing, again."
This reverts commit ad626fe7ee.
2022-05-16 11:02:01 -04:00
Alex Hart ab4e5b1d7c Bump version to 5.38.2 2022-05-13 16:30:36 -03:00
Alex Hart 7b2552e8f2 Updated language translations. 2022-05-13 16:29:28 -03:00
Cody Henthorne a501940909 Fix Payment to Help navigation. 2022-05-13 14:27:12 -04:00
Cody Henthorne a3bbf944e5 Handle bluetooth permission crash during calls. 2022-05-13 12:39:23 -04:00
Greyson Parrelli 97d41fdd1e Small refactor of RecipientDatabase androidTests. 2022-05-13 11:39:43 -04:00
Greyson Parrelli a9bdc1abfc Only stop the FCM foreground service if it was used. 2022-05-13 09:12:02 -04:00
Cody Henthorne ad626fe7ee Fix direct sharing, again. 2022-05-13 08:35:39 -04:00
Cody Henthorne d97184ef60 Always try to close the PartProvider open file pipe. 2022-05-13 08:31:23 -04:00
Alex Hart b527b2ffb9 Bump version to 5.38.1 2022-05-12 17:30:58 -03:00
Alex Hart 468cda034a Updated language translations. 2022-05-12 17:30:58 -03:00
Cody Henthorne 8f2c5d43df Fix system UI freeze with image notifications. 2022-05-12 17:30:58 -03:00
Cody Henthorne 9bc4dfc3f6 Fix PNI crash in in group processing. 2022-05-12 17:30:58 -03:00
Greyson Parrelli dc095c9db4 Give recipient resolves their own thread pool. 2022-05-12 17:30:58 -03:00
Greyson Parrelli ef85b29ddf Fix keyboard icon when opening emoji keyboard. 2022-05-12 17:30:58 -03:00
Alex Hart 392a66ed59 Fix bad toolbar animations when switching to and from archive fragment. 2022-05-12 17:30:58 -03:00
Cody Henthorne c078d08df7 Fix Google Camera social share. 2022-05-12 11:56:55 -04:00
Alex Hart c89b818a31 Bump version to 5.38.0 2022-05-12 10:42:21 -03:00
Alex Hart e495c25687 Updated language translations. 2022-05-12 10:42:21 -03:00
Alex Hart 3b2a3500a1 Do not send viewed receipt to gift sender after redemption. 2022-05-12 10:42:21 -03:00
clauz9 d3d9b95924 Fix navigation for creating a new pin if forgotten or skipped during registration
Co-authored-by: henry <henry.ph2@gmail.com>

Closes #12183
2022-05-12 10:42:21 -03:00
Sgn-32 12d1254d4e Update libphonenumber to 8.12.48 2022-05-12 10:42:21 -03:00
Cody Henthorne ecc358ef40 Consolidate S3 requests into one interface. 2022-05-12 10:42:21 -03:00
Cody Henthorne bb963f9210 Add remote megaphone. 2022-05-12 10:42:21 -03:00
Cody Henthorne 820277800b Ignore identity updates for self. 2022-05-12 10:42:21 -03:00
Cody Henthorne 14b2d12895 Reduce disk reads on main thread. 2022-05-12 10:42:21 -03:00
Greyson Parrelli 92a506e4da Add a donate megaphone for Q2 2022. 2022-05-12 10:42:21 -03:00
Cody Henthorne 12e6ebb4df Improve performance of GV2 profile fetch and mentions initialization. 2022-05-12 10:42:21 -03:00
Greyson Parrelli c0db88960c Make FcmFetchForegroundService stop itself. 2022-05-12 10:42:21 -03:00
Alex Hart eeb4cdf064 Add strict-mode logging for disk access on Spinner variant. 2022-05-12 10:42:21 -03:00
Greyson Parrelli 85cecbb7e9 Remove the chat colors megaphone. 2022-05-12 10:42:21 -03:00
Alex Hart 33d60ebe14 Implement proper group story reply deletion for remotely deleted group stories. 2022-05-12 10:42:21 -03:00
Greyson Parrelli 9afeb206fc Refactor FCM processing to improve use of foreground services. 2022-05-12 10:42:21 -03:00
Cody Henthorne 06a49b5d5a Force use of system settings to configure notifications on SDK30+. 2022-05-12 10:42:21 -03:00
Alex Hart 68ba3433a3 Always display donation receipts page. 2022-05-12 10:42:21 -03:00
Alex Hart eaf36be9f6 NotificationThread migration. 2022-05-12 10:42:21 -03:00
Alex Hart af9465fefe Add sent story syncing. 2022-05-12 10:42:21 -03:00
Alex Hart 8ca0f4baf4 Add support for replying to gift badges. 2022-05-12 10:42:21 -03:00
Winston Cooke 0c1edd6a56 Update CONTRIBUTING.md link from master to main
Fixes #12242
2022-05-12 10:42:21 -03:00
Alex Hart df88c2fd14 Update ViewModel file template to use RxStore. 2022-05-12 10:42:21 -03:00
Alex Hart c698bfca44 Fix currency selection disabled state. 2022-05-12 10:42:21 -03:00
Alex Hart 431f5501c6 Do not display keyboard when entering blocked users page.
Fixes #12241
2022-05-12 10:42:21 -03:00
Alex Hart 9a20447993 Add touch delegate for user avatar in conversation list view. 2022-05-12 10:42:21 -03:00
Sgn-32 049e5a1b99 Fix animation for call buttons.
Closes #12240
2022-05-12 10:42:21 -03:00
Sgn-32 4cbacc9804 Change text when blocking/unblocking unregistered recipient.
Closes #12239
2022-05-12 10:42:20 -03:00
Sgn-32 6462d053ae Add divider in ChatSettingsFragment.
Closes #12238
2022-05-11 09:29:14 -03:00
Alex Hart 0f08acbc04 Verify recipient before launching google pay sheet in badge gifting flow. 2022-05-11 09:29:14 -03:00
Alex Hart dc5f7d0906 Add gift tab in donation receipts page. 2022-05-11 09:29:14 -03:00
Alex Voloshyn 60b20a9b8a Use shorter fog report URI in wallet 2022-05-11 09:29:14 -03:00
Alex Hart ec361d6349 Update gift badge open animation to use anticipate interpolator. 2022-05-11 09:29:14 -03:00
Alex Hart 1f8f1d433b Add Gift badging bow. 2022-05-11 09:29:14 -03:00
Alex Hart bc44704f54 Center currency code in selector. 2022-05-11 09:29:14 -03:00
Alex Hart 756eafe3c8 Add slide animation to conversation list to archive. 2022-05-11 09:29:14 -03:00
Alex Hart e770241ed4 Remove story text posts feature flag. 2022-05-11 09:29:14 -03:00
Cody Henthorne 4b8729c2ae Fix story unavailable emoji render bug. 2022-05-11 09:29:14 -03:00
Alex Hart 8261e21005 Lock story viewer orientation to portrait. 2022-05-11 09:29:14 -03:00
Alex Hart 1b1bbbab7a Add multi-device sync for viewed status of redeemed gift badge. 2022-05-11 09:29:14 -03:00
Alex Hart 964d214434 Remove inset for check circle and update copy for private and group story pickers. 2022-05-11 09:29:14 -03:00
Alex Hart e2b0079a5c Utilize sending reply instead of reply sent in story reply toast. 2022-05-11 09:29:14 -03:00
Alex Hart 158f77a634 Add thread display body and proper image for gift badges. 2022-05-11 09:29:14 -03:00
Alex Hart 1345413645 Ensure new storage id is synchronized to recipient. 2022-05-11 09:29:14 -03:00
Alex Hart ee69895123 Bump version to 5.37.4 2022-05-11 09:23:56 -03:00
Alex Hart f25f47654e Updated language translations. 2022-05-11 09:23:30 -03:00
Alex Hart 8f52f803cf Ensure networking is not performed on main during Subscription creation. 2022-05-11 09:09:07 -03:00
Alex Hart 82d42c03f7 Bump version to 5.37.3 2022-05-09 15:31:04 -03:00
Alex Hart c0f8e5adbf Updated language translations. 2022-05-09 15:30:26 -03:00
Cody Henthorne c54c73cb48 Exclude visible thread from notification shown check. 2022-05-09 12:14:30 -04:00
Cody Henthorne 02c8656b92 Fix remove from group bug. 2022-05-09 12:13:52 -04:00
Cody Henthorne 3553a28683 Bump version to 5.37.2 2022-05-06 14:52:07 -04:00
Cody Henthorne acf4e97578 Updated language translations. 2022-05-06 14:40:09 -04:00
Cody Henthorne 5142c8c58f Fix double divider bug when payments not available. 2022-05-06 13:08:20 -04:00
Cody Henthorne 55919cba59 Add notification not showing debuglog. 2022-05-06 12:59:03 -04:00
Cody Henthorne 1a6bd3d3f2 Add VPN/metered connection status during FCM receives. 2022-05-06 11:47:57 -04:00
Jim Gustafson 100dc54292 Update to RingRTC v2.20.5 2022-05-06 10:11:21 -04:00
Alex Hart cffbfcb957 Hide receipts item if user has none. 2022-05-06 10:01:14 -03:00
Sgn-32 f73c5dde6b Replace use of AlertDialog.Builder with MaterialAlertDialogBuilder. 2022-05-04 09:48:41 -04:00
Victor Ding d5a466851a Use the same SmsManager to divide and send a message. 2022-05-04 09:46:19 -04:00
Greyson Parrelli 38836198a1 Bump version to 5.37.1 2022-05-02 22:38:15 -04:00
Greyson Parrelli 52429dcd33 Updated language translations. 2022-05-02 22:30:52 -04:00
Ehren Kret fae427c09b Revert "Use shorter URLs for MOB FOG"
This reverts commit ef0c6c79cb.
2022-05-02 20:17:33 -05:00
Greyson Parrelli e22ddb8f96 Bump version to 5.37.0 2022-05-02 15:33:58 -04:00
Greyson Parrelli 19f0722df3 Updated language translations. 2022-05-02 15:33:58 -04:00
Greyson Parrelli 921f7a70b3 Rotate the FCM foreground service flag. 2022-05-02 15:33:58 -04:00
Greyson Parrelli bb8faebc7d Improve handling of mismatched expiry timers on messages. 2022-05-02 15:25:55 -04:00
Cody Henthorne 5ed6a05eb9 Adjust how preferred variation is handled for reaction customization. 2022-05-02 15:25:55 -04:00
Alex Hart a4a4665aaa Implement badge gifting behind feature flag. 2022-05-02 15:25:55 -04:00
Alex Hart 5d16d1cd23 Fix story send issues due to insertion of story sends to database. 2022-05-02 15:25:55 -04:00
Rashad Sookram 38b6362b25 Fix enabling video while ringing for an audio-only call.
* Update to RingRTC v2.20.4

Co-authored-by: Jim Gustafson <jim@signal.org>
2022-05-02 15:25:55 -04:00
Ehren Kret ef0c6c79cb Use shorter URLs for MOB FOG 2022-05-02 15:25:55 -04:00
Cody Henthorne f10d5651f0 Fix storage sync bug for distribution lists. 2022-05-02 15:25:55 -04:00
Justin Tracey 8a2f89b4f6 Fix .onion link linkification.
Fixes #11458.
2022-05-02 15:25:55 -04:00
Alex Hart 6563ea970f Revert "Change send method for text stories to cover link previews."
This reverts commit 0f9923e2619ec21eec3f2c4a97a3cc0eb4ab5e29.
2022-05-02 15:25:55 -04:00
Greyson Parrelli f1cb416bda Update workflow to reference main branch. 2022-05-02 15:25:55 -04:00
Greyson Parrelli df48e5ce92 Fix pluralization possibilities for group invite string.
Fixes #12197
2022-05-02 15:25:55 -04:00
Greyson Parrelli e710e231ad Remove notification profile megaphone. 2022-05-02 15:25:55 -04:00
Cody Henthorne 9599d3a0b6 Remove announcement group capability checks. 2022-05-02 15:25:55 -04:00
Greyson Parrelli 1fad4d4f65 Handle early read receipt sync messages. 2022-05-02 15:25:55 -04:00
Alex Hart f57e06677b Change send method for text stories to cover link previews. 2022-05-02 15:25:55 -04:00
Rashad Sookram f7b9942f11 Stop showing video in group calls when it isn't being forwarded. 2022-05-02 15:25:55 -04:00
Alex Hart 2f1b05f882 Start slider progress at RED. 2022-05-02 15:25:55 -04:00
Alex Hart 6650f41200 Pause voice note playback when starting an audio recording. 2022-05-02 15:25:55 -04:00
Alex Hart 2d8de03e05 Fix crash when ViewStub and contained view shared an id. 2022-05-02 15:25:55 -04:00
Alex Hart 9ffa866907 Ensure proper message ID is passed to Story viewer. 2022-05-02 15:25:55 -04:00
Alex Hart 3c0c5478b5 Fix forward sheet weirdness in full screen activities. 2022-05-02 15:25:55 -04:00
Alex Hart aae888f5af Reduce opacity of text story hint text. 2022-05-02 15:25:55 -04:00
Alex Hart e00a3730b4 Remove autosizing of text in story caption and bold styling. 2022-05-02 15:25:55 -04:00
Alex Hart 5c2394aa4f Add corner radius to text story creator. 2022-05-02 15:25:55 -04:00
Alex Hart c6be273a38 Add signal connection image. 2022-04-28 15:45:52 -03:00
Alex Hart 33236ea8e6 Add retry when user resubscribes after canceling. 2022-04-28 14:33:30 -03:00
Alex Hart a6f1e0e972 Log out charge failure for pending payment if present. 2022-04-28 09:02:37 -03:00
Jim Gustafson fa8f8beb56 Add internal setting to disable telecom 2022-04-27 14:57:43 -07:00
Greyson Parrelli 11db59d8a1 Improve the Android 12 splash screen. 2022-04-27 14:44:00 -04:00
Greyson Parrelli 39a11ce26c Ensure message resends are called on a background thread. 2022-04-27 14:24:50 -04:00
Greyson Parrelli 8bb1b2d596 Ignore empty profile fetches in RefreshOwnProfileJob. 2022-04-27 14:15:27 -04:00
Alex Hart 3f1abe05fc Allow users to copy Subscription ID to clipboard. 2022-04-27 12:47:24 -03:00
Greyson Parrelli 08ac99b4c1 Fix crash around unbinding GenericForegroundService. 2022-04-27 10:34:04 -04:00
Greyson Parrelli ebf2ef65e2 Add log section for the last thread dump during a possible deadlock. 2022-04-27 10:30:30 -04:00
Greyson Parrelli 8cb74fb776 Improve updates to CdsDatabase. 2022-04-26 13:59:51 -04:00
Greyson Parrelli eccb796199 Ensure that destinationUuid is always populated. 2022-04-26 12:16:58 -04:00
Greyson Parrelli 4635a77fbc Improve logging in base identity store. 2022-04-26 12:16:58 -04:00
Greyson Parrelli 9505c3d070 Prevent failed Spinner transforms from blocking query. 2022-04-26 12:16:58 -04:00
Cody Henthorne 657a9c7b0a Add ability to reject group invite by PNI. 2022-04-26 12:16:58 -04:00
Alex Hart e22560a794 Add dialog to nav to story or profile photo. 2022-04-26 12:15:50 -04:00
Alex Hart c081193373 Add helper text at the bottom of the private stories list. 2022-04-26 12:15:50 -04:00
Alex Hart d23faf4278 Always allow story error slate this->this transition. 2022-04-26 12:15:50 -04:00
Alex Hart da7e4cefd5 Add properly tinted conversation tab icons. 2022-04-26 12:15:50 -04:00
Sgn-32 0d9a5ef9a6 Fix SMS delivery reports 2022-04-26 12:15:50 -04:00
Victor Ding e5aea7c49e Replace non-ASCII characters in comments to their ASCII equivalent
Fixes #12201
2022-04-26 12:15:50 -04:00
Greyson Parrelli b8c42fa57e Filter out invalid phone numbers from system contacts.
Some phones are putting UUIDs in phone number fields. Who knows why.

Fixes #12191
2022-04-26 12:15:50 -04:00
Cody Henthorne 2a086ad574 Prevent VerifiedMessages from altering self. 2022-04-26 12:15:50 -04:00
Cody Henthorne 33346d8033 Fix bug with receiving GV2 message for previously unknown group. 2022-04-26 12:15:50 -04:00
Greyson Parrelli 1446af97a2 Use newer CellService observer when possible. 2022-04-26 12:15:50 -04:00
Alex Hart 64b5dad783 Fix text story preview on incoming 1to1 replies. 2022-04-26 12:15:50 -04:00
Alex Hart a6e7f9a4c1 Fix incorrect column in query. 2022-04-26 12:15:50 -04:00
Greyson Parrelli 70b0a120f0 Fix partial contact syncs and ignore your own contact info. 2022-04-26 12:12:17 -04:00
Jim Gustafson 4a6569fa1c Update to RingRTC v2.20.2 2022-04-26 12:12:17 -04:00
Greyson Parrelli f5173fa6f5 Update libsignal-client to 0.16.0 2022-04-26 12:12:17 -04:00
Greyson Parrelli 5478285362 Improve contact sync for individual contacts. 2022-04-26 12:12:17 -04:00
Alex Hart e2292dfa34 Add handling for story reply sync messages. 2022-04-26 12:12:17 -04:00
Alex Hart 17111abc72 Add support for smarter story downloads. 2022-04-26 12:12:13 -04:00
Cody Henthorne c4bc2162f2 Bump version to 5.36.3 2022-04-26 11:55:37 -04:00
Cody Henthorne bfd966217f Updated language translations. 2022-04-26 11:49:27 -04:00
Greyson Parrelli 797c02e893 Improve reliability of launching FCM foreground service.
We were getting weird errors around the service not calling
startForeground() that I couldn't reproduce. I figure it must be
something with how we only sometimes start the FcmFetchService in the
foreground. So I think the safest thing to do is to just use
GenericForegroundService.
2022-04-26 11:22:01 -04:00
Cody Henthorne 65372e547a Auto-decline invites to a group by a blocked user. 2022-04-25 13:41:50 -04:00
Alex Hart 2454b2e0db Fix crash when trying to access media controller after activity is destroyed and reference is nullified. 2022-04-25 10:29:30 -03:00
Alex Hart 0505a46603 Fix crash when item animation ends after we leave fragment. 2022-04-25 10:09:28 -03:00
Alex Hart 7f77cd6a22 Prevent crash when user quickly leaves the share fragment. 2022-04-25 10:04:21 -03:00
Alex Hart efe7b3099f Bump version to 5.36.2 2022-04-22 16:50:21 -03:00
Alex Hart 26a831b49f Updated language translations. 2022-04-22 16:50:21 -03:00
Alex Hart a3a5bb8177 Fix direct shares. 2022-04-22 16:50:21 -03:00
Alex Hart 4282f3eb6d Add explicit export to ShareActivity. 2022-04-22 16:50:21 -03:00
Greyson Parrelli 8a49db650a Do not show SMS contacts as shortcuts if we're not the SMS app. 2022-04-22 16:50:21 -03:00
Greyson Parrelli fadd4ac61e Fix possible index out of bounds exception in ConversationAdapter.
If we're deferring to super.getItem(), then we should be guarding with
super.getItemCount().
2022-04-22 16:50:21 -03:00
Alex Hart d0c14895d0 Fix crash when parent does not implement optional bottom sheet callback. 2022-04-22 16:50:21 -03:00
Greyson Parrelli 32ee18240b Fix crash that occurs if we don't have permission to add an account. 2022-04-22 14:32:05 -04:00
Cody Henthorne cd10aa90cc Adjust tap area on forwarding fab. 2022-04-22 13:40:07 -04:00
Rashad Sookram 33d28c4359 Inset audio level indicator by nav bar height. 2022-04-22 12:36:05 -04:00
Greyson Parrelli 530403ec04 Updated emoji to version 14.0 2022-04-22 11:40:07 -04:00
Greyson Parrelli f15072bc8d Fix other group update description bugs and add tests. 2022-04-22 08:32:07 -04:00
Ehren Kret 8c2db972cf Fix crash if recipient appears multiple times in group update description.
Without starting from start index, if the same recipient appears
multiple times in the recipient list, this function will crash.
2022-04-22 07:55:42 -04:00
Alex Hart ff8f9ca81a Bump version to 5.36.1 2022-04-21 12:45:39 -03:00
Alex Hart 40991cc8e9 Updated language translations. 2022-04-21 12:45:12 -03:00
Cody Henthorne 2f551ee3f2 Do not auto-leave groups you have requested to join. 2022-04-21 11:30:13 -04:00
Alex Hart f1ab0a05f1 Fix issue preventing stories shared element transition from starting. 2022-04-21 12:12:15 -03:00
Greyson Parrelli fb919466de Enqueue a profile fetch to resolve identity key conflicts. 2022-04-20 19:04:24 -04:00
Greyson Parrelli 4a4cf08cd8 Do not run StorageForcePushJob if you're not registered. 2022-04-20 18:52:41 -04:00
Alex Hart ed20c24326 Bump version to 5.36.0 2022-04-20 16:32:55 -03:00
Alex Hart e01cbcec62 Updated language translations. 2022-04-20 16:32:55 -03:00
Rashad Sookram 04d6ccc30e Re-enable audio level indicators in calls. 2022-04-20 16:32:55 -03:00
Alex Hart 6860f96973 Add subscription retry on 402 and print out status when we think a sub is active. 2022-04-20 16:32:55 -03:00
Alex Hart 944c8530d8 Improve remote delete handling in group story threads. 2022-04-20 16:32:55 -03:00
Alex Hart 8b1552952c Stories: Pass recipients through via constructor. 2022-04-20 16:32:55 -03:00
Greyson Parrelli dfcadde076 Ensure we enqueue a storage sync after a safety number change. 2022-04-20 16:32:55 -03:00
Cody Henthorne 55acd0f048 Auto-leave group if added by blocked user. 2022-04-20 16:32:55 -03:00
Alex Hart 820c016aad Allow quoted story to launch into viewer. 2022-04-20 16:32:55 -03:00
Alex Hart d1d63d83dc Drop stories from inactive groups. 2022-04-20 16:32:55 -03:00
Alex Hart 7da5b2cdef Fix spinner group change description. 2022-04-20 16:32:55 -03:00
Alex Hart 442dde5c40 Keep caption when forwarding media with a body to a story. 2022-04-20 16:32:55 -03:00
Cody Henthorne f038e81ff3 Fix long name issues in reaction and recipient bottom sheet.
Fixes #12113
2022-04-20 16:32:55 -03:00
Greyson Parrelli 32c4fcb065 Improve handling of empty profiles. 2022-04-20 16:32:55 -03:00
Alex Hart e2703b459f Rework color selector and background. 2022-04-20 16:32:55 -03:00
Cody Henthorne 405d99fbe2 Allow keyboard switch when disabling pin reminders.
Fixes #9862
2022-04-20 16:32:55 -03:00
Evan Hahn 7b89687206 Update donation strings: "One-time", not "One Time". 2022-04-20 16:32:55 -03:00
Alex Hart d74f1a386c Set story text placeholder alpha to 60 percent. 2022-04-20 16:32:55 -03:00
Alex Hart b041ed1510 Ensure delivery receipts are sent for 1:1 story replies and reactions. 2022-04-20 16:32:55 -03:00
Alex Hart 3426556a51 Disable group private replies. 2022-04-20 16:32:54 -03:00
Greyson Parrelli e2cb535f3f Make names in group update descriptions tappable. 2022-04-20 16:32:54 -03:00
Alex Hart 3b17a41415 Send actual quote author in story direct reply. 2022-04-20 16:32:54 -03:00
Alex Hart 631720f111 Ensure direct replies respect disappearing message timeout. 2022-04-20 16:32:54 -03:00
Cody Henthorne ab031d3dad Add internal setting to clear keep longer logs. 2022-04-19 08:17:21 -04:00
Alex Hart 6101048f07 Update donor badge strings. 2022-04-18 16:38:06 -03:00
Alex Hart 115f7063d5 Add support and tracking of ChargeFailure in ActiveSubscription. 2022-04-18 16:37:12 -03:00
Alex Hart 159d67ec59 Fix crash for non-story replies. 2022-04-18 13:26:28 -03:00
Greyson Parrelli e09ce4c820 Use foreground services to process notification when appropriate.
Right now, the only condition is once every 3 minutes on Android 12.

This is ok because Android 12 will allow us (once every 2 minutes or
so) to start a foreground service, and it won't show it for the first 10
seconds. So we can kind of do it without any visual penalty.
2022-04-18 11:27:32 -04:00
Alex Hart 8cfc013960 Fix issue where story viewer would get stuck. 2022-04-18 10:24:54 -03:00
Alex Hart a436c46cb2 Remove label from remote deleted story reactions. 2022-04-18 09:43:59 -03:00
Alex Hart 893be51810 Allow 1:1 replies to increment thread unread counter. 2022-04-18 09:40:05 -03:00
Greyson Parrelli 97b5a49e36 Update default state for whether legacy passwords are disabled.
This was a feature that was removed from the app over 4.5 years ago.
The value should have been manually set to false when they set a
password, meaning that it should be safe to set the default to true.

Fixes #10367
2022-04-17 10:12:44 -04:00
Alex Hart 043f06e188 Prevent sending videos over 30s in length to a story. 2022-04-15 16:07:15 -04:00
Alex Hart fa13b464f8 Fix elongated message bubbles when intentional newlines are present. 2022-04-15 16:07:15 -04:00
Alex Hart bfaaf20fd9 Fix image editor outlining on Android 12+. 2022-04-15 16:07:15 -04:00
Peter Thatcher 2f97b80b9c Add internal setting for call bandwidth mode. 2022-04-15 16:07:15 -04:00
Alex Hart eee9c967fa Fix overlapping text issue in review cards. 2022-04-15 16:07:15 -04:00
Alex Hart 515981c044 Add horizontal margins to donation receipt PNG. 2022-04-15 16:07:15 -04:00
Alex Hart a06528e5e1 Always notifyIfReady for each boolean change. 2022-04-15 16:07:14 -04:00
Alex Hart 98194c854a Add blur and adjust layout for story error slate. 2022-04-15 16:07:14 -04:00
Alex Hart 2d60a88a75 Fix crossfade target aspect ratio. 2022-04-15 16:07:14 -04:00
Alex Hart c3e7d6c74c Fix bug causing cancellation of dialog fragment. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 8da66bc789 Fix corner case in story distribution list syncing. 2022-04-15 16:07:14 -04:00
Alex Hart 9ceb5b2e85 Fix view-off-main bug in Landing fragment. 2022-04-15 16:07:14 -04:00
Alex Hart 17b8e086c9 Fix crash when trying to view 1:1 conversation with reaction quote. 2022-04-15 16:07:14 -04:00
Alex Hart 9a097d113d Update share interstitial to use proper title. 2022-04-15 16:07:14 -04:00
Alex Hart 46ca1e16bb Fix crash when trying to add a link to a text post. 2022-04-15 16:07:14 -04:00
Alex Hart d4d3124a90 Prevent flicker of user avatar in MyStories when moving between tabs. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 35a9fddbb2 Add basic support for receiving messages at your PNI.
We haven't implemented merging yet, so this is still very basic, but it
"works".
2022-04-15 16:07:14 -04:00
Alex Hart 41e417ff0b Add proper interpolator and duration to chrome show/hide. 2022-04-15 16:07:14 -04:00
Alex Hart f6614c1174 Set story viewer background exit fade duration to 100ms. 2022-04-15 16:07:14 -04:00
Alex Hart 9136bcf5e8 Add radius animator to cross fade when launching story viewer. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 7c156d10d6 Keep active table selected in Browse page in Spinner. 2022-04-15 16:07:14 -04:00
Alex Hart 3372d942ec Swap out outlinethumbnailview for shapeableimageviews in mystories. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 7fc9876b1e Start transaction earlier in backup restore.
Fixes #12159
2022-04-15 16:07:14 -04:00
Alex Hart cff62e9528 Add 12dp margin to top of stories context menu. 2022-04-15 16:07:14 -04:00
Alex Hart 24f59b0a17 Hide bottom nav when viewing archived conversations. 2022-04-15 16:07:14 -04:00
Alex Hart 0a07800eba Fix caption sending for outgoing image and video stories. 2022-04-15 16:07:14 -04:00
Alex Hart c863e9ed4d Fix crash when trying to create a text story. 2022-04-15 16:07:14 -04:00
Alex Hart 523537cf05 Enable sharing to stories and refactor share activity. 2022-04-15 16:07:14 -04:00
Greyson Parrelli fd4543ffe0 Improve speed of many SMS/MMS queries by removing unnecessary attachment join. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 83b0309f23 Fix bug in Spinner where some query history items didn't work. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 5cabe5ecfa Default the query history to hidden in Spinner. 2022-04-15 16:07:14 -04:00
Greyson Parrelli fae3004512 Fix issue where you could have multiple context menus.
Fixes #12149
2022-04-15 16:07:14 -04:00
Alex Hart e143c47c25 Fix crash and icon change issue with shared element transition. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 27c3fca324 Change ContactRecordProcess to merge identity key/state as a group. 2022-04-15 16:07:14 -04:00
Alex Hart 26d637cafc Add explicit themes to fabs. 2022-04-15 16:07:14 -04:00
Alex Hart 03e8fe9f27 Migrate all internal shares to MultiselectForwardFragment. 2022-04-15 16:07:14 -04:00
Artem Varaksa 23939aeee3 Removed unused string and the variable that was used in it.
Close #12146
2022-04-15 16:07:14 -04:00
clauz9 d7b793ce4c Fade out fab buttons and megaphone when entering action mode or search.
Closes #12112
2022-04-15 16:07:14 -04:00
Greyson Parrelli d3096c56cb Basic client usage of CDSHv2.
This provides a basic (read: useful-for-development-yet-broken) client
usage of CDSHv2.
2022-04-15 16:07:14 -04:00
clauz9 b0e7b49056 Remove redundant exit animation.
Fixes #12119
2022-04-15 16:07:14 -04:00
Cody Henthorne 2f0f26c328 Add story send multi-send, error, and improved SNC states. 2022-04-15 16:07:14 -04:00
Alex Hart 7f2f5a182f Add shared element transition for camera fab. 2022-04-15 16:07:14 -04:00
Alex Hart 33b88796e8 Simplify layout and do not display until data is loaded to prevent flashing. 2022-04-15 16:07:14 -04:00
Greyson Parrelli 31e4db2186 Bump version to 5.35.3 2022-04-15 16:06:11 -04:00
Greyson Parrelli 76ad7866ec Updated language translations. 2022-04-15 16:05:41 -04:00
Rashad Sookram e9804eccbb Disable audio level indicator in calls. 2022-04-15 15:45:50 -04:00
Rashad Sookram d62d0efb1d Fix screen pulsing when your video is disabled. 2022-04-15 11:44:49 -04:00
Greyson Parrelli 3ec9cd1244 Bump version to 5.35.2 2022-04-13 11:56:41 -04:00
Greyson Parrelli bd5f48f193 Updated language translations. 2022-04-13 11:56:22 -04:00
Alex Hart 98fc3e5b0b Log warning instead of throwing NPE for voice note controller. 2022-04-13 12:37:24 -03:00
Greyson Parrelli d06c633dc4 Fix full text search filter.
Closes #12158
2022-04-13 10:58:16 -04:00
Greyson Parrelli d401386e2d Bump version to 5.35.1 2022-04-11 20:44:10 -04:00
Greyson Parrelli 8f0f9e64b9 Updated language translations. 2022-04-11 20:43:47 -04:00
Greyson Parrelli bd5ac85ac0 Fix old DB migration.
A cautionary tale that serves as a reminder to never call external code
during a migration...

Fixes #12147
2022-04-11 20:38:32 -04:00
Greyson Parrelli 417070e957 Prevent possible deadlock in identity cache. 2022-04-11 12:30:18 -04:00
Greyson Parrelli a92638e897 Fix possible threading issue in RetrieveProfileJob. 2022-04-11 12:00:01 -04:00
Rashad Sookram 08abe890ff Adjust audio levels animation. 2022-04-11 11:56:46 -04:00
Cody Henthorne 66b6420f21 Revert "Fix overlapping text with voice notes."
This reverts commit dabd131222.
2022-04-11 11:54:29 -04:00
Rashad Sookram b21bd5a01e Ensure PiP view is animated to its final position. 2022-04-08 17:34:50 -04:00
Cody Henthorne d11e8ec04b Bump version to 5.35.0 2022-04-08 12:29:29 -04:00
Cody Henthorne 3e5fe0f1cb Updated language translations. 2022-04-08 12:22:37 -04:00
Alex Hart b65d62e065 Update prioritization of donation error bottom sheets. 2022-04-08 12:19:27 -04:00
Alex Hart fc55be0916 Stop voice note on video playback. 2022-04-08 12:19:27 -04:00
Alex Hart a87aa0fbe2 Don't keep around shortcuts for archived chats. 2022-04-08 12:19:27 -04:00
Alex Hart a44a105cbc Add ability to copy text slides in full. 2022-04-08 12:19:27 -04:00
Alex Hart c4817ac017 Allow generic links to be sent as stories. 2022-04-08 12:19:27 -04:00
Cody Henthorne 65835606cc Fix reply UX on reply disabled 1:1 stories. 2022-04-08 12:19:27 -04:00
Cody Henthorne ff26922afb Tweak private story reaction UI. 2022-04-08 12:19:27 -04:00
Alex Hart a894ba7a51 Implement cross-fade for story thumb shared element animation. 2022-04-08 12:19:26 -04:00
Cody Henthorne cb63fe600c Fix react with any closing for story direct replies. 2022-04-08 12:19:26 -04:00
Jim Gustafson 2d6146351d Update to RingRTC v2.20.1 2022-04-08 12:19:26 -04:00
Alex Hart e5953b25e1 Disallow fling gesture when we are translating for on back. 2022-04-08 12:19:26 -04:00
Alex Hart 6354cb194c Update ordering query to display content in expected order. 2022-04-08 12:19:26 -04:00
Cody Henthorne 8d6beb92cb Reply sheet polish. 2022-04-08 12:19:26 -04:00
Cody Henthorne bb5edccf34 Update view count in My Story view. 2022-04-08 12:19:26 -04:00
Cody Henthorne 6d86b25acd Improve story contact search. 2022-04-08 12:19:26 -04:00
Alex Hart 04677d21bb Push repository calls to background. 2022-04-08 12:19:26 -04:00
Alex Hart 20022b88fc Fix issue where if no stories exist we would never display. 2022-04-08 12:19:26 -04:00
Cody Henthorne 3088d7f182 Adjust quote view colors. 2022-04-08 12:19:26 -04:00
Alex Hart ce8dafd33d Start align text when displaying in smallest size otherwise center. 2022-04-08 12:19:26 -04:00
Alex Hart 6054285ddb Add description to story link previews. 2022-04-08 12:19:26 -04:00
Alex Hart dabea5169b Fix opening long messages. 2022-04-08 12:19:26 -04:00
Alex Hart 7fb5ceeda4 Allow hidden story viewing. 2022-04-06 14:37:25 -03:00
Cody Henthorne dc6fd8be7f Fix story reply crash and tweak UI. 2022-04-06 13:17:33 -04:00
Alex Hart c271b9c2de Prevent multiple clicks when accessing the viewer. 2022-04-06 12:38:43 -04:00
Alex Hart 6fb6092a6b Implement a cache for faster typeface resolution. 2022-04-06 12:38:43 -04:00
Alex Hart 46bb64ad24 Display reply icon if you responded 1:1 to the displayed story. 2022-04-06 12:38:43 -04:00
Alex Hart bcd16ce296 Lock orientation when creating a text post. 2022-04-06 12:38:43 -04:00
Alex Hart 07ec14d5c4 Fix issue where reaction animation would only play first time. 2022-04-06 12:38:43 -04:00
Alex Hart d716416d1d Prevent caption from swallowing taps if no overlay needed. 2022-04-06 12:38:43 -04:00
Alex Hart 343871ed8b Use unread thread count for bottom bar. 2022-04-06 12:38:43 -04:00
Alex Hart aa60247e42 Add rounded corners back to secondary story. 2022-04-06 12:38:43 -04:00
Alex Hart 283e3e99a5 Adjust badge positioning on stories landing items. 2022-04-06 12:38:43 -04:00
Alex Hart ca79bdb16b Preserve tab state between configuration changes. 2022-04-06 12:38:43 -04:00
Alex Hart a75d2cfa34 Adjust viewer ordering to match landing page. 2022-04-06 12:38:43 -04:00
Cody Henthorne 1746f37276 Use smarter scrolling for group story replies. 2022-04-06 12:38:43 -04:00
Rashad Sookram 73f32868a2 Display audio levels in 1:1 calls. 2022-04-06 12:38:43 -04:00
Cody Henthorne dabd131222 Fix overlapping text with voice notes. 2022-04-06 12:38:43 -04:00
Greyson Parrelli 612c6db6db Rename some CDS-related classes. 2022-04-06 12:38:43 -04:00
Cody Henthorne c56ef33833 Fix resend after safety number change in groups or distribution lists. 2022-04-06 12:38:43 -04:00
Alex Hart 2253e25ae1 Consolidate toolbar_basic usage to single location. 2022-04-06 12:38:43 -04:00
Alex Hart adb24d480a Remove access modifier from ChatColors constructor. 2022-04-06 12:38:43 -04:00
Cody Henthorne 9fb1dcf28f Fix overlapping text with remote delete. 2022-04-06 12:38:43 -04:00
Cody Henthorne bba36a5724 Keep gif search open when viewing a result. 2022-04-06 12:38:43 -04:00
Alex Hart fa515be258 Hide tab bar during multiselect. 2022-04-06 12:38:43 -04:00
Cody Henthorne be241524db Fix font networking main thread crash. 2022-04-06 12:38:43 -04:00
Cody Henthorne bb66c3fa68 Fix story views not using entire bottom sheet space. 2022-04-06 12:38:43 -04:00
Greyson Parrelli a32d5bef20 Refactor more ContactDiscovery code. 2022-04-06 12:38:43 -04:00
Greyson Parrelli d409278dd5 Do not allow emoji in image editing if device doesn't support it. 2022-04-06 12:37:43 -04:00
Alex Hart 3328e43a40 Add initial shared element transition between conversation list and stories. 2022-04-06 12:37:43 -04:00
Alex Hart 678e832058 Update stories camera fab coloring. 2022-04-06 12:37:43 -04:00
Alex Hart 2c341f450f Add finalized private story icons. 2022-04-06 12:37:43 -04:00
Alex Hart 5854074d4a Update my stories item and landing page empty notice. 2022-04-06 12:37:43 -04:00
Alex Hart dbe186248d Rework sizing on landing page. 2022-04-06 12:37:43 -04:00
Alex Hart 0504161b04 Change stories camera fab icon to outline. 2022-04-06 12:37:43 -04:00
Alex Hart 9748f1cff8 Add padding to context menus in story landing and my story. 2022-04-06 12:37:43 -04:00
Alex Hart 6a061ed52c Remove stroke from story thumbnail. 2022-04-06 12:37:43 -04:00
Alex Hart 102d58502a Fix bad layout of group story text replies. 2022-04-06 12:37:43 -04:00
Alex Hart 477698f917 Use media forward sheet when forwarding in Media preview. 2022-04-06 12:37:43 -04:00
Alex Hart d865b5d7b5 Color the send button properly for insecure chats. 2022-04-06 12:37:43 -04:00
Alex Hart eeaf6df925 Change wording of receipts button. 2022-04-06 12:37:43 -04:00
Alex Hart 95abca4e03 Dismiss after pressing don't show this again. 2022-04-06 12:37:43 -04:00
Alex Hart c5906b6f3a Fix background color on forward sheet bottom bar. 2022-04-06 12:37:43 -04:00
Alex Hart 91c581b475 Do not process story records if capability doesn't support it. 2022-04-06 12:37:43 -04:00
Fumiaki Yoshimatsu 47760867d5 Adds additional padding to the bottom of the line so the following line wouldn't overlap the previous line.
Fixes a bug [reported by Salt505 in the beta forum](https://community.signalusers.org/t/beta-feedback-for-the-upcoming-android-5-26-release/38629/163).
2022-04-06 12:37:43 -04:00
Alex Hart 5612a5d9e4 Fix issue where all forwarded MMS media would try to send as a secure message. 2022-04-06 12:37:43 -04:00
Alex Hart 4c462bd75a Enforce L1 media restrictions on link preview thumbnails.
Co-Authored-By: Alexandre Erwin Ittner <110642+ittner@users.noreply.github.com>
2022-04-06 12:37:43 -04:00
Cesar Valiente 092b30f64f Utilize isSeparating for better foldable device support. 2022-04-06 12:37:43 -04:00
Greyson Parrelli b34ca8ca2f Improve handling of unknown IDs in storage service. 2022-04-06 12:37:43 -04:00
clauz9 e2c54eef77 Filter out some Base64 encoded status messages from search. 2022-04-06 12:37:43 -04:00
Fumiaki Yoshimatsu 2a2c27edef Remove unnecessary marginEnd attribute. 2022-04-06 12:37:43 -04:00
Rashad Sookram ec92d5ddb7 Display audio levels for each participant in group calls. 2022-04-06 12:37:43 -04:00
Alex Hart a9f208153c Fix artifacting corners on landing page. 2022-04-06 12:37:43 -04:00
Alex Hart d9ffd67f36 Update My Stories logic when user has not sent to a distribution list. 2022-04-06 12:37:43 -04:00
Alex Hart 19861ef0d1 Implement specification testing for StoryViewerViewModel. 2022-04-06 12:37:43 -04:00
Alex Hart 469879c211 Implement proper story viewer ordering. 2022-04-06 12:37:43 -04:00
clado 157198fd17 Add content descriptions for incoming call buttons.
Fixes #11995
2022-04-06 12:37:43 -04:00
Greyson Parrelli 0d61b8db38 Fixed threading issues with Spinner recent queries. 2022-04-06 12:37:43 -04:00
Greyson Parrelli 593334456a Add a local query history to Spinner. 2022-04-06 12:37:43 -04:00
Greyson Parrelli 98b9cc23e4 Add extension functions to improve writability of database queries. 2022-04-06 12:37:43 -04:00
Alex Hart 3e42c044b8 Add RxStore and StoryViewerPage forward navigation. 2022-04-06 12:37:43 -04:00
Cody Henthorne 11c3ea769e Fix emoji keyboard bugs and group story replies. 2022-04-06 12:37:43 -04:00
Sgn-32 b8bb2e234b Elimination of country code 0 in Delete account 2022-04-06 12:37:43 -04:00
Cody Henthorne 87b00bb156 Fix various story reply bottom sheet issues. 2022-04-06 12:37:43 -04:00
Alex Hart 3da2fc4d9b Clear storage keys for deleted distribution lists. 2022-04-06 12:37:43 -04:00
Cody Henthorne 972ab9b368 Process incomming story views even if read receipts are disabled. 2022-04-06 12:37:43 -04:00
Alex Hart c359b0134a Implement StoryDistributionListRecord and processing. 2022-04-06 12:37:43 -04:00
Alex Hart 2cd7462573 Add long-press action to mystories items for helpful debugging info. 2022-04-06 12:37:43 -04:00
Alex Hart 2a7d515932 Add handler to My Stories row to open my stories
If there are sent group stories.
2022-04-06 12:37:43 -04:00
Alex Hart 1bb04035ab Update playback to match specifications. 2022-04-06 12:37:43 -04:00
Alex Hart 267efb0763 Start viewer when clicking on story ring. 2022-04-06 12:37:43 -04:00
Alex Hart 0ef215dfc5 Size story bottomsheets to 60 percent height. 2022-04-06 12:37:43 -04:00
Cody Henthorne 50bea8140f Fix story reply mention fragment from taking over screen. 2022-04-06 12:37:43 -04:00
Chris Eager 086e3ed4ec Update message reporting to use sender ACI instead of E164.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-04-06 12:37:43 -04:00
Cody Henthorne b02539684a Fix unresolved authors of story replies. 2022-04-06 12:37:43 -04:00
Alex Hart b9747607ad Add blur to background for non 9:16 story content. 2022-04-06 12:37:43 -04:00
Cody Henthorne 284a6ae667 Add defaults for script/text font pairings and guessing of script based on body contents.
Co-authored-by: Alex Hart <alex@signal.org>
2022-04-06 12:37:43 -04:00
Alex Hart 116e711f1a Add logging for when we do nothing during a keep-alive job. 2022-04-06 12:36:32 -04:00
Cody Henthorne 7aeb641036 Include mentions on incoming story replies. 2022-04-06 12:36:32 -04:00
Fumiaki Yoshimatsu e4f69c0b6f Add padding to the toolbar in media preview activities.
Fixes #10298
2022-04-06 12:36:32 -04:00
Sgn-32 48d7228ae7 Replace use of AlertDialog.Builder with MaterialAlertDialogBuilder. 2022-04-06 12:36:32 -04:00
Alex Hart ae28df901f Hide view once for story first sending. 2022-04-06 12:36:32 -04:00
Alex Hart f17f45f277 Fix bad check for story context. 2022-04-06 12:36:32 -04:00
Alex Hart 2b15fc2966 Do not drop group stories if not profile sharing with sender. 2022-04-06 12:36:32 -04:00
Alex Hart 76a9342afa Safety number check and profile refresh for group sends. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 14849d6e45 Fix case-insensitive queries for groups with non-ASCII characters.
Fixes #11464
2022-04-06 12:36:32 -04:00
Greyson Parrelli 77ea2deada Move more util classes to core-util. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 390b7ff834 Convert SqlUtil to Kotlin. 2022-04-06 12:36:32 -04:00
Greyson Parrelli 0e4187b062 Use existing contact type for our linked entry. Add test to sample app.
Fixes #9431
Closes #9434

Co-authored-by: swatts <github@stargw.net>
2022-04-06 12:36:32 -04:00
Cody Henthorne 4098f77e08 Bump version to 5.34.10 2022-04-06 12:18:57 -04:00
Cody Henthorne a60eed35fe Updated language translations. 2022-04-06 12:18:10 -04:00
Cody Henthorne 904215fe38 Fix font networking main thread crash. 2022-04-06 12:04:55 -04:00
Greyson Parrelli 6376642d38 Pre-resolve recipient needed for group messages in the conversation list. 2022-04-06 11:13:56 -04:00
Cody Henthorne 6b36a446f0 Bump version to 5.34.9 2022-04-05 10:12:35 -04:00
Cody Henthorne 95ee7f5c00 Updated language translations. 2022-04-05 10:08:22 -04:00
Greyson Parrelli b109effc94 Prevent possibility of recursively enqueuing early message jobs. 2022-04-04 19:26:08 -04:00
Alex Hart 44efda8318 Bump version to 5.34.8 2022-03-31 17:09:07 -03:00
Alex Hart e6e1b6d746 Updated language translations. 2022-03-31 17:08:31 -03:00
Greyson Parrelli e303cbcc22 Fix some toolbar theming issues. 2022-03-31 14:06:37 -04:00
Greyson Parrelli c67aed5b65 Keep some crucial local logs (like crashes) for longer. 2022-03-30 16:32:45 -04:00
Greyson Parrelli 0278882c30 Prevent possible crash while reading contacts cursor. 2022-03-30 16:17:15 -04:00
Alex Hart 42ccd638bd Bump version to 5.34.7 2022-03-30 12:19:36 -03:00
Alex Hart e11577bd23 Updated language translations. 2022-03-30 12:15:24 -03:00
Greyson Parrelli 3c0b87bbca Fix possible backup crash due to foreign key constraint. 2022-03-30 09:56:26 -04:00
Christian Clauss 84a61b01ca Fix syntax error in apntool.py
Old-style exceptions are syntax errors in Python 3 while new-style exceptions work as expected on both Python 2 and Python 3.

Closes #10622
2022-03-30 09:39:34 -04:00
Greyson Parrelli c149e008fd Update apostrophes in code comments. 2022-03-30 09:37:12 -04:00
Greyson Parrelli c7a345eb0b Ignore inbound SMS/MMS from yourself. 2022-03-29 18:39:46 -04:00
Sgn-32 348b6e9742 Enable hyphenation on signal bottom action bar item title.
Closes #11896
2022-03-29 18:21:25 -04:00
Greyson Parrelli 003b3e02e4 Add ability to view your own profile photo fullscreen.
From the profile management screen.

Fixes #11033
2022-03-29 18:21:16 -04:00
Fumiaki Yoshimatsu 5b668d7931 Refresh the storage managment title on long-click.
Fixes #9698
Closes #10021
2022-03-29 18:21:08 -04:00
Greyson Parrelli 87748fa80c Use an inset ripple for contact list items.
Closes #10786

Co-authored-by: Thore Goebel <hello@thore.io>
2022-03-29 18:20:58 -04:00
Greyson Parrelli ad0482fb5b Bump version to 5.34.6 2022-03-29 13:58:01 -04:00
Greyson Parrelli 12ceb1cb32 Updated language translations. 2022-03-29 13:57:18 -04:00
Greyson Parrelli 9ec2c5da52 Show some buttons in media preview toolbar if there's room.
Fixes #11661
2022-03-29 10:43:04 -04:00
Greyson Parrelli f742d34588 Remove unnecessary package exclusions.
Fixes #9540
2022-03-29 10:27:24 -04:00
Cody Henthorne 4d8e058d33 Allow hyphenation of enter sms code buttons. 2022-03-29 10:13:32 -04:00
Cody Henthorne 77ba6e0f7b Enable telecom integration for internal users. 2022-03-29 10:08:04 -04:00
Fumiaki Yoshimatsu 76a0e5c851 Allow search view to be full width of the screen in landscape.
Fixes #10299
Closes #10321
2022-03-29 10:05:45 -04:00
Greyson Parrelli f3096cc24c Use more direct language in PIN reminder toast.
Fixes #10207
2022-03-29 09:37:20 -04:00
Greyson Parrelli 49957e1d95 Prevent changing disappearing message timer for blocked users.
Fixes #10973
2022-03-29 09:32:46 -04:00
Cody Henthorne 2f5cb5f090 Add story distribution list deduplication handling. 2022-03-28 19:43:42 -04:00
Ehren Kret ba394e1021 Use notifyDataSetChanged for potentially structural modification.
This has the potential to change the structure of the result set
displayed in the recycler view. Thus the functions that tell it the
structure changed need to be called. For an immediate fix, changing
this back to notifyDataSetChanged seems to resolve the crash.
2022-03-28 19:22:02 -04:00
Greyson Parrelli 7611c64493 Do not bidi-isolate all-ASCII strings.
Fixes #11630
2022-03-28 19:07:21 -04:00
Greyson Parrelli 231248d20a Update android test orchestrator to 1.4.1 2022-03-28 19:07:21 -04:00
Alex Hart a3a79fc58d Adjust text story button protections. 2022-03-28 19:07:21 -04:00
Alex Hart 6476e585c4 Fix cast in story landing fragment. 2022-03-28 19:07:21 -04:00
Greyson Parrelli fd2961710d Fix androidTests. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 7f4ab67f98 Fix timing issue with receipt updates. 2022-03-28 19:07:21 -04:00
Greyson Parrelli b9ce38b85b Don't allow KEEP_LONGER logs to get too large. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 50d5658add Fix possible NPE when backing out of conversation. 2022-03-28 19:07:21 -04:00
Greyson Parrelli 72777bc6cd Disallow some unicode sequences in link previews. 2022-03-28 19:07:21 -04:00
Rashad Sookram f2046c3c05 Vibrate when entering call reconnecting state. 2022-03-28 19:07:21 -04:00
Sgn-32 745dfc3fbb Fix translation issue with media preview.
Fixes #12072
Closes #12092
2022-03-28 19:07:21 -04:00
Greyson Parrelli cb77165b53 Fix issue where search results could flicker.
Shoutout to @clauz9 for the help in researching this bug!
2022-03-28 19:07:21 -04:00
Alex Hart bde4700e87 Hide new user entry in story recipient selection. 2022-03-28 19:07:21 -04:00
Alex Hart e58cea9a26 Fix bad use of toString in StripeApi. 2022-03-28 19:07:21 -04:00
Alex Hart 556dc0d1ec Show story rings for self and if you sent a story to a group. 2022-03-28 19:07:21 -04:00
Alex Hart 8c1ddcf1c0 Fix issue where thumb resource wasn't set to null after clear. 2022-03-28 19:07:21 -04:00
Alex Hart 2549c1f97d Add placeholder for text post thumbs. 2022-03-28 19:07:21 -04:00
Alex Hart 5faa497821 Get receipt credential presentation BEFORE recording receipt so that the retry does not add another receipt. 2022-03-28 19:07:21 -04:00
Alex Hart d7a7e72c3a Fix flashing when entering text story. 2022-03-28 19:07:21 -04:00
Alex Hart af1701e6fa Add HTTPS scheme when user enters a web address. 2022-03-28 19:07:21 -04:00
Rashad Sookram 32d1cc7d54 Recreate MainActivity on language change. 2022-03-28 19:07:20 -04:00
Alex Hart 783a615c07 Adjust text story creation layout size. 2022-03-28 19:07:20 -04:00
Alex Hart 65bfee6eba Display total unread messages including mark as unread instead of unread thread count. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 8d4419705b Update to libsignal-client 0.15.0 2022-03-28 19:07:20 -04:00
Alex Hart 6c3baf229c Fix broadcast send when user sends to both story and non-story. 2022-03-28 19:07:20 -04:00
Rashad Sookram 6e9a6283fc Fix layout for long story replies. 2022-03-28 19:07:20 -04:00
Alex Hart 5b3899237b Trampoline call to generate preview if view is not laid out. 2022-03-28 19:07:20 -04:00
Greyson Parrelli dddf830e47 Move system contact interactions into their own module. 2022-03-28 19:07:20 -04:00
Alex Hart fd930d0b1d Conversation tab bar animations. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 2b5d65ae04 Revert "Update to libsignal-client 0.15.0"
This reverts commit 3d5f04eba757563dd92366d994a96cf323b8d540.
2022-03-28 19:07:20 -04:00
clauz9 2ebaa04c2f Remove leftover code in SearchUtil.
Closes #12090
2022-03-28 19:07:20 -04:00
Jordan Rose 1e316ea19f Update to libsignal-client 0.15.0 2022-03-28 19:07:20 -04:00
Cody Henthorne ac9257ec1c Revert "Track inconsistencies between new and old network availability for internal users."
This reverts commit 007975e7da.
2022-03-28 19:07:20 -04:00
Alex Hart 9b83c5e283 Ensure we do not send captions for non-story messages. 2022-03-28 19:07:20 -04:00
Alex Hart a7a4972013 Check if there is an attachment available before trying to send it to a story. 2022-03-28 19:07:20 -04:00
Alex Hart f6f4e6fde7 Add animations to camera toggle. 2022-03-28 19:07:20 -04:00
Alex Hart e9160c2449 Suppress multiple clicks on tap to add. 2022-03-28 19:07:20 -04:00
Alex Hart b0b1029d0f Add transition fixes and improvements. 2022-03-28 19:07:20 -04:00
Rashad Sookram 72b3a0555d Improve transition back to creation fragment. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 135fde68c1 Migrate some cursor utils to core-util. 2022-03-28 19:07:20 -04:00
Alex Hart 954e45ed97 Fix capitalization retention for text stories. 2022-03-28 19:07:20 -04:00
Alex Hart 2b3f16d3ad Material3 Bottom Bar styling and fab for Stories only. 2022-03-28 19:07:20 -04:00
Alex Hart 6820b84921 Story Viewer shared element transition. 2022-03-28 19:07:20 -04:00
Cody Henthorne 6a5f5f4ffa Show verified badge on Note to Self. 2022-03-28 19:07:20 -04:00
Rashad Sookram 19381342b3 Update UI for replies to unavailable stories. 2022-03-28 19:07:20 -04:00
Greyson Parrelli c2627dda8d Migrate contact interactions to SystemContactsRepository. 2022-03-28 19:07:20 -04:00
Alex Hart db309b7930 Implement slide to close in Story viewer. 2022-03-28 19:07:20 -04:00
Cody Henthorne 403958fed3 Handle 1:1 call reconnecting events. 2022-03-28 19:07:20 -04:00
Cody Henthorne 3af53f2089 Allow ringrtc to decided how to respond to send message failure. 2022-03-28 19:07:20 -04:00
Cody Henthorne e472760d92 Fix MessageCountsViewModel memory leak. 2022-03-28 19:07:20 -04:00
Alex Hart b04acd8ae0 Add caption to outgoing stories. 2022-03-28 19:07:20 -04:00
Cody Henthorne 6890973ce8 Enforce limit for total number of blocked requests. 2022-03-28 19:07:20 -04:00
Alex Hart b3d9a85fa2 Fix button alignment. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 9f027ed584 Ensure unidentified access is correct when fetching own profile. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 8fb598e60a Restart app after refreshing remote config via internal settings. 2022-03-28 19:07:20 -04:00
Greyson Parrelli 2edaba39a0 Fix support for PniIdentity sync message. 2022-03-28 19:07:20 -04:00
clauz9 b0be7effe8 Use notifyItemRangeChange in ConversationListFragment onResume().
Closes #12085
2022-03-28 19:07:20 -04:00
Greyson Parrelli 142979ce93 Add job to clean up early message receipts. 2022-03-28 19:07:20 -04:00
Rashad Sookram 093dd7c62c Update Material Design Components to 1.5.0. 2022-03-28 19:07:20 -04:00
Cody Henthorne 4acafc3d77 Fix translation issue with media preview.
Fixes #12072
2022-03-28 19:07:20 -04:00
Alex Hart 65bf0aad79 Fixes text story preview sizing on pixel 2. 2022-03-28 19:07:20 -04:00
Alex Hart ef6e846512 Text Camera Switch view positioning fixes. 2022-03-28 19:07:20 -04:00
Alex Hart 782464f664 Make messaging in keepalive more explicit. 2022-03-28 19:07:20 -04:00
Jim Gustafson c7352f62e5 Update to RingRTC v2.20.0 2022-03-28 19:07:20 -04:00
Rashad Sookram 90dd6b7cb3 Compile against API level 31. 2022-03-28 19:07:20 -04:00
Rashad Sookram ddfb4bf0a5 Fix context menu animation crash on Kitkat. 2022-03-28 19:07:20 -04:00
Alex Hart cdef21d6c0 Add animation when you directly react to a story. 2022-03-28 19:07:20 -04:00
Alex Hart c0f843061e Donations logs. 2022-03-28 19:07:19 -04:00
Alex Hart 5774771ea6 Reverse direction of swipe to reply. 2022-03-28 19:07:19 -04:00
Rashad Sookram ab8d5474e0 Handle keyboard resize when creating a text story. 2022-03-28 19:07:19 -04:00
Alex Hart 6497ec8098 Adjust sizes for text post text size. 2022-03-28 19:07:19 -04:00
Greyson Parrelli 83c3b16b92 Add ContactDiscovery abstraction for doing CDS refreshes. 2022-03-28 19:07:19 -04:00
Alex Hart 3c2bd032ba Fix NPE when secondary story does not have media and is not a text story. 2022-03-28 19:07:19 -04:00
Alex Hart f798866619 Maintain app bar layout when switching tabs. 2022-03-28 19:07:19 -04:00
Greyson Parrelli ffad2c7386 Bump version to 5.34.5 2022-03-28 12:53:12 -04:00
Greyson Parrelli 7252e54593 Updated language translations. 2022-03-28 12:53:12 -04:00
Greyson Parrelli cfab4dc658 Update stale-bot config. 2022-03-28 12:53:11 -04:00
Cody Henthorne 7f5a8ce6bb Disable telecom integration. 2022-03-28 12:34:50 -04:00
Greyson Parrelli d02a597451 Bump version to 5.34.4 2022-03-23 11:54:11 -04:00
Greyson Parrelli 8d92a1f195 Updated language translations. 2022-03-23 11:53:53 -04:00
Greyson Parrelli 74e630aacb Fix possible crash and remove old fastRecord system. 2022-03-23 11:44:37 -04:00
Greyson Parrelli 65a12767f9 Bump version to 5.34.3 2022-03-21 18:46:52 -04:00
Greyson Parrelli ab9d813636 Updated language translations. 2022-03-21 18:46:07 -04:00
Cody Henthorne 007975e7da Track inconsistencies between new and old network availability for internal users. 2022-03-21 17:21:42 -04:00
Greyson Parrelli 86ca1ebda0 Add basic handling for ProofRequiredExceptions on other message types. 2022-03-21 16:36:07 -04:00
Cody Henthorne 57fb3e6377 Fix crash when accidently triggering a second voice note record. 2022-03-21 13:36:03 -04:00
Cody Henthorne 7e6fcb80a3 Revert all new network detection API usage and refactorings. 2022-03-21 12:21:46 -04:00
Cody Henthorne 5e46e1e3d9 Bump version to 5.34.2 2022-03-18 15:48:26 -04:00
Cody Henthorne 21e370de9b Updated language translations. 2022-03-18 15:45:33 -04:00
Cody Henthorne 77ef877c59 Show block request if request follows a collapsed event. 2022-03-18 15:26:38 -04:00
Greyson Parrelli 77caedb3bb Avoid recipient resolves in Recipient#getGroupName(). 2022-03-18 14:49:06 -04:00
Cody Henthorne 3e77975c17 Revert "Fix soft keyboard popping up when the text was selected when the other keyboard was open."
This reverts commit 6d41d1f6d2.
2022-03-18 14:09:52 -04:00
Alex Hart 75e15c81e1 Ensure messages are marked read when entering a conversation with an active typing indicator. 2022-03-18 13:15:37 -03:00
Cody Henthorne 782a1ce301 Fix NPE in conversation update group info. 2022-03-18 12:00:04 -04:00
Cody Henthorne be21b9e163 Bump version to 5.34.1 2022-03-18 11:14:35 -04:00
Cody Henthorne 284140871e Updated language translations. 2022-03-18 11:06:32 -04:00
Cody Henthorne e6ac40a07c Fix various corner case block/reject join request bugs. 2022-03-18 11:03:38 -04:00
Rashad Sookram b8e98350c1 Add back aapt2 checksums for osx/windows. 2022-03-18 11:03:38 -04:00
Cody Henthorne 445ff263c6 Fix receiving group invite bug. 2022-03-18 11:03:38 -04:00
Alex Hart e10c40d2b8 Fix infinite loop when dealing with large video files. 2022-03-18 11:03:38 -04:00
Alex Hart a41a2b3e64 Fix wonky bottom sheet animations. 2022-03-18 11:03:38 -04:00
Alex Hart e603391c35 Fix several theming issues for Contact Selection. 2022-03-18 10:32:03 -03:00
Cody Henthorne 7e0cd99f48 Fix block request button not showing up always. 2022-03-17 19:51:22 -04:00
Cody Henthorne daedb8261d Fix empty group update bug. 2022-03-17 19:28:50 -04:00
Cody Henthorne 2c8744a319 Bump version to 5.34.0 2022-03-17 16:18:31 -04:00
Cody Henthorne a7c441225b Updated language translations. 2022-03-17 16:06:22 -04:00
Alex Hart e3c491860a Allow forwarding of Text Stories. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 43ad0b2294 Fix issue where group read was happening on main thread. 2022-03-17 16:02:43 -04:00
Alex Hart bf897d10d2 Add avatar to my story row and wire in badges. 2022-03-17 16:02:43 -04:00
Cody Henthorne 0b1a93d3e6 Disable Telecom integration solely for OnePlus devices. 2022-03-17 16:02:43 -04:00
Alex Hart 7edef20f4f Guard first time on add to my story. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 945c308cf5 Update group update messages faster. 2022-03-17 16:02:43 -04:00
Cody Henthorne f91494f813 Remove newer network detection APIs. 2022-03-17 16:02:43 -04:00
Cody Henthorne 9d28caac00 Fix bug preventing adding and inviting by phone number. 2022-03-17 16:02:43 -04:00
Alex Hart 798f3a7b0e Only display tilted image for My Story. 2022-03-17 16:02:43 -04:00
Alex Hart 768e170ed4 Fix hidden story behaviour. 2022-03-17 16:02:43 -04:00
Greyson Parrelli a0ebb891de Resolve multiple times when generating static IPs.
An attempt to make the list somewhat more stable.
2022-03-17 16:02:43 -04:00
Greyson Parrelli 570b39f82e Debounce menu invalidations in conversation. 2022-03-17 16:02:43 -04:00
Alex Hart dc50899fe0 Allow addition of text to text slide by tapping anywhere. 2022-03-17 16:02:43 -04:00
Alex Hart 0f889e0259 Add progress indicator to story text post link loader. 2022-03-17 16:02:43 -04:00
Greyson Parrelli cb906edd11 More accurate timings of conversation-open component. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 604f6709db Fix bug where wallpaper didn't update after changing. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 0359f27cd9 Fix true update queries for blobs. 2022-03-17 16:02:43 -04:00
Greyson Parrelli 0ca438ed25 Update MSL appending to create a new entry if the original one is gone. 2022-03-17 16:02:43 -04:00
Rashad Sookram 6b6e9e92e8 Fix remote delete for private stories. 2022-03-17 16:02:43 -04:00
Greyson Parrelli b5e0991f5e Log early delivery receipts. 2022-03-17 16:02:43 -04:00
Alex Hart f06f0e7ae0 Pop open keyboard when we enter the link entry fragment. 2022-03-17 16:02:43 -04:00
Alex Hart 0fcbb5ffda Send user to My Stories from failure notification. 2022-03-17 16:02:42 -04:00
Alex Hart b1f7dbefd8 Drop stories from users we would normally show a message request for. 2022-03-17 16:02:42 -04:00
Alex Hart 8fc2d5be37 Add 32dp space to bottom of choose story type bottom sheet. 2022-03-17 16:02:42 -04:00
Alex Hart 40020728de Ensure proper text size is used when displaying and editing text stories. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 4abb169568 Do not suggest SMS during onboarding. 2022-03-17 16:02:42 -04:00
Alex Hart da1ac5358f Add initial support for rendering link previews in text story previews. 2022-03-17 16:02:42 -04:00
Greyson Parrelli d504bd593a Improve wallpaper load speed. 2022-03-17 16:02:42 -04:00
Alex Hart 63e48efdfe Allow user to launch directly to a specific story, fix story chronology. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 8bb27b60fa Revert "Fix data race preventing some story sends."
This reverts commit cde70269817464880ddb8e2c67e59ca8b571073b.
2022-03-17 16:02:42 -04:00
Alex Hart 437c1e2f21 Implement UI and backend for sending story reactions.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 16:02:42 -04:00
Alex Hart 7f4a12c179 Fix issue where stories with links would fail to send. 2022-03-17 16:02:42 -04:00
Alex Hart 19d3bbc70a Order recipients in viewer by story sent date. 2022-03-17 16:02:42 -04:00
Alex Hart 559561bf72 Add support for message resends. 2022-03-17 16:02:42 -04:00
Rashad Sookram c8c0589ac4 Hide spell check errors in post preview. 2022-03-17 16:02:42 -04:00
Greyson Parrelli 666218773c Improve conversation open speed.
Co-authored-by: Cody Henthorne <cody@signal.org>
2022-03-17 16:02:42 -04:00
Cody Henthorne d3049a3433 Add block request action button to collapsed join request events. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 130d5a8945 Add index to improve speed of MMS count. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 172751cd42 Iterate over a snapshot of transaction listeners. 2022-03-17 12:12:56 -04:00
Rashad Sookram 3ad7c96a3c Fix ellipsis appearing in the middle of a message. 2022-03-17 12:12:56 -04:00
Fumiaki Yoshimatsu 6d41d1f6d2 Fix soft keyboard popping up when the text was selected when the other keyboard was open.
Fixes #11780
2022-03-17 12:12:56 -04:00
Rashad Sookram cb74833dc2 Fix scheduling of ExpireStoriesAlarm. 2022-03-17 12:12:56 -04:00
Alex Hart 8c7b6293fb Fix data race preventing some story sends. 2022-03-17 12:12:56 -04:00
Cody Henthorne 9d1f46da9f Collapse multiple join request/cancels when from a single person. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 216059b659 Fix layout for long text in SMS verification buttons.
Fixes #12037
2022-03-17 12:12:56 -04:00
Greyson Parrelli 18392ed0a4 Render date dividers based on sent time.
The time we use to render date headers needs to match the time we use to
render timestamps in the footer. We should be using sent time in both
cases.

Fixes #11589
2022-03-17 12:12:56 -04:00
Rashad Sookram 63a4d20ea9 Keep 1:1 replies after expiry and fix queries. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 057231b9c3 Update libsignal-client to 0.14.0 2022-03-17 12:12:56 -04:00
clauz9 749bbf428d Make sure isSearchRequest is true when searchViewItem is expanded.
fixes signalapp#12054
2022-03-17 12:12:56 -04:00
Alex Hart b0458f10a3 Ensure identity records are good before trying to send media. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 5b91c927b6 Refresh our own profile before rotating our profile key. 2022-03-17 12:12:56 -04:00
Greyson Parrelli b45740884b Only upload your avatar if it's being changed.
New server param means we don't have to upload the avatar if we want to
keep it the same.
2022-03-17 12:12:56 -04:00
Alex Hart 87ad4be117 Fix issue where user could not select a group story. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 78de70881f Fix responsiveness of profile photo edit UI.
There were various issues around the profile photo updating correctly in
the edit view. We want to make sure that what the user sees there is
what other people are seeing.

So I made some changes to make sure that when you remove your profile
photo the UI updates right away, as well as fixed most flickering
issues.
2022-03-17 12:12:56 -04:00
Greyson Parrelli e7a370a549 Fix paging issue where DataStatus was not updated on insert. 2022-03-17 12:12:56 -04:00
Alex Hart 54eb579558 Allow external shares to a story. 2022-03-17 12:12:56 -04:00
Alex Hart 732b67d8cb Allow injectable typefaces in image text editor.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 12:12:56 -04:00
Cody Henthorne eed45b57a1 Prevent rejected/kicked group members from joining again via group link. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 3503c60fd1 Add routine check to ensure GV2 profiles are up-to-date. 2022-03-17 12:12:56 -04:00
Cody Henthorne c17ba30cfc Show different messages based on join group link error header. 2022-03-17 12:12:56 -04:00
Rashad Sookram 5167c7235d Don't animate to replies tab during open. 2022-03-17 12:12:56 -04:00
Greyson Parrelli 803f94012a Handle profile key changes consistently. 2022-03-17 12:12:56 -04:00
Alex Hart 9281bcdd7d Only display stories if you entered through stories. 2022-03-17 12:12:56 -04:00
Alex Hart 4dca554967 Add better text reflow as font changes. 2022-03-17 12:12:55 -04:00
Alex Hart 7c45fb6c17 Fix issue where names with emoji would not display. 2022-03-17 12:12:55 -04:00
Alex Hart 8aa283488f Clear search query in Story recipient selection after a selection is made. 2022-03-17 12:12:55 -04:00
Alex Hart 604c65c7fb Add finalized story icon assets. 2022-03-17 12:12:55 -04:00
Alex Hart 711148423d Excise PowerMock and reenable like a bunch of ignored tests.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-17 12:12:55 -04:00
Alex Hart 1f82ceecc6 Story Status for landing page and my stories. 2022-03-17 12:12:55 -04:00
Rashad Sookram 1ac8701ada Update Gradle to 7.4.1. 2022-03-17 12:12:55 -04:00
Alex Hart d61e33fdf3 Fix story display size logic. 2022-03-17 12:12:55 -04:00
Greyson Parrelli e552b5160f Implement CdshV2Service. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 7e063e8ad8 Refactor CDSH to allow for code reuse. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 88a34936cd Add more device info at the top of Spinner. 2022-03-17 12:12:55 -04:00
Greyson Parrelli c1181478dd Remove GV2 capability check. 2022-03-17 12:12:55 -04:00
Greyson Parrelli d13d8628b5 Small UI tweaks.
- Update distance below avatar in conversation settings.
- Slightly increase bubble corner radius.
2022-03-17 12:12:55 -04:00
Alex Hart 6048208c8c Fix crash with incorrectly tagged story. 2022-03-17 12:12:55 -04:00
Alex Hart 78214fb39b Update click boundaries in story viewer. 2022-03-17 12:12:55 -04:00
Alex Hart ff8d7fa6c2 Add send/recv/render support for text stories. 2022-03-17 12:12:55 -04:00
Greyson Parrelli 3a2e8b9b19 Add internal button to force an emoji search index download. 2022-03-17 12:12:55 -04:00
Cody Henthorne bca4289c96 Updated language translations. 2022-03-17 12:12:55 -04:00
Cody Henthorne 3fbd9baf0c Disable telecom integration. 2022-03-17 12:11:51 -04:00
Cody Henthorne e12c96f4b2 Bump version to 5.33.6 2022-03-17 12:09:16 -04:00
Cody Henthorne eec26aa481 Updated language translations. 2022-03-17 12:07:17 -04:00
Cody Henthorne 865aeda6f2 Disable telecom integration. 2022-03-17 12:04:33 -04:00
Cody Henthorne 2c4ebedda4 Bump version to 5.33.5 2022-03-16 13:57:08 -04:00
Cody Henthorne 042bc8d79a Updated language translations. 2022-03-16 13:56:52 -04:00
Cody Henthorne 4c7bd80f72 Fix early ringing state on slow connections. 2022-03-16 13:37:28 -04:00
Cody Henthorne 3a8591fdfb Bump version to 5.33.4 2022-03-16 10:22:25 -04:00
Cody Henthorne 629aaa2093 Updated language translations. 2022-03-16 10:18:41 -04:00
Cody Henthorne 5b5b118b7a Fix disconnect sound on call termination. 2022-03-16 10:08:06 -04:00
Greyson Parrelli c7016aa462 Fallback to legacy network detection. 2022-03-15 11:28:31 -04:00
Cody Henthorne cf857e109a Fix mention rendering bug. 2022-03-15 10:32:59 -04:00
Alex Hart 1c79840684 Bump version to 5.33.3 2022-03-11 16:10:06 -04:00
Alex Hart 4ba7de9519 Updated language translations. 2022-03-11 16:09:14 -04:00
Cody Henthorne 2eb8df347e Fix mention rendering regression. 2022-03-11 14:18:42 -05:00
Rashad Sookram 9056371c41 Fix quote preview being cut off.
When determining the height to force for the animation, the text was
being measured assuming it had infinite width, which made
it seem like it could fit on one line.
2022-03-11 11:38:56 -05:00
Greyson Parrelli 1f57e1f366 Add more logging around network changes. 2022-03-11 10:35:40 -05:00
Alex Hart aeb568bcf4 Fix crash when recreating conversation react with any emoji fragment. 2022-03-11 09:44:47 -04:00
Cody Henthorne b7afe4411e Fix NPE in telecom integration. 2022-03-10 16:17:13 -05:00
Alex Hart cba784b8ec Bump version to 5.33.2 2022-03-10 16:53:01 -04:00
Alex Hart 3aba15e88d Updated language translations. 2022-03-10 16:52:01 -04:00
Cody Henthorne fa384e93dc Fix crash importing backups. 2022-03-10 15:46:45 -05:00
Alex Hart 1f3e04da29 Fix text size in generated text drawables. 2022-03-10 12:32:42 -04:00
Greyson Parrelli a484d48377 Update network connectivity observer to be more optimistic. 2022-03-10 11:21:21 -05:00
Greyson Parrelli 15f51ea26e Fix crash if synced pinned contact is malformed. 2022-03-10 11:14:55 -05:00
Greyson Parrelli 80bfa103ab Fix narrow race around generation of some ACI keys. 2022-03-10 11:11:14 -05:00
Cody Henthorne 66f93e0d32 Do not drop 1:1 messages with mentions due to iOS and desktop regression.
iOS and Desktop both regressed in multi-forwarding by including mentions
in 1:1 forwards instead of replacing them with plain text. Android by
default drops these as invalid messages. Since there are clients in the
wild that do this now, we have to stop dropping them and try to resolve
them per normal mechanisms.
2022-03-09 12:09:46 -05:00
Rashad Sookram 366780f6cb Revert "Avoid querying conversation size twice."
This reverts commit fe088c39c7.
2022-03-09 11:55:52 -05:00
Alex Hart fb4c1fc268 Bump version to 5.33.1 2022-03-08 16:11:46 -04:00
Alex Hart e3bb7ccbd3 Updated language translations. 2022-03-08 16:11:09 -04:00
Alex Hart e3fb8a2137 Only update tab if it has actually changed. 2022-03-08 12:01:21 -04:00
Alex Hart ba4c0386ef Bump version to 5.33.0 2022-03-08 10:41:37 -04:00
Alex Hart 644945825b Updated language translations. 2022-03-08 10:41:37 -04:00
Alex Hart 7590c6dcbb Add warnings to FFs. 2022-03-08 10:41:37 -04:00
Cody Henthorne b25cef86ee Fix different dates being used when saving attachments. 2022-03-08 10:41:37 -04:00
Jim Gustafson fdaaa560e7 Update to RingRTC v2.19.2 2022-03-08 10:41:37 -04:00
Alex Hart 4cd438b2db Fix avatar view clickability. 2022-03-08 10:41:37 -04:00
Greyson Parrelli c0e1507ef4 Don't cancel KeyCachingService if not necessary.
This relates to #12043. There's some xiaomi-specific issue, and this
code was causing a pending intent creation on every app startup,
preventing it from opening. This call shouldn't be necessary unless
screenlock is active.
2022-03-08 10:41:37 -04:00
Alex Hart 8a75d78ce7 Restrict text story post sends to stories only. 2022-03-08 10:41:37 -04:00
Jordy 8176d25b4c Changed copyright to 2022.
Closes #11897
2022-03-08 10:41:37 -04:00
Greyson Parrelli 21273bc165 Fix syncing 'prefer system photos' setting. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 213517f875 Reduce sensitivity of swipe-to-archive. 2022-03-08 10:41:37 -04:00
Greyson Parrelli b1c006657a Fix read receipt timestamp log. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 852dcd9711 Show megaphone to improve network reliability. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 427e73f7fd Improve payment withdrawals. 2022-03-08 10:41:37 -04:00
Alex Hart eae6a971e6 Update volume output stream when audioAttributes change. 2022-03-08 10:41:37 -04:00
Alex Hart 4b23e60dd6 Fix gallery media toast when selected item is too large.
Fixes #12011
2022-03-08 10:41:37 -04:00
Alex Hart f0988f37f3 Update UI for View More in contact lists. 2022-03-08 10:41:37 -04:00
Alex Hart e2e3617be9 Ensure groups stories are sent to are retained in the UI. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 3ac63cc59d Implement new feature flag strategy for AEC selection. 2022-03-08 10:41:37 -04:00
Jim Gustafson d935d1deca Update to RingRTC v2.19.1 2022-03-08 10:41:37 -04:00
Alex Hart 3b1b00027b Fix conversation list tab bar icon colors. 2022-03-08 10:41:37 -04:00
Alex Hart a1bc1aaa98 Only show stories and send stories with respect to capability. 2022-03-08 10:41:37 -04:00
Rashad Sookram 0ccaad1462 Update quote UI for story replies in chat. 2022-03-08 10:41:37 -04:00
Chris Eager ad57e62680 Add staging registration constant to build config. 2022-03-08 10:41:37 -04:00
Alex Hart 4e57432dbb Improve smoothness of segmented progress bar and respect video duration. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 63412b0153 Remove leftover Valentine's Day assets. 2022-03-08 10:41:37 -04:00
Cody Henthorne 35199abf1f Fix rejoining group on linked device not showing as joined. 2022-03-08 10:41:37 -04:00
Rashad Sookram 41b5813984 Open story viewer from MyStoriesFragment. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 83215bb98f Additional work on not sending to blocked recipients. 2022-03-08 10:41:37 -04:00
clauz9 eb12395b8e Do not send to blocked recipients. 2022-03-08 10:41:37 -04:00
Cody Henthorne 4b07da4978 Inline change number flag. 2022-03-08 10:41:37 -04:00
Cody Henthorne 3fbc5423e5 Use jumbo emoji in reaction pickers. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 9d9e6e2972 Ensure inner html is escaped when bolding.
Fixes #12033
2022-03-08 10:41:37 -04:00
Greyson Parrelli 56a8451d07 Add fallback static DNS resolver. 2022-03-08 10:41:37 -04:00
Alex Hart 2483a92975 Implement story error slates.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-08 10:41:37 -04:00
Alex Hart 34bbb98c96 Do not allow forwarding of unsupported content to stories. 2022-03-08 10:41:37 -04:00
Alex Hart 155bdf6164 Fix storyType selection issue in forwarder. 2022-03-08 10:41:37 -04:00
Rashad Sookram 5358ed6eff Open camera after granting permission. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 4f3bb39e5c Double-pulse message highlights. 2022-03-08 10:41:37 -04:00
clauz9 8a49534e2b Ensure bubble is highlighted after jumping.
Fixes #12017
2022-03-08 10:41:37 -04:00
Greyson Parrelli 2c3228d6df Fix issue where send button is invisible in voice note draft.
Fixes #12029
2022-03-08 10:41:37 -04:00
pauliancu97 c82d518d4d Make date view in voice note footer slightly wider.
Fixes #11728
2022-03-08 10:41:37 -04:00
Alex Hart 35cd36e9fe Implement support for 'allows replies' toggle. 2022-03-08 10:41:37 -04:00
Alex Hart ee176cbe3d Never send a link preview via MMS. 2022-03-08 10:41:37 -04:00
Rashad Sookram bd915cdd7f Fix crash from using a closed Cursor.
The call to setActive was causing the cursor held by the ViewModel to be
used, which hadn't been updated yet.
2022-03-08 10:41:37 -04:00
Rashad Sookram c27f5787fe Fix reaction overlay shade with gesture nav. 2022-03-08 10:41:37 -04:00
Alex Hart f6cdf459bb Update Views repo to pull view receipts instead of read receipts. 2022-03-08 10:41:37 -04:00
Alex Hart 4e851f90df Hide empty text until after we've tried to load stories. 2022-03-08 10:41:37 -04:00
Cody Henthorne 8d8a2a8eef Fix navigation crash in welcome fragment. 2022-03-08 10:41:37 -04:00
Cody Henthorne 277c17de83 Fix reactions vibrating in release notes channel. 2022-03-08 10:41:37 -04:00
Alex Hart d5fd424b95 Fix several over-the-wire story issues.
Co-authored-by: Rashad Sookram <rashad@signal.org>
2022-03-08 10:41:37 -04:00
Cody Henthorne e701e4bff0 Don't allow rate limit responses to end all group sends. 2022-03-08 10:41:37 -04:00
Alex Hart 0ddfb4456b Implement better stability while scrolling between pages. 2022-03-08 10:41:37 -04:00
Cody Henthorne 69dc31681d Apply server returned group patch instead of local only. 2022-03-08 10:41:37 -04:00
Alex Hart 2d7655a6bb Implement story ring support. 2022-03-08 10:41:37 -04:00
Greyson Parrelli fe088c39c7 Avoid querying conversation size twice. 2022-03-08 10:41:37 -04:00
Greyson Parrelli 731714d263 Remove unnecessary entry from spinner manifest. 2022-03-08 10:41:37 -04:00
Greyson Parrelli c165636180 Make perf builds profileable. 2022-03-08 10:41:37 -04:00
Jon Chambers 372dd13eba Accept both HTTP/413 and HTTP/429 as rate-limit responses. 2022-03-08 10:41:37 -04:00
Alex Hart b35ef0bb4d Send viewed receipts for stories. 2022-03-08 10:41:37 -04:00
Alex Hart bd58c91d2c Refactor viewer to prepare for enhanced video duration support. 2022-03-08 10:41:36 -04:00
Cody Henthorne 9a5fcdbe4d Fix emoji search showing in recent emoji bug. 2022-03-08 10:41:36 -04:00
Alex Hart 2452056cbe Fix issue where Story preview was not clickable. 2022-03-08 10:41:36 -04:00
Alex Hart bdf7e5d367 Prevent displaying my stories page when none are present in viewer. 2022-03-08 10:41:36 -04:00
Alex Hart aae683af41 Fix ConnectivityManager leak in MediaSelectionActivity. 2022-03-08 10:41:36 -04:00
Alex Hart 174cd860a0 Implement Stories feature behind flag.
Co-Authored-By: Greyson Parrelli <37311915+greyson-signal@users.noreply.github.com>
Co-Authored-By: Rashad Sookram <95182499+rashad-signal@users.noreply.github.com>
2022-03-08 10:41:36 -04:00
Alex Hart 765185952e Do not hook up check changed listener until after view state is restored. 2022-03-08 10:41:36 -04:00
Greyson Parrelli f4002850bb Add a ColumnTransformer system to Spinner. 2022-03-08 10:41:36 -04:00
Greyson Parrelli 935dd7de45 Remove E164s most places and prefer ServiceId more places.\ 2022-03-08 10:41:36 -04:00
Cody Henthorne d6b6884c69 Integrate calling with Android Telecom system. 2022-03-08 10:41:36 -04:00
Alex Hart 2ed39e4448 Add subscription cancellation step during account deletion. 2022-03-01 10:47:24 -05:00
Cody Henthorne 2de5ea43fb Add message type description to spinner as meta_type. 2022-03-01 10:47:23 -05:00
gram-signal 88d2d4d9c7 Switch from binary to streaming protos when using CDSHv1.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2022-03-01 10:47:23 -05:00
Cody Henthorne aff0c43b39 Prevent internal shake to report dialog from showing after locking. 2022-03-01 10:47:23 -05:00
Cody Henthorne bd18b731c8 Add metrics logging around get resumable upload url request. 2022-03-01 10:47:23 -05:00
Alex Hart 7b499f96be Implement donation receipts. 2022-03-01 10:47:23 -05:00
Alex Hart 63dab3f4b0 Add support for specific toasts when backup restoration cannot proceed.
Fixes #10918
2022-03-01 10:47:23 -05:00
Greyson Parrelli 80598814bd Remove unused PushServiceSocket method. 2022-03-01 10:47:23 -05:00
clauz9 b00abf1667 Fix internal-only crash when submitting a debuglog during registration. 2022-03-01 10:47:23 -05:00
Greyson Parrelli 9594be8fcf Add a 'Recent' tab to Spinner. 2022-03-01 10:47:23 -05:00
Greyson Parrelli acecd5f013 Update Spinner font styles. 2022-03-01 10:47:23 -05:00
Greyson Parrelli 2d1efb604c Add paging to Spinner browser. 2022-03-01 10:47:23 -05:00
Greyson Parrelli a84c971cbe Bump version to 5.32.15 2022-03-01 09:17:58 -05:00
Greyson Parrelli 7564ef4811 Updated language translations. 2022-03-01 09:17:23 -05:00
Greyson Parrelli 01e75120a7 Improve network reliability. 2022-03-01 09:17:22 -05:00
Cody Henthorne 1314b04994 Bump version to 5.32.14 2022-02-24 13:06:36 -05:00
Cody Henthorne 253cc5fec4 Updated language translations. 2022-02-24 12:55:47 -05:00
Greyson Parrelli c296a28a4a Update client-side max envelope size to 256KB to match server. 2022-02-24 12:52:06 -05:00
Greyson Parrelli ff95319559 Bump version to 5.32.13 2022-02-23 12:19:58 -05:00
Greyson Parrelli 3aa770ee08 Updated language translations. 2022-02-23 12:19:35 -05:00
Greyson Parrelli 653410cf27 Only generate a PNI key if necessary. 2022-02-23 12:15:18 -05:00
Greyson Parrelli ba08dbef5f Fix crash on conversation settings screen for longtime-unregistered users. 2022-02-23 12:05:44 -05:00
4524 changed files with 560138 additions and 333305 deletions
+1
View File
@@ -2,3 +2,4 @@ root = true
[*.kt] [*.kt]
indent_size = 2 indent_size = 2
twitter_compose_allowed_composition_locals=LocalExtendedColors
+23
View File
@@ -0,0 +1,23 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 7
issues:
exemptLabels:
- acknowledged
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been closed due to inactivity.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 1
+8 -4
View File
@@ -4,21 +4,25 @@ on:
pull_request: pull_request:
push: push:
branches: branches:
- 'master' - 'main'
- '4.**' - '4.**'
- '5.**' - '5.**'
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: set up JDK 11 - name: set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v3
with: with:
distribution: temurin
java-version: 11 java-version: 11
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
@@ -32,7 +36,7 @@ jobs:
- name: Archive reports for failed build - name: Archive reports for failed build
if: ${{ failure() }} if: ${{ failure() }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: reports name: reports
path: '*/build/reports' path: '*/build/reports'
+5 -2
View File
@@ -4,15 +4,18 @@ on:
schedule: schedule:
- cron: '0 5 * * *' - cron: '0 5 * * *'
permissions:
contents: read # to fetch code (actions/checkout)
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Build image - name: Build image
run: cd reproducible-builds && docker build -t signal-android . && cd .. run: cd reproducible-builds && docker build -t signal-android . && cd ..
- name: Test build - name: Test build
run: docker run --rm -v $(pwd):/project -w /project signal-android ./gradlew clean assembleRelease run: docker run --rm -v $(pwd):/project -w /project signal-android ./gradlew clean assemblePlayProdRelease
+1
View File
@@ -27,3 +27,4 @@ obj/
jni/libspeex/.deps/ jni/libspeex/.deps/
pkcs11.password pkcs11.password
dev.keystore dev.keystore
maps.key
+1 -4
View File
@@ -43,10 +43,7 @@
</JavaCodeStyleSettings> </JavaCodeStyleSettings>
<JetCodeStyleSettings> <JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS"> <option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value> <value />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option> </option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" /> <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+4 -4
View File
@@ -1,18 +1,18 @@
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import org.thoughtcrime.securesms.util.livedata.Store import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import org.thoughtcrime.securesms.util.rx.RxStore
#end #end
#parse("File Header.java") #parse("File Header.java")
class ${NAME}ViewModel : ViewModel() { class ${NAME}ViewModel : ViewModel() {
private val store = Store(${NAME}State()) private val store = RxStore(${NAME}State())
private val disposables = CompositeDisposable() private val disposables = CompositeDisposable()
val state: LiveData<${NAME}State> = store.stateLiveData val state: Flowable<${NAME}State> = store.stateFlowable
override fun onCleared() { override fun onCleared() {
disposables.clear() disposables.clear()
-9
View File
@@ -15,11 +15,6 @@ Truths which we believe to be self-evident:
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure. 1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
## Translations
Thanks to a dedicated community of volunteer translators, Signal is now available in more than one hundred languages. We use Transifex to manage our translation efforts, not GitHub. Any suggestions, corrections, or new translations should be submitted to the [Signal localization project for Android](https://www.transifex.com/signalapp/signal-android/).
## Issues ## Issues
### Useful bug reports ### Useful bug reports
@@ -75,10 +70,6 @@ There are several other ways to get involved:
* Redirect support questions to support@signal.org and the [Signal Support Center](https://support.signal.org/). * Redirect support questions to support@signal.org and the [Signal Support Center](https://support.signal.org/).
* Redirect non-bug discussions to the [community forum](https://community.signalusers.org). * Redirect non-bug discussions to the [community forum](https://community.signalusers.org).
* Improve documentation in the [wiki](https://github.com/signalapp/Signal-Android/wiki). * Improve documentation in the [wiki](https://github.com/signalapp/Signal-Android/wiki).
* Join the community of volunteer translators on Transifex:
* [Android](https://www.transifex.com/signalapp/signal-android/)
* [iOS](https://www.transifex.com/signalapp/signal-ios/)
* [Desktop](https://www.transifex.com/signalapp/signal-desktop/)
* Find and mark duplicate issues. * Find and mark duplicate issues.
* Try to reproduce issues and help with troubleshooting. * Try to reproduce issues and help with troubleshooting.
* Discover solutions to open issues and post any relevant findings. * Discover solutions to open issues and post any relevant findings.
+7 -12
View File
@@ -1,10 +1,10 @@
# Signal Android # Signal Android
Signal is a messaging app for simple private communication with friends. Signal is a simple, powerful, and secure messenger.
Signal uses your phone's data connection (WiFi/3G/4G) to communicate securely, optionally supports plain SMS/MMS to function as a unified messenger, and can also encrypt the stored messages on your phone. Signal uses your phone's data connection (WiFi/3G/4G/5G) to communicate securely. Millions of people use Signal every day for free and instantaneous communication anywhere in the world. Send and receive high-fidelity messages, participate in HD voice/video calls, and explore a growing set of new features that help you stay connected. Signals advanced privacy-preserving technology is always enabled, so you can focus on sharing the moments that matter with the people who matter to you.
Currently available on the Play store and [signal.org](https://signal.org/android/apk/). Currently available on the Play Store and [signal.org](https://signal.org/android/apk/).
<a href='https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height='80px'/></a> <a href='https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height='80px'/></a>
@@ -18,22 +18,17 @@ Want to live life on the bleeding edge and help out with testing?
You can subscribe to Signal Android Beta releases here: You can subscribe to Signal Android Beta releases here:
https://play.google.com/apps/testing/org.thoughtcrime.securesms https://play.google.com/apps/testing/org.thoughtcrime.securesms
If you're interested in a life of peace and tranquility, stick with the standard releases. If you're interested in a life of peace and tranquility, stick with the standard releases.
## Contributing Translations
Interested in helping to translate Signal? Contribute here:
https://www.transifex.com/projects/p/signal-android/
## Contributing Code ## Contributing Code
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/main/CONTRIBUTING.md), that might answer some of your questions.
For larger changes and feature ideas, we ask that you propose it on the [unofficial Community Forum](https://community.signalusers.org) for a high-level discussion with the wider community before implementation. For larger changes and feature ideas, we ask that you propose it on the [unofficial Community Forum](https://community.signalusers.org) for a high-level discussion with the wider community before implementation.
## Contributing Ideas ## Contributing Ideas
Have something you want to say about Open Whisper Systems projects or want to be part of the conversation? Get involved in the [community forum](https://community.signalusers.org). Have something you want to say about Signal projects or want to be part of the conversation? Get involved in the [community forum](https://community.signalusers.org).
Help Help
==== ====
@@ -59,7 +54,7 @@ The form and manner of this distribution makes it eligible for export under the
## License ## License
Copyright 2013-2021 Signal Copyright 2013-2022 Signal
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
+1 -1
View File
@@ -96,7 +96,7 @@ try:
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.") print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.") print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
except sqlite3.Error, e: except sqlite3.Error as e:
if connection: if connection:
connection.rollback() connection.rollback()
print("Error: %s" % e.args[0]) print("Error: %s" % e.args[0])
+178 -126
View File
@@ -1,47 +1,21 @@
apply plugin: 'com.android.application' import com.android.build.api.dsl.ManagedVirtualDevice
apply plugin: 'kotlin-android'
apply plugin: 'com.google.protobuf'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
apply from: 'translations.gradle'
apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'app.cash.exhaustive'
apply plugin: 'kotlin-parcelize'
repositories { plugins {
maven { id 'com.android.application'
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/" id 'kotlin-android'
content { id 'com.google.protobuf'
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*" id 'androidx.navigation.safeargs'
} id 'org.jlleitschuh.gradle.ktlint'
} id 'org.jetbrains.kotlin.android'
maven { id 'app.cash.exhaustive'
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/" id 'kotlin-parcelize'
content { id 'com.squareup.wire'
includeGroupByRegex "org\\.signal.*" id 'android-constants'
}
}
maven {
url "https://www.jitpack.io"
}
google()
mavenCentral()
mavenLocal()
maven {
url "https://dl.cloudsmith.io/qxAgwaeEE1vN8aLU/mobilecoin/mobilecoin/maven/"
}
jcenter {
content {
includeVersion "mobi.upod", "time-duration-picker", "1.1.3"
includeVersion "cn.carbswang.android", "NumberPickerView", "1.0.9"
includeVersion "com.takisoft.fix", "colorpicker", "0.9.1"
includeVersion "com.codewaves.stickyheadergrid", "stickyheadergrid", "0.9.4"
includeVersion "com.google.android", "flexbox", "0.3.0"
}
}
} }
apply from: 'translations.gradle'
apply from: 'static-ips.gradle'
protobuf { protobuf {
protoc { protoc {
artifact = 'com.google.protobuf:protoc:3.18.0' artifact = 'com.google.protobuf:protoc:3.18.0'
@@ -57,13 +31,23 @@ protobuf {
} }
} }
ktlint { wire {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507 kotlin {
version = "0.43.2" javaInterop = true
}
sourcePath {
srcDir 'src/main/protowire'
}
} }
def canonicalVersionCode = 1015 ktlint {
def canonicalVersionName = "5.32.12" // Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.47.1"
}
def canonicalVersionCode = 1222
def canonicalVersionName = "6.13.2"
def postFixSize = 100 def postFixSize = 100
def abiPostFix = ['universal' : 0, def abiPostFix = ['universal' : 0,
@@ -78,34 +62,40 @@ def selectableVariants = [
'nightlyProdSpinner', 'nightlyProdSpinner',
'nightlyProdPerf', 'nightlyProdPerf',
'nightlyProdRelease', 'nightlyProdRelease',
'nightlyStagingRelease',
'nightlyPnpPerf',
'nightlyPnpRelease',
'playProdDebug', 'playProdDebug',
'playProdSpinner', 'playProdSpinner',
'playProdPerf', 'playProdPerf',
'playProdInstrumentation',
'playProdRelease', 'playProdRelease',
'playStagingDebug', 'playStagingDebug',
'playStagingSpinner', 'playStagingSpinner',
'playStagingPerf', 'playStagingPerf',
'playStagingInstrumentation',
'playPnpDebug',
'playPnpSpinner',
'playStagingRelease', 'playStagingRelease',
'websiteProdSpinner', 'websiteProdSpinner',
'websiteProdRelease', 'websiteProdRelease',
] ]
android { android {
buildToolsVersion BUILD_TOOL_VERSION namespace 'org.thoughtcrime.securesms'
compileSdkVersion COMPILE_SDK
buildToolsVersion BUILD_TOOLS_VERSION
compileSdkVersion COMPILE_SDK_VERSION
flavorDimensions 'distribution', 'environment' flavorDimensions 'distribution', 'environment'
useLibrary 'org.apache.http.legacy' useLibrary 'org.apache.http.legacy'
testBuildType 'instrumentation'
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
freeCompilerArgs = ["-Xallow-result-return-type"] freeCompilerArgs = ["-Xallow-result-return-type"]
} }
dexOptions {
javaMaxHeapSize "4g"
}
signingConfigs { signingConfigs {
if (keystores.debug != null) { if (keystores.debug != null) {
debug { debug {
@@ -123,14 +113,19 @@ android {
unitTests { unitTests {
includeAndroidResources = true includeAndroidResources = true
} }
managedDevices {
devices {
pixel3api30 (ManagedVirtualDevice) {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
} }
lintOptions {
checkReleaseBuilds false
abortOnError true
baseline file("lint-baseline.xml")
disable "LintError"
}
sourceSets { sourceSets {
test { test {
@@ -147,29 +142,35 @@ android {
sourceCompatibility JAVA_VERSION sourceCompatibility JAVA_VERSION
targetCompatibility JAVA_VERSION targetCompatibility JAVA_VERSION
} }
packagingOptions { packagingOptions {
exclude 'LICENSE.txt' resources {
exclude 'LICENSE' excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/NOTICE', 'META-INF/proguard/androidx-annotations.pro', 'libsignal_jni.dylib', 'signal_jni.dll']
exclude 'NOTICE' }
exclude 'asm-license.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/proguard/androidx-annotations.pro'
} }
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.2'
}
defaultConfig { defaultConfig {
versionCode canonicalVersionCode * postFixSize versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName versionName canonicalVersionName
minSdkVersion MINIMUM_SDK minSdkVersion MIN_SDK_VERSION
targetSdkVersion TARGET_SDK targetSdkVersion TARGET_SDK_VERSION
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal"); project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders = [mapsKey:"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"]
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L" buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
@@ -178,33 +179,38 @@ android {
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"" buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.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_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_CDSH_URL", "\"https://cdsh.staging.signal.org\"" buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"" buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\"" buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\"}" buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\"}" buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}"
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"" buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443" buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String[]", "SIGNAL_SERVICE_IPS", service_ips
buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips
buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips
buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_ips
buildConfigField "String[]", "SIGNAL_CDS_IPS", cds_ips
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\"" buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDSH_PUBLIC_KEY", "\"2fe57da347cd62431528daac5fbb290730fff684afc4cfc2ed90995f58cb3b74\"" buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "CDSH_CODE_HASH", "\"ec58c0d7561de8d5657f3a4b22a635eaa305204e9359dcc80a99dfd0c5f1cbf2\"" buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\"" "\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " + "\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " + buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")" "\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] {" + "\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
"new org.thoughtcrime.securesms.KbsEnclave(\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
"\"fe7c1bfae98f9b073d220366ea31163ee82f6d04bead774f71ca8e5c40847bfe\", " +
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")" +
"}"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXQ==\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}' buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode" buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"" buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
buildConfigField "int[]", "MOBILE_COIN_BLACKLIST", "new int[]{98,963,53,850,7}"
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"" buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"" buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"" buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
@@ -228,7 +234,7 @@ android {
} }
} }
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
} }
@@ -253,6 +259,7 @@ android {
'proguard/proguard-retrofit.pro', 'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro', 'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro', 'proguard/proguard-klinker.pro',
'proguard/proguard-mobilecoin.pro',
'proguard/proguard-retrolambda.pro', 'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro', 'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro', 'proguard/proguard-ez-vcard.pro',
@@ -260,8 +267,21 @@ android {
testProguardFiles 'proguard/proguard-automation.pro', testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg' 'proguard/proguard.cfg'
manifestPlaceholders = [mapsKey:getMapsKey()]
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\"" buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\""
} }
instrumentation {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
applicationIdSuffix ".instrumentation"
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Instrumentation\""
}
spinner { spinner {
initWith debug initWith debug
isDefault false isDefault false
@@ -269,11 +289,13 @@ android {
matchingFallbacks = ['debug'] matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Spinner\"" buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Spinner\""
} }
release { release {
minifyEnabled true minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles proguardFiles = buildTypes.debug.proguardFiles
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\"" buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\""
} }
perf { perf {
initWith debug initWith debug
isDefault false isDefault false
@@ -330,24 +352,39 @@ android {
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-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_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\"" buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"" buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "CDS_MRENCLAVE", "\"c98e00a4e3ff977a56afefe7362a27e4961e4f19e211febfbb19b897e6b80b15\"" buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " + "\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " + "\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\")" buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] {" + "\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
"new org.thoughtcrime.securesms.KbsEnclave(\"823a3b2c037ff0cbe305cc48928cfcc97c9ed4a8ca6d49af6f7d6981fb60a4e9\", " + "\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
"\"16b94ac6d2b7f7b9d72928f36d798dbb35ed32e7bb14c42b4301ad0344b46f29\", " +
"\"a3baab19ef6ce6f34ab9ebb25ba722725ae44a8872dc0ff08ad6d83a9489de87\")" +
"}"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"" buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXQ==\"" buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"" buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"" buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"" buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"" buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\""
} }
pnp {
dimension 'environment'
initWith staging
applicationIdSuffix ".pnp"
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\""
}
}
lint {
abortOnError true
baseline file('lint-baseline.xml')
checkReleaseBuilds false
disable 'LintError'
} }
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
@@ -394,10 +431,11 @@ dependencies {
implementation (libs.androidx.appcompat) { implementation (libs.androidx.appcompat) {
version { version {
strictly '1.2.0' strictly '1.5.1'
} }
} }
implementation libs.androidx.window implementation libs.androidx.window.window
implementation libs.androidx.window.java
implementation libs.androidx.recyclerview implementation libs.androidx.recyclerview
implementation libs.material.material implementation libs.material.material
implementation libs.androidx.legacy.support implementation libs.androidx.legacy.support
@@ -410,7 +448,9 @@ dependencies {
implementation libs.androidx.multidex implementation libs.androidx.multidex
implementation libs.androidx.navigation.fragment.ktx implementation libs.androidx.navigation.fragment.ktx
implementation libs.androidx.navigation.ui.ktx implementation libs.androidx.navigation.ui.ktx
implementation libs.androidx.lifecycle.extensions implementation libs.androidx.lifecycle.viewmodel.ktx
implementation libs.androidx.lifecycle.livedata.ktx
implementation libs.androidx.lifecycle.process
implementation libs.androidx.lifecycle.viewmodel.savedstate implementation libs.androidx.lifecycle.viewmodel.savedstate
implementation libs.androidx.lifecycle.common.java8 implementation libs.androidx.lifecycle.common.java8
implementation libs.androidx.lifecycle.reactivestreams.ktx implementation libs.androidx.lifecycle.reactivestreams.ktx
@@ -445,8 +485,13 @@ dependencies {
implementation project(':device-transfer') implementation project(':device-transfer')
implementation project(':image-editor') implementation project(':image-editor')
implementation project(':donations') implementation project(':donations')
implementation project(':contacts')
implementation project(':qr')
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation libs.signal.client.android implementation libs.libsignal.android
implementation libs.google.protobuf.javalite implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) { implementation(libs.mobilecoin) {
@@ -465,22 +510,17 @@ dependencies {
implementation libs.emilsjolander.stickylistheaders implementation libs.emilsjolander.stickylistheaders
implementation libs.jpardogo.materialtabstrip implementation libs.jpardogo.materialtabstrip
implementation libs.apache.httpclient.android implementation libs.apache.httpclient.android
implementation libs.photoview
implementation libs.glide.glide implementation libs.glide.glide
implementation libs.roundedimageview implementation libs.roundedimageview
implementation libs.materialish.progress implementation libs.materialish.progress
implementation libs.greenrobot.eventbus implementation libs.greenrobot.eventbus
implementation libs.waitingdots implementation libs.waitingdots
implementation libs.floatingactionbutton
implementation libs.google.zxing.android.integration implementation libs.google.zxing.android.integration
implementation libs.time.duration.picker
implementation libs.google.zxing.core implementation libs.google.zxing.core
implementation libs.google.flexbox
implementation (libs.subsampling.scale.image.view) { implementation (libs.subsampling.scale.image.view) {
exclude group: 'com.android.support', module: 'support-annotations' exclude group: 'com.android.support', module: 'support-annotations'
} }
implementation (libs.numberpickerview) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation (libs.android.tooltips) { implementation (libs.android.tooltips) {
exclude group: 'com.android.support', module: 'appcompat-v7' exclude group: 'com.android.support', module: 'appcompat-v7'
} }
@@ -489,16 +529,9 @@ dependencies {
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection' exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
} }
implementation libs.stream implementation libs.stream
implementation (libs.colorpicker) {
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
implementation libs.lottie implementation libs.lottie
implementation libs.stickyheadergrid
implementation libs.circular.progress.button
implementation libs.signal.android.database.sqlcipher implementation libs.signal.android.database.sqlcipher
implementation libs.androidx.sqlite implementation libs.androidx.sqlite
@@ -514,10 +547,7 @@ dependencies {
testImplementation testLibs.junit.junit testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core testImplementation testLibs.assertj.core
testImplementation testLibs.mockito.core testImplementation testLibs.mockito.core
testImplementation testLibs.powermock.api.mockito testImplementation testLibs.mockito.kotlin
testImplementation testLibs.powermock.module.junit4.core
testImplementation testLibs.powermock.module.junit4.rule
testImplementation testLibs.powermock.classloading.xstream
testImplementation testLibs.androidx.test.core testImplementation testLibs.androidx.test.core
testImplementation (testLibs.robolectric.robolectric) { testImplementation (testLibs.robolectric.robolectric) {
@@ -528,11 +558,22 @@ dependencies {
force = true force = true
} }
testImplementation testLibs.hamcrest.hamcrest testImplementation testLibs.hamcrest.hamcrest
testImplementation testLibs.mockk
testImplementation(testFixtures(project(":libsignal-service"))) testImplementation(testFixtures(project(":libsignal-service")))
androidTestImplementation testLibs.androidx.test.ext.junit androidTestImplementation testLibs.androidx.test.ext.junit
androidTestImplementation testLibs.espresso.core androidTestImplementation testLibs.espresso.core
androidTestImplementation testLibs.androidx.test.core
androidTestImplementation testLibs.androidx.test.core.ktx
androidTestImplementation testLibs.androidx.test.ext.junit.ktx
androidTestImplementation testLibs.mockito.android
androidTestImplementation testLibs.mockito.kotlin
androidTestImplementation testLibs.square.okhttp.mockserver
instrumentationImplementation (libs.androidx.fragment.testing) {
exclude group: 'androidx.test', module: 'core'
}
testImplementation testLibs.espresso.core testImplementation testLibs.espresso.core
@@ -544,7 +585,10 @@ dependencies {
implementation libs.rxjava3.rxkotlin implementation libs.rxjava3.rxkotlin
implementation libs.rxdogtag implementation libs.rxdogtag
androidTestUtil 'androidx.test:orchestrator:1.4.0' androidTestUtil testLibs.androidx.test.orchestrator
implementation project(':core-ui')
ktlintRuleset libs.ktlint.twitter.compose
} }
def getLastCommitTimestamp() { def getLastCommitTimestamp() {
@@ -553,7 +597,7 @@ def getLastCommitTimestamp() {
} }
new ByteArrayOutputStream().withStream { os -> new ByteArrayOutputStream().withStream { os ->
def result = exec { exec {
executable = 'git' executable = 'git'
args = ['log', '-1', '--pretty=format:%ct'] args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os standardOutput = os
@@ -565,20 +609,20 @@ def getLastCommitTimestamp() {
def getGitHash() { def getGitHash() {
if (!(new File('.git').exists())) { if (!(new File('.git').exists())) {
return "abcd1234" throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
} }
def stdout = new ByteArrayOutputStream() def stdout = new ByteArrayOutputStream()
exec { exec {
commandLine 'git', 'rev-parse', '--short', 'HEAD' commandLine 'git', 'rev-parse', 'HEAD'
standardOutput = stdout standardOutput = stdout
} }
return stdout.toString().trim() return stdout.toString().trim().substring(0, 12)
} }
def getCurrentGitTag() { def getCurrentGitTag() {
if (!(new File('.git').exists())) { if (!(new File('.git').exists())) {
return '' throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
} }
def stdout = new ByteArrayOutputStream() def stdout = new ByteArrayOutputStream()
@@ -612,14 +656,22 @@ def loadKeystoreProperties(filename) {
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
return keystoreProperties; return keystoreProperties
} else { } else {
return null; return null
} }
} }
def getDateSuffix() { static def getDateSuffix() {
def date = new Date() def date = new Date()
def formattedDate = date.format('yyyy-MM-dd-HH:mm') def formattedDate = date.format('yyyy-MM-dd-HH:mm')
return formattedDate return formattedDate
} }
def getMapsKey() {
def mapKey = file("${project.rootDir}/maps.key")
if (mapKey.exists()) {
return mapKey.readLines()[0]
}
return "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
+2
View File
@@ -25,6 +25,7 @@
<issue id="VectorRaster" severity="error" /> <issue id="VectorRaster" severity="error" />
<issue id="ButtonOrder" severity="error" /> <issue id="ButtonOrder" severity="error" />
<issue id="ExtraTranslation" severity="warning" /> <issue id="ExtraTranslation" severity="warning" />
<issue id="UnspecifiedImmutableFlag" severity="error" />
<!-- Custom lints --> <!-- Custom lints -->
<issue id="LogNotSignal" severity="error" /> <issue id="LogNotSignal" severity="error" />
@@ -41,4 +42,5 @@
<ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" /> <ignore path="*/org/thoughtcrime/securesms/jobs/StickerPackDownloadJob.java" />
</issue> </issue>
<issue id="OptionalUsedAsFieldOrParameterType" severity="ignore" />
</lint> </lint>
+2
View File
@@ -0,0 +1,2 @@
# MobileCoin
-keep class com.mobilecoin.** { *; }
+1
View File
@@ -2,6 +2,7 @@
-dontobfuscate -dontobfuscate
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; } -keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.thoughtcrime.securesms.** { *; } -keep class org.thoughtcrime.securesms.** { *; }
-keepclassmembers class ** { -keepclassmembers class ** {
public void onEvent*(**); public void onEvent*(**);
@@ -0,0 +1,15 @@
package org.thoughtcrime.securesms
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
/**
* Application context for running instrumentation tests (aka androidTests).
*/
class SignalInstrumentationApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() {
val default = ApplicationDependencyProvider(this)
ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
}
}
@@ -0,0 +1,380 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber
import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.MockProvider
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.testing.connectionFailure
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.testing.timeout
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ChangeNumberViewModelTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
private lateinit var kbsRepository: KbsRepository
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
kbsRepository = mock()
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application),
kbsRepository = kbsRepository
)
viewModel.setNewCountry(1)
viewModel.setNewNationalNumber("5555550102")
}
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
/**
* If we encounter a server error, this means the server ack our request and rejected it. In this
* case we know the change *did not* take on the server and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenServerFailedApiCall() {
// GIVEN
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { MockResponse().failure(500) }
)
// WHEN
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs true
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our old details, so we can know the change *did not* take on the server
* and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToServer() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { MockResponse().connectionFailure() },
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) }
)
// WHEN
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs false
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our new details, so we can know the change *did* take on the server
* and need to keep the app in a locked state. The test then uses the ChangeNumberLockActivity to unlock
* and apply the pending state after confirming the change on the server.
*/
@Test
@FlakyTest
@Ignore("Test sometimes requires manual intervention to continue.")
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToClient() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
val newPni = ServiceId.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().timeout()
},
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, newPni, "+15555550102")) },
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs true
SignalStore.misc().pendingChangeNumberMetadata.assertIsNotNull()
// WHEN AGAIN Processing lock
val scenario = harness.launchActivity<ChangeNumberLockActivity>()
scenario.onActivity {}
ThreadUtil.sleep(500)
// THEN AGAIN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else if (changeNumberRequest.deviceMessages.size == 1) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2, 3)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Get("/v2/keys/$aci/3") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore = SignalStore.account().pniPreKeys
Recipient.self().requireE164() assertIs "+15555550102"
Recipient.self().requirePni() assertIs newPni
SignalStore.account().pniRegistrationId assertIs changeNumberRequest.pniRegistrationIds["1"]!!
SignalStore.account().pniIdentityKey.publicKey assertIs changeNumberRequest.pniIdentityKey
pniMetadataStore.activeSignedPreKeyId assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.keyId
val activeSignedPreKey: SignedPreKeyRecord = pniProtocolStore.loadSignedPreKey(pniMetadataStore.activeSignedPreKeyId)
activeSignedPreKey.keyPair.publicKey assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.publicKey
activeSignedPreKey.signature assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.signature
setPreKeysRequest.signedPreKey.publicKey assertIs activeSignedPreKey.keyPair.publicKey
setPreKeysRequest.preKeys assertIsSize 100
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
}
@@ -0,0 +1,154 @@
package org.thoughtcrime.securesms.conversation
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import java.util.Optional
/**
* Helper test for rendering conversation items for preview.
*/
@RunWith(AndroidJUnit4::class)
@Ignore("For testing/previewing manually, no assertions")
class ConversationItemPreviewer {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
@Test
fun testShowLongName() {
val other: Recipient = Recipient.resolved(harness.others.first())
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Seef", "$$$"))
insertFailedMediaMessage(other = other, attachmentCount = 1)
insertFailedMediaMessage(other = other, attachmentCount = 2)
insertFailedMediaMessage(other = other, body = "Test", attachmentCount = 1)
// insertFailedOutgoingMediaMessage(other = other, body = "Test", attachmentCount = 1)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
// insertMediaMessage(other = other)
val scenario: ActivityScenario<ConversationActivity> = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) }
scenario.onActivity {
}
// Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately
// ThreadUtil.sleep(45000)
}
private fun insertMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
attachment()
}
val message = IncomingMediaMessage(
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = System.currentTimeMillis(),
receivedTimeMillis = System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
ThreadUtil.sleep(1)
}
private fun insertFailedMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
attachment()
}
val message = IncomingMediaMessage(
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = System.currentTimeMillis(),
receivedTimeMillis = System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) {
SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert.messageId)
// } else {
// SignalDatabase.attachments.setTransferState(insert.messageId, attachment, TRANSFER_PROGRESS_STARTED)
// }
}
ThreadUtil.sleep(1)
}
private fun insertFailedOutgoingMediaMessage(other: Recipient, body: String? = null, attachmentCount: Int = 1) {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
attachment()
}
val message = OutgoingMessage(
recipient = other,
body = body,
attachments = PointerAttachment.forPointers(Optional.of(attachments)),
timestamp = System.currentTimeMillis(),
isSecure = true
)
val insert = SignalDatabase.messages.insertMessageOutbox(
message,
SignalDatabase.threads.getOrCreateThreadIdFor(other),
false,
null
)
SignalDatabase.attachments.getAttachmentsForMessage(insert).forEachIndexed { index, attachment ->
SignalDatabase.attachments.setTransferProgressPermanentFailure(attachment.attachmentId, insert)
}
ThreadUtil.sleep(1)
}
private fun attachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
SignalServiceAttachmentRemoteId.from(""),
"image/webp",
null,
Optional.empty(),
Optional.empty(),
1024,
1024,
Optional.empty(),
Optional.of("/not-there.jpg"),
false,
false,
false,
Optional.empty(),
Optional.empty(),
System.currentTimeMillis()
)
}
}
@@ -0,0 +1,75 @@
package org.thoughtcrime.securesms.conversation
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.testing.SignalActivityRule
/**
* Android test to help show SNC dialog quickly with custom data to make sure it displays properly.
*/
@Ignore("For testing/previewing manually, no assertions")
@RunWith(AndroidJUnit4::class)
class SafetyNumberChangeDialogPreviewer {
@get:Rule val harness = SignalActivityRule(othersCount = 10)
@Test
fun testShowLongName() {
val other: Recipient = Recipient.resolved(harness.others.first())
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("Super really long name like omg", "But seriously it's long like really really long"))
harness.setVerified(other, IdentityTable.VerifiedStatus.VERIFIED)
harness.changeIdentityKey(other)
val scenario: ActivityScenario<ConversationActivity> = harness.launchActivity { putExtra("recipient_id", other.id.serialize()) }
scenario.onActivity {
SafetyNumberBottomSheet.forRecipientId(other.id).show(it.supportFragmentManager)
}
// Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately
// ThreadUtil.sleep(15000)
}
@Test
fun testShowLargeSheet() {
SignalDatabase.distributionLists.setPrivacyMode(DistributionListId.MY_STORY, DistributionListPrivacyMode.ONLY_WITH)
val othersRecipients = harness.others.map { Recipient.resolved(it) }
othersRecipients.forEach { other ->
SignalDatabase.recipients.setProfileName(other.id, ProfileName.fromParts("My", "Name"))
harness.setVerified(other, IdentityTable.VerifiedStatus.DEFAULT)
harness.changeIdentityKey(other)
SignalDatabase.distributionLists.addMemberToList(DistributionListId.MY_STORY, DistributionListPrivacyMode.ONLY_WITH, other.id)
}
val myStoryRecipientId = SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!!
val scenario: ActivityScenario<ConversationActivity> = harness.launchActivity { putExtra("recipient_id", harness.others.first().serialize()) }
scenario.onActivity { conversationActivity ->
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(
identityRecords = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(othersRecipients).identityRecords,
destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true))
)
.show(conversationActivity.supportFragmentManager)
}
// Uncomment to make dialog stay on screen, otherwise will show/dismiss immediately
// ThreadUtil.sleep( 30000)
}
}
@@ -0,0 +1,106 @@
package org.thoughtcrime.securesms.database
import android.net.Uri
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
@RunWith(AndroidJUnit4::class)
class AttachmentTableTest {
@Before
fun setUp() {
SignalDatabase.attachments.deleteAllAttachments()
}
@Test
fun givenABlob_whenIInsert2AttachmentsForPreUpload_thenIExpectDistinctIdsButSameFileName() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
val highQualityProperties = createHighQualityTransformProperties()
val highQualityImage = createAttachment(1, blob, highQualityProperties)
val attachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
val attachment2 = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
assertNotEquals(attachment2.attachmentId, attachment.attachmentId)
assertEquals(attachment2.fileName, attachment.fileName)
}
@Test
fun givenABlobAndDifferentTransformQuality_whenIInsert2AttachmentsForPreUpload_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
val highQualityProperties = createHighQualityTransformProperties()
val highQualityImage = createAttachment(1, blob, highQualityProperties)
val lowQualityImage = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val attachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
val attachment2 = SignalDatabase.attachments.insertAttachmentForPreUpload(lowQualityImage)
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
false
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3)),
false
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
assertNotEquals(attachment1Info, attachment2Info)
}
@Test
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
val highQualityProperties = createHighQualityTransformProperties()
val highQualityImage = createAttachment(1, blob, highQualityProperties)
val attachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
val attachment2 = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityImage)
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
true
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3, 4)),
true
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
assertNotEquals(attachment1Info, attachment2Info)
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = transformProperties
)
}
private fun createHighQualityTransformProperties(): AttachmentTable.TransformProperties {
return AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH)
}
private fun createMediaStream(byteArray: ByteArray): MediaStream {
return MediaStream(byteArray.inputStream(), MediaUtil.IMAGE_JPEG, 2, 2)
}
}
@@ -0,0 +1,98 @@
package org.thoughtcrime.securesms.database
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import java.util.UUID
class DistributionListTablesTest {
private lateinit var distributionDatabase: DistributionListTables
@Before
fun setup() {
distributionDatabase = SignalDatabase.distributionLists
}
@Test
fun createList_whenNoConflict_insertSuccessfully() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
}
@Test
fun createList_whenNameConflict_failToInsert() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
val id2: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNull(id2)
}
@Test
fun getList_returnCorrectList() {
createRecipients(3)
val members: List<RecipientId> = recipientList(1, 2, 3)
val id: DistributionListId? = distributionDatabase.createList("test", members)
Assert.assertNotNull(id)
val record: DistributionListRecord? = distributionDatabase.getList(id!!)
Assert.assertNotNull(record)
Assert.assertEquals(id, record!!.id)
Assert.assertEquals("test", record.name)
Assert.assertEquals(members, record.members)
}
@Test
fun getMembers_returnsCorrectMembers() {
createRecipients(3)
val members: List<RecipientId> = recipientList(1, 2, 3)
val id: DistributionListId? = distributionDatabase.createList("test", members)
Assert.assertNotNull(id)
val foundMembers: List<RecipientId> = distributionDatabase.getMembers(id!!)
Assert.assertEquals(members, foundMembers)
}
@Test
fun givenStoryExists_getStoryType_returnsStoryWithReplies() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
val storyType = distributionDatabase.getStoryType(id!!)
Assert.assertEquals(StoryType.STORY_WITH_REPLIES, storyType)
}
@Test
fun givenStoryExistsAndMarkedNoReplies_getStoryType_returnsStoryWithoutReplies() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
distributionDatabase.setAllowsReplies(id!!, false)
val storyType = distributionDatabase.getStoryType(id)
Assert.assertEquals(StoryType.STORY_WITHOUT_REPLIES, storyType)
}
@Test(expected = IllegalStateException::class)
fun givenStoryDoesNotExist_getStoryType_throwsIllegalStateException() {
distributionDatabase.getStoryType(DistributionListId.from(12))
Assert.fail("Expected an assertion error.")
}
private fun createRecipients(count: Int) {
for (i in 0 until count) {
SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))
}
}
private fun recipientList(vararg ids: Long): List<RecipientId> {
return ids.map { RecipientId.from(it) }
}
}
@@ -0,0 +1,285 @@
package org.thoughtcrime.securesms.database
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.signal.core.util.delete
import org.signal.core.util.readToList
import org.signal.core.util.requireLong
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import java.security.SecureRandom
import kotlin.random.Random
class GroupTableTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var groupTable: GroupTable
@Before
fun setUp() {
groupTable = SignalDatabase.groups
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
}
@Test
fun whenICreateGroupV2_thenIExpectMemberRowsPopulated() {
val groupId = insertPushGroup()
//language=sql
val members: List<RecipientId> = groupTable.writableDatabase.query(
"""
SELECT ${GroupTable.MembershipTable.RECIPIENT_ID}
FROM ${GroupTable.MembershipTable.TABLE_NAME}
WHERE ${GroupTable.MembershipTable.GROUP_ID} = "${groupId.serialize()}"
""".trimIndent()
).readToList {
RecipientId.from(it.requireLong(GroupTable.RECIPIENT_ID))
}
assertEquals(2, members.size)
}
@Test
fun givenAGroupV2_whenIGetGroupsContainingMember_thenIExpectGroup() {
val groupId = insertPushGroup()
insertThread(groupId)
val groups = groupTable.getGroupsContainingMember(harness.others[0], false)
assertEquals(1, groups.size)
assertEquals(groupId, groups[0].id)
}
@Test
fun givenAnMmsGroup_whenIGetMembers_thenIExpectAllMembers() {
val groupId = insertMmsGroup()
val groups = groupTable.getGroupMemberIds(groupId, GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)
assertEquals(2, groups.size)
}
@Test
fun givenGroups_whenIQueryGroupsByMembership_thenIExpectBothGroups() {
insertPushGroup()
insertMmsGroup(members = listOf(harness.others[1]))
val groups = groupTable.queryGroupsByMembership(
setOf(harness.self.id, harness.others[1]),
includeInactive = false,
excludeV1 = false,
excludeMms = false
)
assertEquals(2, groups.cursor?.count)
}
@Test
fun givenGroups_whenIGetGroups_thenIExpectBothGroups() {
insertPushGroup()
insertMmsGroup(members = listOf(harness.others[1]))
val groups = groupTable.getGroups()
assertEquals(2, groups.cursor?.count)
}
@Test
fun givenAGroup_whenIGetGroup_thenIExpectGroup() {
val v2Group = insertPushGroup()
insertThread(v2Group)
val groupRecord = groupTable.getGroup(v2Group).get()
assertEquals(setOf(harness.self.id, harness.others[0]), groupRecord.members.toSet())
}
@Test
fun givenAGroupAndARemap_whenIGetGroup_thenIExpectRemap() {
val v2Group = insertPushGroup()
insertThread(v2Group)
groupTable.writableDatabase.withinTransaction {
RemappedRecords.getInstance().addRecipient(harness.others[0], harness.others[1])
}
val groupRecord = groupTable.getGroup(v2Group).get()
assertEquals(groupRecord.members.toSet(), setOf(harness.self.id, harness.others[1]))
}
@Test
fun givenAGroupAndMember_whenIIsCurrentMember_thenIExpectTrue() {
val v2Group = insertPushGroup()
val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[0])
assertTrue(actual)
}
@Test
fun givenAGroupAndMember_whenIRemove_thenIExpectNotAMember() {
val v2Group = insertPushGroup()
groupTable.remove(v2Group, harness.others[0])
val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[0])
assertFalse(actual)
}
@Test
fun givenAGroupAndNonMember_whenIIsCurrentMember_thenIExpectFalse() {
val v2Group = insertPushGroup()
val actual = groupTable.isCurrentMember(v2Group.requirePush(), harness.others[1])
assertFalse(actual)
}
@Test
fun givenAGroup_whenIUpdateMembers_thenIExpectUpdatedMembers() {
val v2Group = insertPushGroup()
groupTable.updateMembers(v2Group, listOf(harness.self.id, harness.others[1]))
val groupRecord = groupTable.getGroup(v2Group)
assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet())
}
@Test
fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val members: List<RecipientId> = listOf(harness.self.id, harness.others[0])
val other = insertMmsGroup(members + listOf(harness.others[1]))
val mmsGroup = insertMmsGroup(members)
val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet())
assertNotEquals(other, actual)
assertEquals(mmsGroup, actual)
}
@Test
fun givenMultipleMmsGroups_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1])
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2])
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMultipleMmsGroupsWithDifferentMemberOrders_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1], harness.others[2]).shuffled()
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2], harness.others[3]).shuffled()
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.shuffled().toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.shuffled().toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMmsGroupWithOneMember_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val groupMembers: List<RecipientId> = listOf(harness.self.id)
val group: GroupId = insertMmsGroup(groupMembers)
val groupResult: GroupId = groupTable.getOrCreateMmsGroupForMembers(groupMembers.toSet())
assertEquals(group, groupResult)
}
@Test
fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() {
val g1 = insertPushGroup(listOf())
val g2 = insertPushGroup(listOf())
val gr1 = groupTable.getGroup(g1)
val gr2 = groupTable.getGroup(g2)
assertEquals(g1, gr1.get().id)
assertEquals(g2, gr2.get().id)
}
@Test
fun givenASharedActiveGroupWithoutAThread_whenISearchForRecipientsWithGroupsInCommon_thenIExpectThatGroup() {
val groupInCommon = insertPushGroup()
val expected = Recipient.resolved(harness.others[0])
SignalDatabase.recipients.setProfileSharing(expected.id, false)
SignalDatabase.recipients.queryGroupMemberContacts("Buddy")!!.use {
assertTrue(it.moveToFirst())
assertEquals(1, it.count)
assertEquals(expected.id.toLong(), it.requireLong(RecipientTable.ID))
}
val groups = groupTable.getPushGroupsContainingMember(expected.id)
assertEquals(1, groups.size)
assertEquals(groups[0].id, groupInCommon)
}
private fun insertThread(groupId: GroupId): Long {
val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get()
return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient))
}
private fun insertMmsGroup(members: List<RecipientId> = listOf(harness.self.id, harness.others[0])): GroupId {
val id = GroupId.createMms(SecureRandom())
groupTable.create(
id,
null,
members.apply {
println("Creating a group with ${members.size} members")
}
)
return id
}
private fun insertPushGroup(
members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setUuid(Recipient.resolved(harness.others[0]).requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build()
)
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members)
.setRevision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)
}
}
@@ -0,0 +1,188 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MessageTableTest_gifts {
private lateinit var mms: MessageTable
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
private lateinit var recipients: List<RecipientId>
@Before
fun setUp() {
mms = SignalDatabase.messages
mms.deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
}
@Test
fun givenNoSentGifts_whenISetOutgoingGiftsRevealed_thenIExpectEmptyList() {
val result = mms.setOutgoingGiftsRevealed(listOf(1))
assertTrue(result.isEmpty())
}
@Test
fun givenSentGift_whenISetOutgoingGiftsRevealed_thenIExpectNonEmptyListContainingThatGift() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
assertTrue(result.isNotEmpty())
assertEquals(messageId, result.first().messageId.id)
}
@Test
fun givenViewedSentGift_whenISetOutgoingGiftsRevealed_thenIExpectEmptyList() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
mms.setOutgoingGiftsRevealed(listOf(messageId))
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
assertTrue(result.isEmpty())
}
@Test
fun givenMultipleSentGift_whenISetOutgoingGiftsRevealedForOne_thenIExpectNonEmptyListContainingThatGift() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
assertEquals(1, result.size)
assertEquals(messageId, result.first().messageId.id)
}
@Test
fun givenMultipleSentGift_whenISetOutgoingGiftsRevealedForBoth_thenIExpectNonEmptyListContainingThoseGifts() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
assertEquals(listOf(messageId, messageId2), result.map { it.messageId.id })
}
@Test
fun givenMultipleSentGiftAndNonGift_whenISetOutgoingGiftsRevealedForBothGifts_thenIExpectNonEmptyListContainingJustThoseGifts() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = null
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
assertEquals(listOf(messageId, messageId2), result.map { it.messageId.id })
}
@Test
fun givenMultipleSentGiftAndNonGift_whenISetOutgoingGiftsRevealedForAllThree_thenIExpectNonEmptyListContainingJustThoseGifts() {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = null
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2, messageId3))
assertEquals(listOf(messageId, messageId2), result.map { it.messageId.id })
}
@Test
fun givenMultipleSentGiftAndNonGift_whenISetOutgoingGiftsRevealedForNonGift_thenIExpectEmptyList() {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = null
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId3))
assertTrue(result.isEmpty())
}
}
@@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.database
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional
/**
* Helper methods for inserting an MMS message into the MMS table.
*/
object MmsHelper {
fun insert(
recipient: Recipient = Recipient.UNKNOWN,
body: String = "body",
sentTimeMillis: Long = System.currentTimeMillis(),
subscriptionId: Int = -1,
expiresIn: Long = 0,
viewOnce: Boolean = false,
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipient, distributionType),
storyType: StoryType = StoryType.NONE,
parentStoryId: ParentStoryId? = null,
isStoryReaction: Boolean = false,
giftBadge: GiftBadge? = null,
secure: Boolean = true
): Long {
val message = OutgoingMessage(
recipient = recipient,
body = body,
timestamp = sentTimeMillis,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
viewOnce = viewOnce,
distributionType = distributionType,
storyType = storyType,
parentStoryId = parentStoryId,
isStoryReaction = isStoryReaction,
giftBadge = giftBadge,
isSecure = secure
)
return insert(
message = message,
threadId = threadId
)
}
fun insert(
message: OutgoingMessage,
threadId: Long
): Long {
return SignalDatabase.messages.insertMessageOutbox(message, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null)
}
fun insert(
message: IncomingMediaMessage,
threadId: Long
): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
}
}
@@ -0,0 +1,380 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
import java.util.concurrent.TimeUnit
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MmsTableTest_stories {
private lateinit var mms: MessageTable
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
private lateinit var myStory: Recipient
private lateinit var recipients: List<RecipientId>
private lateinit var releaseChannelRecipient: Recipient
@Before
fun setUp() {
mms = SignalDatabase.messages
mms.deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
}
@Test
fun givenNoStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectAnEmptyList() {
// WHEN
val result = mms.getOrderedStoryRecipientsAndIds(false)
// THEN
assertEquals(0, result.size)
}
@Test
fun givenOneOutgoingAndOneIncomingStory_whenIGetOrderedStoryRecipientsAndIds_thenIExpectIncomingThenOutgoing() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val sender = recipients[0]
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 1,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = threadId
)
MmsHelper.insert(
IncomingMediaMessage(
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
)
// WHEN
val result = mms.getOrderedStoryRecipientsAndIds(false)
// THEN
assertEquals(listOf(sender.toLong(), myStory.id.toLong()), result.map { it.recipientId.toLong() })
}
@Test
fun givenAStory_whenISetIncomingStoryMessageViewed_thenIExpectASetReceiptTimestamp() {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
).get().messageId
val messageBeforeMark = SignalDatabase.messages.getMessageRecord(messageId)
assertFalse(messageBeforeMark.incomingStoryViewedAtTimestamp > 0)
// WHEN
SignalDatabase.messages.setIncomingMessageViewed(messageId)
// THEN
val messageAfterMark = SignalDatabase.messages.getMessageRecord(messageId)
assertTrue(messageAfterMark.incomingStoryViewedAtTimestamp > 0)
}
@Ignore
@Test
fun given5ViewedStories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectLatestViewedFirst() {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
).get().messageId
}
val randomizedOrderedIds = messageIds.shuffled()
randomizedOrderedIds.forEach {
SignalDatabase.messages.setIncomingMessageViewed(it)
Thread.sleep(5)
}
// WHEN
val result = SignalDatabase.messages.getOrderedStoryRecipientsAndIds(false)
val resultOrderedIds = result.map { it.messageId }
// THEN
assertEquals(randomizedOrderedIds.reversed(), resultOrderedIds)
}
@Test
fun given15Stories_whenIGetOrderedStoryRecipientsAndIds_thenIExpectUnviewedThenInterspersedViewedAndSelfSendsAllDescending() {
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
).get().messageId
}
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
).get().messageId
}
val interspersedIds: List<Long> = (0 until 10).map {
Thread.sleep(5)
if (it % 2 == 0) {
SignalDatabase.messages.setIncomingMessageViewed(viewedIds[it / 2])
viewedIds[it / 2]
} else {
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = System.currentTimeMillis(),
storyType = StoryType.STORY_WITH_REPLIES,
threadId = myStoryThread
)
}
}
val result = SignalDatabase.messages.getOrderedStoryRecipientsAndIds(false)
val resultOrderedIds = result.map { it.messageId }
assertEquals(unviewedIds.reversed() + interspersedIds.reversed(), resultOrderedIds)
}
@Test
fun givenNoStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// WHEN
val result = mms.isOutgoingStoryAlreadyInDatabase(recipients[0], 200)
// THEN
assertFalse(result)
}
@Test
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN
MmsHelper.insert(
IncomingMediaMessage(
from = recipients[0],
sentTimeMillis = 200,
serverTimeMillis = 2,
receivedTimeMillis = 2,
storyType = StoryType.STORY_WITH_REPLIES
),
-1L
)
// WHEN
val result = mms.isOutgoingStoryAlreadyInDatabase(recipients[0], 200)
// THEN
assertFalse(result)
}
@Test
fun givenOutgoingStoryExistsForRecipientAndTime_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectTrue() {
// GIVEN
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES
)
// WHEN
val result = mms.isOutgoingStoryAlreadyInDatabase(myStory.id, 200)
// THEN
assertTrue(result)
}
@Test
fun givenAGroupStoryWithNoReplies_whenICheckHasSelfReplyInGroupStory_thenIExpectFalse() {
// GIVEN
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
)
// WHEN
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
// THEN
assertFalse(result)
}
@Ignore
@Test
fun givenAGroupStoryWithAReplyFromSelf_whenICheckHasSelfReplyInGroupStory_thenIExpectTrue() {
// GIVEN
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
)
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 201,
storyType = StoryType.NONE,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
)
// WHEN
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
// THEN
assertTrue(result)
}
@Test
fun givenAGroupStoryWithAReactionFromSelf_whenICheckHasSelfReplyInGroupStory_thenIExpectTrue() {
// GIVEN
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES
)
MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 201,
storyType = StoryType.NONE,
parentStoryId = ParentStoryId.GroupReply(groupStoryId),
isStoryReaction = true
)
// WHEN
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
// THEN
assertTrue(result)
}
@Test
fun givenAGroupStoryWithAReplyFromSomeoneElse_whenICheckHasSelfReplyInGroupStory_thenIExpectFalse() {
// GIVEN
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
)
MmsHelper.insert(
IncomingMediaMessage(
from = myStory.id,
sentTimeMillis = 201,
serverTimeMillis = 201,
receivedTimeMillis = 202,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
),
-1
)
// WHEN
val result = mms.hasGroupReplyOrReactionInStory(groupStoryId)
// THEN
assertFalse(result)
}
@Test
fun givenNotViewedOnboardingAndOnlyStoryIsOnboardingAndAdded2DaysAgo_whenIGetOldestStoryTimestamp_thenIExpectNull() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(releaseChannelRecipient)
MmsHelper.insert(
recipient = releaseChannelRecipient,
sentTimeMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2),
storyType = StoryType.STORY_WITH_REPLIES,
threadId = threadId
)
// WHEN
val oldestTimestamp = SignalDatabase.messages.getOldestStorySendTimestamp(false)
// THEN
assertNull(oldestTimestamp)
}
@Test
fun givenViewedOnboardingAndOnlyStoryIsOnboardingAndAdded2DaysAgo_whenIGetOldestStoryTimestamp_thenIExpectNotNull() {
// GIVEN
val expected = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(2)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(releaseChannelRecipient)
MmsHelper.insert(
recipient = releaseChannelRecipient,
sentTimeMillis = expected,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = threadId
)
// WHEN
val oldestTimestamp = SignalDatabase.messages.getOldestStorySendTimestamp(true)
// THEN
assertEquals(expected, oldestTimestamp)
}
}
@@ -1,560 +0,0 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.RecipientChangedNumberJob
import org.thoughtcrime.securesms.keyvalue.AccountValues
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet
import org.thoughtcrime.securesms.keyvalue.KeyValueStore
import org.thoughtcrime.securesms.keyvalue.MockKeyValuePersistentStorage
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import java.lang.IllegalArgumentException
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class RecipientDatabaseTest {
private lateinit var recipientDatabase: RecipientDatabase
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
@Before
fun setup() {
recipientDatabase = SignalDatabase.recipients
ensureDbEmpty()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
}
// ==============================================================
// If both the ACI and E164 map to no one
// ==============================================================
/** If all you have is an ACI, you can just store that, regardless of trust level. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_aciOnly_highTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, null, true)
val recipient = Recipient.resolved(recipientId)
assertEquals(ACI_A, recipient.requireServiceId())
assertFalse(recipient.hasE164())
}
/** If all you have is an ACI, you can just store that, regardless of trust level. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_aciOnly_lowTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, null, false)
val recipient = Recipient.resolved(recipientId)
assertEquals(ACI_A, recipient.requireServiceId())
assertFalse(recipient.hasE164())
}
/** If all you have is an E164, you can just store that, regardless of trust level. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_e164Only_highTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(null, E164_A, true)
val recipient = Recipient.resolved(recipientId)
assertEquals(E164_A, recipient.requireE164())
assertFalse(recipient.hasServiceId())
}
/** If all you have is an E164, you can just store that, regardless of trust level. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_e164Only_lowTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(null, E164_A, false)
val recipient = Recipient.resolved(recipientId)
assertEquals(E164_A, recipient.requireE164())
assertFalse(recipient.hasServiceId())
}
/** With high trust, you can associate an ACI-e164 pair. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_aciAndE164_highTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val recipient = Recipient.resolved(recipientId)
assertEquals(ACI_A, recipient.requireServiceId())
assertEquals(E164_A, recipient.requireE164())
}
/** With low trust, you cannot associate an ACI-e164 pair, and therefore can only store the ACI. */
@Test
fun getAndPossiblyMerge_aciAndE164MapToNoOne_aciAndE164_lowTrust() {
val recipientId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, false)
val recipient = Recipient.resolved(recipientId)
assertEquals(ACI_A, recipient.requireServiceId())
assertFalse(recipient.hasE164())
}
// ==============================================================
// If the ACI maps to an existing user, but the E164 doesn't
// ==============================================================
/** With high trust, you can associate an e164 with an existing ACI. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciAndE164_highTrust() {
val existingId: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
}
/** With low trust, you cannot associate an ACI-e164 pair, and therefore cannot store the e164. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciAndE164_lowTrust() {
val existingId: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, false)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
}
/** Basically the change number case. High trust lets you update the existing user. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciAndE164_2_highTrust() {
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_B, retrievedRecipient.requireE164())
}
/** Low trust means you cant update the underlying data, but you also dont need to create any new rows. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciAndE164_2_lowTrust() {
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, false)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
}
// ==============================================================
// If the E164 maps to an existing user, but the ACI doesn't
// ==============================================================
/** With high trust, you can associate an e164 with an existing ACI. */
@Test
fun getAndPossiblyMerge_e164MapsToExistingUserButAciDoesNot_aciAndE164_highTrust() {
val existingId: RecipientId = recipientDatabase.getOrInsertFromE164(E164_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
}
/** With low trust, you cannot associate an ACI-e164 pair, and therefore need to create a new person with just the ACI. */
@Test
fun getAndPossiblyMerge_e164MapsToExistingUserButAciDoesNot_aciAndE164_lowTrust() {
val existingId: RecipientId = recipientDatabase.getOrInsertFromE164(E164_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, false)
assertNotEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
val existingRecipient = Recipient.resolved(existingId)
assertEquals(E164_A, existingRecipient.requireE164())
assertFalse(existingRecipient.hasServiceId())
}
/** We never change the ACI of an existing row. New ACI = new person, regardless of trust. But high trust lets us take the e164 from the current holder. */
@Test
fun getAndPossiblyMerge_e164MapsToExistingUserButAciDoesNot_aciAndE164_2_highTrust() {
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
recipientDatabase.setPni(existingId, PNI_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, true)
recipientDatabase.setPni(retrievedId, PNI_A)
assertNotEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_B, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingRecipient = Recipient.resolved(existingId)
assertEquals(ACI_A, existingRecipient.requireServiceId())
assertFalse(existingRecipient.hasE164())
}
/** We never change the ACI of an existing row. New ACI = new person, regardless of trust. And low trust means we cant take the e164. */
@Test
fun getAndPossiblyMerge_e164MapsToExistingUserButAciDoesNot_aciAndE164_2_lowTrust() {
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, false)
assertNotEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_B, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
val existingRecipient = Recipient.resolved(existingId)
assertEquals(ACI_A, existingRecipient.requireServiceId())
assertEquals(E164_A, existingRecipient.requireE164())
}
/** We never want to remove the e164 of our own contact entry. So basically treat this as a low-trust case, and leave the e164 alone. */
@Test
fun getAndPossiblyMerge_e164MapsToExistingUserButAciDoesNot_e164BelongsToLocalUser_highTrust() {
val dataSet = KeyValueDataSet().apply {
putString(AccountValues.KEY_E164, E164_A)
putString(AccountValues.KEY_ACI, ACI_A.toString())
}
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, true)
assertNotEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_B, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
val existingRecipient = Recipient.resolved(existingId)
assertEquals(ACI_A, existingRecipient.requireServiceId())
assertEquals(E164_A, existingRecipient.requireE164())
}
// ==============================================================
// If both the ACI and E164 map to an existing user
// ==============================================================
/** Regardless of trust, if your ACI and e164 match, youre good. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_highTrust() {
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
}
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_merge_highTrust() {
val changeNumberListener = ChangeNumberListener()
changeNumberListener.enqueue()
val existingAciId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, null, true)
val existingE164Id: RecipientId = recipientDatabase.getAndPossiblyMerge(null, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingAciId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingE164Recipient = Recipient.resolved(existingE164Id)
assertEquals(retrievedId, existingE164Recipient.id)
changeNumberListener.waitForJobManager()
assertFalse(changeNumberListener.numberChangeWasEnqueued)
}
/** Same as [getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_merge_highTrust], but with a number change. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_merge_highTrust_changedNumber() {
val changeNumberListener = ChangeNumberListener()
changeNumberListener.enqueue()
val existingAciId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
val existingE164Id: RecipientId = recipientDatabase.getAndPossiblyMerge(null, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingAciId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingE164Recipient = Recipient.resolved(existingE164Id)
assertEquals(retrievedId, existingE164Recipient.id)
changeNumberListener.waitForJobManager()
assert(changeNumberListener.numberChangeWasEnqueued)
}
/** Low trust means you cant merge. If youre retrieving a user from the table with this data, prefer the ACI one. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_lowTrust() {
val existingAciId: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_A)
val existingE164Id: RecipientId = recipientDatabase.getOrInsertFromE164(E164_A)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, false)
assertEquals(existingAciId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
val existingE164Recipient = Recipient.resolved(existingE164Id)
assertEquals(E164_A, existingE164Recipient.requireE164())
assertFalse(existingE164Recipient.hasServiceId())
}
/** Another high trust case. No new rules here, just a more complex scenario to show how different rules interact. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_complex_highTrust() {
val changeNumberListener = ChangeNumberListener()
changeNumberListener.enqueue()
val existingId1: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
val existingId2: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId1, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingRecipient2 = Recipient.resolved(existingId2)
assertEquals(ACI_B, existingRecipient2.requireServiceId())
assertFalse(existingRecipient2.hasE164())
assert(changeNumberListener.numberChangeWasEnqueued)
}
/** Another low trust case. No new rules here, just a more complex scenario to show how different rules interact. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_complex_lowTrust() {
val existingId1: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
val existingId2: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, false)
assertEquals(existingId1, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_B, retrievedRecipient.requireE164())
val existingRecipient2 = Recipient.resolved(existingId2)
assertEquals(ACI_B, existingRecipient2.requireServiceId())
assertEquals(E164_A, existingRecipient2.requireE164())
}
/**
* Another high trust case that results in a merge. Nothing strictly new here, but this case is called out because its a merge but *also* an E164 change,
* which clients may need to know for UX purposes.
*/
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_aciAndE164_mergeAndPhoneNumberChange_highTrust() {
val existingId1: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
val existingId2: RecipientId = recipientDatabase.getAndPossiblyMerge(null, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId1, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
assertFalse(recipientDatabase.getByE164(E164_B).isPresent)
val recipientWithId2 = Recipient.resolved(existingId2)
assertEquals(retrievedId, recipientWithId2.id)
}
/** We never want to remove the e164 of our own contact entry. So basically treat this as a low-trust case, and leave the e164 alone. */
@Test
fun getAndPossiblyMerge_bothAciAndE164MapToExistingUser_e164BelongsToLocalUser() {
val dataSet = KeyValueDataSet().apply {
putString(AccountValues.KEY_E164, E164_A)
putString(AccountValues.KEY_ACI, ACI_B.toString())
}
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
val existingId1: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_B, E164_A, true)
val existingId2: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, null, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
assertEquals(existingId2, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertFalse(retrievedRecipient.hasE164())
val recipientWithId1 = Recipient.resolved(existingId1)
assertEquals(ACI_B, recipientWithId1.requireServiceId())
assertEquals(E164_A, recipientWithId1.requireE164())
}
/** This is a case where normally we'd update the E164 of a user, but here the changeSelf flag is disabled, so we shouldn't. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciBelongsToLocalUser_highTrust_changeSelfFalse() {
val dataSet = KeyValueDataSet().apply {
putString(AccountValues.KEY_E164, E164_A)
putString(AccountValues.KEY_ACI, ACI_A.toString())
}
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, highTrust = true, changeSelf = false)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
}
/** This is a case where we're changing our own number, and it's allowed because changeSelf = true. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_aciBelongsToLocalUser_highTrust_changeSelfTrue() {
val dataSet = KeyValueDataSet().apply {
putString(AccountValues.KEY_E164, E164_A)
putString(AccountValues.KEY_ACI, ACI_A.toString())
}
SignalStore.inject(KeyValueStore(MockKeyValuePersistentStorage.withDataSet(dataSet)))
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, highTrust = true, changeSelf = true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_B, retrievedRecipient.requireE164())
}
/** Verifying a case where a change number job is expected to be enqueued. */
@Test
fun getAndPossiblyMerge_aciMapsToExistingUserButE164DoesNot_highTrust_changedNumber() {
val changeNumberListener = ChangeNumberListener()
changeNumberListener.enqueue()
val existingId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_B, true)
assertEquals(existingId, retrievedId)
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_B, retrievedRecipient.requireE164())
changeNumberListener.waitForJobManager()
assert(changeNumberListener.numberChangeWasEnqueued)
}
// ==============================================================
// Misc
// ==============================================================
@Test
fun createByE164SanityCheck() {
// GIVEN one recipient
val recipientId: RecipientId = recipientDatabase.getOrInsertFromE164(E164_A)
// WHEN I retrieve one by E164
val possible: Optional<RecipientId> = recipientDatabase.getByE164(E164_A)
// THEN I get it back, and it has the properties I expect
assertTrue(possible.isPresent)
assertEquals(recipientId, possible.get())
val recipient = Recipient.resolved(recipientId)
assertTrue(recipient.e164.isPresent)
assertEquals(E164_A, recipient.e164.get())
}
@Test
fun createByUuidSanityCheck() {
// GIVEN one recipient
val recipientId: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_A)
// WHEN I retrieve one by UUID
val possible: Optional<RecipientId> = recipientDatabase.getByServiceId(ACI_A)
// THEN I get it back, and it has the properties I expect
assertTrue(possible.isPresent)
assertEquals(recipientId, possible.get())
val recipient = Recipient.resolved(recipientId)
assertTrue(recipient.serviceId.isPresent)
assertEquals(ACI_A, recipient.serviceId.get())
}
@Test(expected = IllegalArgumentException::class)
fun getAndPossiblyMerge_noArgs_invalid() {
recipientDatabase.getAndPossiblyMerge(null, null, true)
}
private fun ensureDbEmpty() {
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor ->
assertTrue(cursor.moveToFirst())
assertEquals(0, cursor.getLong(0))
}
}
private class ChangeNumberListener {
var numberChangeWasEnqueued = false
private set
fun waitForJobManager() {
ApplicationDependencies.getJobManager().flush()
ThreadUtil.sleep(500)
}
fun enqueue() {
ApplicationDependencies.getJobManager().addListener(
{ job -> job.factoryKey == RecipientChangedNumberJob.KEY },
{ _, _ -> numberChangeWasEnqueued = true }
)
}
}
companion object {
val ACI_A = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
val ACI_B = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
val PNI_A = PNI.from(UUID.fromString("154b8d92-c960-4f6c-8385-671ad2ffb999"))
val PNI_B = PNI.from(UUID.fromString("ba92b1fb-cd55-40bf-adda-c35a85375533"))
const val E164_A = "+12221234567"
const val E164_B = "+13331234567"
}
}
@@ -1,270 +0,0 @@
package org.thoughtcrime.securesms.database
import android.app.Application
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.signal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.model.Mention
import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.CursorUtil
import org.whispersystems.libsignal.IdentityKey
import org.whispersystems.libsignal.SignalProtocolAddress
import org.whispersystems.libsignal.state.SessionRecord
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class RecipientDatabaseTest_merges {
private lateinit var recipientDatabase: RecipientDatabase
private lateinit var identityDatabase: IdentityDatabase
private lateinit var groupReceiptDatabase: GroupReceiptDatabase
private lateinit var groupDatabase: GroupDatabase
private lateinit var threadDatabase: ThreadDatabase
private lateinit var smsDatabase: MessageDatabase
private lateinit var mmsDatabase: MessageDatabase
private lateinit var sessionDatabase: SessionDatabase
private lateinit var mentionDatabase: MentionDatabase
private lateinit var reactionDatabase: ReactionDatabase
private lateinit var notificationProfileDatabase: NotificationProfileDatabase
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
@Before
fun setup() {
recipientDatabase = SignalDatabase.recipients
identityDatabase = SignalDatabase.identities
groupReceiptDatabase = SignalDatabase.groupReceipts
groupDatabase = SignalDatabase.groups
threadDatabase = SignalDatabase.threads
smsDatabase = SignalDatabase.sms
mmsDatabase = SignalDatabase.mms
sessionDatabase = SignalDatabase.sessions
mentionDatabase = SignalDatabase.mentions
reactionDatabase = SignalDatabase.reactions
notificationProfileDatabase = SignalDatabase.notificationProfiles
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
ensureDbEmpty()
}
/** High trust lets you merge two different users into one. You should prefer the ACI user. Not shown: merging threads, dropping e164 sessions, etc. */
@Test
fun getAndPossiblyMerge_general() {
// Setup
val recipientIdAci: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_A)
val recipientIdE164: RecipientId = recipientDatabase.getOrInsertFromE164(E164_A)
val recipientIdAciB: RecipientId = recipientDatabase.getOrInsertFromServiceId(ACI_B)
val smsId1: Long = smsDatabase.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 0, body = "0")).get().messageId
val smsId2: Long = smsDatabase.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
val smsId3: Long = smsDatabase.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
val mmsId1: Long = mmsDatabase.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = mmsDatabase.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = mmsDatabase.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val threadIdAci: Long = threadDatabase.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = threadDatabase.getThreadIdFor(recipientIdE164)!!
assertNotEquals(threadIdAci, threadIdE164)
mentionDatabase.insert(threadIdAci, mmsId1, listOf(Mention(recipientIdE164, 0, 1)))
mentionDatabase.insert(threadIdE164, mmsId2, listOf(Mention(recipientIdAci, 0, 1)))
groupReceiptDatabase.insert(listOf(recipientIdAci, recipientIdE164), mmsId1, 0, 3)
val identityKeyAci: IdentityKey = identityKey(1)
val identityKeyE164: IdentityKey = identityKey(2)
identityDatabase.saveIdentity(ACI_A.toString(), recipientIdAci, identityKeyAci, IdentityDatabase.VerifiedStatus.VERIFIED, false, 0, false)
identityDatabase.saveIdentity(E164_A, recipientIdE164, identityKeyE164, IdentityDatabase.VerifiedStatus.VERIFIED, false, 0, false)
sessionDatabase.store(localAci, SignalProtocolAddress(ACI_A.toString(), 1), SessionRecord())
reactionDatabase.addReaction(MessageId(smsId1, false), ReactionRecord("a", recipientIdAci, 1, 1))
reactionDatabase.addReaction(MessageId(mmsId1, true), ReactionRecord("b", recipientIdE164, 1, 1))
val profile1: NotificationProfile = notificationProfile(name = "Test")
val profile2: NotificationProfile = notificationProfile(name = "Test2")
notificationProfileDatabase.addAllowedRecipient(profileId = profile1.id, recipientId = recipientIdAci)
notificationProfileDatabase.addAllowedRecipient(profileId = profile1.id, recipientId = recipientIdE164)
notificationProfileDatabase.addAllowedRecipient(profileId = profile2.id, recipientId = recipientIdE164)
notificationProfileDatabase.addAllowedRecipient(profileId = profile2.id, recipientId = recipientIdAciB)
// Merge
val retrievedId: RecipientId = recipientDatabase.getAndPossiblyMerge(ACI_A, E164_A, true)
val retrievedThreadId: Long = threadDatabase.getThreadIdFor(retrievedId)!!
assertEquals(recipientIdAci, retrievedId)
// Recipient validation
val retrievedRecipient = Recipient.resolved(retrievedId)
assertEquals(ACI_A, retrievedRecipient.requireServiceId())
assertEquals(E164_A, retrievedRecipient.requireE164())
val existingE164Recipient = Recipient.resolved(recipientIdE164)
assertEquals(retrievedId, existingE164Recipient.id)
// Thread validation
assertEquals(threadIdAci, retrievedThreadId)
assertNull(threadDatabase.getThreadIdFor(recipientIdE164))
assertNull(threadDatabase.getThreadRecord(threadIdE164))
// SMS validation
val sms1: MessageRecord = smsDatabase.getMessageRecord(smsId1)!!
val sms2: MessageRecord = smsDatabase.getMessageRecord(smsId2)!!
val sms3: MessageRecord = smsDatabase.getMessageRecord(smsId3)!!
assertEquals(retrievedId, sms1.recipient.id)
assertEquals(retrievedId, sms2.recipient.id)
assertEquals(retrievedId, sms3.recipient.id)
assertEquals(retrievedThreadId, sms1.threadId)
assertEquals(retrievedThreadId, sms2.threadId)
assertEquals(retrievedThreadId, sms3.threadId)
// MMS validation
val mms1: MessageRecord = mmsDatabase.getMessageRecord(mmsId1)!!
val mms2: MessageRecord = mmsDatabase.getMessageRecord(mmsId2)!!
val mms3: MessageRecord = mmsDatabase.getMessageRecord(mmsId3)!!
assertEquals(retrievedId, mms1.recipient.id)
assertEquals(retrievedId, mms2.recipient.id)
assertEquals(retrievedId, mms3.recipient.id)
assertEquals(retrievedThreadId, mms1.threadId)
assertEquals(retrievedThreadId, mms2.threadId)
assertEquals(retrievedThreadId, mms3.threadId)
// Mention validation
val mention1: MentionModel = getMention(mmsId1)
assertEquals(retrievedId, mention1.recipientId)
assertEquals(retrievedThreadId, mention1.threadId)
val mention2: MentionModel = getMention(mmsId2)
assertEquals(retrievedId, mention2.recipientId)
assertEquals(retrievedThreadId, mention2.threadId)
// Group receipt validation
val groupReceipts: List<GroupReceiptDatabase.GroupReceiptInfo> = groupReceiptDatabase.getGroupReceiptInfo(mmsId1)
assertEquals(retrievedId, groupReceipts[0].recipientId)
assertEquals(retrievedId, groupReceipts[1].recipientId)
// Identity validation
assertEquals(identityKeyAci, identityDatabase.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
assertNull(identityDatabase.getIdentityStoreRecord(E164_A))
// Session validation
assertNotNull(sessionDatabase.load(localAci, SignalProtocolAddress(ACI_A.toString(), 1)))
// Reaction validation
val reactionsSms: List<ReactionRecord> = reactionDatabase.getReactions(MessageId(smsId1, false))
val reactionsMms: List<ReactionRecord> = reactionDatabase.getReactions(MessageId(mmsId1, true))
assertEquals(1, reactionsSms.size)
assertEquals(ReactionRecord("a", recipientIdAci, 1, 1), reactionsSms[0])
assertEquals(1, reactionsMms.size)
assertEquals(ReactionRecord("b", recipientIdAci, 1, 1), reactionsMms[0])
// Notification Profile validation
val updatedProfile1: NotificationProfile = notificationProfileDatabase.getProfile(profile1.id)!!
val updatedProfile2: NotificationProfile = notificationProfileDatabase.getProfile(profile2.id)!!
assertThat("Notification Profile 1 should now only contain ACI $recipientIdAci", updatedProfile1.allowedMembers, Matchers.containsInAnyOrder(recipientIdAci))
assertThat("Notification Profile 2 should now contain ACI A ($recipientIdAci) and ACI B ($recipientIdAciB)", updatedProfile2.allowedMembers, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
}
private val context: Application
get() = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
private fun ensureDbEmpty() {
SignalDatabase.rawDatabase.rawQuery("SELECT COUNT(*) FROM ${RecipientDatabase.TABLE_NAME}", null).use { cursor ->
assertTrue(cursor.moveToFirst())
assertEquals(0, cursor.getLong(0))
}
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
}
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.absent()): IncomingMediaMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.absent())
}
private fun identityKey(value: Byte): IdentityKey {
val bytes = ByteArray(33)
bytes[0] = 0x05
bytes[1] = value
return IdentityKey(bytes)
}
private fun groupMasterKey(value: Byte): GroupMasterKey {
val bytes = ByteArray(32)
bytes[0] = value
return GroupMasterKey(bytes)
}
private fun decryptedGroup(members: Collection<UUID>): DecryptedGroup {
return DecryptedGroup.newBuilder()
.addAllMembers(members.map { DecryptedMember.newBuilder().setUuid(UuidUtil.toByteString(it)).build() })
.build()
}
private fun getMention(messageId: Long): MentionModel {
SignalDatabase.rawDatabase.rawQuery("SELECT * FROM ${MentionDatabase.TABLE_NAME} WHERE ${MentionDatabase.MESSAGE_ID} = $messageId").use { cursor ->
cursor.moveToFirst()
return MentionModel(
recipientId = RecipientId.from(CursorUtil.requireLong(cursor, MentionDatabase.RECIPIENT_ID)),
threadId = CursorUtil.requireLong(cursor, MentionDatabase.THREAD_ID)
)
}
}
private fun notificationProfile(name: String): NotificationProfile {
return (notificationProfileDatabase.createProfile(name = name, emoji = "", color = AvatarColor.A210, System.currentTimeMillis()) as NotificationProfileDatabase.NotificationProfileChangeResult.Success).notificationProfile
}
/** The normal mention model doesn't have a threadId, so we need to do it ourselves for the test */
data class MentionModel(
val recipientId: RecipientId,
val threadId: Long
)
companion object {
val ACI_A = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
val ACI_B = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
val E164_A = "+12221234567"
val E164_B = "+13331234567"
}
}
@@ -0,0 +1,210 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class RecipientTableTest {
@get:Rule
val harness = SignalActivityRule()
@Test
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.queryAllContacts("Hidden")!!
assertEquals(0, results.count)
}
@Test
fun givenAHiddenRecipient_whenIGetSignalContacts_thenIDoNotExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
val ids = mutableListOf<RecipientId>()
while (it.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
}
ids
}!!
assertNotEquals(0, results.size)
assertFalse(hiddenRecipient in results)
}
@Test
fun givenAHiddenRecipient_whenIQuerySignalContacts_thenIDoNotExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
assertEquals(0, results.count)
}
@Test
fun givenAHiddenRecipient_whenIQueryNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.queryNonGroupContacts("Hidden", false)!!
assertEquals(0, results.count)
}
@Test
fun givenAHiddenRecipient_whenIGetNonGroupContacts_thenIDoNotExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
val ids = mutableListOf<RecipientId>()
while (it.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
}
ids
}!!
assertNotEquals(0, results.size)
assertFalse(hiddenRecipient in results)
}
@Test
fun givenABlockedRecipient_whenIQueryAllContacts_thenIDoNotExpectBlockedToBeReturned() {
val blockedRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results = SignalDatabase.recipients.queryAllContacts("Blocked")!!
assertEquals(0, results.count)
}
@Test
fun givenABlockedRecipient_whenIGetSignalContacts_thenIDoNotExpectBlockedToBeReturned() {
val blockedRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results: MutableList<RecipientId> = SignalDatabase.recipients.getSignalContacts(false)?.use {
val ids = mutableListOf<RecipientId>()
while (it.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
}
ids
}!!
assertNotEquals(0, results.size)
assertFalse(blockedRecipient in results)
}
@Test
fun givenABlockedRecipient_whenIQuerySignalContacts_thenIDoNotExpectBlockedToBeReturned() {
val blockedRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results = SignalDatabase.recipients.querySignalContacts("Blocked", false)!!
assertEquals(0, results.count)
}
@Test
fun givenABlockedRecipient_whenIQueryNonGroupContacts_thenIDoNotExpectBlockedToBeReturned() {
val blockedRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results = SignalDatabase.recipients.queryNonGroupContacts("Blocked", false)!!
assertEquals(0, results.count)
}
@Test
fun givenABlockedRecipient_whenIGetNonGroupContacts_thenIDoNotExpectBlockedToBeReturned() {
val blockedRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results: MutableList<RecipientId> = SignalDatabase.recipients.getNonGroupContacts(false)?.use {
val ids = mutableListOf<RecipientId>()
while (it.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(it, RecipientTable.ID)))
}
ids
}!!
assertNotEquals(0, results.size)
assertFalse(blockedRecipient in results)
}
@Test
fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
SignalDatabase.recipients.markUnregistered(mainId)
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)
assertNotEquals(byAci, byE164)
}
@Test
fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!)
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)
assertNotEquals(byAci, byE164)
}
companion object {
val ACI_A = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val PNI_A = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
const val E164_A = "+12222222222"
}
}
@@ -122,4 +122,62 @@ class SQLiteDatabaseTest {
assertTrue(hasRun1.get()) assertTrue(hasRun1.get())
assertFalse(hasRun2.get()) assertFalse(hasRun2.get())
} }
@Test
fun runPostSuccessfulTransaction_runsAndPerformsAnotherTransaction() {
val hasRun = AtomicBoolean(false)
db.beginTransaction()
db.runPostSuccessfulTransaction {
try {
db.beginTransaction()
hasRun.set(true)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
assertFalse(hasRun.get())
db.setTransactionSuccessful()
db.endTransaction()
assertTrue(hasRun.get())
}
@Test
fun runPostSuccessfulTransaction_runsAndPerformsAnotherTransactionAndRunPostNested() {
val hasRun1 = AtomicBoolean(false)
val hasRun2 = AtomicBoolean(false)
db.beginTransaction()
db.runPostSuccessfulTransaction {
db.beginTransaction()
db.runPostSuccessfulTransaction {
assertTrue(hasRun1.get())
assertFalse(hasRun2.get())
hasRun2.set(true)
}
assertFalse(hasRun1.get())
hasRun1.set(true)
assertFalse(hasRun2.get())
db.setTransactionSuccessful()
db.endTransaction()
}
assertFalse(hasRun1.get())
assertFalse(hasRun2.get())
db.setTransactionSuccessful()
db.endTransaction()
assertTrue(hasRun1.get())
assertTrue(hasRun2.get())
}
} }
@@ -0,0 +1,292 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Hex
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@RunWith(AndroidJUnit4::class)
class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
private lateinit var recipients: RecipientTable
private lateinit var sms: MessageTable
private val localAci = ACI.from(UUID.randomUUID())
private val localPni = PNI.from(UUID.randomUUID())
private var wallClock: Long = 1000
private lateinit var alice: RecipientId
private lateinit var bob: RecipientId
@Before
fun setUp() {
recipients = SignalDatabase.recipients
sms = SignalDatabase.messages
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
alice = recipients.getOrInsertFromServiceId(aliceServiceId)
bob = recipients.getOrInsertFromServiceId(bobServiceId)
}
/**
* Do nothing if no previous messages.
*/
@Test
fun noPreviousMessage() {
val result = sms.collapseJoinRequestEventsIfPossible(
1,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous message is text.
*/
@Test
fun previousTextMesssage() {
val threadId = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous is unrelated group change.
*/
@Test
fun previousUnrelatedGroupChange() {
val threadId = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addMember(bobServiceId)
}
}
)
).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Do nothing if previous join request is from a different recipient.
*/
@Test
fun previousJoinRequestFromADifferentRecipient() {
val threadId = sms.insertMessageInbox(
groupUpdateMessage(
sender = bob,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = bobServiceId) {
deleteRequestingMember(bobServiceId)
}
}
)
).get().threadId
val result = sms.collapseJoinRequestEventsIfPossible(
threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is null when not collapsing", result.orElse(null), nullValue())
}
/**
* Collapse if previous is join request from same.
*/
@Test
fun previousJoinRequestCollapse() {
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
}
/**
* Collapse if previous is join request from same, and leave second previous alone if text.
*/
@Test
fun previousJoinThenTextCollapse() {
val secondLatestMessage = sms.insertMessageInbox(smsMessage(sender = alice, body = "What up")).get()
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
assert(secondLatestMessage.threadId == latestMessage.threadId)
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as latest message", result.get().messageId, `is`(latestMessage.messageId))
}
/**
* Collapse "twice" is previous is a join request and second previous is already collapsed join/delete from the same recipient.
*/
@Test
fun previousCollapseAndJoinRequestDoubleCollapse() {
val secondLatestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
deleteRequestingMember(aliceServiceId)
}
}
)
).get()
val latestMessage: MessageTable.InsertResult = sms.insertMessageInbox(
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
addRequestingMember(aliceServiceId)
}
}
)
).get()
assert(secondLatestMessage.threadId == latestMessage.threadId)
val result = sms.collapseJoinRequestEventsIfPossible(
latestMessage.threadId,
groupUpdateMessage(
sender = alice,
groupContext = groupContext(masterKey = masterKey) {
change = groupChange(editor = aliceServiceId) {
deleteRequestingMember(aliceServiceId)
}
}
)
)
assertThat("result is not null when collapsing", result.orElse(null), notNullValue())
assertThat("result message id should be same as second latest message", result.get().messageId, `is`(secondLatestMessage.messageId))
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
}
companion object {
private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
private val groupId = GroupId.v2(masterKey)
}
}
@@ -0,0 +1,466 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.TestCase.assertNull
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsInAnyOrder
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendTableTest {
private val distributionId1 = DistributionId.from(UUID.randomUUID())
private val distributionId2 = DistributionId.from(UUID.randomUUID())
private val distributionId3 = DistributionId.from(UUID.randomUUID())
private lateinit var distributionList1: DistributionListId
private lateinit var distributionList2: DistributionListId
private lateinit var distributionList3: DistributionListId
private lateinit var distributionListRecipient1: Recipient
private lateinit var distributionListRecipient2: Recipient
private lateinit var distributionListRecipient3: Recipient
private lateinit var recipients1to10: List<RecipientId>
private lateinit var recipients11to20: List<RecipientId>
private lateinit var recipients6to15: List<RecipientId>
private lateinit var recipients6to10: List<RecipientId>
private var messageId1: Long = 0
private var messageId2: Long = 0
private var messageId3: Long = 0
private lateinit var storySends: StorySendTable
@Before
fun setup() {
storySends = SignalDatabase.storySends
recipients1to10 = makeRecipients(10)
recipients11to20 = makeRecipients(10)
distributionList1 = SignalDatabase.distributionLists.createList("1", emptyList(), distributionId = distributionId1)!!
distributionList2 = SignalDatabase.distributionLists.createList("2", emptyList(), distributionId = distributionId2)!!
distributionList3 = SignalDatabase.distributionLists.createList("3", emptyList(), distributionId = distributionId3)!!
distributionListRecipient1 = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(distributionList1))
distributionListRecipient2 = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(distributionList2))
distributionListRecipient3 = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(distributionList3))
messageId1 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES
)
messageId2 = MmsHelper.insert(
recipient = distributionListRecipient2,
storyType = StoryType.STORY_WITH_REPLIES
)
messageId3 = MmsHelper.insert(
recipient = distributionListRecipient3,
storyType = StoryType.STORY_WITHOUT_REPLIES
)
recipients6to15 = recipients1to10.takeLast(5) + recipients11to20.take(5)
recipients6to10 = recipients1to10.takeLast(5)
}
@Test
fun getRecipientsToSendTo_noOverlap() {
storySends.insert(messageId1, recipients1to10, 100, false, distributionId1)
storySends.insert(messageId2, recipients11to20, 200, true, distributionId2)
storySends.insert(messageId3, recipients1to10, 300, false, distributionId3)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 200, true)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
}
@Test
fun getRecipientsToSendTo_overlap() {
storySends.insert(messageId1, recipients1to10, 100, false, distributionId1)
storySends.insert(messageId2, recipients6to15, 100, true, distributionId2)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
assertThat(recipientIdsForMessage1, hasSize(5))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
}
@Test
fun getRecipientsToSendTo_overlapAll() {
val recipient1 = recipients1to10.first()
val recipient2 = recipients11to20.first()
storySends.insert(messageId1, listOf(recipient1, recipient2), 100, false, distributionId1)
storySends.insert(messageId2, listOf(recipient1), 100, true, distributionId2)
storySends.insert(messageId3, listOf(recipient2), 100, true, distributionId3)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, false)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, true)
val recipientIdsForMessage3 = storySends.getRecipientsToSendTo(messageId3, 100, true)
assertThat(recipientIdsForMessage1, hasSize(0))
assertThat(recipientIdsForMessage2, hasSize(1))
assertThat(recipientIdsForMessage2, containsInAnyOrder(recipient1))
assertThat(recipientIdsForMessage3, hasSize(1))
assertThat(recipientIdsForMessage3, containsInAnyOrder(recipient2))
}
@Test
fun getRecipientsToSendTo_overlapWithEarlierMessage() {
storySends.insert(messageId1, recipients6to15, 100, true, distributionId1)
storySends.insert(messageId2, recipients1to10, 100, false, distributionId2)
val recipientIdsForMessage1 = storySends.getRecipientsToSendTo(messageId1, 100, true)
val recipientIdsForMessage2 = storySends.getRecipientsToSendTo(messageId2, 100, false)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients6to15.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(5))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_noOverlap() {
storySends.insert(messageId1, recipients1to10, 100, false, distributionId1)
storySends.insert(messageId2, recipients11to20, 200, true, distributionId2)
storySends.insert(messageId3, recipients1to10, 300, false, distributionId3)
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 100)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage1, hasSize(10))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients11to20.toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_overlapNoPreviousDeletes() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients6to15, 200, true, distributionId2)
val recipientIdsForMessage1 = storySends.getRemoteDeleteRecipients(messageId1, 200)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage1, hasSize(5))
assertThat(recipientIdsForMessage1, containsInAnyOrder(*recipients1to10.take(5).toTypedArray()))
assertThat(recipientIdsForMessage2, hasSize(5))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.takeLast(5).toTypedArray()))
}
@Test
fun getRemoteDeleteRecipients_overlapWithPreviousDeletes() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
SignalDatabase.messages.markAsRemoteDelete(messageId1)
storySends.insert(messageId2, recipients6to15, 200, true, distributionId2)
val recipientIdsForMessage2 = storySends.getRemoteDeleteRecipients(messageId2, 200)
assertThat(recipientIdsForMessage2, hasSize(10))
assertThat(recipientIdsForMessage2, containsInAnyOrder(*recipients6to15.toTypedArray()))
}
@Test
fun canReply_storyWithReplies() {
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
val canReply = storySends.canReply(recipients1to10[0], 200)
assertThat(canReply, `is`(true))
}
@Test
fun canReply_storyWithoutReplies() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
val canReply = storySends.canReply(recipients1to10[0], 200)
assertThat(canReply, `is`(false))
}
@Test
fun canReply_storyWithAndWithoutRepliesOverlap() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients6to10, 200, true, distributionId2)
val message1OnlyRecipientCanReply = storySends.canReply(recipients1to10[0], 200)
val message2RecipientCanReply = storySends.canReply(recipients6to10[0], 200)
assertThat(message1OnlyRecipientCanReply, `is`(false))
assertThat(message2RecipientCanReply, `is`(true))
}
@Test
fun givenASingleStory_whenIGetFullSentStorySyncManifest_thenIExpectNotNull() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
val manifest = storySends.getFullSentStorySyncManifest(messageId1, 200)
assertNotNull(manifest)
}
@Test
fun givenTwoStories_whenIGetFullSentStorySyncManifestForStory2_thenIExpectNull() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, false, distributionId2)
val manifest = storySends.getFullSentStorySyncManifest(messageId2, 200)
assertNull(manifest)
}
@Test
fun givenTwoStories_whenIGetFullSentStorySyncManifestForStory1_thenIExpectOneManifestPerRecipient() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
val manifest = storySends.getFullSentStorySyncManifest(messageId1, 200)!!
assertEquals(recipients1to10, manifest.entries.map { it.recipientId })
}
@Test
fun givenTwoStories_whenIGetFullSentStorySyncManifestForStory1_thenIExpectTwoListsPerRecipient() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
val manifest = storySends.getFullSentStorySyncManifest(messageId1, 200)!!
manifest.entries.forEach { entry ->
assertEquals(listOf(distributionId1, distributionId2), entry.distributionLists)
}
}
@Test
fun givenTwoStories_whenIGetFullSentStorySyncManifestForStory1_thenIExpectAllRecipientsCanReply() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
val manifest = storySends.getFullSentStorySyncManifest(messageId1, 200)!!
manifest.entries.forEach { entry ->
assertTrue(entry.allowedToReply)
}
}
@Test
fun givenTwoStoriesAndOneIsRemoteDeleted_whenIGetFullSentStorySyncManifestForStory2_thenIExpectNonNullResult() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
SignalDatabase.messages.markAsRemoteDelete(messageId1)
val manifest = storySends.getFullSentStorySyncManifest(messageId2, 200)!!
assertNotNull(manifest)
}
/*
@Test
fun givenTwoStoriesAndOneIsRemoteDeleted_whenIGetRecipientIdsForManifestUpdate_thenIExpectOnlyRecipientsWithStory2() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId1, recipients11to20, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
SignalDatabase.messages.markAsRemoteDelete(messageId1)
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
assertEquals(recipients1to10.toHashSet(), recipientIds)
}
@Test
fun givenTwoStoriesAndOneIsRemoteDeleted_whenIGetPartialSentStorySyncManifest_thenIExpectOnlyRecipientsThatHadStory1() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
storySends.insert(messageId2, recipients11to20, 200, true, distributionId2)
SignalDatabase.messages.markAsRemoteDelete(messageId1)
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
val results = storySends.getSentStorySyncManifestForUpdate(200, recipientIds)
val manifestRecipients = results.entries.map { it.recipientId }
assertEquals(recipients1to10, manifestRecipients)
}
@Test
fun givenTwoStoriesAndTheOneThatAllowedRepliesIsRemoteDeleted_whenIGetPartialSentStorySyncManifest_thenIExpectAllowRepliesToBeTrue() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
storySends.insert(messageId2, recipients1to10, 200, true, distributionId2)
SignalDatabase.messages.markAsRemoteDelete(messageId2)
val recipientIds = storySends.getRecipientIdsForManifestUpdate(200, messageId1)
val results = storySends.getSentStorySyncManifestForUpdate(200, recipientIds)
assertTrue(results.entries.all { it.allowedToReply })
}
*/
@Test
fun givenEmptyManifest_whenIApplyRemoteManifest_thenNothingChanges() {
storySends.insert(messageId1, recipients1to10, 200, false, distributionId1)
val expected = storySends.getFullSentStorySyncManifest(messageId1, 200)
val emptyManifest = SentStorySyncManifest(emptyList())
storySends.applySentStoryManifest(emptyManifest, 200)
val result = storySends.getFullSentStorySyncManifest(messageId1, 200)
assertEquals(expected, result)
}
@Test
fun givenAnIdenticalManifest_whenIApplyRemoteManifest_thenNothingChanges() {
val messageId4 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 200
)
storySends.insert(messageId4, recipients1to10, 200, false, distributionId1)
val expected = storySends.getFullSentStorySyncManifest(messageId4, 200)
storySends.applySentStoryManifest(expected!!, 200)
val result = storySends.getFullSentStorySyncManifest(messageId4, 200)
assertEquals(expected, result)
}
@Test(expected = NoSuchMessageException::class)
fun givenAManifest_whenIApplyRemoteManifestWithoutOneList_thenIExpectMessageToBeDeleted() {
val messageId4 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 200
)
val messageId5 = MmsHelper.insert(
recipient = distributionListRecipient2,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 200
)
storySends.insert(messageId4, recipients1to10, 200, false, distributionId1)
val remote = storySends.getFullSentStorySyncManifest(messageId4, 200)!!
storySends.insert(messageId5, recipients1to10, 200, false, distributionId2)
storySends.applySentStoryManifest(remote, 200)
SignalDatabase.messages.getMessageRecord(messageId5)
fail("Expected messageId5 to no longer exist.")
}
@Test
fun givenAManifest_whenIApplyRemoteManifestWithoutOneList_thenIExpectSharedMessageToNotBeMarkedRemoteDeleted() {
val messageId4 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 200
)
val messageId5 = MmsHelper.insert(
recipient = distributionListRecipient2,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 200
)
storySends.insert(messageId4, recipients1to10, 200, false, distributionId1)
val remote = storySends.getFullSentStorySyncManifest(messageId4, 200)!!
storySends.insert(messageId5, recipients1to10, 200, false, distributionId2)
storySends.applySentStoryManifest(remote, 200)
assertFalse(SignalDatabase.messages.getMessageRecord(messageId4).isRemoteDelete)
}
@Test
fun givenNoLocalEntries_whenIApplyRemoteManifest_thenIExpectLocalManifestToMatch() {
val messageId4 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 2000
)
val remote = SentStorySyncManifest(
recipients1to10.map {
SentStorySyncManifest.Entry(
recipientId = it,
allowedToReply = true,
distributionLists = listOf(distributionId1)
)
}
)
storySends.applySentStoryManifest(remote, 2000)
val local = storySends.getFullSentStorySyncManifest(messageId4, 2000)
assertEquals(remote, local)
}
@Test
fun givenNonStoryMessageAtSentTimestamp_whenIApplyRemoteManifest_thenIExpectLocalManifestToMatchAndNoCrashes() {
val messageId4 = MmsHelper.insert(
recipient = distributionListRecipient1,
storyType = StoryType.STORY_WITHOUT_REPLIES,
sentTimeMillis = 2000
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients1to10.first()),
sentTimeMillis = 2000
)
val remote = SentStorySyncManifest(
recipients1to10.map {
SentStorySyncManifest.Entry(
recipientId = it,
allowedToReply = true,
distributionLists = listOf(distributionId1)
)
}
)
storySends.applySentStoryManifest(remote, 2000)
val local = storySends.getFullSentStorySyncManifest(messageId4, 2000)
assertEquals(remote, local)
}
private fun makeRecipients(count: Int): List<RecipientId> {
return (1..count).map {
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
}
}
}
@@ -0,0 +1,75 @@
package org.thoughtcrime.securesms.database
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@Suppress("ClassName")
class ThreadTableTest_pinned {
@Rule
@JvmField
val databaseRule = SignalDatabaseRule()
private lateinit var recipient: Recipient
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
}
@Test
fun givenAPinnedThread_whenIDeleteTheLastMessage_thenIDoNotDeleteOrUnpinTheThread() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val messageId = MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.pinConversations(listOf(threadId))
// WHEN
SignalDatabase.messages.deleteMessage(messageId)
// THEN
val pinned = SignalDatabase.threads.getPinnedThreadIds()
assertTrue(threadId in pinned)
}
@Test
fun givenAPinnedThread_whenIDeleteTheLastMessage_thenIExpectTheThreadInUnarchivedCount() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val messageId = MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.pinConversations(listOf(threadId))
// WHEN
SignalDatabase.messages.deleteMessage(messageId)
// THEN
val unarchivedCount = SignalDatabase.threads.getUnarchivedConversationListCount(ConversationFilter.OFF)
assertEquals(1, unarchivedCount)
}
@Test
fun givenAPinnedThread_whenIDeleteTheLastMessage_thenIExpectPinnedThreadInUnarchivedList() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val messageId = MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.pinConversations(listOf(threadId))
// WHEN
SignalDatabase.messages.deleteMessage(messageId)
// THEN
SignalDatabase.threads.getUnarchivedConversationList(ConversationFilter.OFF, true, 0, 1).use {
it.moveToFirst()
assertEquals(threadId, CursorUtil.requireLong(it, ThreadTable.ID))
}
}
}
@@ -0,0 +1,52 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertFalse
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class ThreadTableTest_recents {
@Rule
@JvmField
val databaseRule = SignalDatabaseRule()
private lateinit var recipient: Recipient
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
}
@Test
fun givenARecentRecipient_whenIBlockAndGetRecents_thenIDoNotExpectToSeeThatRecipient() {
// GIVEN
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, true)
// WHEN
SignalDatabase.recipients.setBlocked(recipient.id, true)
val results: MutableList<RecipientId> = SignalDatabase.threads.getRecentConversationList(10, false, false, false, false, false, false).use { cursor ->
val ids = mutableListOf<RecipientId>()
while (cursor.moveToNext()) {
ids.add(RecipientId.from(CursorUtil.requireLong(cursor, ThreadTable.RECIPIENT_ID)))
}
ids
}
// THEN
assertFalse(recipient.id in results)
}
}
@@ -0,0 +1,44 @@
package org.thoughtcrime.securesms.database
import android.net.Uri
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.stickers.StickerLocator
object UriAttachmentBuilder {
fun build(
id: Long,
uri: Uri = Uri.parse("content://$id"),
contentType: String,
transferState: Int = AttachmentTable.TRANSFER_PROGRESS_PENDING,
size: Long = 0L,
fileName: String = "file$id",
voiceNote: Boolean = false,
borderless: Boolean = false,
videoGif: Boolean = false,
quote: Boolean = false,
caption: String? = null,
stickerLocator: StickerLocator? = null,
blurHash: BlurHash? = null,
audioHash: AudioHash? = null,
transformProperties: AttachmentTable.TransformProperties? = null
): UriAttachment {
return UriAttachment(
uri,
contentType,
transferState,
size,
fileName,
voiceNote,
borderless,
videoGif,
quote,
caption,
stickerLocator,
blurHash,
audioHash,
transformProperties
)
}
}
@@ -0,0 +1,119 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import androidx.core.content.contentValuesOf
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.SqlUtil
import org.thoughtcrime.securesms.database.DistributionListTables
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.DistributionId
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class MyStoryMigrationTest {
@get:Rule val harness = SignalDatabaseRule(deleteAllThreadsOnEachRun = false)
@Test
fun givenAValidMyStory_whenIMigrate_thenIExpectMyStoryToBeValid() {
// GIVEN
assertValidMyStoryExists()
// WHEN
runMigration()
// THEN
assertValidMyStoryExists()
}
@Test
fun givenNoMyStory_whenIMigrate_thenIExpectMyStoryToBeCreated() {
// GIVEN
deleteMyStory()
// WHEN
runMigration()
// THEN
assertValidMyStoryExists()
}
@Test
fun givenA00000000DistributionIdForMyStory_whenIMigrate_thenIExpectMyStoryToBeCreated() {
// GIVEN
setMyStoryDistributionId("0000-0000")
// WHEN
runMigration()
// THEN
assertValidMyStoryExists()
}
@Test
fun givenARandomDistributionIdForMyStory_whenIMigrate_thenIExpectMyStoryToBeCreated() {
// GIVEN
setMyStoryDistributionId(UUID.randomUUID().toString())
// WHEN
runMigration()
// THEN
assertValidMyStoryExists()
}
private fun setMyStoryDistributionId(serializedId: String) {
SignalDatabase.rawDatabase.update(
DistributionListTables.LIST_TABLE_NAME,
contentValuesOf(
DistributionListTables.DISTRIBUTION_ID to serializedId
),
"_id = ?",
SqlUtil.buildArgs(DistributionListId.MY_STORY)
)
}
private fun deleteMyStory() {
SignalDatabase.rawDatabase.delete(
DistributionListTables.LIST_TABLE_NAME,
"_id = ?",
SqlUtil.buildArgs(DistributionListId.MY_STORY)
)
}
private fun assertValidMyStoryExists() {
SignalDatabase.rawDatabase.query(
DistributionListTables.LIST_TABLE_NAME,
SqlUtil.COUNT,
"_id = ? AND ${DistributionListTables.DISTRIBUTION_ID} = ?",
SqlUtil.buildArgs(DistributionListId.MY_STORY, DistributionId.MY_STORY.toString()),
null,
null,
null
).use {
if (it.moveToNext()) {
val count = it.getInt(0)
assertEquals("assertValidMyStoryExists: Query produced an unexpected count.", 1, count)
} else {
fail("assertValidMyStoryExists: Query did not produce a count.")
}
}
}
private fun runMigration() {
V151_MyStoryMigration.migrate(
InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
SignalDatabase.rawDatabase,
0,
1
)
}
}
@@ -0,0 +1,116 @@
package org.thoughtcrime.securesms.dependencies
import android.app.Application
import okhttp3.ConnectionSpec
import okhttp3.mockwebserver.Dispatcher
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.KbsEnclave
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.thoughtcrime.securesms.recipients.LiveRecipientCache
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import java.security.KeyStore
import java.util.Optional
/**
* Dependency provider used for instrumentation tests (aka androidTests).
*
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess] and
* [KeyBackupService].
*/
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val keyBackupService: KeyBackupService
private val recipientCache: LiveRecipientCache
init {
runSync {
webServer = MockWebServer()
baseUrl = webServer.url("").toString()
}
webServer.setDispatcher(object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val handler = handlers.firstOrNull {
request.method == it.verb && request.path.startsWith("/${it.path}")
}
return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500)
}
})
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {
on { getConfiguration() } doReturn uncensoredConfiguration
on { getConfiguration(any()) } doReturn uncensoredConfiguration
on { uncensoredConfiguration } doReturn uncensoredConfiguration
}
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() }
}
override fun provideSignalServiceNetworkAccess(): SignalServiceNetworkAccess {
return serviceNetworkAccessMock
}
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
override fun provideRecipientCache(): LiveRecipientCache {
return recipientCache
}
companion object {
lateinit var webServer: MockWebServer
private set
lateinit var baseUrl: String
private set
private val handlers: MutableList<Verb> = mutableListOf()
fun addMockWebRequestHandlers(vararg verbs: Verb) {
handlers.addAll(verbs)
}
fun clearHandlers() {
handlers.clear()
}
}
}
@@ -0,0 +1,212 @@
package org.thoughtcrime.securesms.jobs
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.protocol.ecc.Curve
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyState
import org.whispersystems.signalservice.internal.push.PreKeyStatus
@RunWith(AndroidJUnit4::class)
class PreKeysSyncJobTest {
@get:Rule
val harness = SignalActivityRule()
private val aciPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().aciPreKeys
private val pniPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().pniPreKeys
private lateinit var job: PreKeysSyncJob
@Before
fun setUp() {
job = PreKeysSyncJob()
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
/**
* Create signed prekeys for both identities when both do not have registered prekeys according
* to our local state.
*/
@Test
fun runWithoutRegisteredKeysForBothIdentities() {
// GIVEN
aciPreKeyMeta.isSignedPreKeyRegistered = false
pniPreKeyMeta.isSignedPreKeyRegistered = false
lateinit var aciSignedPreKey: SignedPreKeyEntity
lateinit var pniSignedPreKey: SignedPreKeyEntity
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v2/keys/signed?identity=aci") { r ->
aciSignedPreKey = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/signed?identity=pni") { r ->
pniSignedPreKey = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
val aciVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
aciSignedPreKey.publicKey.serialize(),
aciSignedPreKey.signature
)
aciVerifySignatureResult assertIs true
val pniVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
pniSignedPreKey.publicKey.serialize(),
pniSignedPreKey.signature
)
pniVerifySignatureResult assertIs true
}
/**
* With 100 prekeys registered for each identity, do nothing.
*/
@Test
fun runWithRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
}
/**
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
* do nothing for ACI but create PNI prekeys and update local state.
*/
@Test
fun runWithRegisteredKeysForAciIdentityOnly() {
// GIVEN
pniPreKeyMeta.isSignedPreKeyRegistered = false
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Put("/v2/keys/signed?identity=pni") { MockResponse().success() }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
}
/**
* With <10 prekeys registered for each identity, upload new.
*/
@Test
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId
lateinit var aciPreKeyStateRequest: PreKeyState
lateinit var pniPreKeyStateRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
Put("/v2/keys/?identity=aci") { r ->
aciPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/?identity=pni") { r ->
pniPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey
val verifySignatureResult = Curve.verifySignature(
aciIdentityKey.publicKey,
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
aciPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey
val verifySignatureResult = Curve.verifySignature(
pniIdentityKey.publicKey,
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
pniPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
}
}
@@ -0,0 +1,175 @@
package org.thoughtcrime.securesms.jobs
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.usernames.Username
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import org.whispersystems.util.Base64UrlSafe
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RefreshOwnProfileJob__checkUsernameIsInSyncTest {
@get:Rule
val harness = SignalActivityRule()
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync()
}
@Test
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
}
@Test
fun givenLocalUsernameDoesNotMatchServerUsername_whenICheckUsernameIsInSync_thenIExpectRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
val serverUsername = "hello.3232"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(serverUsername))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertTrue(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenLocalAndNoServer_whenICheckUsernameIsInSync_thenIExpectRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(WhoAmIResponse())
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertTrue(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenLocalAndServerMatch_whenICheckUsernameIsInSync_thenIExpectNoRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertFalse(didReserve)
assertFalse(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenMismatchAndReservationFails_whenICheckUsernameIsInSync_thenIExpectNoConfirm() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash("${username}23"))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().failure(418)
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertFalse(didConfirm)
assertTrue(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
}
@@ -4,7 +4,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.thoughtcrime.securesms.util.Hex; import org.signal.core.util.Hex;
import org.whispersystems.signalservice.api.kbs.HashedPin; import org.whispersystems.signalservice.api.kbs.HashedPin;
import org.whispersystems.signalservice.api.kbs.KbsData; import org.whispersystems.signalservice.api.kbs.KbsData;
import org.whispersystems.signalservice.api.kbs.MasterKey; import org.whispersystems.signalservice.api.kbs.MasterKey;
@@ -0,0 +1,62 @@
package org.thoughtcrime.securesms.messages
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import org.junit.Rule
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.TestProtos
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
abstract class MessageContentProcessorTest {
@get:Rule
val harness = SignalActivityRule()
protected fun MessageContentProcessor.doProcess(
messageState: MessageState = MessageState.DECRYPTED_OK,
content: SignalServiceContent,
exceptionMetadata: ExceptionMetadata = ExceptionMetadata("sender", 1),
timestamp: Long = 100L,
smsMessageId: Long = -1L
) {
process(messageState, content, exceptionMetadata, timestamp, smsMessageId)
}
protected fun createNormalContentTestSubject(): MessageContentProcessor {
val context = ApplicationProvider.getApplicationContext<Application>()
return MessageContentProcessor.create(context)
}
/**
* Creates a valid ServiceContentProto with a data message which can be built via
* `injectDataMessage`. This function is intended to be built on-top of for more
* specific scenario in subclasses.
*
* Example can be seen in __handleStoryMessageTest
*/
protected fun createServiceContentWithDataMessage(
messageSender: Recipient = Recipient.resolved(harness.others.first()),
injectDataMessage: SignalServiceProtos.DataMessage.Builder.() -> Unit
): SignalServiceContentProto {
return TestProtos.build {
serviceContent(
localAddress = address(uuid = harness.self.requireServiceId().uuid()).build(),
metadata = metadata(
address = address(uuid = messageSender.requireServiceId().uuid()).build()
).build()
).apply {
content = content().apply {
dataMessage = dataMessage().apply {
injectDataMessage()
}.build()
}.build()
}.build()
}
}
}
@@ -0,0 +1,181 @@
package org.thoughtcrime.securesms.messages
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.signal.core.util.requireLong
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MmsHelper
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.TestProtos
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import kotlin.random.Random
@Suppress("ClassName")
class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorTest() {
@Before
fun setUp() {
SignalDatabase.messages.deleteAllThreads()
}
@After
fun tearDown() {
SignalDatabase.messages.deleteAllThreads()
}
@Test
fun givenContentWithADirectStoryReplyWhenIProcessThenIInsertAReplyInTheCorrectThread() {
val sender = Recipient.resolved(harness.others.first())
val senderThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(sender)
val myStory = Recipient.resolved(SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!!)
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val expectedSentTime = 200L
val storyMessageId = MmsHelper.insert(
sentTimeMillis = expectedSentTime,
recipient = myStory,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = myStoryThread
)
SignalDatabase.storySends.insert(
messageId = storyMessageId,
recipientIds = listOf(sender.id),
sentTimestamp = expectedSentTime,
allowsReplies = true,
distributionId = DistributionId.MY_STORY
)
val expectedBody = "Hello!"
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
messageSender = sender,
storyAuthor = harness.self,
storySentTimestamp = expectedSentTime
) {
body = expectedBody
}
runTestWithContent(contentProto = storyContent)
val replyId = SignalDatabase.messages.getConversation(senderThreadId, 0, 1).use {
it.moveToFirst()
it.requireLong(MessageTable.ID)
}
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
assertEquals(ParentStoryId.DirectReply(storyMessageId).serialize(), replyRecord.parentStoryId!!.serialize())
assertEquals(expectedBody, replyRecord.body)
SignalDatabase.messages.deleteAllThreads()
}
@Test
fun givenContentWithAGroupStoryReplyWhenIProcessThenIInsertAReplyToTheCorrectStory() {
val sender = Recipient.resolved(harness.others[0])
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(
listOf(
DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setUuid(sender.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build()
)
)
.setRevision(0)
.build()
val group = SignalDatabase.groups.create(
groupMasterKey,
decryptedGroupState
)
val groupRecipient = Recipient.externalGroupExact(group)
val threadForGroup = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
val insertResult = MmsHelper.insert(
message = IncomingMediaMessage(
from = sender.id,
sentTimeMillis = 100L,
serverTimeMillis = 101L,
receivedTimeMillis = 102L,
storyType = StoryType.STORY_WITH_REPLIES
),
threadId = threadForGroup
)
val expectedBody = "Hello, World!"
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
messageSender = sender,
storyAuthor = sender,
storySentTimestamp = 100L
) {
groupV2 = TestProtos.build { groupContextV2(masterKeyBytes = groupMasterKey.serialize()).build() }
body = expectedBody
}
runTestWithContent(storyContent)
val replyId = SignalDatabase.messages.getStoryReplies(insertResult.get().messageId).use { cursor ->
assertEquals(1, cursor.count)
cursor.moveToFirst()
cursor.requireLong(MessageTable.ID)
}
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
assertEquals(threadForGroup, replyRecord.threadId)
assertEquals(expectedBody, replyRecord.body)
SignalDatabase.messages.deleteGroupStoryReplies(insertResult.get().messageId)
SignalDatabase.messages.deleteAllThreads()
}
/**
* Creates a ServiceContent proto with a StoryContext, and then
* uses `injectDataMessage` to fill in the data message object.
*/
private fun createServiceContentWithStoryContext(
messageSender: Recipient,
storyAuthor: Recipient,
storySentTimestamp: Long,
injectDataMessage: DataMessage.Builder.() -> Unit
): SignalServiceContentProto {
return createServiceContentWithDataMessage(messageSender) {
storyContext = TestProtos.build {
storyContext(
sentTimestamp = storySentTimestamp,
authorUuid = storyAuthor.requireServiceId().toString()
).build()
}
injectDataMessage()
}
}
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
val content = SignalServiceContent.createFromProto(contentProto)
val testSubject = createNormalContentTestSubject()
testSubject.doProcess(content = content!!)
}
}
@@ -0,0 +1,33 @@
package org.thoughtcrime.securesms.messages
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
@Suppress("ClassName")
class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTest() {
@Test
fun givenContentWithATextMessageWhenIProcessThenIInsertTheTextMessage() {
val testSubject: MessageContentProcessor = createNormalContentTestSubject()
val expectedBody = "Hello, World!"
val contentProto: SignalServiceContentProto = createServiceContentWithDataMessage {
body = expectedBody
}
val content = SignalServiceContent.createFromProto(contentProto)
// WHEN
testSubject.doProcess(content = content!!)
// THEN
val record = SignalDatabase.messages.getMessageRecord(1)
val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId)
assertEquals(1, threadSize)
assertTrue(record.isSecure)
assertEquals(expectedBody, record.body)
}
}
@@ -0,0 +1,141 @@
package org.thoughtcrime.securesms.profiles.manage
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.lifecycle.Lifecycle
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
import androidx.test.espresso.matcher.ViewMatchers.isNotEnabled
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.reactivex.rxjava3.schedulers.TestScheduler
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.RxTestSchedulerRule
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
class UsernameEditFragmentTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
private val ioScheduler = TestScheduler()
private val computationScheduler = TestScheduler()
@get:Rule
val testSchedulerRule = RxTestSchedulerRule(
ioTestScheduler = ioScheduler,
computationTestScheduler = computationScheduler
)
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testUsernameCreationInRegistration() {
val scenario = createScenario(true)
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
noViewFoundException.assertIsNull()
val toolbar = view as Toolbar
toolbar.navigationIcon.assertIsNull()
}
onView(withText(R.string.UsernameEditFragment__add_a_username)).check(matches(isDisplayed()))
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
}
@Ignore("Flakey espresso test.")
@Test
fun testUsernameCreationOutsideOfRegistration() {
val scenario = createScenario()
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
noViewFoundException.assertIsNull()
val toolbar = view as Toolbar
toolbar.navigationIcon.assertIsNotNull()
}
onView(withText(R.string.UsernameEditFragment_username)).check(matches(isDisplayed()))
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
}
@Ignore("Flakey espresso test.")
@Test
fun testNicknameUpdateHappyPath() {
val nickname = "Spiderman"
val discriminator = "4578"
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/username/reserved") {
MockResponse().success(ReserveUsernameResponse(username))
},
Put("/v1/accounts/username/confirm") {
MockResponse().success()
}
)
val scenario = createScenario(isInRegistration = true)
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.username_text)).perform(typeText(nickname))
computationScheduler.advanceTimeBy(501, TimeUnit.MILLISECONDS)
computationScheduler.triggerActions()
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
ioScheduler.triggerActions()
computationScheduler.triggerActions()
onView(withId(R.id.username_text)).perform(closeSoftKeyboard())
onView(withId(R.id.username_done_button)).check(matches(isDisplayed()))
onView(withId(R.id.username_done_button)).check(matches(isEnabled()))
onView(withText(username)).check(matches(isDisplayed()))
onView(withId(R.id.username_done_button)).perform(click())
computationScheduler.triggerActions()
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
}
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
return launchFragmentInContainer(
fragmentArgs = fragmentArgs,
themeResId = R.style.Signal_DayNight_NoActionBar
)
}
}
@@ -0,0 +1,139 @@
package org.thoughtcrime.securesms.safety
import io.reactivex.rxjava3.plugins.RxJavaPlugins
import io.reactivex.rxjava3.schedulers.TestScheduler
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
class SafetyNumberBottomSheetRepositoryTest {
@get:Rule val harness = SignalActivityRule(othersCount = 10)
private val testScheduler = TestScheduler()
private val subjectUnderTest = SafetyNumberBottomSheetRepository()
@Before
fun setUp() {
RxJavaPlugins.setInitIoSchedulerHandler { testScheduler }
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
}
@Test
fun givenIOnlyHave1to1Destinations_whenIGetBuckets_thenIOnlyHaveContactsBucketContainingAllRecipients() {
val recipients = harness.others
val destinations = harness.others.map { ContactSearchKey.RecipientSearchKey(it, false) }
val result = subjectUnderTest.getBuckets(recipients, destinations).test()
testScheduler.triggerActions()
result.assertValueAt(0) { map ->
assertMatch(map, mapOf(SafetyNumberBucket.ContactsBucket to harness.others))
}
}
@Test
fun givenIOnlyHaveASingle1to1Destination_whenIGetBuckets_thenIOnlyHaveContactsBucketContainingAllRecipients() {
// GIVEN
val recipients = harness.others
val destination = harness.others.take(1).map { ContactSearchKey.RecipientSearchKey(it, false) }
// WHEN
val result = subjectUnderTest.getBuckets(recipients, destination).test(1)
testScheduler.triggerActions()
// THEN
result.assertValue { map ->
assertMatch(map, mapOf(SafetyNumberBucket.ContactsBucket to harness.others.take(1)))
}
}
@Test
fun givenIHaveADistributionListDestination_whenIGetBuckets_thenIOnlyHaveDistributionListDestinationWithCorrespondingMembers() {
// GIVEN
val distributionListMembers = harness.others.take(5)
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
// WHEN
val result = subjectUnderTest.getBuckets(harness.others, listOf(destinationKey)).test(1)
testScheduler.triggerActions()
// THEN
result.assertValue { map ->
assertMatch(
map,
mapOf(
SafetyNumberBucket.DistributionListBucket(distributionList, "ListA") to harness.others.take(5)
)
)
}
}
@Test
fun givenIHaveADistributionListDestinationAndIGetBuckets_whenIRemoveFromStories_thenIOnlyHaveDistributionListDestinationWithCorrespondingMembers() {
// GIVEN
val distributionListMembers = harness.others.take(5)
val toRemove = distributionListMembers.last()
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
val testSubscriber = subjectUnderTest.getBuckets(distributionListMembers, listOf(destinationKey)).test(2)
testScheduler.triggerActions()
// WHEN
subjectUnderTest.removeFromStories(toRemove, listOf(destinationKey)).subscribe()
testSubscriber.request(1)
testScheduler.triggerActions()
testSubscriber.awaitCount(3)
// THEN
testSubscriber.assertValueAt(2) { map ->
assertMatch(
map,
mapOf(
SafetyNumberBucket.DistributionListBucket(distributionList, "ListA") to distributionListMembers.dropLast(1)
)
)
}
}
@Test
fun givenIHaveADistributionListDestinationAndIGetBuckets_whenIRemoveAllFromStory_thenINoLongerHaveEntryForThatBucket() {
// GIVEN
val distributionListMembers = harness.others.take(5)
val distributionList = SignalDatabase.distributionLists.createList("ListA", distributionListMembers)!!
val destinationKey = ContactSearchKey.RecipientSearchKey(SignalDatabase.distributionLists.getRecipientId(distributionList)!!, true)
val testSubscriber = subjectUnderTest.getBuckets(distributionListMembers, listOf(destinationKey)).test(2)
testScheduler.triggerActions()
// WHEN
subjectUnderTest.removeAllFromStory(distributionListMembers, distributionList).subscribe()
testSubscriber.request(1)
testScheduler.triggerActions()
testSubscriber.awaitCount(3)
// THEN
testSubscriber.assertValueAt(2) { map ->
assertMatch(map, mapOf())
}
}
private fun assertMatch(
resultMap: Map<SafetyNumberBucket, List<SafetyNumberRecipient>>,
idMap: Map<SafetyNumberBucket, List<RecipientId>>
): Boolean {
assertEquals("Result and ID Maps had different key sets", idMap.keys, resultMap.keys)
resultMap.forEach { (bucket, members) ->
assertEquals("Mismatch in Bucket $bucket", idMap[bucket], members.map { it.recipient.id })
}
return true
}
}
@@ -0,0 +1,127 @@
package org.thoughtcrime.securesms.storage
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ContactRecordProcessorTest {
@Before
fun setup() {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
}
@Test
fun process_splitContact_normalSplit() {
// GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setServiceId(ACI_A.toString())
setUnregisteredAtTimestamp(100)
}
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
assertEquals(originalId, byAci)
assertEquals(byE164, byPni)
assertNotEquals(byAci, byE164)
}
@Test
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
// GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setServiceId(ACI_A.toString())
setUnregisteredAtTimestamp(0)
}
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(originalId, byAci)
assertEquals(byE164, byPni)
assertEquals(byAci, byE164)
}
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
}
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
.where("${RecipientTable.ID} = ?", recipientId)
.run()
}
companion object {
val ACI_A = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val ACI_B = ACI.from(UUID.fromString("bbbb0000-0b60-4a68-9cd9-ed2f8453f9ed"))
val ACI_SELF = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val PNI_A = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
val PNI_B = PNI.from(UUID.fromString("bbbb1111-cd55-40bf-adda-c35a85375533"))
val PNI_SELF = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val E164_A = "+12222222222"
const val E164_B = "+13333333333"
const val E164_SELF = "+10000000000"
val STORAGE_ID_A: StorageId = StorageId.forContact(byteArrayOf(1, 2, 3, 4))
val STORAGE_ID_B: StorageId = StorageId.forContact(byteArrayOf(5, 6, 7, 8))
val STORAGE_ID_C: StorageId = StorageId.forContact(byteArrayOf(9, 10, 11, 12))
}
}
@@ -0,0 +1,114 @@
package org.thoughtcrime.securesms.testing
import io.reactivex.rxjava3.core.Single
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.protocol.util.Medium
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.kbs.HashedPin
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyResponse
import org.whispersystems.signalservice.internal.push.PreKeyResponseItem
import org.whispersystems.signalservice.internal.push.PushServiceSocket
import org.whispersystems.signalservice.internal.push.SenderCertificate
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import java.security.SecureRandom
/**
* Warehouse of reusable test data and mock configurations.
*/
object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
backupCredentials = AuthCredentials.create("username", "password")
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
devices = listOf(
DeviceInfo().apply {
id = 1
}
)
}
fun createVerifyAccountResponse(aci: ServiceId, newPni: ServiceId): VerifyAccountResponse {
return VerifyAccountResponse().apply {
uuid = aci.toString()
pni = newPni.toString()
storageCapable = false
}
}
fun createWhoAmIResponse(aci: ServiceId, pni: ServiceId, e164: String): WhoAmIResponse {
return WhoAmIResponse().apply {
this.uuid = aci.toString()
this.pni = pni.toString()
this.number = e164
}
}
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
val tokenData: TokenData = mock {
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
on { basicAuth } doReturn "basicAuth"
on { triesRemaining } doReturn 10
on { tokenResponse } doReturn TokenResponse()
}
kbsRepository.stub {
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
}
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: HashedPin?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(any(), any()) } doReturn session
}
}
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())
val device = PreKeyResponseItem().apply {
this.deviceId = deviceId
registrationId = KeyHelper.generateRegistrationId(false)
signedPreKey = SignedPreKeyEntity(signedPreKeyRecord.id, signedPreKeyRecord.keyPair.publicKey, signedPreKeyRecord.signature)
preKey = PreKeyEntity(oneTimePreKey.id, oneTimePreKey.keyPair.publicKey)
}
return PreKeyResponse().apply {
identityKey = identity.publicKey
devices = listOf(device)
}
}
}
@@ -0,0 +1,48 @@
package org.thoughtcrime.securesms.testing
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.RecordedRequest
import okhttp3.mockwebserver.SocketPolicy
import org.thoughtcrime.securesms.util.JsonUtils
import java.util.concurrent.TimeUnit
typealias ResponseFactory = (request: RecordedRequest) -> MockResponse
/**
* Represent an HTTP verb for mocking web requests.
*/
sealed class Verb(val verb: String, val path: String, val responseFactory: ResponseFactory)
class Get(path: String, responseFactory: ResponseFactory) : Verb("GET", path, responseFactory)
class Put(path: String, responseFactory: ResponseFactory) : Verb("PUT", path, responseFactory)
fun MockResponse.success(response: Any? = null): MockResponse {
return setResponseCode(200).apply {
if (response != null) {
setBody(JsonUtils.toJson(response))
}
}
}
fun MockResponse.failure(code: Int, response: Any? = null): MockResponse {
return setResponseCode(code).apply {
if (response != null) {
setBody(JsonUtils.toJson(response))
}
}
}
fun MockResponse.connectionFailure(): MockResponse {
return setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
}
fun MockResponse.timeout(): MockResponse {
return setHeadersDelay(1, TimeUnit.DAYS)
.setBodyDelay(1, TimeUnit.DAYS)
}
inline fun <reified T> RecordedRequest.parsedRequestBody(): T {
val bodyString = String(body.readByteArray())
return JsonUtils.fromJson(bodyString, T::class.java)
}
@@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.testing
import io.reactivex.rxjava3.plugins.RxJavaPlugins
import io.reactivex.rxjava3.schedulers.TestScheduler
import org.junit.rules.ExternalResource
/**
* JUnit Rule which initialises Rx thread schedulers. If a specific
* scheduler is not specified, it defaults to the `defaultTestScheduler`
*/
class RxTestSchedulerRule(
val defaultTestScheduler: TestScheduler = TestScheduler(),
val ioTestScheduler: TestScheduler = defaultTestScheduler,
val computationTestScheduler: TestScheduler = defaultTestScheduler,
val singleTestScheduler: TestScheduler = defaultTestScheduler,
val newThreadTestScheduler: TestScheduler = defaultTestScheduler
) : ExternalResource() {
override fun before() {
RxJavaPlugins.setInitIoSchedulerHandler { ioTestScheduler }
RxJavaPlugins.setIoSchedulerHandler { ioTestScheduler }
RxJavaPlugins.setInitComputationSchedulerHandler { computationTestScheduler }
RxJavaPlugins.setComputationSchedulerHandler { computationTestScheduler }
RxJavaPlugins.setInitSingleSchedulerHandler { singleTestScheduler }
RxJavaPlugins.setSingleSchedulerHandler { singleTestScheduler }
RxJavaPlugins.setInitNewThreadSchedulerHandler { newThreadTestScheduler }
RxJavaPlugins.setNewThreadSchedulerHandler { newThreadTestScheduler }
}
override fun after() {
RxJavaPlugins.reset()
}
}
@@ -0,0 +1,137 @@
package org.thoughtcrime.securesms.testing
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.preference.PreferenceManager
import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.mockwebserver.MockResponse
import org.junit.rules.ExternalResource
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.registration.RegistrationData
import org.thoughtcrime.securesms.registration.RegistrationRepository
import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.VerifyResponse
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import java.lang.IllegalArgumentException
import java.util.UUID
/**
* Test rule to use that sets up the application in a mostly registered state. Enough so that most
* activities should be launchable directly.
*
* To use: `@get:Rule val harness = SignalActivityRule()`
*/
class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication()
lateinit var context: Context
private set
lateinit var self: Recipient
private set
lateinit var others: List<RecipientId>
private set
override fun before() {
context = InstrumentationRegistry.getInstrumentation().targetContext
self = setupSelf()
others = setupOthers()
InstrumentationApplicationDependencyProvider.clearHandlers()
}
private fun setupSelf(): Recipient {
DeviceTransferBlockingInterceptor.getInstance().blockNetwork()
PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit()
val masterSecret = MasterSecretUtil.generateMasterSecret(application, MasterSecretUtil.UNENCRYPTED_PASSPHRASE)
MasterSecretUtil.generateAsymmetricMasterSecret(application, masterSecret)
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
val registrationRepository = RegistrationRepository(application)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
RegistrationData(
code = "123123",
e164 = "+15555550101",
password = Util.getSecret(18),
registrationId = registrationRepository.registrationId,
profileKey = registrationRepository.getProfileKey("+15555550101"),
fcmToken = null,
pniRegistrationId = registrationRepository.pniRegistrationId
),
VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
RegistrationUtil.maybeMarkRegistrationComplete(application)
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
return Recipient.self()
}
private fun setupOthers(): List<RecipientId> {
val others = mutableListOf<RecipientId>()
if (othersCount !in 0 until 1000) {
throw IllegalArgumentException("$othersCount must be between 0 and 1000")
}
for (i in 0 until othersCount) {
val aci = ACI.from(UUID.randomUUID())
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), IdentityKeyUtil.generateIdentityKeyPair().publicKey)
others += recipientId
}
return others
}
inline fun <reified T : Activity> launchActivity(initIntent: Intent.() -> Unit = {}): ActivityScenario<T> {
return androidx.test.core.app.launchActivity(Intent(context, T::class.java).apply(initIntent))
}
fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) {
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
}
fun getIdentity(recipient: Recipient): IdentityKey {
return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
}
fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) {
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
}
}
@@ -0,0 +1,40 @@
package org.thoughtcrime.securesms.testing
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import java.util.UUID
/**
* Sets up bare-minimum to allow writing unit tests against the database,
* including setting up the local ACI and PNI pair.
*
* @param deleteAllThreadsOnEachRun Run deleteAllThreads between each unit test
*/
class SignalDatabaseRule(
private val deleteAllThreadsOnEachRun: Boolean = true
) : TestWatcher() {
val localAci: ACI = ACI.from(UUID.randomUUID())
val localPni: PNI = PNI.from(UUID.randomUUID())
override fun starting(description: Description?) {
deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
}
override fun finished(description: Description?) {
deleteAllThreads()
}
private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) {
SignalDatabase.messages.deleteAllThreads()
}
}
}
@@ -0,0 +1,16 @@
package org.thoughtcrime.securesms.testing
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import org.thoughtcrime.securesms.SignalInstrumentationApplicationContext
/**
* Custom runner that replaces application with [SignalInstrumentationApplicationContext].
*/
@Suppress("unused")
class SignalTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, SignalInstrumentationApplicationContext::class.java.name, context)
}
}
@@ -0,0 +1,70 @@
package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto
import org.whispersystems.signalservice.internal.serialize.protos.MetadataProto
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import java.util.UUID
import kotlin.random.Random
class TestProtos private constructor() {
fun address(
uuid: UUID = UUID.randomUUID()
): AddressProto.Builder {
return AddressProto.newBuilder()
.setUuid(ServiceId.from(uuid).toByteString())
}
fun metadata(
address: AddressProto = address().build()
): MetadataProto.Builder {
return MetadataProto.newBuilder()
.setAddress(address)
}
fun groupContextV2(
revision: Int = 0,
masterKeyBytes: ByteArray = Random.Default.nextBytes(GroupMasterKey.SIZE)
): GroupContextV2.Builder {
return GroupContextV2.newBuilder()
.setRevision(revision)
.setMasterKey(ByteString.copyFrom(masterKeyBytes))
}
fun storyContext(
sentTimestamp: Long = Random.nextLong(),
authorUuid: String = UUID.randomUUID().toString()
): DataMessage.StoryContext.Builder {
return DataMessage.StoryContext.newBuilder()
.setAuthorUuid(authorUuid)
.setSentTimestamp(sentTimestamp)
}
fun dataMessage(): DataMessage.Builder {
return DataMessage.newBuilder()
}
fun content(): SignalServiceProtos.Content.Builder {
return SignalServiceProtos.Content.newBuilder()
}
fun serviceContent(
localAddress: AddressProto = address().build(),
metadata: MetadataProto = metadata().build()
): SignalServiceContentProto.Builder {
return SignalServiceContentProto.newBuilder()
.setLocalAddress(localAddress)
.setMetadata(metadata)
}
companion object {
fun <T> build(buildFn: TestProtos.() -> T): T {
return TestProtos().buildFn()
}
}
}
@@ -0,0 +1,46 @@
package org.thoughtcrime.securesms.testing
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import java.util.concurrent.CountDownLatch
/**
* Run the given [runnable] on a new thread and wait for it to finish.
*/
fun runSync(runnable: () -> Unit) {
val lock = CountDownLatch(1)
Thread {
try {
runnable.invoke()
} finally {
lock.countDown()
}
}.start()
lock.await()
}
/* Various kotlin-ifications of hamcrest matchers */
fun <T : Any?> T.assertIsNull() {
assertThat(this, nullValue())
}
fun <T : Any?> T.assertIsNotNull() {
assertThat(this, notNullValue())
}
infix fun <T : Any> T.assertIs(expected: T) {
assertThat(this, `is`(expected))
}
infix fun <T : Any> T.assertIsNot(expected: T) {
assertThat(this, not(`is`(expected)))
}
infix fun <E, T : Collection<E>> T.assertIsSize(expected: Int) {
assertThat(this, hasSize(expected))
}
@@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.util;
/**
* A class that allows us to inject feature flags during tests.
*/
public final class FeatureFlagsAccessor {
public static void forceValue(String key, Object value) {
FeatureFlags.FORCED_VALUES.put(FeatureFlags.PHONE_NUMBER_PRIVACY, true);
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:usesCleartextTraffic="true"
tools:replace="android:usesCleartextTraffic"
tools:ignore="UnusedAttribute" />
</manifest>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/signal_accent_green"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,4 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="app_name">Signal (Instrumentation)</string>
</resources>
+131 -42
View File
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="org.thoughtcrime.securesms">
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.view" /> <uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2,androidx.camera.lifecycle,androidx.camera.view" />
@@ -22,7 +21,6 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/> <uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/> <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" <uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/> tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/>
@@ -91,13 +89,18 @@
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<application android:name=".ApplicationContext" <application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
tools:replace="android:allowBackup"
android:resizeableActivity="true" android:resizeableActivity="true"
android:allowBackup="false" android:fullBackupOnly="false"
android:allowBackup="true"
android:backupAgent=".absbackup.SignalBackupAgent"
android:theme="@style/TextSecure.LightTheme" android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true"> android:largeHeap="true">
@@ -107,7 +110,7 @@
<meta-data <meta-data
android:name="com.google.android.geo.API_KEY" android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"/> android:value="${mapsKey}"/>
<meta-data android:name="android.supports_size_changes" <meta-data android:name="android.supports_size_changes"
android:value="true" /> android:value="true" />
@@ -153,7 +156,8 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity" <activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -177,8 +181,9 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
<activity android:name=".sharing.ShareActivity" <activity android:name=".sharing.v2.ShareActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:taskAffinity="" android:taskAffinity=""
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
@@ -209,19 +214,20 @@
</activity> </activity>
<activity android:name=".stickers.StickerPackPreviewActivity" <activity android:name=".stickers.StickerPackPreviewActivity"
android:exported="true"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask" android:launchMode="singleTask"
android:noHistory="true" android:noHistory="true"
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" android:exported="true" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sgnl" <data android:scheme="sgnl"
android:host="addstickers" /> android:host="addstickers" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@@ -252,10 +258,11 @@
</activity-alias> </activity-alias>
<activity android:name=".deeplinks.DeepLinkEntryActivity" <activity android:name=".deeplinks.DeepLinkEntryActivity"
android:exported="true"
android:noHistory="true" android:noHistory="true"
android:theme="@style/Signal.Transparent"> android:theme="@style/Signal.Transparent">
<intent-filter> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@@ -272,7 +279,7 @@
android:host="signal.group"/> android:host="signal.group"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@@ -282,7 +289,7 @@
android:host="signal.tube" /> android:host="signal.tube" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
@@ -325,11 +332,6 @@
android:theme="@style/Signal.DayNight.NoActionBar" android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"/> android:windowSoftInputMode="adjustResize"/>
<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" <activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask" android:launchMode="singleTask"
@@ -363,20 +365,32 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity" <activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.FullScreenMedia" android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop" android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".PassphraseChangeActivity" <activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase" android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".verify.VerifyIdentityActivity" <activity android:name=".verify.VerifyIdentityActivity"
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar" android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".components.settings.app.AppSettingsActivity" <activity android:name=".components.settings.app.AppSettingsActivity"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar" android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
@@ -386,6 +400,32 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".stories.my.MyStoriesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".stories.settings.StorySettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
<activity
android:name=".stories.viewer.StoryViewerActivity"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity" <activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar" android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -396,6 +436,11 @@
android:windowSoftInputMode="stateAlwaysHidden"> android:windowSoftInputMode="stateAlwaysHidden">
</activity> </activity>
<activity android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity android:name=".wallpaper.ChatWallpaperActivity" <activity android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -419,7 +464,7 @@
<activity android:name=".registration.RegistrationNavigationActivity" <activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/TextSecure.LightRegistrationTheme" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -437,6 +482,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity" <activity android:name=".DeviceActivity"
android:screenOrientation="portrait"
android:label="@string/AndroidManifest__linked_devices" android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -444,10 +490,12 @@
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaPreviewActivity" <activity android:name=".mediapreview.MediaPreviewV2Activity"
android:label="@string/AndroidManifest__media_preview" android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".AvatarPreviewActivity" <activity android:name=".AvatarPreviewActivity"
@@ -472,10 +520,11 @@
android:finishOnTaskLaunch="true" /> android:finishOnTaskLaunch="true" />
<activity android:name=".PlayServicesProblemActivity" <activity android:name=".PlayServicesProblemActivity"
android:exported="false"
android:theme="@style/TextSecure.DialogActivity" android:theme="@style/TextSecure.DialogActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".SmsSendtoActivity"> <activity android:name=".SmsSendtoActivity" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SENDTO" /> <action android:name="android.intent.action.SENDTO" />
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -494,6 +543,7 @@
</activity> </activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare" <activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:exported="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE" android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen" android:theme="@style/NoAnimation.Theme.BlackScreen"
@@ -509,11 +559,12 @@
</activity> </activity>
<activity android:name=".mediasend.AvatarSelectionActivity" <activity android:name=".mediasend.AvatarSelectionActivity"
android:theme="@style/TextSecure.FullScreenMedia" android:theme="@style/TextSecure.DarkNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".blocked.BlockedUsersActivity" <activity android:name=".blocked.BlockedUsersActivity"
android:theme="@style/TextSecure.LightTheme" android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.ImageEditorStickerSelectActivity" <activity android:name=".scribbles.ImageEditorStickerSelectActivity"
@@ -524,6 +575,10 @@
android:theme="@style/TextSecure.LightRegistrationTheme" android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" /> android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".profiles.username.AddAUsernameActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".profiles.manage.ManageProfileActivity" <activity android:name=".profiles.manage.ManageProfileActivity"
android:theme="@style/TextSecure.LightTheme" android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize" /> android:windowSoftInputMode="stateVisible|adjustResize" />
@@ -607,13 +662,19 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
<activity android:name=".ratelimit.RecaptchaProofActivity" <activity android:name=".ratelimit.RecaptchaProofActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" /> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity" <activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.FullScreenMedia" /> android:theme="@style/TextSecure.DarkNoActionBar" />
<activity android:name=".wallpaper.crop.WallpaperCropActivity" <activity android:name=".wallpaper.crop.WallpaperCropActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -624,18 +685,35 @@
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService"/> <activity android:name=".exporter.flow.SmsExportActivity"
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/> android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/> <service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/> <service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
<service android:name=".service.webrtc.AndroidCallConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
<service android:name=".components.voice.VoiceNotePlaybackService"> <service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name="androidx.media.session.MediaButtonReceiver" > <receiver android:name="androidx.media.session.MediaButtonReceiver" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
@@ -671,9 +749,11 @@
<service android:name=".service.GenericForegroundService"/> <service android:name=".service.GenericForegroundService"/>
<service android:name=".gcm.FcmFetchService" /> <service android:name=".gcm.FcmFetchBackgroundService" />
<service android:name=".gcm.FcmReceiveService"> <service android:name=".gcm.FcmFetchForegroundService" />
<service android:name=".gcm.FcmReceiveService" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" /> <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter> </intent-filter>
@@ -730,8 +810,12 @@
<receiver android:name=".service.ExpirationListener" /> <receiver android:name=".service.ExpirationListener" />
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" /> <receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" /> <receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" /> <receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
@@ -762,51 +846,53 @@
</provider> </provider>
<receiver android:name=".service.BootReceiver"> <receiver android:name=".service.BootReceiver" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="org.thoughtcrime.securesms.RESTART"/> <action android:name="org.thoughtcrime.securesms.RESTART"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.DirectoryRefreshListener"> <receiver android:name=".service.DirectoryRefreshListener" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.RotateSignedPreKeyListener"> <receiver android:name=".service.RotateSignedPreKeyListener" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.RotateSenderCertificateListener"> <receiver android:name=".service.RotateSenderCertificateListener" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver"> <receiver android:name=".messageprocessingalarm.MessageProcessReceiver" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" /> <action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.LocalBackupListener"> <receiver android:name=".service.LocalBackupListener" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".service.PersistentConnectionBootListener"> <receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".notifications.LocaleChangedReceiver"> <receiver android:name=".notifications.LocaleChangedReceiver" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.LOCALE_CHANGED"/> <action android:name="android.intent.action.LOCALE_CHANGED"/>
</intent-filter> </intent-filter>
@@ -814,7 +900,7 @@
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/> <receiver android:name=".notifications.MessageNotifier$ReminderReceiver"/>
<receiver android:name=".notifications.DeleteNotificationReceiver"> <receiver android:name=".notifications.DeleteNotificationReceiver" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/> <action android:name="org.thoughtcrime.securesms.DELETE_NOTIFICATION"/>
</intent-filter> </intent-filter>
@@ -842,16 +928,19 @@
<service <service
android:name=".jobmanager.KeepAliveService" android:name=".jobmanager.KeepAliveService"
android:enabled="@bool/enable_alarm_manager" /> android:enabled="@bool/enable_alarm_manager"
android:exported="false"/>
<receiver <receiver
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver" android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
android:enabled="@bool/enable_alarm_manager" /> android:enabled="@bool/enable_alarm_manager"
android:exported="false"/>
<!-- Probably don't need this one --> <!-- Probably don't need this one -->
<receiver <receiver
android:name=".jobmanager.BootReceiver" android:name=".jobmanager.BootReceiver"
android:enabled="true"> android:enabled="true"
android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter> </intent-filter>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long
@@ -1,848 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.view;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.Surface;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.FocusMeteringAction;
import androidx.camera.core.FocusMeteringResult;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Logger;
import androidx.camera.core.MeteringPoint;
import androidx.camera.core.MeteringPointFactory;
import androidx.camera.core.VideoCapture;
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
import androidx.camera.core.impl.LensFacingConverter;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.core.util.Consumer;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import com.google.common.util.concurrent.ListenableFuture;
import org.signal.core.util.logging.Log;
import java.io.File;
import java.util.concurrent.Executor;
/**
* A {@link View} that displays a preview of the camera with methods {@link
* #takePicture(Executor, OnImageCapturedCallback)},
* {@link #takePicture(ImageCapture.OutputFileOptions, Executor, OnImageSavedCallback)},
* {@link #startRecording(File , Executor , OnVideoSavedCallback callback)}
* and {@link #stopRecording()}.
*
* <p>Because the Camera is a limited resource and consumes a high amount of power, CameraView must
* be opened/closed. CameraView will handle opening/closing automatically through use of a {@link
* LifecycleOwner}. Use {@link #bindToLifecycle(LifecycleOwner)} to start the camera.
*/
@RequiresApi(21)
@SuppressLint("RestrictedApi")
public final class SignalCameraView extends FrameLayout {
static final String TAG = Log.tag(SignalCameraView.class);
static final int INDEFINITE_VIDEO_DURATION = -1;
static final int INDEFINITE_VIDEO_SIZE = -1;
private static final String EXTRA_SUPER = "super";
private static final String EXTRA_ZOOM_RATIO = "zoom_ratio";
private static final String EXTRA_PINCH_TO_ZOOM_ENABLED = "pinch_to_zoom_enabled";
private static final String EXTRA_FLASH = "flash";
private static final String EXTRA_MAX_VIDEO_DURATION = "max_video_duration";
private static final String EXTRA_MAX_VIDEO_SIZE = "max_video_size";
private static final String EXTRA_SCALE_TYPE = "scale_type";
private static final String EXTRA_CAMERA_DIRECTION = "camera_direction";
private static final String EXTRA_CAPTURE_MODE = "captureMode";
private static final int LENS_FACING_NONE = 0;
private static final int LENS_FACING_FRONT = 1;
private static final int LENS_FACING_BACK = 2;
private static final int FLASH_MODE_AUTO = 1;
private static final int FLASH_MODE_ON = 2;
private static final int FLASH_MODE_OFF = 4;
// For tap-to-focus
private long mDownEventTimestamp;
// For pinch-to-zoom
private PinchToZoomGestureDetector mPinchToZoomGestureDetector;
private boolean mIsPinchToZoomEnabled = true;
SignalCameraXModule mCameraModule;
private final DisplayManager.DisplayListener mDisplayListener =
new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
}
@Override
public void onDisplayRemoved(int displayId) {
}
@Override
public void onDisplayChanged(int displayId) {
mCameraModule.invalidateView();
}
};
private PreviewView mPreviewView;
// For accessibility event
private MotionEvent mUpEvent;
// BEGIN Custom Signal Code Block
private Consumer<Throwable> errorConsumer;
private Throwable pendingError;
// END Custom Signal Code Block
public SignalCameraView(@NonNull Context context) {
this(context, null);
}
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
@RequiresApi(21)
public SignalCameraView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
/**
* Binds control of the camera used by this view to the given lifecycle.
*
* <p>This links opening/closing the camera to the given lifecycle. The camera will not operate
* unless this method is called with a valid {@link LifecycleOwner} that is not in the {@link
* androidx.lifecycle.Lifecycle.State#DESTROYED} state. Call this method only once camera
* permissions have been obtained.
*
* <p>Once the provided lifecycle has transitioned to a {@link
* androidx.lifecycle.Lifecycle.State#DESTROYED} state, CameraView must be bound to a new
* lifecycle through this method in order to operate the camera.
*
* @param lifecycleOwner The lifecycle that will control this view's camera
* @throws IllegalArgumentException if provided lifecycle is in a {@link
* androidx.lifecycle.Lifecycle.State#DESTROYED} state.
* @throws IllegalStateException if camera permissions are not granted.
*/
// BEGIN Custom Signal Code Block
@RequiresPermission(permission.CAMERA)
public void bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner, Consumer<Throwable> errorConsumer) {
mCameraModule.bindToLifecycle(lifecycleOwner);
this.errorConsumer = errorConsumer;
if (pendingError != null) {
errorConsumer.accept(pendingError);
}
}
// END Custom Signal Code Block
private void init(Context context, @Nullable AttributeSet attrs) {
addView(mPreviewView = new PreviewView(getContext()), 0 /* view position */);
// Begin custom signal code block
mPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
mCameraModule = new SignalCameraXModule(this, error -> {
if (errorConsumer != null) {
errorConsumer.accept(error);
} else {
pendingError = error;
}
});
// End custom signal code block
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CameraView);
setScaleType(
PreviewView.ScaleType.fromId(
a.getInteger(R.styleable.CameraView_scaleType,
getScaleType().getId())));
setPinchToZoomEnabled(
a.getBoolean(
R.styleable.CameraView_pinchToZoomEnabled, isPinchToZoomEnabled()));
setCaptureMode(
CaptureMode.fromId(
a.getInteger(R.styleable.CameraView_captureMode,
getCaptureMode().getId())));
int lensFacing = a.getInt(R.styleable.CameraView_lensFacing, LENS_FACING_BACK);
switch (lensFacing) {
case LENS_FACING_NONE:
setCameraLensFacing(null);
break;
case LENS_FACING_FRONT:
setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
break;
case LENS_FACING_BACK:
setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
break;
default:
// Unhandled event.
}
int flashMode = a.getInt(R.styleable.CameraView_flash, 0);
switch (flashMode) {
case FLASH_MODE_AUTO:
setFlash(ImageCapture.FLASH_MODE_AUTO);
break;
case FLASH_MODE_ON:
setFlash(ImageCapture.FLASH_MODE_ON);
break;
case FLASH_MODE_OFF:
setFlash(ImageCapture.FLASH_MODE_OFF);
break;
default:
// Unhandled event.
}
a.recycle();
}
if (getBackground() == null) {
setBackgroundColor(0xFF111111);
}
mPinchToZoomGestureDetector = new PinchToZoomGestureDetector(context);
}
@Override
@NonNull
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
@NonNull
protected Parcelable onSaveInstanceState() {
// TODO(b/113884082): Decide what belongs here or what should be invalidated on
// configuration
// change
Bundle state = new Bundle();
state.putParcelable(EXTRA_SUPER, super.onSaveInstanceState());
state.putInt(EXTRA_SCALE_TYPE, getScaleType().getId());
state.putFloat(EXTRA_ZOOM_RATIO, getZoomRatio());
state.putBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED, isPinchToZoomEnabled());
state.putString(EXTRA_FLASH, FlashModeConverter.nameOf(getFlash()));
state.putLong(EXTRA_MAX_VIDEO_DURATION, getMaxVideoDuration());
state.putLong(EXTRA_MAX_VIDEO_SIZE, getMaxVideoSize());
if (getCameraLensFacing() != null) {
state.putString(EXTRA_CAMERA_DIRECTION,
LensFacingConverter.nameOf(getCameraLensFacing()));
}
state.putInt(EXTRA_CAPTURE_MODE, getCaptureMode().getId());
return state;
}
@Override
protected void onRestoreInstanceState(@Nullable Parcelable savedState) {
// TODO(b/113884082): Decide what belongs here or what should be invalidated on
// configuration
// change
if (savedState instanceof Bundle) {
Bundle state = (Bundle) savedState;
super.onRestoreInstanceState(state.getParcelable(EXTRA_SUPER));
setScaleType(PreviewView.ScaleType.fromId(state.getInt(EXTRA_SCALE_TYPE)));
setZoomRatio(state.getFloat(EXTRA_ZOOM_RATIO));
setPinchToZoomEnabled(state.getBoolean(EXTRA_PINCH_TO_ZOOM_ENABLED));
setFlash(FlashModeConverter.valueOf(state.getString(EXTRA_FLASH)));
setMaxVideoDuration(state.getLong(EXTRA_MAX_VIDEO_DURATION));
setMaxVideoSize(state.getLong(EXTRA_MAX_VIDEO_SIZE));
String lensFacingString = state.getString(EXTRA_CAMERA_DIRECTION);
setCameraLensFacing(
TextUtils.isEmpty(lensFacingString)
? null
: LensFacingConverter.valueOf(lensFacingString));
setCaptureMode(CaptureMode.fromId(state.getInt(EXTRA_CAPTURE_MODE)));
} else {
super.onRestoreInstanceState(savedState);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
DisplayManager dpyMgr =
(DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
dpyMgr.registerDisplayListener(mDisplayListener, new Handler(Looper.getMainLooper()));
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
DisplayManager dpyMgr =
(DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE);
dpyMgr.unregisterDisplayListener(mDisplayListener);
}
/**
* Gets the {@link LiveData} of the underlying {@link PreviewView}'s
* {@link PreviewView.StreamState}.
*
* @return A {@link LiveData} containing the {@link PreviewView.StreamState}. Apps can either
* get current value by {@link LiveData#getValue()} or register a observer by
* {@link LiveData#observe}.
* @see PreviewView#getPreviewStreamState()
*/
@NonNull
public LiveData<PreviewView.StreamState> getPreviewStreamState() {
return mPreviewView.getPreviewStreamState();
}
@NonNull
PreviewView getPreviewView() {
return mPreviewView;
}
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Since bindToLifecycle will depend on the measured dimension, only call it when measured
// dimension is not 0x0
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
mCameraModule.bindToLifecycleAfterViewMeasured();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// In case that the CameraView size is always set as 0x0, we still need to trigger to force
// binding to lifecycle
mCameraModule.bindToLifecycleAfterViewMeasured();
mCameraModule.invalidateView();
super.onLayout(changed, left, top, right, bottom);
}
/**
* @return One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90}, {@link
* Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
*/
int getDisplaySurfaceRotation() {
Display display = getDisplay();
// Null when the View is detached. If we were in the middle of a background operation,
// better to not NPE. When the background operation finishes, it'll realize that the camera
// was closed.
if (display == null) {
return 0;
}
return display.getRotation();
}
/**
* Returns the scale type used to scale the preview.
*
* @return The current {@link PreviewView.ScaleType}.
*/
@NonNull
public PreviewView.ScaleType getScaleType() {
return mPreviewView.getScaleType();
}
/**
* Sets the view finder scale type.
*
* <p>This controls how the view finder should be scaled and positioned within the view.
*
* @param scaleType The desired {@link PreviewView.ScaleType}.
*/
public void setScaleType(@NonNull PreviewView.ScaleType scaleType) {
mPreviewView.setScaleType(scaleType);
}
/**
* Returns the scale type used to scale the preview.
*
* @return The current {@link CaptureMode}.
*/
@NonNull
public CaptureMode getCaptureMode() {
return mCameraModule.getCaptureMode();
}
/**
* Sets the CameraView capture mode
*
* <p>This controls only image or video capture function is enabled or both are enabled.
*
* @param captureMode The desired {@link CaptureMode}.
*/
public void setCaptureMode(@NonNull CaptureMode captureMode) {
mCameraModule.setCaptureMode(captureMode);
}
/**
* Returns the maximum duration of videos, or {@link #INDEFINITE_VIDEO_DURATION} if there is no
* timeout.
*
* @hide Not currently implemented.
*/
@RestrictTo(Scope.LIBRARY_GROUP)
public long getMaxVideoDuration() {
return mCameraModule.getMaxVideoDuration();
}
/**
* Sets the maximum video duration before
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)} is called
* automatically.
* Use {@link #INDEFINITE_VIDEO_DURATION} to disable the timeout.
*/
private void setMaxVideoDuration(long duration) {
mCameraModule.setMaxVideoDuration(duration);
}
/**
* Returns the maximum size of videos in bytes, or {@link #INDEFINITE_VIDEO_SIZE} if there is no
* timeout.
*/
private long getMaxVideoSize() {
return mCameraModule.getMaxVideoSize();
}
/**
* Sets the maximum video size in bytes before
* {@link OnVideoSavedCallback#onVideoSaved(VideoCapture.OutputFileResults)}
* is called automatically. Use {@link #INDEFINITE_VIDEO_SIZE} to disable the size restriction.
*/
private void setMaxVideoSize(long size) {
mCameraModule.setMaxVideoSize(size);
}
/**
* Takes a picture, and calls {@link OnImageCapturedCallback#onCaptureSuccess(ImageProxy)}
* once when done.
*
* @param executor The executor in which the callback methods will be run.
* @param callback Callback which will receive success or failure callbacks.
*/
public void takePicture(@NonNull Executor executor, @NonNull OnImageCapturedCallback callback) {
mCameraModule.takePicture(executor, callback);
}
/**
* Takes a picture and calls
* {@link OnImageSavedCallback#onImageSaved(ImageCapture.OutputFileResults)} when done.
*
* <p> The value of {@link ImageCapture.Metadata#isReversedHorizontal()} in the
* {@link ImageCapture.OutputFileOptions} will be overwritten based on camera direction. For
* front camera, it will be set to true; for back camera, it will be set to false.
*
* @param outputFileOptions Options to store the newly captured image.
* @param executor The executor in which the callback methods will be run.
* @param callback Callback which will receive success or failure.
*/
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
@NonNull Executor executor,
@NonNull OnImageSavedCallback callback) {
mCameraModule.takePicture(outputFileOptions, executor, callback);
}
/**
* Takes a video and calls the OnVideoSavedCallback when done.
*
* @param outputFileOptions Options to store the newly captured video.
* @param executor The executor in which the callback methods will be run.
* @param callback Callback which will receive success or failure.
*/
public void startRecording(@NonNull VideoCapture.OutputFileOptions outputFileOptions,
@NonNull Executor executor,
@NonNull OnVideoSavedCallback callback) {
mCameraModule.startRecording(outputFileOptions, executor, callback);
}
/** Stops an in progress video. */
public void stopRecording() {
mCameraModule.stopRecording();
}
/** @return True if currently recording. */
public boolean isRecording() {
return mCameraModule.isRecording();
}
/**
* Queries whether the current device has a camera with the specified direction.
*
* @return True if the device supports the direction.
* @throws IllegalStateException if the CAMERA permission is not currently granted.
*/
@RequiresPermission(permission.CAMERA)
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
return mCameraModule.hasCameraWithLensFacing(lensFacing);
}
/**
* Toggles between the primary front facing camera and the primary back facing camera.
*
* <p>This will have no effect if not already bound to a lifecycle via {@link
* #bindToLifecycle(LifecycleOwner)}.
*/
public void toggleCamera() {
mCameraModule.toggleCamera();
}
/**
* Sets the desired camera by specifying desired lensFacing.
*
* <p>This will choose the primary camera with the specified camera lensFacing.
*
* <p>If called before {@link #bindToLifecycle(LifecycleOwner)}, this will set the camera to be
* used when first bound to the lifecycle. If the specified lensFacing is not supported by the
* device, as determined by {@link #hasCameraWithLensFacing(int)}, the first supported
* lensFacing will be chosen when {@link #bindToLifecycle(LifecycleOwner)} is called.
*
* <p>If called with {@code null} AFTER binding to the lifecycle, the behavior would be
* equivalent to unbind the use cases without the lifecycle having to be destroyed.
*
* @param lensFacing The desired camera lensFacing.
*/
public void setCameraLensFacing(@Nullable Integer lensFacing) {
mCameraModule.setCameraLensFacing(lensFacing);
}
/** Returns the currently selected lensFacing. */
@Nullable
public Integer getCameraLensFacing() {
return mCameraModule.getLensFacing();
}
/** Gets the active flash strategy. */
@ImageCapture.FlashMode
public int getFlash() {
return mCameraModule.getFlash();
}
// Begin Signal Custom Code Block
public boolean hasFlash() {
return mCameraModule.hasFlash();
}
// End Signal Custom Code Block
/** Sets the active flash strategy. */
public void setFlash(@ImageCapture.FlashMode int flashMode) {
mCameraModule.setFlash(flashMode);
}
private long delta() {
return System.currentTimeMillis() - mDownEventTimestamp;
}
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
// Disable pinch-to-zoom and tap-to-focus while the camera module is paused.
if (mCameraModule.isPaused()) {
return false;
}
// Only forward the event to the pinch-to-zoom gesture detector when pinch-to-zoom is
// enabled.
if (isPinchToZoomEnabled()) {
mPinchToZoomGestureDetector.onTouchEvent(event);
}
if (event.getPointerCount() == 2 && isPinchToZoomEnabled() && isZoomSupported()) {
return true;
}
// Camera focus
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownEventTimestamp = System.currentTimeMillis();
break;
case MotionEvent.ACTION_UP:
if (delta() < ViewConfiguration.getLongPressTimeout()
&& mCameraModule.isBoundToLifecycle()) {
mUpEvent = event;
performClick();
}
break;
default:
// Unhandled event.
return false;
}
return true;
}
/**
* Focus the position of the touch event, or focus the center of the preview for
* accessibility events
*/
@Override
public boolean performClick() {
super.performClick();
final float x = (mUpEvent != null) ? mUpEvent.getX() : getX() + getWidth() / 2f;
final float y = (mUpEvent != null) ? mUpEvent.getY() : getY() + getHeight() / 2f;
mUpEvent = null;
Camera camera = mCameraModule.getCamera();
if (camera != null) {
MeteringPointFactory pointFactory = mPreviewView.getMeteringPointFactory();
float afPointWidth = 1.0f / 6.0f; // 1/6 total area
float aePointWidth = afPointWidth * 1.5f;
MeteringPoint afPoint = pointFactory.createPoint(x, y, afPointWidth);
MeteringPoint aePoint = pointFactory.createPoint(x, y, aePointWidth);
ListenableFuture<FocusMeteringResult> future =
camera.getCameraControl().startFocusAndMetering(
new FocusMeteringAction.Builder(afPoint,
FocusMeteringAction.FLAG_AF).addPoint(aePoint,
FocusMeteringAction.FLAG_AE).build());
Futures.addCallback(future, new FutureCallback<FocusMeteringResult>() {
@Override
public void onSuccess(@Nullable FocusMeteringResult result) {
}
@Override
public void onFailure(Throwable t) {
// Throw the unexpected error.
throw new RuntimeException(t);
}
}, CameraXExecutors.directExecutor());
} else {
Logger.d(TAG, "cannot access camera");
}
return true;
}
float rangeLimit(float val, float max, float min) {
return Math.min(Math.max(val, min), max);
}
/**
* Returns whether the view allows pinch-to-zoom.
*
* @return True if pinch to zoom is enabled.
*/
public boolean isPinchToZoomEnabled() {
return mIsPinchToZoomEnabled;
}
/**
* Sets whether the view should allow pinch-to-zoom.
*
* <p>When enabled, the user can pinch the camera to zoom in/out. This only has an effect if the
* bound camera supports zoom.
*
* @param enabled True to enable pinch-to-zoom.
*/
public void setPinchToZoomEnabled(boolean enabled) {
mIsPinchToZoomEnabled = enabled;
}
/**
* Returns the current zoom ratio.
*
* @return The current zoom ratio.
*/
public float getZoomRatio() {
return mCameraModule.getZoomRatio();
}
/**
* Sets the current zoom ratio.
*
* <p>Valid zoom values range from {@link #getMinZoomRatio()} to {@link #getMaxZoomRatio()}.
*
* @param zoomRatio The requested zoom ratio.
*/
public void setZoomRatio(float zoomRatio) {
mCameraModule.setZoomRatio(zoomRatio);
}
/**
* Returns the minimum zoom ratio.
*
* <p>For most cameras this should return a zoom ratio of 1. A zoom ratio of 1 corresponds to a
* non-zoomed image.
*
* @return The minimum zoom ratio.
*/
public float getMinZoomRatio() {
return mCameraModule.getMinZoomRatio();
}
/**
* Returns the maximum zoom ratio.
*
* <p>The zoom ratio corresponds to the ratio between both the widths and heights of a
* non-zoomed image and a maximally zoomed image for the selected camera.
*
* @return The maximum zoom ratio.
*/
public float getMaxZoomRatio() {
return mCameraModule.getMaxZoomRatio();
}
/**
* Returns whether the bound camera supports zooming.
*
* @return True if the camera supports zooming.
*/
public boolean isZoomSupported() {
return mCameraModule.isZoomSupported();
}
/**
* Turns on/off torch.
*
* @param torch True to turn on torch, false to turn off torch.
*/
public void enableTorch(boolean torch) {
mCameraModule.enableTorch(torch);
}
/**
* Returns current torch status.
*
* @return true if torch is on , otherwise false
*/
public boolean isTorchOn() {
return mCameraModule.isTorchOn();
}
/**
* The capture mode used by CameraView.
*
* <p>This enum can be used to determine which capture mode will be enabled for {@link
* SignalCameraView}.
*/
public enum CaptureMode {
/** A mode where image capture is enabled. */
IMAGE(0),
/** A mode where video capture is enabled. */
VIDEO(1),
/**
* A mode where both image capture and video capture are simultaneously enabled. Note that
* this mode may not be available on every device.
*/
MIXED(2);
private final int mId;
int getId() {
return mId;
}
CaptureMode(int id) {
mId = id;
}
static CaptureMode fromId(int id) {
for (CaptureMode f : values()) {
if (f.mId == id) {
return f;
}
}
throw new IllegalArgumentException();
}
}
static class S extends ScaleGestureDetector.SimpleOnScaleGestureListener {
private ScaleGestureDetector.OnScaleGestureListener mListener;
void setRealGestureDetector(ScaleGestureDetector.OnScaleGestureListener l) {
mListener = l;
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
return mListener.onScale(detector);
}
}
private class PinchToZoomGestureDetector extends ScaleGestureDetector
implements ScaleGestureDetector.OnScaleGestureListener {
PinchToZoomGestureDetector(Context context) {
this(context, new S());
}
PinchToZoomGestureDetector(Context context, S s) {
super(context, s);
s.setRealGestureDetector(this);
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scale = detector.getScaleFactor();
// Speeding up the zoom by 2X.
if (scale > 1f) {
scale = 1.0f + (scale - 1.0f) * 2;
} else {
scale = 1.0f - (1.0f - scale) * 2;
}
float newRatio = getZoomRatio() * scale;
newRatio = rangeLimit(newRatio, getMaxZoomRatio(), getMinZoomRatio());
setZoomRatio(newRatio);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
}
}
}
@@ -1,699 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.camera.view;
import android.Manifest.permission;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.util.Rational;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RequiresPermission;
import androidx.camera.core.Camera;
import androidx.camera.core.CameraInfoUnavailableException;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCapture.OnImageCapturedCallback;
import androidx.camera.core.ImageCapture.OnImageSavedCallback;
import androidx.camera.core.Logger;
import androidx.camera.core.Preview;
import androidx.camera.core.TorchState;
import androidx.camera.core.UseCase;
import androidx.camera.core.VideoCapture;
import androidx.camera.core.VideoCapture.OnVideoSavedCallback;
import androidx.camera.core.impl.CameraInternal;
import androidx.camera.core.impl.LensFacingConverter;
import androidx.camera.core.impl.utils.CameraOrientationUtil;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.impl.utils.futures.FutureCallback;
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.util.Consumer;
import androidx.core.util.Preconditions;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.OnLifecycleEvent;
import com.google.common.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.video.VideoUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static androidx.camera.core.ImageCapture.FLASH_MODE_OFF;
/** CameraX use case operation built on @{link androidx.camera.core}. */
@RequiresApi(21)
@SuppressLint("RestrictedApi")
final class SignalCameraXModule {
public static final String TAG = "CameraXModule";
private static final float UNITY_ZOOM_SCALE = 1f;
private static final float ZOOM_NOT_SUPPORTED = UNITY_ZOOM_SCALE;
private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
private static final Rational ASPECT_RATIO_9_16 = new Rational(9, 16);
private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
private final Preview.Builder mPreviewBuilder;
private final VideoCapture.Builder mVideoCaptureBuilder;
private final ImageCapture.Builder mImageCaptureBuilder;
private final SignalCameraView mCameraView;
final AtomicBoolean mVideoIsRecording = new AtomicBoolean(false);
private SignalCameraView.CaptureMode mCaptureMode = SignalCameraView.CaptureMode.IMAGE;
private long mMaxVideoDuration = SignalCameraView.INDEFINITE_VIDEO_DURATION;
private long mMaxVideoSize = SignalCameraView.INDEFINITE_VIDEO_SIZE;
@ImageCapture.FlashMode
private int mFlash = FLASH_MODE_OFF;
@Nullable
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
Camera mCamera;
@Nullable
private ImageCapture mImageCapture;
@Nullable
private VideoCapture mVideoCapture;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@Nullable
Preview mPreview;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@Nullable
LifecycleOwner mCurrentLifecycle;
private final LifecycleObserver mCurrentLifecycleObserver =
new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void onDestroy(LifecycleOwner owner) {
if (owner == mCurrentLifecycle) {
clearCurrentLifecycle();
}
}
};
@Nullable
private LifecycleOwner mNewLifecycle;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@Nullable
Integer mCameraLensFacing = CameraSelector.LENS_FACING_BACK;
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
@Nullable
ProcessCameraProvider mCameraProvider;
// BEGIN Custom Signal Code Block
SignalCameraXModule(SignalCameraView view, Consumer<Throwable> errorConsumer) {
// END Custom Signal Code Block
mCameraView = view;
Futures.addCallback(ProcessCameraProvider.getInstance(view.getContext()),
new FutureCallback<ProcessCameraProvider>() {
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
@Override
public void onSuccess(@Nullable ProcessCameraProvider provider) {
Preconditions.checkNotNull(provider);
mCameraProvider = provider;
if (mCurrentLifecycle != null) {
bindToLifecycle(mCurrentLifecycle);
}
}
@Override
public void onFailure(Throwable t) {
// BEGIN Custom Signal Code Block
errorConsumer.accept(t);
// END Custom Signal Code Block
}
}, CameraXExecutors.mainThreadExecutor());
mPreviewBuilder = new Preview.Builder().setTargetName("Preview");
mImageCaptureBuilder = new ImageCapture.Builder().setTargetName("ImageCapture");
mVideoCaptureBuilder = new VideoCapture.Builder().setTargetName("VideoCapture")
.setAudioBitRate(VideoUtil.AUDIO_BIT_RATE)
.setVideoFrameRate(VideoUtil.VIDEO_FRAME_RATE)
.setBitRate(VideoUtil.VIDEO_BIT_RATE);
}
@RequiresPermission(permission.CAMERA)
void bindToLifecycle(LifecycleOwner lifecycleOwner) {
mNewLifecycle = lifecycleOwner;
if (getMeasuredWidth() > 0 && getMeasuredHeight() > 0) {
bindToLifecycleAfterViewMeasured();
}
}
@RequiresPermission(permission.CAMERA)
void bindToLifecycleAfterViewMeasured() {
if (mNewLifecycle == null) {
return;
}
clearCurrentLifecycle();
if (mNewLifecycle.getLifecycle().getCurrentState() == Lifecycle.State.DESTROYED) {
// Lifecycle is already in a destroyed state. Since it may have been a valid
// lifecycle when bound, but became destroyed while waiting for layout, treat this as
// a no-op now that we have cleared the previous lifecycle.
mNewLifecycle = null;
return;
}
mCurrentLifecycle = mNewLifecycle;
mNewLifecycle = null;
if (mCameraProvider == null) {
// try again once the camera provider is no longer null
return;
}
Set<Integer> available = getAvailableCameraLensFacing();
if (available.isEmpty()) {
Logger.w(TAG, "Unable to bindToLifeCycle since no cameras available");
mCameraLensFacing = null;
}
// Ensure the current camera exists, or default to another camera
if (mCameraLensFacing != null && !available.contains(mCameraLensFacing)) {
Logger.w(TAG, "Camera does not exist with direction " + mCameraLensFacing);
// Default to the first available camera direction
mCameraLensFacing = available.iterator().next();
Logger.w(TAG, "Defaulting to primary camera with direction " + mCameraLensFacing);
}
// Do not attempt to create use cases for a null cameraLensFacing. This could occur if
// the user explicitly sets the LensFacing to null, or if we determined there
// were no available cameras, which should be logged in the logic above.
if (mCameraLensFacing == null) {
return;
}
// Set the preferred aspect ratio as 4:3 if it is IMAGE only mode. Set the preferred aspect
// ratio as 16:9 if it is VIDEO or MIXED mode. Then, it will be WYSIWYG when the view finder
// is in CENTER_INSIDE mode.
boolean isDisplayPortrait = getDisplayRotationDegrees() == 0
|| getDisplayRotationDegrees() == 180;
// Begin Signal Custom Code Block
int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels);
// End Signal Custom Code Block
Rational targetAspectRatio;
// Begin Signal Custom Code Block
mImageCaptureBuilder.setTargetResolution(CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, isDisplayPortrait));
targetAspectRatio = isDisplayPortrait ? ASPECT_RATIO_9_16 : ASPECT_RATIO_16_9;
// End Signal Custom Code Block
// Begin Signal Custom Code Block
mImageCaptureBuilder.setCaptureMode(CameraXUtil.getOptimalCaptureMode());
// End Signal Custom Code Block
mImageCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
mImageCapture = mImageCaptureBuilder.build();
// Begin Signal Custom Code Block
Size size = VideoUtil.getVideoRecordingSize();
mVideoCaptureBuilder.setTargetResolution(size);
mVideoCaptureBuilder.setMaxResolution(size);
// End Signal Custom Code Block
mVideoCaptureBuilder.setTargetRotation(getDisplaySurfaceRotation());
// Begin Signal Custom Code Block
if (MediaConstraints.isVideoTranscodeAvailable()) {
mVideoCapture = mVideoCaptureBuilder.build();
}
// End Signal Custom Code Block
// Adjusts the preview resolution according to the view size and the target aspect ratio.
int height = (int) (getMeasuredWidth() / targetAspectRatio.floatValue());
mPreviewBuilder.setTargetResolution(new Size(getMeasuredWidth(), height));
mPreview = mPreviewBuilder.build();
mPreview.setSurfaceProvider(mCameraView.getPreviewView().getSurfaceProvider());
CameraSelector cameraSelector =
new CameraSelector.Builder().requireLensFacing(mCameraLensFacing).build();
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
mImageCapture,
mPreview);
} else if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
mVideoCapture,
mPreview);
} else {
mCamera = mCameraProvider.bindToLifecycle(mCurrentLifecycle, cameraSelector,
mImageCapture,
mVideoCapture, mPreview);
}
setZoomRatio(UNITY_ZOOM_SCALE);
mCurrentLifecycle.getLifecycle().addObserver(mCurrentLifecycleObserver);
// Enable flash setting in ImageCapture after use cases are created and binded.
setFlash(getFlash());
}
public void open() {
throw new UnsupportedOperationException(
"Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
}
public void close() {
throw new UnsupportedOperationException(
"Explicit open/close of camera not yet supported. Use bindtoLifecycle() instead.");
}
public void takePicture(Executor executor, OnImageCapturedCallback callback) {
if (mImageCapture == null) {
return;
}
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
}
if (callback == null) {
throw new IllegalArgumentException("OnImageCapturedCallback should not be empty");
}
mImageCapture.takePicture(executor, callback);
}
public void takePicture(@NonNull ImageCapture.OutputFileOptions outputFileOptions,
@NonNull Executor executor, OnImageSavedCallback callback) {
if (mImageCapture == null) {
return;
}
if (getCaptureMode() == SignalCameraView.CaptureMode.VIDEO) {
throw new IllegalStateException("Can not take picture under VIDEO capture mode.");
}
if (callback == null) {
throw new IllegalArgumentException("OnImageSavedCallback should not be empty");
}
outputFileOptions.getMetadata().setReversedHorizontal(mCameraLensFacing != null
&& mCameraLensFacing == CameraSelector.LENS_FACING_FRONT);
mImageCapture.takePicture(outputFileOptions, executor, callback);
}
public void startRecording(VideoCapture.OutputFileOptions outputFileOptions,
Executor executor, final OnVideoSavedCallback callback) {
if (mVideoCapture == null) {
return;
}
if (getCaptureMode() == SignalCameraView.CaptureMode.IMAGE) {
throw new IllegalStateException("Can not record video under IMAGE capture mode.");
}
if (callback == null) {
throw new IllegalArgumentException("OnVideoSavedCallback should not be empty");
}
mVideoIsRecording.set(true);
mVideoCapture.startRecording(
outputFileOptions,
executor,
new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(
@NonNull VideoCapture.OutputFileResults outputFileResults) {
mVideoIsRecording.set(false);
callback.onVideoSaved(outputFileResults);
}
@Override
public void onError(
@VideoCapture.VideoCaptureError int videoCaptureError,
@NonNull String message,
@Nullable Throwable cause) {
mVideoIsRecording.set(false);
Logger.e(TAG, message, cause);
callback.onError(videoCaptureError, message, cause);
}
});
}
public void stopRecording() {
if (mVideoCapture == null) {
return;
}
mVideoCapture.stopRecording();
}
public boolean isRecording() {
return mVideoIsRecording.get();
}
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
public void setCameraLensFacing(@Nullable Integer lensFacing) {
// Setting same lens facing is a no-op, so check for that first
if (!Objects.equals(mCameraLensFacing, lensFacing)) {
// If we're not bound to a lifecycle, just update the camera that will be opened when we
// attach to a lifecycle.
mCameraLensFacing = lensFacing;
if (mCurrentLifecycle != null) {
// Re-bind to lifecycle with new camera
bindToLifecycle(mCurrentLifecycle);
}
}
}
@RequiresPermission(permission.CAMERA)
public boolean hasCameraWithLensFacing(@CameraSelector.LensFacing int lensFacing) {
if (mCameraProvider == null) {
return false;
}
try {
return mCameraProvider.hasCamera(
new CameraSelector.Builder().requireLensFacing(lensFacing).build());
} catch (CameraInfoUnavailableException e) {
return false;
}
}
@Nullable
public Integer getLensFacing() {
return mCameraLensFacing;
}
public void toggleCamera() {
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
Set<Integer> availableCameraLensFacing = getAvailableCameraLensFacing();
if (availableCameraLensFacing.isEmpty()) {
return;
}
if (mCameraLensFacing == null) {
setCameraLensFacing(availableCameraLensFacing.iterator().next());
return;
}
if (mCameraLensFacing == CameraSelector.LENS_FACING_BACK
&& availableCameraLensFacing.contains(CameraSelector.LENS_FACING_FRONT)) {
setCameraLensFacing(CameraSelector.LENS_FACING_FRONT);
return;
}
if (mCameraLensFacing == CameraSelector.LENS_FACING_FRONT
&& availableCameraLensFacing.contains(CameraSelector.LENS_FACING_BACK)) {
setCameraLensFacing(CameraSelector.LENS_FACING_BACK);
return;
}
}
public float getZoomRatio() {
if (mCamera != null) {
return mCamera.getCameraInfo().getZoomState().getValue().getZoomRatio();
} else {
return UNITY_ZOOM_SCALE;
}
}
public void setZoomRatio(float zoomRatio) {
if (mCamera != null) {
ListenableFuture<Void> future = mCamera.getCameraControl().setZoomRatio(
zoomRatio);
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
}
@Override
public void onFailure(Throwable t) {
// Throw the unexpected error.
throw new RuntimeException(t);
}
}, CameraXExecutors.directExecutor());
} else {
Logger.e(TAG, "Failed to set zoom ratio");
}
}
public float getMinZoomRatio() {
if (mCamera != null) {
return mCamera.getCameraInfo().getZoomState().getValue().getMinZoomRatio();
} else {
return UNITY_ZOOM_SCALE;
}
}
public float getMaxZoomRatio() {
if (mCamera != null) {
return mCamera.getCameraInfo().getZoomState().getValue().getMaxZoomRatio();
} else {
return ZOOM_NOT_SUPPORTED;
}
}
public boolean isZoomSupported() {
return getMaxZoomRatio() != ZOOM_NOT_SUPPORTED;
}
// TODO(b/124269166): Rethink how we can handle permissions here.
@SuppressLint("MissingPermission")
private void rebindToLifecycle() {
if (mCurrentLifecycle != null) {
bindToLifecycle(mCurrentLifecycle);
}
}
boolean isBoundToLifecycle() {
return mCamera != null;
}
int getRelativeCameraOrientation(boolean compensateForMirroring) {
int rotationDegrees = 0;
if (mCamera != null) {
rotationDegrees =
mCamera.getCameraInfo().getSensorRotationDegrees(getDisplaySurfaceRotation());
if (compensateForMirroring) {
rotationDegrees = (360 - rotationDegrees) % 360;
}
}
return rotationDegrees;
}
@SuppressLint("UnsafeExperimentalUsageError")
public void invalidateView() {
if (mPreview != null) {
mPreview.setTargetRotation(getDisplaySurfaceRotation()); // Fixes issue #10940 (rotation not updated on phones using "Legacy API")
}
updateViewInfo();
}
void clearCurrentLifecycle() {
if (mCurrentLifecycle != null && mCameraProvider != null) {
// Remove previous use cases
List<UseCase> toUnbind = new ArrayList<>();
if (mImageCapture != null && mCameraProvider.isBound(mImageCapture)) {
toUnbind.add(mImageCapture);
}
if (mVideoCapture != null && mCameraProvider.isBound(mVideoCapture)) {
toUnbind.add(mVideoCapture);
}
if (mPreview != null && mCameraProvider.isBound(mPreview)) {
toUnbind.add(mPreview);
}
if (!toUnbind.isEmpty()) {
mCameraProvider.unbind(toUnbind.toArray((new UseCase[0])));
}
// Remove surface provider once unbound.
if (mPreview != null) {
mPreview.setSurfaceProvider(null);
}
}
mCamera = null;
mCurrentLifecycle = null;
}
// Update view related information used in use cases
private void updateViewInfo() {
if (mImageCapture != null) {
mImageCapture.setCropAspectRatio(new Rational(getWidth(), getHeight()));
mImageCapture.setTargetRotation(getDisplaySurfaceRotation());
}
if (mVideoCapture != null) {
mVideoCapture.setTargetRotation(getDisplaySurfaceRotation());
}
}
@RequiresPermission(permission.CAMERA)
private Set<Integer> getAvailableCameraLensFacing() {
// Start with all camera directions
Set<Integer> available = new LinkedHashSet<>(Arrays.asList(LensFacingConverter.values()));
// If we're bound to a lifecycle, remove unavailable cameras
if (mCurrentLifecycle != null) {
if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_BACK)) {
available.remove(CameraSelector.LENS_FACING_BACK);
}
if (!hasCameraWithLensFacing(CameraSelector.LENS_FACING_FRONT)) {
available.remove(CameraSelector.LENS_FACING_FRONT);
}
}
return available;
}
@ImageCapture.FlashMode
public int getFlash() {
return mFlash;
}
// Begin Signal Custom Code Block
public boolean hasFlash() {
if (mImageCapture == null) {
return false;
}
CameraInternal camera = mImageCapture.getCamera();
if (camera == null) {
return false;
}
return camera.getCameraInfoInternal().hasFlashUnit();
}
// End Signal Custom Code Block
public void setFlash(@ImageCapture.FlashMode int flash) {
this.mFlash = flash;
if (mImageCapture == null) {
// Do nothing if there is no imageCapture
return;
}
mImageCapture.setFlashMode(flash);
}
public void enableTorch(boolean torch) {
if (mCamera == null) {
return;
}
ListenableFuture<Void> future = mCamera.getCameraControl().enableTorch(torch);
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void result) {
}
@Override
public void onFailure(Throwable t) {
// Throw the unexpected error.
throw new RuntimeException(t);
}
}, CameraXExecutors.directExecutor());
}
public boolean isTorchOn() {
if (mCamera == null) {
return false;
}
return mCamera.getCameraInfo().getTorchState().getValue() == TorchState.ON;
}
public Context getContext() {
return mCameraView.getContext();
}
public int getWidth() {
return mCameraView.getWidth();
}
public int getHeight() {
return mCameraView.getHeight();
}
public int getDisplayRotationDegrees() {
return CameraOrientationUtil.surfaceRotationToDegrees(getDisplaySurfaceRotation());
}
protected int getDisplaySurfaceRotation() {
return mCameraView.getDisplaySurfaceRotation();
}
private int getMeasuredWidth() {
return mCameraView.getMeasuredWidth();
}
private int getMeasuredHeight() {
return mCameraView.getMeasuredHeight();
}
@Nullable
public Camera getCamera() {
return mCamera;
}
@NonNull
public SignalCameraView.CaptureMode getCaptureMode() {
return mCaptureMode;
}
public void setCaptureMode(@NonNull SignalCameraView.CaptureMode captureMode) {
this.mCaptureMode = captureMode;
rebindToLifecycle();
}
public long getMaxVideoDuration() {
return mMaxVideoDuration;
}
public void setMaxVideoDuration(long duration) {
mMaxVideoDuration = duration;
}
public long getMaxVideoSize() {
return mMaxVideoSize;
}
public void setMaxVideoSize(long size) {
mMaxVideoSize = size;
}
public boolean isPaused() {
return false;
}
}
@@ -0,0 +1,38 @@
package androidx.documentfile.provider;
import android.content.Context;
import android.net.Uri;
import android.provider.DocumentsContract;
import org.signal.core.util.logging.Log;
/**
* Located in androidx package as {@link TreeDocumentFile} is package protected.
*/
public class DocumentFileHelper {
private static final String TAG = Log.tag(DocumentFileHelper.class);
/**
* System implementation swallows the exception and we are having problems with the rename. This inlines the
* same call and logs the exception. Note this implementation does not update the passed in document file like
* the system implementation. Do not use the provided document file after calling this method.
*
* @return true if rename successful
*/
public static boolean renameTo(Context context, DocumentFile documentFile, String displayName) {
if (documentFile instanceof TreeDocumentFile) {
Log.d(TAG, "Renaming document directly");
try {
final Uri result = DocumentsContract.renameDocument(context.getContentResolver(), documentFile.getUri(), displayName);
return result != null;
} catch (Exception e) {
Log.w(TAG, "Unable to rename document file", e);
return false;
}
} else {
Log.d(TAG, "Letting OS rename document: " + documentFile.getClass().getSimpleName());
return documentFile.renameTo(displayName);
}
}
}
@@ -0,0 +1,14 @@
package com.google.android.material.bottomsheet
import android.view.View
import android.widget.FrameLayout
import java.lang.ref.WeakReference
/**
* Manually adjust the nested scrolling child for a given [BottomSheetBehavior].
*/
object BottomSheetBehaviorHack {
fun setNestedScrollingChild(behavior: BottomSheetBehavior<FrameLayout>, view: View) {
behavior.nestedScrollingChildRef = WeakReference(view)
}
}
@@ -1,24 +0,0 @@
package org.thoughtcrime.securesms;
import org.whispersystems.signalservice.api.account.AccountAttributes;
public final class AppCapabilities {
private AppCapabilities() {
}
private static final boolean UUID_CAPABLE = false;
private static final boolean GV2_CAPABLE = true;
private static final boolean GV1_MIGRATION = true;
private static final boolean ANNOUNCEMENT_GROUPS = true;
private static final boolean SENDER_KEY = true;
private static final boolean CHANGE_NUMBER = true;
/**
* @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 AccountAttributes.Capabilities getCapabilities(boolean storageCapable) {
return new AccountAttributes.Capabilities(UUID_CAPABLE, GV2_CAPABLE, storageCapable, GV1_MIGRATION, SENDER_KEY, ANNOUNCEMENT_GROUPS, CHANGE_NUMBER);
}
}
@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.account.AccountAttributes
object AppCapabilities {
/**
* @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.
*/
@JvmStatic
fun getCapabilities(storageCapable: Boolean): AccountAttributes.Capabilities {
return AccountAttributes.Capabilities(
isUuid = false,
isGv2 = true,
isStorage = storageCapable,
isGv1Migration = true,
isSenderKey = true,
isAnnouncementGroup = true,
isChangeNumber = true,
isStories = true,
isGiftBadges = true,
isPnp = FeatureFlags.phoneNumberPrivacy(),
paymentActivation = true
)
}
}
@@ -16,13 +16,14 @@
*/ */
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread; import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
import com.google.android.gms.security.ProviderInstaller; import com.google.android.gms.security.ProviderInstaller;
@@ -34,39 +35,46 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.AndroidLogger; import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.core.util.tracing.Tracer; import org.signal.core.util.tracing.Tracer;
import org.signal.donations.GooglePayApi;
import org.signal.donations.StripeApi;
import org.signal.glide.SignalGlideCodecs; import org.signal.glide.SignalGlideCodecs;
import org.thoughtcrime.securesms.emoji.JumboEmoji; import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider;
import org.thoughtcrime.securesms.jobs.RetrieveReleaseChannelJob;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.signal.ringrtc.CallManager; import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.avatar.AvatarPickerStorage; import org.thoughtcrime.securesms.avatar.AvatarPickerStorage;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider; import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
import org.thoughtcrime.securesms.database.LogDatabase; import org.thoughtcrime.securesms.database.LogDatabase;
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.SqlCipherLibraryLoader;
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.emoji.EmojiSource; import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.gcm.FcmJobService; import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob; import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob; import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob; import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob; import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob; import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob; import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob; import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob; import org.thoughtcrime.securesms.jobs.SubscriptionKeepAliveJob;
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger; import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger; import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.mms.SignalGlideComponents; import org.thoughtcrime.securesms.mms.SignalGlideComponents;
import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.ratelimit.RateLimitUtil; import org.thoughtcrime.securesms.ratelimit.RateLimitUtil;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
@@ -78,10 +86,12 @@ import org.thoughtcrime.securesms.service.LocalBackupListener;
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.service.webrtc.AndroidTelecomUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AppForegroundObserver; import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Environment;
import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.SignalLocalMetrics; import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler; import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
@@ -89,12 +99,12 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker; import org.thoughtcrime.securesms.util.VersionTracker;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.security.Security; import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@@ -150,19 +160,12 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addBlocking("rx-init", this::initializeRx) .addBlocking("rx-init", this::initializeRx)
.addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus()) .addBlocking("event-bus", () -> EventBus.builder().logNoSubscriberMessages(false).installDefaultEventBus())
.addBlocking("app-dependencies", this::initializeAppDependencies) .addBlocking("app-dependencies", this::initializeAppDependencies)
.addBlocking("notification-channels", () -> NotificationChannels.create(this))
.addBlocking("first-launch", this::initializeFirstEverAppLaunch) .addBlocking("first-launch", this::initializeFirstEverAppLaunch)
.addBlocking("app-migrations", this::initializeApplicationMigrations) .addBlocking("app-migrations", this::initializeApplicationMigrations)
.addBlocking("ring-rtc", this::initializeRingRtc)
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this)) .addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
.addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this)) .addBlocking("lifecycle-observer", () -> ApplicationDependencies.getAppForegroundObserver().addListener(this))
.addBlocking("message-retriever", this::initializeMessageRetrieval) .addBlocking("message-retriever", this::initializeMessageRetrieval)
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this)) .addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
.addBlocking("vector-compat", () -> {
if (Build.VERSION.SDK_INT < 21) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
})
.addBlocking("proxy-init", () -> { .addBlocking("proxy-init", () -> {
if (SignalStore.proxy().isProxyEnabled()) { if (SignalStore.proxy().isProxyEnabled()) {
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()"); Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
@@ -171,31 +174,44 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
}) })
.addBlocking("blob-provider", this::initializeBlobProvider) .addBlocking("blob-provider", this::initializeBlobProvider)
.addBlocking("feature-flags", FeatureFlags::init) .addBlocking("feature-flags", FeatureFlags::init)
.addBlocking("ring-rtc", this::initializeRingRtc)
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents())) .addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
.addNonBlocking(this::checkIsGooglePayReady)
.addNonBlocking(this::cleanAvatarStorage) .addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager) .addNonBlocking(this::initializeRevealableMessageManager)
.addNonBlocking(this::initializePendingRetryReceiptManager) .addNonBlocking(this::initializePendingRetryReceiptManager)
.addNonBlocking(this::initializeScheduledMessageManager)
.addNonBlocking(this::initializeFcmCheck) .addNonBlocking(this::initializeFcmCheck)
.addNonBlocking(CreateSignedPreKeyJob::enqueueIfNeeded) .addNonBlocking(PreKeysSyncJob::enqueueIfNeeded)
.addNonBlocking(this::initializePeriodicTasks) .addNonBlocking(this::initializePeriodicTasks)
.addNonBlocking(this::initializeCircumvention) .addNonBlocking(this::initializeCircumvention)
.addNonBlocking(this::initializePendingMessages) .addNonBlocking(this::initializePendingMessages)
.addNonBlocking(this::initializeCleanup) .addNonBlocking(this::initializeCleanup)
.addNonBlocking(this::initializeGlideCodecs) .addNonBlocking(this::initializeGlideCodecs)
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync) .addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop()) .addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
.addNonBlocking(EmojiSource::refresh) .addNonBlocking(EmojiSource::refresh)
.addNonBlocking(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this)) .addNonBlocking(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this))
.addNonBlocking(this::ensureProfileUploaded) .addNonBlocking(this::ensureProfileUploaded)
.addNonBlocking(() -> ApplicationDependencies.getExpireStoriesManager().scheduleIfNecessary())
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this)) .addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
.addPostRender(this::initializeExpiringMessageManager) .addPostRender(this::initializeExpiringMessageManager)
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this))) .addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
.addPostRender(this::initializeTrimThreadsByDateManager)
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this)) .addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary) .addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge())) .addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
.addPostRender(() -> JumboEmoji.updateCurrentVersion(this)) .addPostRender(() -> JumboEmoji.updateCurrentVersion(this))
.addPostRender(RetrieveReleaseChannelJob::enqueue) .addPostRender(RetrieveRemoteAnnouncementsJob::enqueue)
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
.addPostRender(GroupV2UpdateSelfProfileKeyJob::enqueueForGroupsIfNecessary)
.addPostRender(StoryOnboardingDownloadJob.Companion::enqueueIfNeeded)
.addPostRender(PnpInitializeDevicesJob::enqueueIfNecessary)
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
.addPostRender(() -> SignalDatabase.groupCallRings().removeOldRings())
.execute(); .execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms"); Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
@@ -211,7 +227,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getFrameRateTracker().start(); ApplicationDependencies.getFrameRateTracker().start();
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded(); ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
ApplicationDependencies.getDeadlockDetector().start(); ApplicationDependencies.getDeadlockDetector().start();
SubscriptionKeepAliveJob.launchSubscriberIdKeepAliveJobIfNecessary(); SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary();
SignalExecutors.BOUNDED.execute(() -> { SignalExecutors.BOUNDED.execute(() -> {
FeatureFlags.refreshIfNecessary(); FeatureFlags.refreshIfNecessary();
@@ -221,6 +237,16 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
KeyCachingService.onAppForegrounded(this); KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getShakeToReport().enable(); ApplicationDependencies.getShakeToReport().enable();
checkBuildExpiration(); checkBuildExpiration();
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
long currentTime = System.currentTimeMillis();
long timeDiff = currentTime - lastForegroundTime;
if (timeDiff < 0) {
Log.w(TAG, "Time travel! The system clock has moved backwards. (currentTime: " + currentTime + " ms, lastForegroundTime: " + lastForegroundTime + " ms, diff: " + timeDiff + " ms)");
}
SignalStore.misc().setLastForegroundTime(currentTime);
}); });
Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms"); Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms");
@@ -320,7 +346,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getIncomingMessageObserver(); ApplicationDependencies.getIncomingMessageObserver();
} }
private void initializeAppDependencies() { @VisibleForTesting
void initializeAppDependencies() {
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this)); ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
} }
@@ -364,6 +391,17 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary(); ApplicationDependencies.getPendingRetryReceiptManager().scheduleIfNecessary();
} }
private void initializeScheduledMessageManager() {
ApplicationDependencies.getScheduledMessageManager().scheduleIfNecessary();
}
private void initializeTrimThreadsByDateManager() {
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
if (keepMessagesDuration != KeepMessagesDuration.FOREVER) {
ApplicationDependencies.getTrimThreadsByDateManager().scheduleIfNecessary();
}
}
private void initializePeriodicTasks() { private void initializePeriodicTasks() {
RotateSignedPreKeyListener.schedule(this); RotateSignedPreKeyListener.schedule(this);
DirectoryRefreshListener.schedule(this); DirectoryRefreshListener.schedule(this);
@@ -378,7 +416,11 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
private void initializeRingRtc() { private void initializeRingRtc() {
try { try {
CallManager.initialize(this, new RingRtcLogger()); Map<String, String> fieldTrials = new HashMap<>();
if (FeatureFlags.callingFieldTrialAnyAddressPortsKillSwitch()) {
fieldTrials.put("RingRTC-AnyAddressPortsKillSwitch", "Enabled");
}
CallManager.initialize(this, new RingRtcLogger(), fieldTrials);
} catch (UnsatisfiedLinkError e) { } catch (UnsatisfiedLinkError e) {
throw new AssertionError("Unable to load ringrtc library", e); throw new AssertionError("Unable to load ringrtc library", e);
} }
@@ -430,6 +472,18 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
AvatarPickerStorage.cleanOrphans(this); AvatarPickerStorage.cleanOrphans(this);
} }
@SuppressLint("CheckResult")
private void checkIsGooglePayReady() {
GooglePayApi.queryIsReadyToPay(
this,
new StripeApi.Gateway(Environment.Donations.getStripeConfiguration()),
Environment.Donations.getGooglePayConfiguration()
).subscribe(
/* onComplete = */ () -> SignalStore.donationsValues().setGooglePayReady(true),
/* onError = */ t -> SignalStore.donationsValues().setGooglePayReady(false)
);
}
@WorkerThread @WorkerThread
private void initializeCleanup() { private void initializeCleanup() {
int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments(); int deleted = SignalDatabase.attachments().deleteAbandonedPreuploadedAttachments();
@@ -14,6 +14,7 @@ import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityOptionsCompat; import androidx.core.app.ActivityOptionsCompat;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -58,6 +59,12 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle(); return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, from, "avatar").toBundle();
} }
@Override
protected void attachBaseContext(@NonNull Context newBase) {
getDelegate().setLocalNightMode(AppCompatDelegate.MODE_NIGHT_YES);
super.attachBaseContext(newBase);
}
@Override @Override
protected void onCreate(Bundle savedInstanceState, boolean ready) { protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready); super.onCreate(savedInstanceState, ready);
@@ -65,12 +72,10 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
setTheme(R.style.TextSecure_MediaPreview); setTheme(R.style.TextSecure_MediaPreview);
setContentView(R.layout.contact_photo_preview_activity); setContentView(R.layout.contact_photo_preview_activity);
if (Build.VERSION.SDK_INT >= 21) { postponeEnterTransition();
postponeEnterTransition(); TransitionInflater inflater = TransitionInflater.from(this);
TransitionInflater inflater = TransitionInflater.from(this); getWindow().setSharedElementEnterTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_enter_transition_set));
getWindow().setSharedElementEnterTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_enter_transition_set)); getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
getWindow().setSharedElementReturnTransition(inflater.inflateTransition(R.transition.full_screen_avatar_image_return_transition_set));
}
Toolbar toolbar = findViewById(R.id.toolbar); Toolbar toolbar = findViewById(R.id.toolbar);
EmojiTextView title = findViewById(R.id.title); EmojiTextView title = findViewById(R.id.title);
@@ -85,7 +90,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA)); RecipientId recipientId = RecipientId.from(getIntent().getStringExtra(RECIPIENT_ID_EXTRA));
Recipient.live(recipientId).observe(this, recipient -> { Recipient.live(recipientId).observe(this, recipient -> {
ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(recipient, recipient.getProfileAvatar()) ContactPhoto contactPhoto = recipient.isSelf() ? new ProfileContactPhoto(recipient)
: recipient.getContactPhoto(); : recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isSelf() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large) FallbackContactPhoto fallbackPhoto = recipient.isSelf() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto(); : recipient.getFallbackContactPhoto();
@@ -115,9 +120,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
@Override @Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
avatar.setImageDrawable(RoundedBitmapDrawableFactory.create(resources, resource)); avatar.setImageDrawable(RoundedBitmapDrawableFactory.create(resources, resource));
if (Build.VERSION.SDK_INT >= 21) { startPostponedEnterTransition();
startPostponedEnterTransition();
}
} }
@Override @Override
@@ -132,7 +135,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility()); findViewById(android.R.id.content).setOnClickListener(v -> fullscreenHelper.toggleUiVisibility());
fullscreenHelper.configureToolbarSpacer(findViewById(R.id.toolbar_cutout_spacer)); fullscreenHelper.configureToolbarLayout(findViewById(R.id.toolbar_cutout_spacer), toolbar);
fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout)); fullscreenHelper.showAndHideWithSystemUI(getWindow(), findViewById(R.id.toolbar_layout));
} }
@@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.ConfigurationUtil; import org.thoughtcrime.securesms.util.ConfigurationUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper; import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import java.util.Objects; import java.util.Objects;
@@ -42,7 +43,7 @@ public abstract class BaseActivity extends AppCompatActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
initializeScreenshotSecurity(); WindowUtil.initializeScreenshotSecurity(this, getWindow());
} }
@Override @Override
@@ -64,14 +65,6 @@ public abstract class BaseActivity extends AppCompatActivity {
super.onDestroy(); super.onDestroy();
} }
private void initializeScreenshotSecurity() {
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) { protected void startActivitySceneTransition(Intent intent, View sharedView, String transitionName) {
Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName) Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(this, sharedView, transitionName)
.toBundle(); .toBundle();
@@ -10,6 +10,8 @@ import androidx.lifecycle.Observer;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode;
import org.thoughtcrime.securesms.conversation.ConversationMessage; import org.thoughtcrime.securesms.conversation.ConversationMessage;
import org.thoughtcrime.securesms.conversation.colors.Colorizable; import org.thoughtcrime.securesms.conversation.colors.Colorizable;
import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.colors.Colorizer;
@@ -22,14 +24,15 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
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.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional;
import java.util.Set; import java.util.Set;
public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable { public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, Colorizable, Multiselectable {
@@ -46,7 +49,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
boolean hasWallpaper, boolean hasWallpaper,
boolean isMessageRequestAccepted, boolean isMessageRequestAccepted,
boolean canPlayInline, boolean canPlayInline,
@NonNull Colorizer colorizer); @NonNull Colorizer colorizer,
@NonNull ConversationItemDisplayMode displayMode);
@NonNull ConversationMessage getConversationMessage(); @NonNull ConversationMessage getConversationMessage();
@@ -60,9 +64,14 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
// Intentionally Blank. // Intentionally Blank.
} }
default void updateSelectedState() {
// Intentionally Blank.
}
interface EventListener { interface EventListener {
void onQuoteClicked(MmsMessageRecord messageRecord); void onQuoteClicked(MmsMessageRecord messageRecord);
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview); void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
void onQuotedIndicatorClicked(@NonNull MessageRecord messageRecord);
void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms); void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms);
void onStickerClicked(@NonNull StickerLocator stickerLocator); void onStickerClicked(@NonNull StickerLocator stickerLocator);
void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord); void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord);
@@ -94,8 +103,19 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onChangeNumberUpdateContact(@NonNull Recipient recipient); void onChangeNumberUpdateContact(@NonNull Recipient recipient);
void onCallToAction(@NonNull String action); void onCallToAction(@NonNull String action);
void onDonateClicked(); void onDonateClicked();
void onBlockJoinRequest(@NonNull Recipient recipient);
void onRecipientNameClicked(@NonNull RecipientId target);
void onInviteToSignalClicked();
void onActivatePaymentsClicked();
void onSendPaymentClicked(@NonNull RecipientId recipientId);
void onScheduledIndicatorClicked(@NonNull View view, @NonNull MessageRecord messageRecord);
/** @return true if handled, false if you want to let the normal url handling continue */ /** @return true if handled, false if you want to let the normal url handling continue */
boolean onUrlClicked(@NonNull String url); boolean onUrlClicked(@NonNull String url);
void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord);
void onGiftBadgeRevealed(@NonNull MessageRecord messageRecord);
void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args);
} }
} }
@@ -1,8 +1,8 @@
package org.thoughtcrime.securesms; package org.thoughtcrime.securesms;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.conversationlist.model.ConversationSet; import org.thoughtcrime.securesms.conversationlist.model.ConversationSet;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -12,7 +12,8 @@ import java.util.Set;
public interface BindableConversationListItem extends Unbindable { public interface BindableConversationListItem extends Unbindable {
void bind(@NonNull ThreadRecord thread, void bind(@NonNull LifecycleOwner lifecycleOwner,
@NonNull ThreadRecord thread,
@NonNull GlideRequests glideRequests, @NonNull Locale locale, @NonNull GlideRequests glideRequests, @NonNull Locale locale,
@NonNull Set<Long> typingThreads, @NonNull Set<Long> typingThreads,
@NonNull ConversationSet selectedConversations); @NonNull ConversationSet selectedConversations);
@@ -0,0 +1,83 @@
package org.thoughtcrime.securesms
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.activity.result.contract.ActivityResultContract
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.util.ServiceUtil
/**
* Authentication using phone biometric (face, fingerprint recognition) or device lock (pattern, pin or passphrase).
*/
class BiometricDeviceAuthentication(
private val biometricManager: BiometricManager,
private val biometricPrompt: BiometricPrompt,
private val biometricPromptInfo: PromptInfo
) {
companion object {
const val AUTHENTICATED = 1
const val NOT_AUTHENTICATED = -1
const val TAG: String = "BiometricDeviceAuth"
const val BIOMETRIC_AUTHENTICATORS = BiometricManager.Authenticators.BIOMETRIC_STRONG or BiometricManager.Authenticators.BIOMETRIC_WEAK
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_AUTHENTICATORS or BiometricManager.Authenticators.DEVICE_CREDENTIAL
/**
* From the docs on [BiometricManager.canAuthenticate]
*
* > Note that not all combinations of authenticator types are supported prior to Android 11 (API 30). Specifically, DEVICE_CREDENTIAL alone is unsupported
* > prior to API 30, and BIOMETRIC_STRONG | DEVICE_CREDENTIAL is unsupported on API 28-29.
*/
private val DISALLOWED_BIOMETRIC_VERSIONS = setOf(28, 29)
}
fun authenticate(context: Context, force: Boolean, showConfirmDeviceCredentialIntent: () -> Unit): Boolean {
val isKeyGuardSecure = ServiceUtil.getKeyguardManager(context).isKeyguardSecure
if (!isKeyGuardSecure) {
Log.w(TAG, "Keyguard not secure...")
return false
}
return if (!DISALLOWED_BIOMETRIC_VERSIONS.contains(Build.VERSION.SDK_INT) && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS) {
if (force) {
Log.i(TAG, "Listening for biometric authentication...")
biometricPrompt.authenticate(biometricPromptInfo)
} else {
Log.i(TAG, "Skipping show system biometric or device lock dialog unless forced")
}
true
} else {
if (force) {
Log.i(TAG, "firing intent...")
showConfirmDeviceCredentialIntent()
} else {
Log.i(TAG, "Skipping firing intent unless forced")
}
true
}
}
fun cancelAuthentication() {
biometricPrompt.cancelAuthentication()
}
}
class BiometricDeviceLockContract : ActivityResultContract<String, Int>() {
override fun createIntent(context: Context, input: String): Intent {
val keyguardManager = ServiceUtil.getKeyguardManager(context)
return keyguardManager.createConfirmDeviceCredentialIntent(input, "")
}
override fun parseResult(resultCode: Int, intent: Intent?) =
if (resultCode != Activity.RESULT_OK) {
BiometricDeviceAuthentication.NOT_AUTHENTICATED
} else {
BiometricDeviceAuthentication.AUTHENTICATED
}
}
@@ -13,7 +13,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.signal.core.util.concurrent.SimpleTask;
/** /**
* This should be used whenever we want to prompt the user to block/unblock a recipient. * This should be used whenever we want to prompt the user to block/unblock a recipient.
@@ -83,7 +83,8 @@ public final class BlockUnblockDialog {
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
} else { } else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_block_s, recipient.getDisplayName(context))); 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); builder.setMessage(recipient.isRegistered() ? R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_call_you_or_send_you_messages
: R.string.BlockUnblockDialog_blocked_people_wont_be_able_to_send_you_messages);
if (onBlockAndReportSpam != null) { if (onBlockAndReportSpam != null) {
builder.setNeutralButton(android.R.string.cancel, null); builder.setNeutralButton(android.R.string.cancel, null);
@@ -128,7 +129,8 @@ public final class BlockUnblockDialog {
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
} else { } else {
builder.setTitle(resources.getString(R.string.BlockUnblockDialog_unblock_s, recipient.getDisplayName(context))); 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.setMessage(recipient.isRegistered() ? R.string.BlockUnblockDialog_you_will_be_able_to_call_and_message_each_other
: R.string.BlockUnblockDialog_you_will_be_able_to_message_each_other);
builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run())); builder.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, ((dialog, which) -> onUnblock.run()));
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
} }
@@ -20,22 +20,24 @@ import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.ContactFilterView; import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.RecipientId; 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.ServiceUtil;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
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;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@@ -68,8 +70,8 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@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 = Util.isDefaultSmsProvider(this) ? DisplayMode.FLAG_ALL boolean includeSms = Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS | DisplayMode.FLAG_SELF; int displayMode = includeSms ? ContactSelectionDisplayMode.FLAG_ALL : ContactSelectionDisplayMode.FLAG_PUSH | ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_SELF;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode); getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
} }
@@ -123,12 +125,12 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
} }
@Override @Override
public void onBeforeContactSelected(Optional<RecipientId> recipientId, String number, Consumer<Boolean> callback) { public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
callback.accept(true); callback.accept(true);
} }
@Override @Override
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {} public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, String number) {}
@Override @Override
public void onBeginScroll() { public void onBeginScroll() {
@@ -152,7 +154,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
@Override @Override
protected Void doInBackground(Context... params) { protected Void doInBackground(Context... params) {
try { try {
DirectoryHelper.refreshDirectory(params[0], true); ContactDiscovery.refreshAll(params[0], true);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
@@ -0,0 +1,88 @@
package org.thoughtcrime.securesms
import android.content.Context
import android.view.View
import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
import org.thoughtcrime.securesms.contacts.paged.ContactSearchData
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
class ContactSelectionListAdapter(
context: Context,
fixedContacts: Set<ContactSearchKey>,
displayCheckBox: Boolean,
displaySmsTag: DisplaySmsTag,
displaySecondaryInformation: DisplaySecondaryInformation,
onClickCallbacks: OnContactSelectionClick,
longClickCallbacks: LongClickCallbacks,
storyContextMenuCallbacks: StoryContextMenuCallbacks
) : ContactSearchAdapter(context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, onClickCallbacks, longClickCallbacks, storyContextMenuCallbacks) {
init {
registerFactory(NewGroupModel::class.java, LayoutFactory({ NewGroupViewHolder(it, onClickCallbacks::onNewGroupClicked) }, R.layout.contact_selection_new_group_item))
registerFactory(InviteToSignalModel::class.java, LayoutFactory({ InviteToSignalViewHolder(it, onClickCallbacks::onInviteToSignalClicked) }, R.layout.contact_selection_invite_action_item))
}
class NewGroupModel : MappingModel<NewGroupModel> {
override fun areItemsTheSame(newItem: NewGroupModel): Boolean = true
override fun areContentsTheSame(newItem: NewGroupModel): Boolean = true
}
class InviteToSignalModel : MappingModel<InviteToSignalModel> {
override fun areItemsTheSame(newItem: InviteToSignalModel): Boolean = true
override fun areContentsTheSame(newItem: InviteToSignalModel): Boolean = true
}
private class InviteToSignalViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<InviteToSignalModel>(itemView) {
init {
itemView.setOnClickListener { onClickListener() }
}
override fun bind(model: InviteToSignalModel) = Unit
}
private class NewGroupViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<NewGroupModel>(itemView) {
init {
itemView.setOnClickListener { onClickListener() }
}
override fun bind(model: NewGroupModel) = Unit
}
class ArbitraryRepository : org.thoughtcrime.securesms.contacts.paged.ArbitraryRepository {
enum class ArbitraryRow(val code: String) {
NEW_GROUP("new-group"),
INVITE_TO_SIGNAL("invite-to-signal");
companion object {
fun fromCode(code: String) = values().first { it.code == code }
}
}
override fun getSize(section: ContactSearchConfiguration.Section.Arbitrary, query: String?): Int {
return if (query.isNullOrEmpty()) section.types.size else 0
}
override fun getData(section: ContactSearchConfiguration.Section.Arbitrary, query: String?, startIndex: Int, endIndex: Int, totalSearchSize: Int): List<ContactSearchData.Arbitrary> {
check(section.types.size == 1)
return listOf(ContactSearchData.Arbitrary(section.types.first()))
}
override fun getMappingModel(arbitrary: ContactSearchData.Arbitrary): MappingModel<*> {
val code = ArbitraryRow.fromCode(arbitrary.type)
return when (code) {
ArbitraryRow.NEW_GROUP -> NewGroupModel()
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
}
}
}
interface OnContactSelectionClick : ClickCallbacks {
fun onNewGroupClicked()
fun onInviteToSignalClicked()
}
}
@@ -18,11 +18,10 @@ package org.thoughtcrime.securesms;
import android.Manifest; import android.Manifest;
import android.animation.LayoutTransition;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.graphics.Rect;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
@@ -31,7 +30,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Button; import android.widget.Button;
import android.widget.HorizontalScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -41,9 +39,7 @@ import androidx.annotation.Px;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet; import androidx.constraintlayout.widget.ConstraintSet;
import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -53,58 +49,61 @@ import androidx.transition.TransitionManager;
import com.annimon.stream.Collectors; import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.google.android.material.chip.ChipGroup;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.pnikosis.materialishprogress.ProgressWheel; import com.pnikosis.materialishprogress.ProgressWheel;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller; import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.components.recyclerview.ToolbarShadowAnimationHelper; import org.thoughtcrime.securesms.contacts.ContactChipViewModel;
import org.thoughtcrime.securesms.contacts.AbstractContactsCursorLoader; import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
import org.thoughtcrime.securesms.contacts.ContactChip; import org.thoughtcrime.securesms.contacts.HeaderAction;
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.LetterHeaderDecoration; import org.thoughtcrime.securesms.contacts.LetterHeaderDecoration;
import org.thoughtcrime.securesms.contacts.SelectedContact; import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.contacts.SelectedContacts;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchAdapter;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchData;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchMediator;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchState;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog; import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
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.LifecycleDisposable;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.UsernameUtil; import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter; import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter;
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader; import org.thoughtcrime.securesms.util.adapter.mapping.MappingModelList;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import io.reactivex.rxjava3.disposables.Disposable;
import kotlin.Unit;
/** /**
* Fragment for selecting a one or more contacts from a list. * Fragment for selecting a one or more contacts from a list.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
*
*/ */
public final class ContactSelectionListFragment extends LoggingFragment public final class ContactSelectionListFragment extends LoggingFragment
implements LoaderManager.LoaderCallbacks<Cursor>
{ {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static final String TAG = Log.tag(ContactSelectionListFragment.class); 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_EMPTY_CHILD_COUNT = 0;
private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150; private static final int CHIP_GROUP_REVEAL_DURATION_MS = 150;
public static final int NO_LIMIT = Integer.MAX_VALUE; public static final int NO_LIMIT = Integer.MAX_VALUE;
@@ -131,25 +130,23 @@ public final class ContactSelectionListFragment extends LoggingFragment
private String cursorFilter; private String cursorFilter;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private RecyclerViewFastScroller fastScroller; private RecyclerViewFastScroller fastScroller;
private ContactSelectionListAdapter cursorRecyclerViewAdapter; private RecyclerView chipRecycler;
private ChipGroup chipGroup;
private HorizontalScrollView chipGroupScrollContainer;
private OnSelectionLimitReachedListener onSelectionLimitReachedListener; private OnSelectionLimitReachedListener onSelectionLimitReachedListener;
private AbstractContactsCursorLoaderFactoryProvider cursorFactoryProvider; private MappingAdapter contactChipAdapter;
private View shadowView; private ContactChipViewModel contactChipViewModel;
private ToolbarShadowAnimationHelper toolbarShadowAnimationHelper; private LifecycleDisposable lifecycleDisposable;
private HeaderActionProvider headerActionProvider;
private TextView headerActionView;
private ContactSearchMediator contactSearchMediator;
@Nullable private ListCallback listCallback;
@Nullable private FixedViewsAdapter headerAdapter; @Nullable private ScrollCallback scrollCallback;
@Nullable private FixedViewsAdapter footerAdapter; @Nullable private OnItemLongClickListener onItemLongClickListener;
@Nullable private ListCallback listCallback; private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS;
@Nullable private ScrollCallback scrollCallback; private Set<RecipientId> currentSelection;
private GlideRequests glideRequests; private boolean isMulti;
private SelectionLimits selectionLimit = SelectionLimits.NO_LIMITS; private boolean canSelectSelf;
private Set<RecipientId> currentSelection; private ListClickListener listClickListener = new ListClickListener();
private boolean isMulti;
private boolean hideCount;
private boolean canSelectSelf;
@Override @Override
public void onAttach(@NonNull Context context) { public void onAttach(@NonNull Context context) {
@@ -183,12 +180,20 @@ public final class ContactSelectionListFragment extends LoggingFragment
onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) getParentFragment(); onSelectionLimitReachedListener = (OnSelectionLimitReachedListener) getParentFragment();
} }
if (context instanceof AbstractContactsCursorLoaderFactoryProvider) { if (context instanceof HeaderActionProvider) {
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) context; headerActionProvider = (HeaderActionProvider) context;
} }
if (getParentFragment() instanceof AbstractContactsCursorLoaderFactoryProvider) { if (getParentFragment() instanceof HeaderActionProvider) {
cursorFactoryProvider = (AbstractContactsCursorLoaderFactoryProvider) getParentFragment(); headerActionProvider = (HeaderActionProvider) getParentFragment();
}
if (context instanceof OnItemLongClickListener) {
onItemLongClickListener = (OnItemLongClickListener) context;
}
if (getParentFragment() instanceof OnItemLongClickListener) {
onItemLongClickListener = (OnItemLongClickListener) getParentFragment();
} }
} }
@@ -210,16 +215,14 @@ public final class ContactSelectionListFragment extends LoggingFragment
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) { if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
handleContactPermissionGranted(); handleContactPermissionGranted();
} else { } else {
LoaderManager.getInstance(this).initLoader(0, null, this); contactSearchMediator.refresh();
} }
}) })
.onAnyDenied(() -> { .onAnyDenied(() -> {
FragmentActivity activity = requireActivity(); requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); if (safeArguments().getBoolean(RECENTS, requireActivity().getIntent().getBooleanExtra(RECENTS, false))) {
contactSearchMediator.refresh();
if (safeArguments().getBoolean(RECENTS, activity.getIntent().getBooleanExtra(RECENTS, false))) {
LoaderManager.getInstance(this).initLoader(0, null, ContactSelectionListFragment.this);
} else { } else {
initializeNoContactsPermission(); initializeNoContactsPermission();
} }
@@ -239,15 +242,13 @@ public final class ContactSelectionListFragment extends LoggingFragment
showContactsButton = view.findViewById(R.id.show_contacts_button); showContactsButton = view.findViewById(R.id.show_contacts_button);
showContactsDescription = view.findViewById(R.id.show_contacts_description); showContactsDescription = view.findViewById(R.id.show_contacts_description);
showContactsProgress = view.findViewById(R.id.progress); showContactsProgress = view.findViewById(R.id.progress);
chipGroup = view.findViewById(R.id.chipGroup); chipRecycler = view.findViewById(R.id.chipRecycler);
chipGroupScrollContainer = view.findViewById(R.id.chipGroupScrollContainer);
constraintLayout = view.findViewById(R.id.container); constraintLayout = view.findViewById(R.id.container);
shadowView = view.findViewById(R.id.toolbar_shadow); headerActionView = view.findViewById(R.id.header_action);
toolbarShadowAnimationHelper = new ToolbarShadowAnimationHelper(shadowView); final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
recyclerView.addOnScrollListener(toolbarShadowAnimationHelper); recyclerView.setLayoutManager(layoutManager);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setItemAnimator(new DefaultItemAnimator() { recyclerView.setItemAnimator(new DefaultItemAnimator() {
@Override @Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) { public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
@@ -255,6 +256,18 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
}); });
contactChipViewModel = new ViewModelProvider(this).get(ContactChipViewModel.class);
contactChipAdapter = new MappingAdapter();
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(getViewLifecycleOwner());
SelectedContacts.register(contactChipAdapter, this::onChipCloseIconClicked);
chipRecycler.setAdapter(contactChipAdapter);
Disposable disposable = contactChipViewModel.getState().subscribe(this::handleSelectedContactsChanged);
lifecycleDisposable.add(disposable);
Intent intent = requireActivity().getIntent(); Intent intent = requireActivity().getIntent();
Bundle arguments = safeArguments(); Bundle arguments = safeArguments();
@@ -271,7 +284,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
swipeRefresh.setNestedScrollingEnabled(isRefreshable); swipeRefresh.setNestedScrollingEnabled(isRefreshable);
swipeRefresh.setEnabled(isRefreshable); swipeRefresh.setEnabled(isRefreshable);
hideCount = arguments.getBoolean(HIDE_COUNT, intent.getBooleanExtra(HIDE_COUNT, false));
selectionLimit = arguments.getParcelable(SELECTION_LIMITS); selectionLimit = arguments.getParcelable(SELECTION_LIMITS);
if (selectionLimit == null) { if (selectionLimit == null) {
selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS); selectionLimit = intent.getParcelableExtra(SELECTION_LIMITS);
@@ -285,9 +297,109 @@ public final class ContactSelectionListFragment extends LoggingFragment
currentSelection = getCurrentSelection(); currentSelection = getCurrentSelection();
final HeaderAction headerAction;
if (headerActionProvider != null) {
headerAction = headerActionProvider.getHeaderAction();
headerActionView.setEnabled(true);
headerActionView.setText(headerAction.getLabel());
headerActionView.setCompoundDrawablesRelativeWithIntrinsicBounds(headerAction.getIcon(), 0, 0, 0);
headerActionView.setOnClickListener(v -> headerAction.getAction().run());
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private final Rect bounds = new Rect();
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (hideLetterHeaders()) {
return;
}
int firstPosition = layoutManager.findFirstVisibleItemPosition();
if (firstPosition == 0) {
View firstChild = recyclerView.getChildAt(0);
recyclerView.getDecoratedBoundsWithMargins(firstChild, bounds);
headerActionView.setTranslationY(bounds.top);
}
}
});
} else {
headerActionView.setEnabled(false);
}
contactSearchMediator = new ContactSearchMediator(
this,
currentSelection.stream()
.map(r -> new ContactSearchKey.RecipientSearchKey(r, false))
.collect(java.util.stream.Collectors.toSet()),
selectionLimit,
isMulti,
ContactSearchAdapter.DisplaySmsTag.DEFAULT,
ContactSearchAdapter.DisplaySecondaryInformation.ALWAYS,
this::mapStateToConfiguration,
new ContactSearchMediator.SimpleCallbacks() {
@Override
public void onAdapterListCommitted(int size) {
onLoadFinished(size);
}
},
false,
(context, fixedContacts, displayCheckBox, displaySmsTag, displaySecondaryInformation, callbacks, longClickCallbacks, storyContextMenuCallbacks) -> new ContactSelectionListAdapter(
context,
fixedContacts,
displayCheckBox,
displaySmsTag,
displaySecondaryInformation,
new ContactSelectionListAdapter.OnContactSelectionClick() {
@Override
public void onNewGroupClicked() {
listCallback.onNewGroup(false);
}
@Override
public void onInviteToSignalClicked() {
listCallback.onInvite();
}
@Override
public void onStoryClicked(@NonNull View view1, @NonNull ContactSearchData.Story story, boolean isSelected) {
throw new UnsupportedOperationException();
}
@Override
public void onKnownRecipientClicked(@NonNull View view1, @NonNull ContactSearchData.KnownRecipient knownRecipient, boolean isSelected) {
listClickListener.onItemClick(knownRecipient.getContactSearchKey());
}
@Override
public void onExpandClicked(@NonNull ContactSearchData.Expand expand) {
callbacks.onExpandClicked(expand);
}
@Override
public void onUnknownRecipientClicked(@NonNull View view, @NonNull ContactSearchData.UnknownRecipient unknownRecipient, boolean isSelected) {
listClickListener.onItemClick(unknownRecipient.getContactSearchKey());
}
},
(anchorView, data) -> listClickListener.onItemLongClick(anchorView, data.getContactSearchKey()),
storyContextMenuCallbacks
),
new ContactSelectionListAdapter.ArbitraryRepository()
);
return view; return view;
} }
@Override
public void onDestroyView() {
super.onDestroyView();
constraintLayout = null;
}
private @NonNull Bundle safeArguments() { private @NonNull Bundle safeArguments() {
return getArguments() != null ? getArguments() : new Bundle(); return getArguments() != null ? getArguments() : new Bundle();
} }
@@ -298,27 +410,30 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
public @NonNull List<SelectedContact> getSelectedContacts() { public @NonNull List<SelectedContact> getSelectedContacts() {
if (cursorRecyclerViewAdapter == null) { if (contactSearchMediator == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return cursorRecyclerViewAdapter.getSelectedContacts(); return contactSearchMediator.getSelectedContacts()
.stream()
.map(ContactSearchKey::requireSelectedContact)
.collect(java.util.stream.Collectors.toList());
} }
public int getSelectedContactsCount() { public int getSelectedContactsCount() {
if (cursorRecyclerViewAdapter == null) { if (contactSearchMediator == null) {
return 0; return 0;
} }
return cursorRecyclerViewAdapter.getSelectedContactsCount(); return contactSearchMediator.getSelectedContacts().size();
} }
public int getTotalMemberCount() { public int getTotalMemberCount() {
if (cursorRecyclerViewAdapter == null) { if (contactSearchMediator == null) {
return 0; return 0;
} }
return cursorRecyclerViewAdapter.getSelectedContactsCount() + cursorRecyclerViewAdapter.getCurrentContactsCount(); return getSelectedContactsCount() + contactSearchMediator.getFixedContactsSize();
} }
private Set<RecipientId> getCurrentSelection() { private Set<RecipientId> getCurrentSelection() {
@@ -328,7 +443,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
return currentSelection == null ? Collections.emptySet() return currentSelection == null ? Collections.emptySet()
: Collections.unmodifiableSet(Stream.of(currentSelection).collect(Collectors.toSet())); : Collections.unmodifiableSet(new HashSet<>(currentSelection));
} }
public boolean isMulti() { public boolean isMulti() {
@@ -336,33 +451,8 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
private void initializeCursor() { private void initializeCursor() {
glideRequests = GlideApp.with(this);
cursorRecyclerViewAdapter = new ContactSelectionListAdapter(requireContext(),
glideRequests,
null,
new ListClickListener(),
isMulti,
currentSelection);
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
if (listCallback != null) {
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.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders)); recyclerView.addItemDecoration(new LetterHeaderDecoration(requireContext(), this::hideLetterHeaders));
recyclerView.setAdapter(concatenateAdapter); recyclerView.setAdapter(contactSearchMediator.getAdapter());
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override @Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
@@ -383,20 +473,6 @@ public final class ContactSelectionListFragment extends LoggingFragment
return hasQueryFilter() || shouldDisplayRecents(); return hasQueryFilter() || shouldDisplayRecents();
} }
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 void initializeNoContactsPermission() { private void initializeNoContactsPermission() {
swipeRefresh.setVisibility(View.GONE); swipeRefresh.setVisibility(View.GONE);
@@ -421,7 +497,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
public void setQueryFilter(String filter) { public void setQueryFilter(String filter) {
this.cursorFilter = filter; this.cursorFilter = filter;
LoaderManager.getInstance(this).restartLoader(0, null, this); contactSearchMediator.onFilterChanged(filter);
} }
public void resetQueryFilter() { public void resetQueryFilter() {
@@ -438,51 +514,21 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
public void reset() { public void reset() {
cursorRecyclerViewAdapter.clearSelectedContacts(); contactSearchMediator.clearSelection();
fastScroller.setVisibility(View.GONE);
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) { headerActionView.setVisibility(View.GONE);
LoaderManager.getInstance(this).restartLoader(0, null, this);
}
} }
public void setRecyclerViewPaddingBottom(@Px int paddingBottom) { public void setRecyclerViewPaddingBottom(@Px int paddingBottom) {
ViewUtil.setPaddingBottom(recyclerView, paddingBottom); ViewUtil.setPaddingBottom(recyclerView, paddingBottom);
} }
@Override private void onLoadFinished(int count) {
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
FragmentActivity activity = requireActivity();
int displayMode = safeArguments().getInt(DISPLAY_MODE, activity.getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL));
boolean displayRecents = shouldDisplayRecents();
if (cursorFactoryProvider != null) {
return cursorFactoryProvider.get().create();
} else {
return new ContactsCursorLoader.Factory(activity, displayMode, cursorFilter, displayRecents).create();
}
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, @Nullable Cursor data) {
swipeRefresh.setVisibility(View.VISIBLE); swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE); 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); emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
boolean useFastScroller = data != null && data.getCount() > 20; boolean useFastScroller = count > 20;
recyclerView.setVerticalScrollBarEnabled(!useFastScroller); recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
if (useFastScroller) { if (useFastScroller) {
fastScroller.setVisibility(View.VISIBLE); fastScroller.setVisibility(View.VISIBLE);
@@ -491,12 +537,12 @@ public final class ContactSelectionListFragment extends LoggingFragment
fastScroller.setRecyclerView(null); fastScroller.setRecyclerView(null);
fastScroller.setVisibility(View.GONE); fastScroller.setVisibility(View.GONE);
} }
}
@Override if (headerActionView.isEnabled() && !hasQueryFilter()) {
public void onLoaderReset(@NonNull Loader<Cursor> loader) { headerActionView.setVisibility(View.VISIBLE);
cursorRecyclerViewAdapter.changeCursor(null); } else {
fastScroller.setVisibility(View.GONE); headerActionView.setVisibility(View.GONE);
}
} }
private boolean shouldDisplayRecents() { private boolean shouldDisplayRecents() {
@@ -521,7 +567,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
try { try {
DirectoryHelper.refreshDirectory(context, false); ContactDiscovery.refreshAll(context, false);
return true; return true;
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Log.w(TAG, e);
@@ -546,18 +592,42 @@ public final class ContactSelectionListFragment extends LoggingFragment
}.execute(); }.execute();
} }
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener { /**
@Override * Allows the caller to submit a list of recipients to be marked selected. Useful for when a screen needs to load preselected
public void onItemClick(ContactSelectionListItem contact) { * entries in the background before setting them in the adapter.
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber()) *
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber()); * @param contacts List of the contacts to select. This will not overwrite the current selection, but append to it.
*/
public void markSelected(@NonNull Set<RecipientId> contacts) {
if (contacts.isEmpty()) {
return;
}
if (!canSelectSelf && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) { Set<SelectedContact> toMarkSelected = contacts.stream()
.filter(r -> !contactSearchMediator.getSelectedContacts()
.contains(new ContactSearchKey.RecipientSearchKey(r, false)))
.map(SelectedContact::forRecipientId)
.collect(java.util.stream.Collectors.toSet());
if (toMarkSelected.isEmpty()) {
return;
}
for (final SelectedContact selectedContact : toMarkSelected) {
markContactSelected(selectedContact);
}
}
private class ListClickListener {
public void onItemClick(ContactSearchKey contact) {
SelectedContact selectedContact = contact.requireSelectedContact();
if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show(); Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
return; return;
} }
if (!isMulti || !cursorRecyclerViewAdapter.isSelectedContact(selectedContact)) { if (!isMulti || !contactSearchMediator.getSelectedContacts().contains(selectedContact.toContactSearchKey())) {
if (selectionHardLimitReached()) { if (selectionHardLimitReached()) {
if (onSelectionLimitReachedListener != null) { if (onSelectionLimitReachedListener != null) {
onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit()); onSelectionLimitReachedListener.onHardLimitReached(selectionLimit.getHardLimit());
@@ -567,58 +637,62 @@ public final class ContactSelectionListFragment extends LoggingFragment
return; return;
} }
if (contact.isUsernameType()) { if (contact instanceof ContactSearchKey.UnknownRecipientKey && ((ContactSearchKey.UnknownRecipientKey) contact).getSectionKey() == ContactSearchConfiguration.SectionKey.USERNAME) {
String username = ((ContactSearchKey.UnknownRecipientKey) contact).getQuery();
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext()); AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> { SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
return UsernameUtil.fetchAciForUsername(contact.getNumber()); return UsernameUtil.fetchAciForUsername(username);
}, uuid -> { }, uuid -> {
loadingDialog.dismiss(); loadingDialog.dismiss();
if (uuid.isPresent()) { if (uuid.isPresent()) {
Recipient recipient = Recipient.externalUsername(uuid.get(), contact.getNumber()); Recipient recipient = Recipient.externalUsername(uuid.get(), username);
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), contact.getNumber()); SelectedContact selected = SelectedContact.forUsername(recipient.getId(), username);
if (onContactSelectedListener != null) { if (onContactSelectedListener != null) {
onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null, allowed -> { onContactSelectedListener.onBeforeContactSelected(Optional.of(recipient.getId()), null, allowed -> {
if (allowed) { if (allowed) {
markContactSelected(selected); markContactSelected(selected);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
} }
}); });
} else { } else {
markContactSelected(selected); markContactSelected(selected);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
} }
} else { } else {
new MaterialAlertDialogBuilder(requireContext()) new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.ContactSelectionListFragment_username_not_found) .setTitle(R.string.ContactSelectionListFragment_username_not_found)
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber())) .setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, username))
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()) .setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
.show(); .show();
} }
}); });
} else { } else {
if (onContactSelectedListener != null) { if (onContactSelectedListener != null) {
onContactSelectedListener.onBeforeContactSelected(contact.getRecipientId(), contact.getNumber(), allowed -> { onContactSelectedListener.onBeforeContactSelected(Optional.ofNullable(selectedContact.getRecipientId()), selectedContact.getNumber(), allowed -> {
if (allowed) { if (allowed) {
markContactSelected(selectedContact); markContactSelected(selectedContact);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
} }
}); });
} else { } else {
markContactSelected(selectedContact); markContactSelected(selectedContact);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
} }
} }
} else { } else {
markContactUnselected(selectedContact); markContactUnselected(selectedContact);
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE);
if (onContactSelectedListener != null) { if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber()); onContactSelectedListener.onContactDeselected(Optional.ofNullable(selectedContact.getRecipientId()), selectedContact.getNumber());
} }
} }
} }
public boolean onItemLongClick(View anchorView, ContactSearchKey item) {
if (onItemLongClickListener != null) {
return onItemLongClickListener.onLongClick(anchorView, item, recyclerView);
} else {
return false;
}
}
} }
private boolean selectionHardLimitReached() { private boolean selectionHardLimitReached() {
@@ -634,7 +708,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
private void markContactSelected(@NonNull SelectedContact selectedContact) { private void markContactSelected(@NonNull SelectedContact selectedContact) {
cursorRecyclerViewAdapter.addSelectedContact(selectedContact); contactSearchMediator.setKeysSelected(Collections.singleton(selectedContact.toContactSearchKey()));
if (isMulti) { if (isMulti) {
addChipForSelectedContact(selectedContact); addChipForSelectedContact(selectedContact);
} }
@@ -644,77 +718,23 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
private void markContactUnselected(@NonNull SelectedContact selectedContact) { private void markContactUnselected(@NonNull SelectedContact selectedContact) {
cursorRecyclerViewAdapter.removeFromSelectedContacts(selectedContact); contactSearchMediator.setKeysNotSelected(Collections.singleton(selectedContact.toContactSearchKey()));
cursorRecyclerViewAdapter.notifyItemRangeChanged(0, cursorRecyclerViewAdapter.getItemCount(), ContactSelectionListAdapter.PAYLOAD_SELECTION_CHANGE); contactChipViewModel.remove(selectedContact);
removeChipForContact(selectedContact);
if (onContactSelectedListener != null) { if (onContactSelectedListener != null) {
onContactSelectedListener.onSelectionChanged(); onContactSelectedListener.onSelectionChanged();
} }
} }
private void removeChipForContact(@NonNull SelectedContact contact) { private void handleSelectedContactsChanged(@NonNull List<SelectedContacts.Model> selectedContacts) {
for (int i = chipGroup.getChildCount() - 1; i >= 0; i--) { contactChipAdapter.submitList(new MappingModelList(selectedContacts), this::smoothScrollChipsToEnd);
View v = chipGroup.getChildAt(i);
if (v instanceof ContactChip && contact.matches(((ContactChip) v).getContact())) {
chipGroup.removeView(v);
}
}
if (getChipCount() == 0) { if (selectedContacts.isEmpty()) {
setChipGroupVisibility(ConstraintSet.GONE); setChipGroupVisibility(ConstraintSet.GONE);
} } else {
}
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); 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 (getView() == null || !requireView().isAttachedToWindow()) {
Log.w(TAG, "Fragment's view was detached before the animation completed.");
return;
}
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);
if (selectionWarningLimitReachedExactly()) { if (selectionWarningLimitReachedExactly()) {
if (onSelectionLimitReachedListener != null) { if (onSelectionLimitReachedListener != null) {
onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit()); onSelectionLimitReachedListener.onSuggestedLimitReached(selectionLimit.getRecommendedLimit());
@@ -724,21 +744,25 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
} }
private int getChipCount() { private void addChipForSelectedContact(@NonNull SelectedContact selectedContact) {
int count = chipGroup.getChildCount() - CHIP_GROUP_EMPTY_CHILD_COUNT; SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
if (count < 0) throw new AssertionError(); () -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
return count; resolved -> contactChipViewModel.add(selectedContact));
} }
private void registerChipRecipientObserver(@NonNull ContactChip chip, @Nullable LiveRecipient recipient) { private Unit onChipCloseIconClicked(SelectedContacts.Model model) {
if (recipient != null) { markContactUnselected(model.getSelectedContact());
recipient.observe(getViewLifecycleOwner(), resolved -> { if (onContactSelectedListener != null) {
if (chip.isAttachedToWindow()) { onContactSelectedListener.onContactDeselected(Optional.of(model.getRecipient().getId()), model.getRecipient().getE164().orElse(null));
chip.setAvatar(glideRequests, resolved, null);
chip.setText(resolved.getShortDisplayName(chip.getContext()));
}
});
} }
return Unit.INSTANCE;
}
private int getChipCount() {
int count = contactChipViewModel.getCount() - CHIP_GROUP_EMPTY_CHILD_COUNT;
if (count < 0) throw new AssertionError();
return count;
} }
private void setChipGroupVisibility(int visibility) { private void setChipGroupVisibility(int visibility) {
@@ -754,7 +778,7 @@ public final class ContactSelectionListFragment extends LoggingFragment
ConstraintSet constraintSet = new ConstraintSet(); ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout); constraintSet.clone(constraintLayout);
constraintSet.setVisibility(R.id.chipGroupScrollContainer, visibility); constraintSet.setVisibility(R.id.chipRecycler, visibility);
constraintSet.applyTo(constraintLayout); constraintSet.applyTo(constraintLayout);
} }
@@ -763,24 +787,140 @@ public final class ContactSelectionListFragment extends LoggingFragment
} }
private void smoothScrollChipsToEnd() { private void smoothScrollChipsToEnd() {
int x = ViewUtil.isLtr(chipGroupScrollContainer) ? chipGroup.getWidth() : 0; int x = ViewUtil.isLtr(chipRecycler) ? chipRecycler.getWidth() : 0;
chipGroupScrollContainer.smoothScrollTo(x, 0); chipRecycler.smoothScrollBy(x, 0);
}
private @NonNull ContactSearchConfiguration mapStateToConfiguration(@NonNull ContactSearchState contactSearchState) {
int displayMode = safeArguments().getInt(DISPLAY_MODE, requireActivity().getIntent().getIntExtra(DISPLAY_MODE, ContactSelectionDisplayMode.FLAG_ALL));
boolean includeRecents = safeArguments().getBoolean(RECENTS, requireActivity().getIntent().getBooleanExtra(RECENTS, false));
boolean includePushContacts = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_PUSH);
boolean includeSmsContacts = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_SMS);
boolean includeActiveGroups = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS);
boolean includeInactiveGroups = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS);
boolean includeSelf = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_SELF);
boolean includeV1Groups = !flagSet(displayMode, ContactSelectionDisplayMode.FLAG_HIDE_GROUPS_V1);
boolean includeNew = !flagSet(displayMode, ContactSelectionDisplayMode.FLAG_HIDE_NEW);
boolean includeRecentsHeader = !flagSet(displayMode, ContactSelectionDisplayMode.FLAG_HIDE_RECENT_HEADER);
boolean includeGroupsAfterContacts = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_GROUPS_AFTER_CONTACTS);
boolean blocked = flagSet(displayMode, ContactSelectionDisplayMode.FLAG_BLOCK);
ContactSearchConfiguration.TransportType transportType = resolveTransportType(includePushContacts, includeSmsContacts);
ContactSearchConfiguration.Section.Recents.Mode mode = resolveRecentsMode(transportType, includeActiveGroups);
ContactSearchConfiguration.NewRowMode newRowMode = resolveNewRowMode(blocked, includeActiveGroups);
return ContactSearchConfiguration.build(builder -> {
builder.setQuery(contactSearchState.getQuery());
if (listCallback != null) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
}
if (transportType != null) {
if (TextUtils.isEmpty(contactSearchState.getQuery()) && includeRecents) {
builder.addSection(new ContactSearchConfiguration.Section.Recents(
25,
mode,
includeInactiveGroups,
includeV1Groups,
includeSmsContacts,
includeSelf,
includeRecentsHeader,
null
));
}
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
includeSelf,
transportType,
true,
null,
!hideLetterHeaders()
));
}
if ((includeGroupsAfterContacts || !TextUtils.isEmpty(contactSearchState.getQuery())) && includeActiveGroups) {
builder.addSection(new ContactSearchConfiguration.Section.Groups(
includeSmsContacts,
includeV1Groups,
includeInactiveGroups,
false,
ContactSearchSortOrder.NATURAL,
false,
true,
null
));
}
if (listCallback != null) {
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.INVITE_TO_SIGNAL.getCode());
}
if (includeNew) {
builder.phone(newRowMode);
builder.username(newRowMode);
}
return Unit.INSTANCE;
});
}
private static @Nullable ContactSearchConfiguration.TransportType resolveTransportType(boolean includePushContacts, boolean includeSmsContacts) {
if (includePushContacts && includeSmsContacts) {
return ContactSearchConfiguration.TransportType.ALL;
} else if (includePushContacts) {
return ContactSearchConfiguration.TransportType.PUSH;
} else if (includeSmsContacts) {
return ContactSearchConfiguration.TransportType.SMS;
} else {
return null;
}
}
private static @NonNull ContactSearchConfiguration.Section.Recents.Mode resolveRecentsMode(ContactSearchConfiguration.TransportType transportType, boolean includeGroupContacts) {
if (transportType != null && includeGroupContacts) {
return ContactSearchConfiguration.Section.Recents.Mode.ALL;
} else if (includeGroupContacts) {
return ContactSearchConfiguration.Section.Recents.Mode.GROUPS;
} else {
return ContactSearchConfiguration.Section.Recents.Mode.INDIVIDUALS;
}
}
private static @NonNull ContactSearchConfiguration.NewRowMode resolveNewRowMode(boolean isBlocked, boolean isActiveGroups) {
if (isBlocked) {
return ContactSearchConfiguration.NewRowMode.BLOCK;
} else if (isActiveGroups) {
return ContactSearchConfiguration.NewRowMode.NEW_CONVERSATION;
} else {
return ContactSearchConfiguration.NewRowMode.ADD_TO_GROUP;
}
}
private static boolean flagSet(int mode, int flag) {
return (mode & flag) > 0;
} }
public interface OnContactSelectedListener { public interface OnContactSelectedListener {
/** Provides an opportunity to disallow selecting an item. Call the callback with false to disallow, or true to allow it. */ /**
void onBeforeContactSelected(Optional<RecipientId> recipientId, @Nullable String number, Consumer<Boolean> callback); * Provides an opportunity to disallow selecting an item. Call the callback with false to disallow, or true to allow it.
void onContactDeselected(Optional<RecipientId> recipientId, @Nullable String number); */
void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, @Nullable String number, @NonNull Consumer<Boolean> callback);
void onContactDeselected(@NonNull Optional<RecipientId> recipientId, @Nullable String number);
void onSelectionChanged(); void onSelectionChanged();
} }
public interface OnSelectionLimitReachedListener { public interface OnSelectionLimitReachedListener {
void onSuggestedLimitReached(int limit); void onSuggestedLimitReached(int limit);
void onHardLimitReached(int limit); void onHardLimitReached(int limit);
} }
public interface ListCallback { public interface ListCallback {
void onInvite(); void onInvite();
void onNewGroup(boolean forceV1); void onNewGroup(boolean forceV1);
} }
@@ -788,7 +928,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
void onBeginScroll(); void onBeginScroll();
} }
public interface AbstractContactsCursorLoaderFactoryProvider { public interface HeaderActionProvider {
@NonNull AbstractContactsCursorLoader.Factory get(); @NonNull HeaderAction getHeaderAction();
}
public interface OnItemLongClickListener {
boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView);
} }
} }
@@ -1,201 +0,0 @@
package org.thoughtcrime.securesms;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.thoughtcrime.securesms.database.SmsMigrator.ProgressDescription;
import org.thoughtcrime.securesms.service.ApplicationMigrationService;
import org.thoughtcrime.securesms.service.ApplicationMigrationService.ImportState;
public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
private final ImportServiceConnection serviceConnection = new ImportServiceConnection();
private final ImportStateHandler importStateHandler = new ImportStateHandler();
private final BroadcastReceiver completedReceiver = new NullReceiver();
private LinearLayout promptLayout;
private LinearLayout progressLayout;
private Button skipButton;
private Button importButton;
private ProgressBar progress;
private TextView progressLabel;
private ApplicationMigrationService importService;
private boolean isVisible = false;
@Override
protected void onCreate(Bundle bundle, boolean ready) {
setContentView(R.layout.database_migration_activity);
initializeResources();
initializeServiceBinding();
}
@Override
public void onResume() {
super.onResume();
isVisible = true;
registerForCompletedNotification();
}
@Override
public void onPause() {
super.onPause();
isVisible = false;
unregisterForCompletedNotification();
}
@Override
public void onDestroy() {
super.onDestroy();
shutdownServiceBinding();
}
@Override
public void onBackPressed() {
}
private void initializeServiceBinding() {
Intent intent = new Intent(this, ApplicationMigrationService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private void initializeResources() {
this.promptLayout = (LinearLayout)findViewById(R.id.prompt_layout);
this.progressLayout = (LinearLayout)findViewById(R.id.progress_layout);
this.skipButton = (Button) findViewById(R.id.skip_button);
this.importButton = (Button) findViewById(R.id.import_button);
this.progress = (ProgressBar) findViewById(R.id.import_progress);
this.progressLabel = (TextView) findViewById(R.id.import_status);
this.progressLayout.setVisibility(View.GONE);
this.promptLayout.setVisibility(View.GONE);
this.importButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DatabaseMigrationActivity.this, ApplicationMigrationService.class);
intent.setAction(ApplicationMigrationService.MIGRATE_DATABASE);
intent.putExtra("master_secret", (Parcelable)getIntent().getParcelableExtra("master_secret"));
startService(intent);
promptLayout.setVisibility(View.GONE);
progressLayout.setVisibility(View.VISIBLE);
}
});
this.skipButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ApplicationMigrationService.setDatabaseImported(DatabaseMigrationActivity.this);
handleImportComplete();
}
});
}
private void registerForCompletedNotification() {
IntentFilter filter = new IntentFilter();
filter.addAction(ApplicationMigrationService.COMPLETED_ACTION);
filter.setPriority(1000);
registerReceiver(completedReceiver, filter);
}
private void unregisterForCompletedNotification() {
unregisterReceiver(completedReceiver);
}
private void shutdownServiceBinding() {
unbindService(serviceConnection);
}
private void handleStateIdle() {
this.promptLayout.setVisibility(View.VISIBLE);
this.progressLayout.setVisibility(View.GONE);
}
private void handleStateProgress(ProgressDescription update) {
this.promptLayout.setVisibility(View.GONE);
this.progressLayout.setVisibility(View.VISIBLE);
this.progressLabel.setText(update.primaryComplete + "/" + update.primaryTotal);
double max = this.progress.getMax();
double primaryTotal = update.primaryTotal;
double primaryComplete = update.primaryComplete;
double secondaryTotal = update.secondaryTotal;
double secondaryComplete = update.secondaryComplete;
this.progress.setProgress((int)Math.round((primaryComplete / primaryTotal) * max));
this.progress.setSecondaryProgress((int)Math.round((secondaryComplete / secondaryTotal) * max));
}
private void handleImportComplete() {
if (isVisible) {
if (getIntent().hasExtra("next_intent")) {
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
} else {
// TODO [greyson] Navigation
startActivity(MainActivity.clearTop(this));
}
}
finish();
}
private class ImportStateHandler extends Handler {
public ImportStateHandler() {
super(Looper.getMainLooper());
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case ImportState.STATE_IDLE: handleStateIdle(); break;
case ImportState.STATE_MIGRATING_IN_PROGRESS: handleStateProgress((ProgressDescription)message.obj); break;
case ImportState.STATE_MIGRATING_COMPLETE: handleImportComplete(); break;
}
}
}
private class ImportServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
importService = ((ApplicationMigrationService.ApplicationMigrationBinder)service).getService();
importService.setImportStateHandler(importStateHandler);
ImportState state = importService.getState();
importStateHandler.obtainMessage(state.state, state.progress).sendToTarget();
}
@Override
public void onServiceDisconnected(ComponentName name) {
importService.setImportStateHandler(null);
}
}
private static class NullReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
abortBroadcast();
}
}
}
@@ -10,6 +10,8 @@ import android.os.Bundle;
import android.os.Vibrator; import android.os.Vibrator;
import android.text.TextUtils; import android.text.TextUtils;
import android.transition.TransitionInflater; import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
@@ -17,28 +19,25 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import org.signal.core.util.ThreadUtil; import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.signal.zkgroup.profiles.ProfileKey; import org.signal.libsignal.protocol.IdentityKeyPair;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.signal.libsignal.protocol.InvalidKeyException;
import org.signal.libsignal.protocol.ecc.Curve;
import org.signal.libsignal.protocol.ecc.ECPublicKey;
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
import org.signal.qr.kitkat.ScanListener;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
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.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.NotFoundException; import org.whispersystems.signalservice.api.push.exceptions.NotFoundException;
import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException; import org.whispersystems.signalservice.internal.push.DeviceLimitExceededException;
@@ -57,6 +56,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
private DeviceAddFragment deviceAddFragment; private DeviceAddFragment deviceAddFragment;
private DeviceListFragment deviceListFragment; private DeviceListFragment deviceListFragment;
private DeviceLinkFragment deviceLinkFragment; private DeviceLinkFragment deviceLinkFragment;
private MenuItem cameraSwitchItem = null;
@Override @Override
public void onPreCreate() { public void onPreCreate() {
@@ -105,6 +105,18 @@ public class DeviceActivity extends PassphraseRequiredActivity
return false; return false;
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.device_add, menu);
cameraSwitchItem = menu.findItem(R.id.device_add_camera_switch);
cameraSwitchItem.setVisible(false);
return super.onCreateOptionsMenu(menu);
}
public MenuItem getCameraSwitchItem() {
return cameraSwitchItem;
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Permissions.with(this) Permissions.with(this)
@@ -128,27 +140,18 @@ public class DeviceActivity extends PassphraseRequiredActivity
Uri uri = Uri.parse(data); Uri uri = Uri.parse(data);
deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this); deviceLinkFragment.setLinkClickedListener(uri, DeviceActivity.this);
if (Build.VERSION.SDK_INT >= 21) { deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceAddFragment.setSharedElementReturnTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared)); deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
deviceAddFragment.setExitTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared)); deviceLinkFragment.setSharedElementEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(R.transition.fragment_shared));
deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade)); deviceLinkFragment.setEnterTransition(TransitionInflater.from(DeviceActivity.this).inflateTransition(android.R.transition.fade));
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.addToBackStack(null) .addToBackStack(null)
.addSharedElement(deviceAddFragment.getDevicesImage(), "devices") .addSharedElement(deviceAddFragment.getDevicesImage(), "devices")
.replace(R.id.fragment_container, deviceLinkFragment) .replace(R.id.fragment_container, deviceLinkFragment)
.commit(); .commit();
} else {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_bottom, R.anim.slide_to_bottom,
R.anim.slide_from_bottom, R.anim.slide_to_bottom)
.replace(R.id.fragment_container, deviceLinkFragment)
.addToBackStack(null)
.commit();
}
}); });
} }
@@ -2,109 +2,96 @@ package org.thoughtcrime.securesms;
import android.animation.Animator; import android.animation.Animator;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewAnimationUtils; import android.view.ViewAnimationUtils;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator; import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import org.thoughtcrime.securesms.components.camera.CameraView; import org.signal.qr.QrScannerView;
import org.thoughtcrime.securesms.qr.ScanListener; import org.signal.qr.kitkat.ScanListener;
import org.thoughtcrime.securesms.qr.ScanningThread; import org.thoughtcrime.securesms.mediasend.camerax.CameraXModelBlocklist;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.Disposable;
public class DeviceAddFragment extends LoggingFragment { public class DeviceAddFragment extends LoggingFragment {
private ViewGroup container; private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
private LinearLayout overlay;
private ImageView devicesImage; private ImageView devicesImage;
private CameraView scannerView; private ScanListener scanListener;
private ScanningThread scanningThread; private QrScannerView scannerView;
private ScanListener scanListener;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment); ViewGroup container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
this.overlay = this.container.findViewById(R.id.overlay);
this.scannerView = this.container.findViewById(R.id.scanner);
this.devicesImage = this.container.findViewById(R.id.devices);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { this.scannerView = container.findViewById(R.id.scanner);
this.overlay.setOrientation(LinearLayout.HORIZONTAL); this.devicesImage = container.findViewById(R.id.devices);
} else { ViewCompat.setTransitionName(devicesImage, "devices");
this.overlay.setOrientation(LinearLayout.VERTICAL);
}
if (Build.VERSION.SDK_INT >= 21) { container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
this.container.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override
@TargetApi(21) public void onLayoutChange(View v, int left, int top, int right, int bottom,
@Override int oldLeft, int oldTop, int oldRight, int oldBottom)
public void onLayoutChange(View v, int left, int top, int right, int bottom, {
int oldLeft, int oldTop, int oldRight, int oldBottom) v.removeOnLayoutChangeListener(this);
{
v.removeOnLayoutChangeListener(this);
Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom)); Animator reveal = ViewAnimationUtils.createCircularReveal(v, right, bottom, 0, (int) Math.hypot(right, bottom));
reveal.setInterpolator(new DecelerateInterpolator(2f)); reveal.setInterpolator(new DecelerateInterpolator(2f));
reveal.setDuration(800); reveal.setDuration(800);
reveal.start(); reveal.start();
} }
});
scannerView.start(getViewLifecycleOwner(), CameraXModelBlocklist.isBlocklisted());
lifecycleDisposable.bindTo(getViewLifecycleOwner());
Disposable qrDisposable = scannerView
.getQrData()
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(qrData -> {
if (scanListener != null) {
scanListener.onQrDataFound(qrData);
}
});
lifecycleDisposable.add(qrDisposable);
return container;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
MenuItem switchCamera = ((DeviceActivity) requireActivity()).getCameraSwitchItem();
if (switchCamera != null) {
switchCamera.setVisible(true);
switchCamera.setOnMenuItemClickListener(v -> {
scannerView.toggleCamera();
return true;
}); });
} }
return this.container;
} }
@Override
public void onResume() {
super.onResume();
this.scanningThread = new ScanningThread();
this.scanningThread.setScanListener(scanListener);
this.scannerView.onResume();
this.scannerView.setPreviewCallback(scanningThread);
this.scanningThread.start();
}
@Override
public void onPause() {
super.onPause();
this.scannerView.onPause();
this.scanningThread.stopScanning();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
this.scannerView.onPause();
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
overlay.setOrientation(LinearLayout.HORIZONTAL);
} else {
overlay.setOrientation(LinearLayout.VERTICAL);
}
this.scannerView.onResume();
this.scannerView.setPreviewCallback(scanningThread);
}
public ImageView getDevicesImage() { public ImageView getDevicesImage() {
return devicesImage; return devicesImage;
} }
public void setScanListener(ScanListener scanListener) { public void setScanListener(ScanListener scanListener) {
this.scanListener = scanListener; this.scanListener = scanListener;
if (this.scanningThread != null) {
this.scanningThread.setScanListener(scanListener);
}
} }
} }
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
public class DeviceLinkFragment extends Fragment implements View.OnClickListener { public class DeviceLinkFragment extends Fragment implements View.OnClickListener {
@@ -21,6 +22,7 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false); this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false);
this.container.findViewById(R.id.link_device).setOnClickListener(this); this.container.findViewById(R.id.link_device).setOnClickListener(this);
ViewCompat.setTransitionName(container.findViewById(R.id.devices), "devices");
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
container.setOrientation(LinearLayout.HORIZONTAL); container.setOrientation(LinearLayout.HORIZONTAL);
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms;
import android.annotation.SuppressLint; 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.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -22,7 +21,7 @@ import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader; import androidx.loader.content.Loader;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.melnykov.fab.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader; import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
@@ -16,20 +16,22 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.AnimRes; import androidx.annotation.AnimRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import org.thoughtcrime.securesms.components.ContactFilterView; import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener; import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode; import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact; import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mms.OutgoingMessage;
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.sms.MessageSender; import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme; import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@@ -37,8 +39,8 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener; import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.text.AfterTextChanged; import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -60,7 +62,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
@Override @Override
protected void onCreate(Bundle savedInstanceState, boolean ready) { protected void onCreate(Bundle savedInstanceState, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS); getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, ContactSelectionDisplayMode.FLAG_SMS);
getIntent().putExtra(ContactSelectionListFragment.SELECTION_LIMITS, SelectionLimits.NO_LIMITS); getIntent().putExtra(ContactSelectionListFragment.SELECTION_LIMITS, SelectionLimits.NO_LIMITS);
getIntent().putExtra(ContactSelectionListFragment.HIDE_COUNT, true); getIntent().putExtra(ContactSelectionListFragment.HIDE_COUNT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false); getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
@@ -117,7 +119,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
smsSendButton.setOnClickListener(new SmsSendClickListener()); smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener()); contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
if (Util.isDefaultSmsProvider(this)) { if (Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
shareButton.setOnClickListener(new ShareClickListener()); shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener()); smsButton.setOnClickListener(new SmsClickListener());
} else { } else {
@@ -134,13 +136,13 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
} }
@Override @Override
public void onBeforeContactSelected(Optional<RecipientId> recipientId, String number, Consumer<Boolean> callback) { public void onBeforeContactSelected(@NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
updateSmsButtonText(contactsFragment.getSelectedContacts().size() + 1); updateSmsButtonText(contactsFragment.getSelectedContacts().size() + 1);
callback.accept(true); callback.accept(true);
} }
@Override @Override
public void onContactDeselected(Optional<RecipientId> recipientId, String number) { public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, String number) {
updateSmsButtonText(contactsFragment.getSelectedContacts().size()); updateSmsButtonText(contactsFragment.getSelectedContacts().size());
} }
@@ -250,9 +252,9 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
for (SelectedContact contact : contacts) { for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context); RecipientId recipientId = contact.getOrCreateRecipientId(context);
Recipient recipient = Recipient.resolved(recipientId); Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1); int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null, null); MessageSender.send(context, OutgoingMessage.sms(recipient, message, subscriptionId), -1L, MessageSender.SendType.SMS, null, null);
if (recipient.getContactUri() != null) { if (recipient.getContactUri() != null) {
SignalDatabase.recipients().setHasSentInvite(recipient.getId()); SignalDatabase.recipients().setHasSentInvite(recipient.getId());
@@ -5,19 +5,27 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog; import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.stories.Stories;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup; import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.CachedInflater; import org.thoughtcrime.securesms.util.CachedInflater;
import org.thoughtcrime.securesms.util.CommunicationActions; import org.thoughtcrime.securesms.util.CommunicationActions;
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.SplashScreenUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner { public class MainActivity extends PassphraseRequiredActivity implements VoiceNoteMediaControllerOwner {
@@ -26,13 +34,14 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme(); private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final MainNavigator navigator = new MainNavigator(this); private final MainNavigator navigator = new MainNavigator(this);
private VoiceNoteMediaController mediaController; private VoiceNoteMediaController mediaController;
private ConversationListTabsViewModel conversationListTabsViewModel;
public static @NonNull Intent clearTop(@NonNull Context context) { public static @NonNull Intent clearTop(@NonNull Context context) {
Intent intent = new Intent(context, MainActivity.class); Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_SINGLE_TOP); Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent; return intent;
@@ -42,22 +51,28 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
protected void onCreate(Bundle savedInstanceState, boolean ready) { protected void onCreate(Bundle savedInstanceState, boolean ready) {
AppStartup.getInstance().onCriticalRenderEventStart(); AppStartup.getInstance().onCriticalRenderEventStart();
super.onCreate(savedInstanceState, ready); super.onCreate(savedInstanceState, ready);
setContentView(R.layout.main_activity); setContentView(R.layout.main_activity);
mediaController = new VoiceNoteMediaController(this); mediaController = new VoiceNoteMediaController(this);
navigator.onCreate(savedInstanceState);
ConversationListTabRepository repository = new ConversationListTabRepository();
ConversationListTabsViewModel.Factory factory = new ConversationListTabsViewModel.Factory(repository);
handleGroupLinkInIntent(getIntent()); handleGroupLinkInIntent(getIntent());
handleProxyInIntent(getIntent()); handleProxyInIntent(getIntent());
handleSignalMeIntent(getIntent()); handleSignalMeIntent(getIntent());
CachedInflater.from(this).clear(); CachedInflater.from(this).clear();
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
updateTabVisibility();
} }
@Override @Override
public Intent getIntent() { public Intent getIntent() {
return super.getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | return super.getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_SINGLE_TOP); Intent.FLAG_ACTIVITY_SINGLE_TOP);
} }
@@ -82,6 +97,14 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
if (SignalStore.misc().isOldDeviceTransferLocked()) { if (SignalStore.misc().isOldDeviceTransferLocked()) {
OldDeviceTransferLockedDialog.show(getSupportFragmentManager()); OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
} }
updateTabVisibility();
}
@Override
protected void onStop() {
super.onStop();
SplashScreenUtil.setSplashScreenThemeIfNecessary(this, SignalStore.settings().getTheme());
} }
@Override @Override
@@ -99,6 +122,17 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
} }
} }
private void updateTabVisibility() {
if (Stories.isFeatureEnabled()) {
findViewById(R.id.conversation_list_tabs).setVisibility(View.VISIBLE);
WindowUtil.setNavigationBarColor(this, ContextCompat.getColor(this, R.color.signal_colorSurface2));
} else {
findViewById(R.id.conversation_list_tabs).setVisibility(View.GONE);
WindowUtil.setNavigationBarColor(this, ContextCompat.getColor(this, R.color.signal_colorBackground));
conversationListTabsViewModel.onChatsSelected();
}
}
public @NonNull MainNavigator getNavigator() { public @NonNull MainNavigator getNavigator() {
return navigator; return navigator;
} }

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