Compare commits

..

1924 Commits

Author SHA1 Message Date
Alex Hart 30cc3ff9fc Bump version to 7.4.0 2024-04-10 16:31:53 -03:00
Alex Hart 6f5f299035 Update baseline profile. 2024-04-10 16:31:47 -03:00
Alex Hart 02eed02cb8 Update translations and other static files. 2024-04-10 16:29:24 -03:00
Greyson Parrelli c1d29b5c39 Set internalUser=true for nightly builds. 2024-04-10 14:54:35 -04:00
Greyson Parrelli db4442939d Remove Environment.IS_PNP 2024-04-10 14:52:59 -04:00
tedgravlin 6ece776382 Fix navbar color in multiple instances. 2024-04-10 14:29:58 -03:00
Alex Hart 0eda714755 Send recipients when sending group story sync. 2024-04-10 14:21:34 -03:00
Greyson Parrelli 831d099503 Inline the nicknames feature flag. 2024-04-10 13:18:01 -04:00
Alex Hart fa23e4ca70 Convert members collection to set to avoid duplicate entries. 2024-04-10 13:45:46 -03:00
Greyson Parrelli 982f602178 Regularly analyze database tables to improve index usage. 2024-04-09 16:55:25 -04:00
Greyson Parrelli 713298109a Specify indexes for mention table queries. 2024-04-09 16:18:21 -04:00
Greyson Parrelli 8793981804 Add a log section for the database schema. 2024-04-09 16:18:21 -04:00
Greyson Parrelli 9bd4e9524c Convert MentionTable to kotlin. 2024-04-09 16:18:21 -04:00
Cody Henthorne 791dc2724f Attempt to fix bad notification for call service shutdown. 2024-04-09 16:18:21 -04:00
Cody Henthorne ba3473c61a Fix scroll to message when bubble is under toolbar. 2024-04-09 16:18:21 -04:00
moiseev-signal 3ea194255d Add getUsername default method to CredentialsProvider 2024-04-09 16:18:21 -04:00
Cody Henthorne ea081e981f Treat unregistered user during send as general failure. 2024-04-09 16:18:21 -04:00
Alex Konradi 2ce6ea9a2a Use existing libsignal proguard rules. 2024-04-09 16:18:20 -04:00
Alex Konradi 295c9310e9 Map libsignal CDSI errors to existing exceptions. 2024-04-09 16:18:20 -04:00
Greyson Parrelli 7447ed2eac Add the ability to jump to a specific date in search. 2024-04-09 16:18:20 -04:00
Cody Henthorne d5bf16b91a Fix incorrect thread body adjustments containing media, mentions, and styling. 2024-04-09 16:18:06 -04:00
Cody Henthorne 76665c1f0d Prevent excessive video toggling in group calls due to server instability. 2024-04-09 16:18:06 -04:00
Cody Henthorne dd28523b05 Transition full screen call UX to terminal state when call handled by linked device. 2024-04-09 16:18:06 -04:00
Cody Henthorne 16588c401e Reduce verbosity of WebRtcViewModel event logging during calls. 2024-04-09 16:18:06 -04:00
Greyson Parrelli dbf8a7ca87 Rotate libsignal-net flag. 2024-04-09 16:18:06 -04:00
moiseev-signal e92c76434e Upgrade to libsignal-client 0.44.0 2024-04-09 16:18:06 -04:00
Greyson Parrelli 7adb581271 Bump version to 7.3.1 2024-04-09 16:17:21 -04:00
Greyson Parrelli 869476a41b Update translations and other static files. 2024-04-09 16:16:47 -04:00
Greyson Parrelli 8daf1bca20 Improve handling of unknown groups. 2024-04-09 15:56:15 -04:00
Greyson Parrelli d044b3c931 Remove most lazy properties from Recipient. 2024-04-09 15:02:36 -04:00
Cody Henthorne 0fcb19e1cc Fix group recipient resolve race that can cause unknown group recipients in live cache. 2024-04-09 14:59:47 -04:00
Nicholas Tinsley 2a6977da75 Nickname screen copy update. 2024-04-04 09:45:26 -04:00
Nicholas Tinsley 26bd435bf6 Update nickname delete dialog copy. 2024-04-03 16:48:26 -04:00
Greyson Parrelli 91f8d6075c Bump version to 7.3.0 2024-04-03 15:55:34 -04:00
Greyson Parrelli 9ed9a330f4 Update baseline profile. 2024-04-03 15:54:13 -04:00
Greyson Parrelli 8bbf6b790f Update translations and other static files. 2024-04-03 15:47:24 -04:00
Greyson Parrelli a277e9b307 Fix compilation of benchmark build. 2024-04-03 15:47:24 -04:00
Cody Henthorne f8e6bcf290 Add username_edit release note cta action. 2024-04-03 14:07:56 -04:00
Greyson Parrelli 3ba2b46bb0 Convert Recipient to kotlin. 2024-04-03 14:02:55 -04:00
Greyson Parrelli b50eab230d Update strings for 'system contact' -> 'phone contact'. 2024-04-03 14:02:13 -04:00
Alex Hart 3f91824325 Fix bug preventing the review sheet from opening. 2024-04-03 14:02:13 -04:00
Alex Hart 879e05148b Fix database revocation for call links. 2024-04-03 14:02:13 -04:00
moiseev-signal 78e36b85d4 Make sure not more than one libsignal Network instance is ever created
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2024-04-03 14:02:13 -04:00
Alex Hart 544cc06f13 Add chevron to conversation heading. 2024-04-03 14:02:13 -04:00
Cody Henthorne 133b7ef3f1 Fix multiple exception crash in rx message send flow. 2024-04-03 14:02:13 -04:00
Cody Henthorne 08a407dc23 Prevent thread starvation during message sending. 2024-04-03 14:02:13 -04:00
Greyson Parrelli 6c697fad8b Stop reading the PNP capability. 2024-04-03 14:02:13 -04:00
Greyson Parrelli c904a7aa97 Delete LegacyAttachmentUploadJob.
It's been over 4 months since it was replaced. That's beyond the 90 day
build expiration + 1 day job lifespan. Should be safe to remove.
2024-04-03 14:02:13 -04:00
Greyson Parrelli ad131d7c65 Enqueue AccountConsistency check when prekey syncs fail. 2024-04-03 14:02:13 -04:00
Alex Hart e12d2d1e98 Fix local pip movement when in RTL language. 2024-04-03 14:02:12 -04:00
adel-signal f01e044662 Update to new calling turn info endpoint, add support for turn server ips.
Co-authored-by: Adel Lahlou <adel@signal.com>
2024-04-03 14:02:12 -04:00
Jim Gustafson 03d3ae7043 Update to RingRTC v2.39.3 2024-04-03 14:02:12 -04:00
Greyson Parrelli 6b60a22879 Bump version to 7.2.4 2024-04-03 13:32:38 -04:00
Greyson Parrelli bbded8caa8 Update translations and other static files. 2024-04-03 13:32:08 -04:00
Greyson Parrelli 3a6352d2a3 Don't show profile name in parens if it's the same as display name. 2024-04-03 13:19:37 -04:00
Greyson Parrelli 8293d6bc4c Allow last-name-only nicknames to be saved. 2024-04-03 11:54:07 -04:00
Greyson Parrelli 56bdb28c2f Fix bug around entering text in the middle of a full note.
There's likely other weirdness, but this at least addresses the most
commond variation, where entering text in the middle of a full note
would start chopping stuff off the end.
2024-04-03 11:20:35 -04:00
Greyson Parrelli b081fb1e13 Improve recipient shortname selection. 2024-04-03 10:45:47 -04:00
Greyson Parrelli 58c1f64dfe Allow familyName-only nicknames in storage service. 2024-04-03 10:44:04 -04:00
Greyson Parrelli 92b7147dcd Always take the remote nickname. 2024-04-03 10:39:43 -04:00
Greyson Parrelli fa3a85c948 Bump version to 7.2.3 2024-04-02 15:30:07 -04:00
Greyson Parrelli 9da4513694 Update translations and other static files. 2024-04-02 15:29:14 -04:00
Greyson Parrelli de520036a9 Allow last-name-only nicknames. 2024-04-02 15:19:44 -04:00
Greyson Parrelli 97ca15a1c0 Allow multi-line entry in note field. 2024-04-02 14:50:13 -04:00
Greyson Parrelli 713a34a5e7 Ensure that conversation count check is on background thread. 2024-04-02 14:36:07 -04:00
Alex Hart d688280a30 Fix search for users without thread. 2024-04-02 15:27:57 -03:00
Greyson Parrelli ebbf8fad4b Bump version to 7.2.2 2024-04-01 20:17:55 -04:00
Greyson Parrelli 5891c6fb2d Update translations and other static files. 2024-04-01 20:17:23 -04:00
Greyson Parrelli 7c96319fb6 Fix potential NPE in forwarding flow. 2024-04-01 20:17:23 -04:00
Greyson Parrelli 0d652ccfd6 Listen for recipient name changes in conversation item. 2024-04-01 19:58:05 -04:00
Greyson Parrelli d3718aa7ef Make nickname FF hot-swappable and default to true. 2024-04-01 19:17:07 -04:00
Cody Henthorne fcdcb9fd33 Fix linked device nickname change not syncing bug. 2024-04-01 19:06:08 -04:00
Nicholas Tinsley a8f925def0 Move delete button in Nickname activity. 2024-04-01 17:49:48 -04:00
Nicholas Tinsley 53cb125712 Prevent NPE in video editor. 2024-04-01 14:33:21 -04:00
Nicholas Tinsley 2a5793d96e Allow saving empty notes with empty nicknames. 2024-04-01 14:13:39 -04:00
Nicholas Tinsley d460fa7ed4 Disable view-once toggle in media caption editor.
Fixes #13492.
2024-04-01 11:37:34 -04:00
Nicholas Tinsley 5272b13c41 Bump version to 7.2.1 2024-03-29 17:35:46 -04:00
Nicholas Tinsley a66ac42038 Update translations and other static files. 2024-03-29 17:27:15 -04:00
Nicholas Tinsley 0014a2cba7 Hide camera switch icon during calls for devices with 1 or fewer cameras. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley 7a9c01e6e5 Remove vestigial call camera toggle button. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley 16402e43a5 Make in-app camera compatible with multi-window. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley b1944da58d Show nickname for 1:1 chat bottom sheet. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley 9081d3c826 Revise recipient bottom sheet. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley d4ae0ca4cb Update conversation settings string. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley 88f6ab915e Do not show nickname field for Note to Self. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley 939024faff Don't show profile name parentheses if we don't have one. 2024-03-29 12:43:58 -04:00
Nicholas Tinsley 4c6e7991df Preserve username exceptions in ProGuard. 2024-03-29 12:40:12 -04:00
Nicholas Tinsley 036d91c039 Align linked device megaphone lifespan.
Thank you to Signal.DE from the community forum.
2024-03-28 15:59:08 -04:00
oguzhandogdu 869c922532 Remove second bullet span function 2024-03-28 14:57:26 -04:00
oguzhandogdu 217d15a853 Add gap width to bullet span 2024-03-28 14:57:26 -04:00
Nicholas Tinsley 931ffd0ba3 Bump version to 7.2.0 2024-03-27 16:01:18 -04:00
Nicholas Tinsley fecac297fa Update translations and other static files. 2024-03-27 15:57:11 -04:00
Nicholas Tinsley b0ea8d7df5 Prevent crash on devices with camera killswitches.
Addresses #13450.
2024-03-27 15:54:35 -04:00
Nicholas Tinsley f126df2120 Put custom controller behind feature flag. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley 42450024fc Add profile name to about sheet. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley 101db6e164 Apply SignalTheme to NicknameActivity. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley 13bef94bf7 Update icons in conversation settings. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley 02792c5a6f Remove extraneous time unit conversion. 2024-03-27 15:54:35 -04:00
Alex Hart 303929090b Implement the majority of the new nicknames and notes feature. 2024-03-27 15:54:35 -04:00
Alex Hart 7a24554b68 Update ContactRecord proto with new nickname fields. 2024-03-27 15:54:35 -04:00
Greyson Parrelli 5b10aa6fa7 Handle 428 for captcha submissions. 2024-03-27 15:54:35 -04:00
Alex Konradi e6eefac609 Upgrade to libsignal 0.42.0 2024-03-27 15:54:35 -04:00
Alex Hart 5f5a80dcbe Stub out MoreOptionsSheet and RestoreFromBackupFragment. 2024-03-27 15:54:35 -04:00
Cody Henthorne 7802448b24 Fix unblock icon tint in dark theme. 2024-03-27 15:54:35 -04:00
Clark 16d231f718 Persist blur hash with undownloaded attachments. 2024-03-27 15:54:35 -04:00
Cody Henthorne 62ca6cdd2f Fix can't receive audio and video pip render bug. 2024-03-27 15:54:35 -04:00
Cody Henthorne 7d81ed1150 Fix call controls disappearing when returning from system pip. 2024-03-27 15:54:35 -04:00
Greyson Parrelli 27812bb1ec Don't save duplicate queries in Spinner. 2024-03-27 15:54:35 -04:00
Greyson Parrelli 6854f7eb2a Add an 'internal details' screen for message details. 2024-03-27 15:54:35 -04:00
Clark de86c5622d Integrate more variation in backup test generation. 2024-03-27 15:54:35 -04:00
Jim Gustafson 6bf1a4295f Update to RingRTC v2.39.2 2024-03-27 15:54:35 -04:00
Alex Hart 7de2f0f460 Add nickname and notes fields to the RecipientTable. 2024-03-27 15:54:35 -04:00
Greyson Parrelli 50149a3803 Show a megaphone when a device is about to unlink. 2024-03-27 15:54:35 -04:00
Greyson Parrelli d7ee9639fd Be more lenient with quality matches when forwarding attachments. 2024-03-19 14:49:56 -04:00
Nicholas Tinsley 7d5627b17b Fix in-app camera rotation in multiview. 2024-03-19 14:48:38 -04:00
Greyson Parrelli e24c951d83 Convert MiscellaneousValues to kotlin. 2024-03-19 14:47:58 -04:00
Alex Hart e6a11c1ccf Revert "Fix pip placement in large calls."
This reverts commit aaeba4efe1.
2024-03-19 14:47:58 -04:00
Greyson Parrelli 3f66981359 Do not show username megaphone after a fresh install. 2024-03-19 14:47:58 -04:00
Cody Henthorne 874f808d56 Add process read sync tests. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 450dc2f368 Improve logging around APNG animation disabling. 2024-03-19 14:47:58 -04:00
Alex Hart 7a69df42a7 Add receive support for new call log event data. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 1ce1e30d32 Carry over the sent media quality when forwarding a video. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 011f1d592e Fix bug with quote deduping. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 1d29b0166d Backfill missing attachment hashes. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 6df1a68213 Refactor and improve attachment deduping logic. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley b7ee6bfcb3 Don't show transfer overlay for scheduled messages. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley c0cb2b5e12 Allow seeking in video timeline. 2024-03-19 14:47:58 -04:00
Alex Hart b38865bdc7 Implement UI element refresh on transfer or restore screen. 2024-03-19 14:47:58 -04:00
Alex Hart 6f46331772 Add call log event proto updates. 2024-03-19 14:47:58 -04:00
Clark 989bd662c6 Add tests to generate backup with large amount of messages and chats. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley 359e593481 Add support for hardware camera features in the in-app camera. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley b7e0fe22db Add CameraX Extensions and update CameraX to 1.3.2. 2024-03-19 14:47:58 -04:00
Alex Hart 61cfbd6852 Allow ringer to ring in certain dnd situations. 2024-03-19 14:47:58 -04:00
Rashad Sookram 02c0d3ed6e Update to RingRTC v2.39.1 2024-03-19 14:47:58 -04:00
Cody Henthorne e4d6c3aeb2 Do not send viewed receipt for release channel. 2024-03-19 14:47:58 -04:00
Chris Eager 0c6761fcfd Update Option.RECAPTCHA to Option.CAPTCHA 2024-03-19 14:47:58 -04:00
Greyson Parrelli 8f884fdd5c Fix potential crash when parsing PreKeySyncJobData.
Honestly at this point I have no idea how this is happening.
Maybe somehow getting old data that was empty but not null?
A mystery for the ages.
2024-03-19 14:47:58 -04:00
Greyson Parrelli 07cea1818e Ensure that protocol stores are reset after setting ACI/PNI. 2024-03-19 14:47:58 -04:00
Cody Henthorne 132bc15373 Fix ANR when changing the configuration of a foldable. 2024-03-19 14:47:58 -04:00
Clark d993748753 Generate backup protos with message backup instrumentation tests. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 3372565a39 Improve logging around consistency checks. 2024-03-19 14:47:58 -04:00
Alex Hart 134ac2b2fd Fix display name resolution for my story. 2024-03-19 14:47:58 -04:00
Alex Hart 0e0e91b4fe Hide invite banner when entering conversation search. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 25b50bdb8f Rotate the libsignal-cdsi feature flag. 2024-03-19 14:47:58 -04:00
Alex Konradi 1988085171 Don't strip libsignal.net classes. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley f892e9baff Update disappearing messages text. 2024-03-19 14:47:58 -04:00
Alex Konradi 4828d84caf Update libsignal to 0.41.2 2024-03-19 14:47:58 -04:00
Greyson Parrelli aeae6ac292 Remove deprecated blocked field from DeviceContact. 2024-03-19 14:47:58 -04:00
Alex Hart 0544c1f249 Display group call permissions dialog when trying to start a call in annoucment group when not an admin. 2024-03-19 14:47:58 -04:00
Greyson Parrelli 5027159ed8 Improve handling of unregistered states in profile screen. 2024-03-19 14:47:58 -04:00
Cody Henthorne ce778be895 Resume call PIP on app foreground. 2024-03-19 14:47:58 -04:00
Cody Henthorne 9e349d2b30 Mute video when closing system PIP during a call. 2024-03-19 14:47:58 -04:00
Fumiaki Yoshimatsu 72f19758db Fix chat search when using Japanese IMEs.
Resolves #13467
2024-03-19 14:47:58 -04:00
Greyson Parrelli 55bce1fa12 Fix potential NPE when pinning a PNI chat. 2024-03-19 14:47:58 -04:00
AsamK 5e1ebaa5d4 Fix various storage service issues.
Resolves #13466
2024-03-19 14:47:58 -04:00
Clark 742c348998 Add test restore flow to staging reg. 2024-03-19 14:47:58 -04:00
Clark 9d46b52786 Backup attachments as Attachment locators. 2024-03-19 14:47:57 -04:00
Clark ef374952ab Add tests for update messages except for groups and calls. 2024-03-19 14:47:57 -04:00
Clark f8ef4d5985 Add tests for text messages with mentions, quotes, reactions, and ranges. 2024-03-19 14:47:57 -04:00
Cody Henthorne 85929809f0 Bump version to 7.1.3 2024-03-19 14:36:32 -04:00
Cody Henthorne 068540120e Updated baseline profile. 2024-03-19 14:33:44 -04:00
Cody Henthorne 471c4fc200 Update translations and other static files. 2024-03-19 14:28:49 -04:00
Nicholas Tinsley 398c67362d Improve layout for view once toast for older devices. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley 4ceeda5f02 Fix video review page for API <28. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley 2bf6b993fe Somewhat reduce emoji keyboard jankiness in media review fragment. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley 68363c5b82 Disable emoji button for view-once media. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley 9f47a41017 Restore pinch to zoom gesture in in-app camera. 2024-03-19 13:46:19 -04:00
Nicholas Tinsley ba70101efd Add view-once button to media caption. 2024-03-19 12:00:36 -04:00
Greyson Parrelli 3aa54c9982 Remove some unused permissions. 2024-03-18 19:21:47 -04:00
Greyson Parrelli 825ca0d737 Remove more SMS vestiges. 2024-03-18 19:21:08 -04:00
Clark Chen 6754fef164 Bump version to 7.1.2 2024-03-11 18:50:24 -04:00
Clark Chen 4c079a8c25 Update translations and other static files. 2024-03-11 18:32:59 -04:00
Nicholas Tinsley 6e09d101b5 Debounce camera switcher button. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley 39aa583297 Respect newlines in media review UI. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley b08db7a8c5 Fix unexpected trimming behavior with long videos. 2024-03-11 18:26:45 -04:00
Alex Hart 865bf0d056 Fix nav bar getting out of sync with keyboard pager. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley d52c520c02 Explicitly pause video player when not focused. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley 1eabf11cdb Fix tap-to-focus UI for in-app camera. 2024-03-11 18:26:45 -04:00
Cody Henthorne cfb16d3f17 Fix link rendering under spoilers in read more view. 2024-03-11 18:26:45 -04:00
Alex Hart d5707638a6 Apply proper theming to FindByActivity. 2024-03-11 18:26:45 -04:00
Alex Hart 5cda5db7f7 Disable text field when view-once is selected. 2024-03-11 11:46:52 -03:00
Alex Hart 5c5d55d265 Introduce glyph fonts to correct spacing. 2024-03-11 11:14:05 -03:00
Alex Hart 4dd3b92eda Prevent crash when review banner wants to display self. 2024-03-11 09:52:24 -03:00
Cody Henthorne 112579079f Fix bad button text wrapping in message request view. 2024-03-08 16:57:15 -05:00
Greyson Parrelli 9897ba4b28 Properly pluralize a string. 2024-03-08 14:39:28 -05:00
Greyson Parrelli c64dfff4c7 Fix typo in string. 2024-03-07 22:40:26 -05:00
Alex Hart 915b3f0cd3 Bump version to 7.1.1 2024-03-07 17:02:41 -04:00
Alex Hart c295d11fc4 Updated baseline profile. 2024-03-07 17:01:52 -04:00
Alex Hart bc47c5436d Update translations and other static files. 2024-03-07 16:56:59 -04:00
Alex Hart aaeba4efe1 Fix pip placement in large calls. 2024-03-07 16:53:36 -04:00
Alex Hart 3c0eb58381 Apply alpha to v2 conversation item footer content. 2024-03-07 16:53:36 -04:00
Alex Hart c4f22449f9 Hide thumbnails in specific cases in quote view. 2024-03-07 16:53:36 -04:00
Greyson Parrelli bca346ec2f Improve copy for unregistered users. 2024-03-07 16:53:36 -04:00
Alex Hart e0bd60f87c Adjust styling and sizing for rationale dialog. 2024-03-07 16:53:36 -04:00
Alex Hart aeedab1531 Adjust spacing for contact and verified images on conversation settings page. 2024-03-07 16:53:35 -04:00
Greyson Parrelli c959f41c68 Improve message send performance. 2024-03-07 16:53:35 -04:00
Alex Hart 9ba755da16 Add section header to find by username / ph row. 2024-03-07 16:53:35 -04:00
Alex Hart 34026c5538 Add proper tinting to refresh and invite rows. 2024-03-07 10:04:16 -04:00
Nicholas Tinsley ea64425456 Media sending design improvements. 2024-03-07 09:38:13 -04:00
Alex Hart eb34a20195 Bump version to 7.1.0 2024-03-06 20:50:34 -04:00
Alex Hart 445513cc32 Updated baseline profile. 2024-03-06 20:50:06 -04:00
Alex Hart e431518a9d Update translations and other static files. 2024-03-06 20:45:13 -04:00
Alex Hart 61df88e094 Fix TestUsers construction in benchmark. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 891c130e12 Sync the PNI identity used in sent transcripts. 2024-03-06 20:42:01 -04:00
Greyson Parrelli b4ced5278e Fix recipient merging case that causes a change number event. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 10364e9342 Disable next button in FindByActivity when input is blank. 2024-03-06 20:42:01 -04:00
Alex Hart 74dc222a54 Add Recency support for contact search ordering. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 2e4ac7ede1 Always perform CDSI lookups when starting new chats. 2024-03-06 20:42:01 -04:00
Cody Henthorne 184c1b67cc Add learned profile name event. 2024-03-06 20:42:01 -04:00
Alex Hart f702338129 Fix hide story action state. 2024-03-06 20:42:01 -04:00
Alex Hart 4b4b263423 Usernames 1.01 Fast-Follow Part 1. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 83c16a46de Update assets for image editor. 2024-03-06 20:42:01 -04:00
Clark 6383896a79 Fix incoming group updates showing as updated. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 5fa1560a10 Add stroke to draw tool color bar. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 9bd6ad36cc Round corners of selected region in video trimmer. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 83cc7d5181 Adjust media tool button animation. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 44150673e9 Adjust size of quality selector buttons. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley 5092d723a8 Update media send symbols. 2024-03-06 20:42:01 -04:00
Cody Henthorne 218964cbda Add archive media apis. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley ccc9752485 Hoist video editor state out of VideoEditorFragment. 2024-03-06 20:42:01 -04:00
Cody Henthorne 619038f27d Improve local fanout send performance. 2024-03-06 20:42:01 -04:00
Alex Hart 9f197b12ed Add support for call log mark as read. 2024-03-06 20:42:01 -04:00
Jim Gustafson 690608cdf3 Update to RingRTC v2.39.0
Co-authored-by: Alex Hart <alex@signal.org>
2024-03-06 20:42:01 -04:00
Alex Hart 4035932340 Fix find-by slide animations. 2024-03-06 20:42:01 -04:00
Clark fc9d94701c Disable job manager in instrumentation tests by default. 2024-03-06 20:42:01 -04:00
Alex Hart 6d54ae5f3d Update call link info sheet to match new designs. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley c53abe0941 Video Sending Redesign 2024-03-06 20:42:01 -04:00
Greyson Parrelli 276e253fdf Fix individual send metrics. 2024-03-06 20:42:01 -04:00
Greyson Parrelli f160e960be Stop setting pq flag since it's no longer read. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 41b57b9207 Remove unnecessary uniqueness constraint on prekey tables. 2024-03-06 20:42:01 -04:00
Alex Konradi 56eae8c7bf Add libsignal-net CDSI implementation. 2024-03-06 20:42:01 -04:00
Alex Hart 46c8b3b690 Replace full text with call link name as title in call info sheet. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 58b11f3c47 Do a CDS refresh when a new chat is created. 2024-03-06 20:42:01 -04:00
Alex Hart 40b4b316b3 Add new options to share call link details from details fragment. 2024-03-06 20:42:01 -04:00
Clark 7a31f69aea Add tests for import/export of call logs. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 648c99e81d Ensure we are updating last resort metadata after change number. 2024-03-06 20:42:01 -04:00
Greyson Parrelli 56b482a26f Allow scanning QR code from 'Find by username' screen. 2024-03-06 20:42:01 -04:00
Cody Henthorne c6df4af53a Update bank transfer timeline strings. 2024-03-06 20:42:01 -04:00
Clark 32fe927bfc Add import/export tests for backup of recipients and threads. 2024-03-06 20:42:01 -04:00
Ehren Kret 5740b768d0 Use full organization name in README. 2024-03-06 20:42:01 -04:00
Ehren Kret d8e74c730a Update README copyright year. 2024-03-06 20:42:00 -04:00
Clark 58846bbf42 Add import/export test for initially account data. 2024-03-06 20:42:00 -04:00
Greyson Parrelli 78d30fc479 Remove deprecated SVR2 enclaves. 2024-03-06 20:42:00 -04:00
Cody Henthorne 86afa988a0 Add ability to scan username qr from gallery. 2024-03-06 20:42:00 -04:00
Greyson Parrelli 6104ef62df Detect username QR codes in our camera-first capture flow. 2024-03-06 20:42:00 -04:00
Cody Henthorne 3f89acf9bd Disable parallel gradle in GH actions. 2024-03-06 20:42:00 -04:00
Cody Henthorne 591d499462 Show system contact icon in more places. 2024-03-06 20:42:00 -04:00
Greyson Parrelli c31a7152bc Update built-in emoji to v15.1 2024-03-06 20:42:00 -04:00
Greyson Parrelli 343cc3ca67 Update third party licenses. 2024-03-06 20:42:00 -04:00
Cody Henthorne 23e18cee22 Add nobody can find me by number setting warning. 2024-03-06 20:42:00 -04:00
Cody Henthorne 5b2c458bcf Show username in all display name locations if only option. 2024-03-06 20:42:00 -04:00
Cody Henthorne c10c64a6a6 Prepopulate find number with local user country code. 2024-03-06 20:42:00 -04:00
Cody Henthorne 957221e118 Fix cut off icon in conversation header. 2024-03-06 20:42:00 -04:00
Cody Henthorne e18e4454e4 Fix multi-invite group create dialog. 2024-03-06 20:42:00 -04:00
Greyson Parrelli e1067e30de Add support for endpoint checking prekey consistency. 2024-03-06 20:42:00 -04:00
Greyson Parrelli 09b0f15294 Remove unused capabilities. 2024-03-06 20:42:00 -04:00
Greyson Parrelli b1d6ff4bbd Remove the PNP build variant. 2024-03-06 20:42:00 -04:00
Cody Henthorne a49e9dd96d Fix crash adjusting constraints during large calls. 2024-03-06 20:42:00 -04:00
Clark 5e428e2c4d Backup and restore mentions. 2024-03-06 20:42:00 -04:00
Clark 0f6ff3c101 Integrate backup file validation to backup playground. 2024-03-06 20:42:00 -04:00
Clark 1ade8b502f Convert and store new group changes in MessageExtras. 2024-03-06 20:42:00 -04:00
Alex Konradi cc25f0685c Update libsignal version to v0.40.1 2024-03-06 20:42:00 -04:00
Nicholas Tinsley e91ed88785 CameraX Custom Controller.
Addresses #12817, #13316, #13389
2024-03-06 20:42:00 -04:00
Jon Chambers 39bc6d5eb3 Remove legacy signed prekey endpoint. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley b7f472b0cd Update Google services libraries. 2024-02-23 16:42:58 -05:00
Cody Henthorne 942f4a45bf Fix reply icon not mirroring in RTL. 2024-02-23 16:42:58 -05:00
Cody Henthorne 767896b14c Fix infinite pending link preview on large previews. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley 8c35628863 Adjust copy depending on PNP settings. 2024-02-23 16:42:58 -05:00
Cody Henthorne d555370076 Fix edit call link copy in bottom sheet. 2024-02-23 16:42:58 -05:00
Cody Henthorne bbbe76697d Fix invalid date on group member search results. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley fc1d60e65b Fallback to matching video decoder by MIME type. 2024-02-23 16:42:57 -05:00
Nicholas Tinsley 9dc856202a Don't use 0 milliseconds as sending retry interval. 2024-02-23 16:42:57 -05:00
Jim Gustafson 6bc41776b1 Update to RingRTC v2.38.0 2024-02-23 16:42:57 -05:00
Greyson Parrelli 940cee0f30 Bump version to 7.0.2 2024-02-23 16:26:12 -05:00
Greyson Parrelli cdb6c16473 Update translations and other static files. 2024-02-23 16:25:05 -05:00
Greyson Parrelli c4842ae7c5 Attempt to prevent message retry loops. 2024-02-23 15:36:23 -05:00
Greyson Parrelli dc32e51ac2 Make a specific crash more clear to improve debuggability. 2024-02-23 15:36:23 -05:00
Greyson Parrelli 43caaf7efc Update a specific recipient case to merge rather than just steal PNI. 2024-02-23 15:36:23 -05:00
Greyson Parrelli dcd0d433b0 Fix potential charset crash on some devices. 2024-02-23 15:36:23 -05:00
Cody Henthorne 763e891dfd Show username in group invite flow. 2024-02-23 15:36:23 -05:00
Cody Henthorne c04f761f5a Show rate limit specific error message on username reservation. 2024-02-23 15:36:23 -05:00
Cody Henthorne b147882e4f Fix text input selection handle colors. 2024-02-23 15:36:23 -05:00
Cody Henthorne c9f5f91aad Fix black bars on username scan crosshair. 2024-02-23 15:36:23 -05:00
Cody Henthorne 0a3de42729 Fix username QR image generation for multiline usernames. 2024-02-23 15:36:23 -05:00
Greyson Parrelli 64fc0209f4 Remove unused endpoint. 2024-02-23 15:36:23 -05:00
Cody Henthorne 418ad51e77 Fix share your username popup icon tint. 2024-02-23 15:36:23 -05:00
Cody Henthorne 16faf41a84 Fix profile name not updating correctly. 2024-02-23 15:36:23 -05:00
Cody Henthorne d5cf8d36b3 Fix username recovery UX bugs. 2024-02-23 15:36:23 -05:00
Greyson Parrelli 755fafb0b6 Always use US locale when logging rounded numbers. 2024-02-21 13:08:43 -05:00
Greyson Parrelli 8fc9893ecd Improve logging around retries archiving sessions. 2024-02-21 13:04:42 -05:00
Greyson Parrelli 9071fd0024 Bump version to 7.0.1 2024-02-20 21:45:57 -05:00
Greyson Parrelli 9a3233bb28 Update translations and other static files. 2024-02-20 21:45:30 -05:00
Greyson Parrelli e77bc9170a Fix RTL rendering of username edit screen. 2024-02-20 21:36:24 -05:00
Greyson Parrelli 23d6a71a3b Update username validation to use libsignal. 2024-02-20 21:36:24 -05:00
Greyson Parrelli 67c3f41dff Fix crash when attempting to start a call via username. 2024-02-20 16:02:57 -05:00
Greyson Parrelli e22fa499c2 Reduce username debounce rate to 500ms. 2024-02-20 15:25:27 -05:00
Cody Henthorne fdef13ae92 Apply LQA feedback. 2024-02-20 15:02:39 -05:00
Greyson Parrelli c0a6f2316c Do not acknowledge retry receipts sent to PNI. 2024-02-20 12:24:32 -05:00
Greyson Parrelli 7d6a87c825 Attempt to fix missing session crash for resends. 2024-02-20 12:13:54 -05:00
Greyson Parrelli 6c863fe99c Clean up capability logging. 2024-02-18 18:34:18 -05:00
Greyson Parrelli 5cf8242ea0 Bump version to 7.0.0 2024-02-16 16:44:19 -05:00
Greyson Parrelli a804e8a27c Update translations and other static files. 2024-02-16 16:42:53 -05:00
Greyson Parrelli 3b598e2f07 Update string for username creation. 2024-02-16 15:53:05 -05:00
Greyson Parrelli 03c5a254e8 Fix issue with receiving server delivery receipts at PNI. 2024-02-15 22:05:57 -05:00
Greyson Parrelli bdb34e16c6 Update UI and strings for the duplicate name review screen. 2024-02-15 21:43:36 -05:00
Cody Henthorne e7c018283a Perform directory refresh after a PNI invite accept. 2024-02-15 21:43:36 -05:00
Cody Henthorne ebd8d85a3d Implement UX feedback in new conversation start flows. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 3f8a9e1be2 Reduce max discriminator length to 9. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 0d5961baf9 Update string on find by username screen. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 872ee805d1 Assume PNP capability is true. 2024-02-15 21:43:36 -05:00
Nicholas Tinsley b19bcd88b9 Null check VideoPlayer during view binding. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 5c9d65386b Fix username deletion sync issue. 2024-02-15 21:43:36 -05:00
Greyson Parrelli a86a0938ce Fix avatar fallback photo for self when useSelfProfileAvatar=true. 2024-02-15 21:43:36 -05:00
Greyson Parrelli a886e5f9a0 Directly show about sheet when you show a recipient sheet for yourself. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 83c1bd61cb Add bottom sheet handle to RecipientBottomSheet. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 4ce1789110 Do not show the discriminator field until one is chosen. 2024-02-15 21:43:36 -05:00
Greyson Parrelli f484fdbbac Remove unused 'registration' variant of username screen. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 57ac7cb328 Show some more info in the about sheet. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 47cdc50a81 Add confirmation dialog when changing username would reset link. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 555ddb5b20 Show dialog for successfully resetting your username link. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 8e8ba23da7 Do not show the QR code shortuct if you have no username. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 54a1b97167 Update username description string in edit profile screen. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 7530d44d28 Refactor username link share screen to enable previews. 2024-02-15 21:43:36 -05:00
Greyson Parrelli 252aa3714e Sync the 'hasCompletedUsernameOnboarding' flag. 2024-02-15 21:43:36 -05:00
Greyson Parrelli ce09e9a217 Update UI for PNP launch megaphone. 2024-02-15 21:43:35 -05:00
Greyson Parrelli 8797236b5a Add migration for ensuring we set the latest pnp settings. 2024-02-15 21:43:35 -05:00
Greyson Parrelli 6097e6c305 Default discoverability to 'off' until registration is complete. 2024-02-15 21:43:35 -05:00
Greyson Parrelli 879fca0e11 Interpret unknown phone number sharing setting as 'off'. 2024-02-15 21:43:35 -05:00
Greyson Parrelli c359ddf3c8 Inline the pnp feature flag. 2024-02-15 21:43:35 -05:00
Greyson Parrelli 8ad77ac7aa Inline the username flag. 2024-02-15 21:43:35 -05:00
Cody Henthorne bd3b779282 Bump version to 6.47.4 2024-02-15 21:43:16 -05:00
Cody Henthorne 42b805eb91 Updated baseline profile. 2024-02-15 21:37:08 -05:00
Cody Henthorne 107f2cd3b1 Update translations and other static files. 2024-02-15 21:31:52 -05:00
Nicholas Tinsley c713ccf76c Don't mark outgoing media as upload only. 2024-02-15 21:26:30 -05:00
Cody Henthorne dd9c65012b Fix NPE when find by phone row not supported. 2024-02-15 19:36:26 -05:00
Cody Henthorne 31e872a34e Bump version to 6.47.3 2024-02-14 20:05:57 -05:00
Cody Henthorne 81579dc9bf Update translations and other static files. 2024-02-14 20:00:20 -05:00
Greyson Parrelli 3c6c03cd75 Fix bug when syncing username deletions. 2024-02-14 11:59:15 -05:00
Greyson Parrelli ba41df19bb Fix thread merges where one thread is inactive. 2024-02-14 11:15:49 -05:00
Cody Henthorne 0cc7178cdc Bump version to 6.47.2 2024-02-13 15:49:18 -05:00
Cody Henthorne 239e4a7e66 Updated baseline profile. 2024-02-13 15:46:30 -05:00
Cody Henthorne 0b031d35e3 Update translations and other static files. 2024-02-13 15:43:43 -05:00
Nicholas Tinsley 32e81049f5 Cycle audio remux feature flag. 2024-02-13 15:39:04 -05:00
Nicholas Tinsley 6a65a1c149 Default to 1x camera in video recording.
Addresses #11955, #12482, #13017

Shout out to @tedgravlin for #13411.
2024-02-13 15:39:04 -05:00
Cody Henthorne 36ea2a7f5d Fix active call manager.
For real this time.
2024-02-13 15:39:04 -05:00
Cody Henthorne cc40c9d09f Bump version to 6.47.1 2024-02-12 16:32:01 -05:00
Cody Henthorne fb9e731e00 Updated baseline profile. 2024-02-12 16:27:07 -05:00
Cody Henthorne 264607e0f3 Update translations and other static files. 2024-02-12 16:21:55 -05:00
Greyson Parrelli cdfc42cc38 Fix possible crash in story replies. 2024-02-12 15:01:58 -05:00
Cody Henthorne 19cfae1da5 Remove duplicate future code. 2024-02-12 15:00:27 -05:00
Jim Gustafson 577b11a349 Update to RingRTC v2.37.1 2024-02-12 09:15:55 -05:00
Clark Chen 671dfceac3 Bump version to 6.47.0 2024-02-09 19:43:41 -05:00
Clark Chen 5626fb74ae Update translations and other static files. 2024-02-09 19:28:27 -05:00
Greyson Parrelli 88d6c91517 Fix some bugs related to how the conversation header renders. 2024-02-09 18:41:26 -05:00
Cody Henthorne aa76cefb1c Update spam UX and reporting flows. 2024-02-09 18:41:26 -05:00
Cody Henthorne a4fde60c1c Fix crash in ActiveCallManager for lower Android versions. 2024-02-09 15:19:25 -05:00
Nicholas Tinsley 8e86612fc2 Derive PIN keyboard type if none set.
Addresses #13338.
2024-02-09 15:11:38 -05:00
Clark f6ded23383 Ignore message latency when latency is before device boot time. 2024-02-09 14:55:58 -05:00
Nicholas Tinsley 2ab689c59b Add plural string for chat deletion. 2024-02-09 13:32:05 -05:00
Greyson Parrelli 155f6a88f8 Ensure one-time kyber prekeys are generated during change number. 2024-02-09 13:32:05 -05:00
Greyson Parrelli 3d84fc9c98 Use 'deleted account' as name for certain unregisterd user cases. 2024-02-09 13:32:05 -05:00
Greyson Parrelli 976e146248 Only count active threads when determining possible communication. 2024-02-09 13:32:05 -05:00
Greyson Parrelli d9b0723194 Make message footer clickable when there's an error. 2024-02-09 13:32:05 -05:00
Greyson Parrelli 0630a6910a Update strings and previews for pnp settings. 2024-02-09 13:32:05 -05:00
Greyson Parrelli bc930345b9 Remove blockSSE feature flag. 2024-02-08 14:10:20 -05:00
Greyson Parrelli d7d7963101 Defer to the latest revision for backup inclusion. 2024-02-08 14:10:20 -05:00
Greyson Parrelli fa2551dfcf Always show the new 'find by phone number' item. 2024-02-08 14:10:20 -05:00
Greyson Parrelli b260a47b49 Fix issue where media sent transcripts didn't trigger thread updates. 2024-02-08 14:10:20 -05:00
Greyson Parrelli 47e55fc621 Do not show add to contacts if there's no e164. 2024-02-08 14:10:20 -05:00
Alex Hart 700fe5e463 Add Find By Username and Find By Phone Number interstitials.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2024-02-08 14:10:20 -05:00
Greyson Parrelli ca3d239ce2 Bump version to 6.46.7 2024-02-08 12:54:26 -05:00
Greyson Parrelli 12385b9c5a Update stores to use a constant accountId for PNIs. 2024-02-08 12:53:22 -05:00
Clark Chen 65b26adb3d Bump version to 6.46.6 2024-02-06 15:26:38 -05:00
Clark Chen 31b8927291 Update translations and other static files. 2024-02-06 15:16:55 -05:00
Cody Henthorne 94ffbb3e8e Fix NPE crash when scanning safety numbers. 2024-02-06 10:32:20 -05:00
Greyson Parrelli 1b7616b4db Fix issue with PNIs in contact sync. 2024-02-06 09:47:12 -05:00
Clark Chen 71850e1e35 Bump version to 6.46.5 2024-02-05 18:56:05 -05:00
Clark Chen 380fb60791 Update translations and other static files. 2024-02-05 18:36:27 -05:00
Greyson Parrelli 2cf9fa0524 Fix crash when opening group story replies. 2024-02-05 16:30:54 -05:00
Nicholas Tinsley c8c4fdc65e Bump version to 6.46.4 2024-02-05 10:59:36 -05:00
Nicholas Tinsley 9fb16be74f Updated baseline profile. 2024-02-05 10:56:19 -05:00
Nicholas Tinsley 2d4d4aab66 Update translations and other static files. 2024-02-05 10:53:17 -05:00
Nicholas Tinsley f18070b78c Revert "Don't recreate attachment InputStream if we don't have to."
This reverts commit 467dae8132.
2024-02-05 10:49:05 -05:00
Nicholas Tinsley adf1d8a43a Bump version to 6.46.3 2024-02-03 14:08:31 -05:00
Nicholas Tinsley f360934ddd Updated baseline profile. 2024-02-03 14:04:58 -05:00
Nicholas Tinsley b986c4d54c Update translations and other static files. 2024-02-03 14:02:12 -05:00
Nicholas Tinsley 4545e70384 Remix audio remuxing into its own feature flag. 2024-02-03 13:57:49 -05:00
Nicholas Tinsley 3c5cbc3114 Bump version to 6.46.2 2024-02-03 12:45:27 -05:00
Nicholas Tinsley 879b0ad95d Updated baseline profile. 2024-02-03 12:41:31 -05:00
Nicholas Tinsley 9982810edb Update translations and other static files. 2024-02-03 12:38:24 -05:00
Greyson Parrelli 699b788187 Fix possible stack overflow in backup export. 2024-02-03 12:26:38 -05:00
Nicholas Tinsley bd61044643 Bump version to 6.46.1 2024-02-02 15:24:32 -05:00
Nicholas Tinsley 6804d58323 Updated baseline profile. 2024-02-02 15:17:04 -05:00
Nicholas Tinsley bfe57b4b8f Update translations and other static files. 2024-02-02 15:12:26 -05:00
Greyson Parrelli e3fe852a34 Do not backup past revision if latest revision is soon-to-expire. 2024-02-02 15:07:30 -05:00
Cody Henthorne 673fe2625c Show iDEAL 0,01 temporary charge warning dialog for monthly subscription. 2024-02-02 15:07:30 -05:00
Cody Henthorne e798feb1d7 Use paymentId from create PayPal intent instead of confirm. 2024-02-02 15:07:30 -05:00
Nicholas Tinsley fa937f9f43 Bump version to 6.46.0 2024-01-31 22:42:48 -05:00
Nicholas Tinsley ec5f3fc333 Updated baseline profile. 2024-01-31 22:37:58 -05:00
Nicholas Tinsley d85c150a8c Update translations and other static files. 2024-01-31 22:35:13 -05:00
Nicholas Tinsley a5faf0e098 Match output video file size limit in video editor. 2024-01-31 22:13:46 -05:00
Alex Hart 1234c63836 Add scaffolding for backupsV2. 2024-01-31 22:13:46 -05:00
Cody Henthorne 91920319c7 Cycle calling without a service remote config. 2024-01-31 22:13:46 -05:00
Nicholas Tinsley 950d9d5a4c Validate image preview as URI.
Resolves #13392.
2024-01-31 22:13:46 -05:00
Nicholas Tinsley 467dae8132 Don't recreate attachment InputStream if we don't have to. 2024-01-31 22:13:46 -05:00
Nicholas Tinsley d1ef9d5dcf Align video compression limit with attachment limit. 2024-01-31 22:13:46 -05:00
Nicholas Tinsley a60419a442 Add remote config for GIF keyboard page. 2024-01-31 22:13:46 -05:00
Nicholas Tinsley f97a034c34 Null check multi share attachment quality guard. 2024-01-31 22:13:46 -05:00
Greyson Parrelli 4d0fbe2343 Add a dark theme for spinner. 2024-01-31 22:13:46 -05:00
Jameson Williams 1d5e108cd4 QA target can pass on ARM MacBook
The Conscrypt library does not have a native library suitable for
running on the ARM MacBooks. As a result, unit tests that rely on
Conscrypt also don't work on this hardware.

Fortunately, though, the tests will pass without Conscrypt anyway. As a
workaround, we can avoid using Conscrypt only when running the unit test
suite on these machines.

See also: https://github.com/google/conscrypt/issues/1034

Resolves #13382.
2024-01-31 22:13:46 -05:00
Greyson Parrelli 716afc98ac Sync PNI verification status to storage service. 2024-01-31 22:13:46 -05:00
Greyson Parrelli 459607adae Clear local state after requesting to ignore battery optimizations. 2024-01-31 22:13:46 -05:00
Cody Henthorne 784b705265 Fix spoiler in notification with emoji prefix. 2024-01-31 22:13:46 -05:00
Alex Hart 326b95bd10 Upgrade Compose and AndroidX libraries. 2024-01-31 22:13:46 -05:00
Greyson Parrelli 466acaf504 Fix possible exception when rendering SSE event.
Fixes #13390
2024-01-31 22:13:46 -05:00
Maksym Moroz 838165c3e6 Register gradle tasks instead of creating eagerly.
Signed-off-by: Maksym Moroz <maksymmoroz@duck.com>

Resolves #13391
2024-01-31 22:13:46 -05:00
Jim Gustafson e1d7ad7d03 Update to RingRTC v2.37.0 2024-01-31 22:13:46 -05:00
Greyson Parrelli 3776e86b83 Update the types of messages we backup. 2024-01-31 22:13:46 -05:00
Greyson Parrelli ea60858a07 Improve logging around 'contact joined' messages. 2024-01-31 22:13:46 -05:00
Greyson Parrelli d4488c72fb Simplify contact splitting when reading from storage service. 2024-01-31 22:13:46 -05:00
Greyson Parrelli 9146f2fb30 Remove some unused methods.
Fixes ##13384
2024-01-31 22:12:17 -05:00
Greyson Parrelli 92993f967e Clear MSL after SN change. 2024-01-31 22:12:17 -05:00
Greyson Parrelli 2c2735af6d Do not create new threads to show error messages. 2024-01-31 22:12:17 -05:00
Alex Hart 80c0e19692 Improve sizing of shareable QR image. 2024-01-31 22:12:17 -05:00
Alex Hart c9e2162afc Remove old username education fragment. 2024-01-31 22:12:17 -05:00
Nicholas Tinsley 9a52f4e3ff Remux audio if possible when transcoding.
Addresses #11712, #12674, #12945, #13084, #13346.
2024-01-31 22:12:17 -05:00
Greyson Parrelli c0235d4cc2 Ensure contacts are split after profile 404. 2024-01-31 22:12:17 -05:00
Cody Henthorne f1704fbb57 Fix file attachment dedup logic. 2024-01-31 22:12:17 -05:00
Alex Hart 38d5d3ad1b Add polish to usernames UX. 2024-01-31 22:12:17 -05:00
Jameson Williams ec96b4e3aa Update Glide to use ksp, drop kapt.
Resolves #13381
2024-01-31 22:12:17 -05:00
Cody Henthorne aa33fd44b8 Remove SMS export. 2024-01-31 22:12:17 -05:00
Clark 98865d61dd Convert gv2 update messages to backup distinct protos. 2024-01-31 22:12:17 -05:00
Cody Henthorne 0036b8e2d6 Migrate legacy png and webp to signal symbols. 2024-01-31 22:12:17 -05:00
Alex Hart c021d26103 Add polish to PNP registration settings. 2024-01-31 22:12:17 -05:00
Alex Hart 8bca5b4901 Update PnP description text for everyone listing. 2024-01-31 22:12:17 -05:00
Greyson Parrelli d4db6c8912 Keep some recipient logs longer. 2024-01-31 22:12:17 -05:00
Jameson Williams c93b4909f4 Remove Jetifier from build.
materialish-progress and subsampling-scale-image-view were bringing in
Android Support libraries as transitive dependencies. This required
Jetifier to be run as part of the build. More recent versions of these
dependencies have been released, which now use AndroidX directly. By
upgrading these dependencies, Jetifier is no longer needed to build
Signal.

Resolves #13378
2024-01-31 22:12:17 -05:00
Maksym Moroz 67d3c8e777 Clean up app module build.gradle.kts
Closes #13379

Signed-off-by: Maksym Moroz <maksymmoroz@duck.com>
2024-01-31 22:12:17 -05:00
Nicholas Tinsley 8d44222097 Enable logcat in the video sample app. 2024-01-31 22:12:17 -05:00
Nicholas Tinsley 9cb2024334 Option to persist task in video sample app. 2024-01-31 22:12:17 -05:00
Alex Hart e71bb33b23 Fix conversation bubbles becoming too long.
Co-authored-by: Cody Henthorne <cody@signal.org>
2024-01-31 22:12:17 -05:00
Alex Hart 4ada7c9be9 Implement several polish items for Call Links. 2024-01-31 22:12:16 -05:00
Nicholas Tinsley 56a2d8891f Bump version to 6.45.2 2024-01-31 22:10:10 -05:00
Nicholas Tinsley 4585e90a00 Updated baseline profile. 2024-01-31 21:25:05 -05:00
Nicholas Tinsley 86d2ddc168 Update translations and other static files. 2024-01-31 21:18:34 -05:00
Greyson Parrelli 9d1514308a Fix potential crash during contact splits. 2024-01-31 19:39:39 -05:00
Greyson Parrelli 7abff55981 Bump version to 6.45.1 2024-01-25 19:10:08 -05:00
Greyson Parrelli f9d7eba7d4 Update translations and other static files. 2024-01-25 19:09:11 -05:00
Greyson Parrelli 9ce021afa2 Improve SenderKeyDistributionMessage envelope validation. 2024-01-25 18:59:57 -05:00
Nicholas Tinsley 6fc9055221 Calculate video encoding settings based on trimmed duration. 2024-01-25 18:59:57 -05:00
Greyson Parrelli a3438d3345 Improve DecryptionErrorMessage envelope validation. 2024-01-25 14:42:50 -05:00
Nicholas Tinsley d2cbf11264 Don't fail video send on postprocess failure. 2024-01-25 13:32:03 -05:00
Greyson Parrelli c584156c86 Bump version to 6.45.0 2024-01-23 13:27:17 -05:00
Greyson Parrelli 78e04f3ad8 Update baseline profile. 2024-01-23 13:18:57 -05:00
Greyson Parrelli 6302725678 Update translations and other static files. 2024-01-23 13:18:57 -05:00
Nicholas Tinsley 431e65808d Rotate custom video muxer flag. 2024-01-23 13:18:57 -05:00
Clark 653914f47e Allow multiple message latency percentiles. 2024-01-23 13:18:57 -05:00
Cody Henthorne 96823e944d Introduce ActiveCallManager to prevent android service crashes during call handling. 2024-01-23 13:18:57 -05:00
Nicholas Tinsley ee19520e1b Improve video sample app UI. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 6f16b3fee7 Add video sample app test dependencies. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 89ee7e8e19 Update AndroidX test library verisons. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 3c7996aa99 Rotate instant video feature flag. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 3a314c565c Deduplicate call participants in call info screen.. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 8c9b668cd7 Rotate incrementalMac proto field. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 7666462de2 Support Dolby Vision HDR videos in transcoder. 2024-01-23 13:18:56 -05:00
Nicholas Tinsley 54cf11a78b Request notification permissions for video sample app. 2024-01-23 11:52:04 -05:00
Greyson Parrelli 16b78f0843 Update username recovery flow. 2024-01-23 11:52:04 -05:00
Greyson Parrelli 5e97a6b192 Do not show username education if username is set. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 595cced5b7 Display overflow button with reactions OR raise hand. 2024-01-23 11:52:04 -05:00
Alex Hart d17f12dd76 Update call event state after revoking a call link. 2024-01-23 11:52:04 -05:00
Alex Hart 8b5498cfbd Fix if check for call links. 2024-01-23 11:52:04 -05:00
Clark Chen e4b755ced8 Correctly count message events for notification heuristic. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 5b7eb9c332 Update libsignal-client to 0.39.2 2024-01-23 11:52:04 -05:00
Alex Hart cd8e07c102 Do not jump to top of the list when no search query present in story privacy. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley a36f31c2d0 Allow full disablement of StreamingTranscoder output size limit. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley ac0812a6dd Update AndroidX Media3 to 1.2.1. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 69c864f984 Allow slight variations in video output frame count. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 3c9a7fd329 Reorganize video converter library packages 2024-01-23 11:52:04 -05:00
Alex Hart f81dc11f61 Prevent crash if a call doesn't have a message associated with it. 2024-01-23 11:52:04 -05:00
Greyson Parrelli ce9a8f62d4 Check for network connectivity before making any substantial username alteration request. 2024-01-23 11:52:04 -05:00
Alex Hart 7e00d50078 Fix add to groups display without query. 2024-01-23 11:52:04 -05:00
Alex Hart 4af3f5038f Fix call labeling in settings page. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 7bb1c58452 Refactor Mp4FaststartPostProcessor. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 8e7383be05 Make Mp4Writer output compatible with Quicktime. 2024-01-23 11:52:04 -05:00
Cody Henthorne bc5d27ed90 Prevent calling service crash loop when system restarts us in background. 2024-01-23 11:52:04 -05:00
Greyson Parrelli ae884d79a1 Remove system contact links from undiscoverable contacts. 2024-01-23 11:52:04 -05:00
Pratyush Venkatakrishnan cf89c988cf Set structured name when creating contacts.
Fixes #12305. When creating copies of contacts, set the structured name
fields instead of the unstructured name fields. This fixes bugs where
certain types of names are displayed incorrectly because the structured
name is inaccurately reconstructed from the unstructured name.

Closes #13366
2024-01-23 11:52:04 -05:00
Cody Henthorne c54313c32e Add another verification code test case. 2024-01-23 11:52:04 -05:00
Cody Henthorne 3e001ddf1b Fix poor calling audio on certain devices. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley c41795e7f0 Extract postprocessor lambda into interface. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 52120afdbd Remove dependency on Guava limited stream. 2024-01-23 11:52:04 -05:00
Alex Hart 73d98da32b Polish about sheet UX. 2024-01-23 11:52:04 -05:00
Alex Hart 99f936ff97 Fix about sheet groups in common placeholder count. 2024-01-23 11:52:04 -05:00
Cody Henthorne 15afaeabe3 Use WebpSanitizer. 2024-01-23 11:52:04 -05:00
Greyson Parrelli debf964b5f Add more info to internal conversation details. 2024-01-23 11:52:04 -05:00
Nicholas Tinsley 393730cea9 Fix video sample app output. 2024-01-23 11:52:04 -05:00
Greyson Parrelli 2194fbd535 Add support for fetching archive media metadata. 2024-01-23 11:52:04 -05:00
Alex Hart cf59249d3d Rewrite chat colors delegation. 2024-01-23 11:52:04 -05:00
Alex Hart 2c554a3a20 Implement new ways to connect splash and megaphone. 2024-01-23 11:52:04 -05:00
Alex Hart 7b9554a42c Align profile screen with figma. 2024-01-23 11:52:03 -05:00
Alex Hart dd527ce33c Align pnp privacy settings screens with figma. 2024-01-23 11:52:03 -05:00
Nicholas Tinsley ddcc06c6b7 Update video transcoding sample cleanup file extension. 2024-01-23 11:52:03 -05:00
Alex Hart a827033f25 Ensure incoming story reactions are marked as such. 2024-01-23 11:52:03 -05:00
Alex Hart 01841a4aa8 Set body on OutgoingMessage when adding to a group story. 2024-01-23 11:52:03 -05:00
Alex Hart bb52e7159c Pluralize groups in common. 2024-01-23 11:52:03 -05:00
Ruben De Smet 3988b46a60 Fix typo for generateLastRes*t*ortKyberPreKey. 2024-01-23 11:52:03 -05:00
Nicholas Tinsley caa5e233df Expose StreamingTranscoder configuration options in sample app. 2024-01-23 11:52:03 -05:00
Nicholas Tinsley c7609f9a2a StreamingTranscoder sample app. 2024-01-23 11:52:03 -05:00
Greyson Parrelli 750fd4efe1 Improve safety of update and delete database methods. 2024-01-23 11:52:03 -05:00
Greyson Parrelli e361795184 Improve logging and the naming of some fields. 2024-01-23 11:52:03 -05:00
Greyson Parrelli 64fff2adb2 Do not show contact info for non-discoverable contacts. 2024-01-23 11:52:03 -05:00
farewelltospring 846e398577 Add emoji to content description in EmojiImageView.
Fixes #13358
2024-01-23 11:52:03 -05:00
Greyson Parrelli c1e9ee7a66 Allow changing the PNP config remotely. 2024-01-23 11:52:03 -05:00
Greyson Parrelli 8dc9e09f31 Bump version to 6.44.3 2024-01-23 11:44:33 -05:00
Greyson Parrelli d1930d4936 Update baseline profile. 2024-01-23 11:44:31 -05:00
Greyson Parrelli 14539eb036 Update translations and other static files. 2024-01-23 11:28:39 -05:00
Greyson Parrelli 45f1853c44 Fix issue opening files in external apps. 2024-01-23 11:16:09 -05:00
Cody Henthorne 6eaebd112b Bump version to 6.44.2 2024-01-16 12:00:33 -05:00
Cody Henthorne f0503faeff Updated baseline profile. 2024-01-16 11:54:11 -05:00
Cody Henthorne 64052d9dd2 Update translations and other static files. 2024-01-16 11:48:28 -05:00
Greyson Parrelli db4634a0dd Fix potential NPE during group send. 2024-01-16 10:13:21 -05:00
Alex Hart c725a2fabb Bump version to 6.44.1 2024-01-12 16:40:49 -04:00
Alex Hart 1ddececa16 Updated baseline profile. 2024-01-12 16:36:13 -04:00
Alex Hart 4e2e6cd83e Update translations and other static files. 2024-01-12 16:33:05 -04:00
Alex Hart efcfe2dafc Fix call disposition update issue. 2024-01-12 15:59:45 -04:00
Alex Hart b8ddb9e673 Fix several story issues around bad manifest interactions. 2024-01-12 10:11:06 -04:00
Alex Hart a1f19e9d8a Bump version to 6.44.0 2024-01-11 17:13:48 -04:00
Alex Hart 5464edf639 Updated baseline profile. 2024-01-11 17:13:27 -04:00
Alex Hart 179c3790e6 Update translations and other static files. 2024-01-11 17:10:48 -04:00
Greyson Parrelli cfae9753a3 Cleanup 2024-01-11 15:56:52 -05:00
Greyson Parrelli 61a4a3b322 Add support for restoring usernames post-registration. 2024-01-11 15:56:51 -05:00
Nicholas Tinsley c16bf65a80 Change audio tone for raise hand. 2024-01-11 14:09:08 -05:00
Greyson Parrelli 16ea1912b4 fixup! Combine username confirmation and link creation into a single operation. 2024-01-11 12:29:40 -05:00
Greyson Parrelli 54012cb33a Combine username confirmation and link creation into a single operation. 2024-01-11 12:00:44 -05:00
Alex Hart 459c5c0a55 Username UX polish. 2024-01-11 11:32:38 -04:00
Greyson Parrelli 4216b56443 Update types of messages we backup. 2024-01-11 10:26:05 -05:00
Alex Hart d7b79314d9 Fix crash if donation error dialog is dismissed after fragment disappears. 2024-01-11 11:09:37 -04:00
Cody Henthorne a340b13f65 Fix group call continuing to ring after accepted on another device. 2024-01-11 10:01:27 -05:00
Alex Hart 72f6b15dba Hide header decorations when no subtitle or description is set. 2024-01-11 09:48:27 -04:00
Alex Hart 64dbb77e63 Fix clipping and padding on about sheet. 2024-01-10 17:01:56 -04:00
Alex Hart af10b0e4f6 Clip avatar click indication to avatar circle. 2024-01-10 16:49:54 -04:00
Cody Henthorne 6f15c16a42 Add notification profile specific events for missed calls. 2024-01-10 15:30:45 -05:00
Alex Hart 86158027d7 Fix conversation header margins on thinner screens. 2024-01-10 16:25:26 -04:00
Greyson Parrelli 50369890f7 Refactor username state to use Username models. 2024-01-10 14:57:31 -05:00
Cody Henthorne b8dea25aef Fix loss of formatted text on copy. 2024-01-10 14:08:02 -05:00
Cody Henthorne 64e9324aa0 Default new notification profiles to allow calls. 2024-01-10 11:53:08 -05:00
Cody Henthorne 20f8c69b07 Add donate_friend remote megaphone action. 2024-01-10 11:52:55 -05:00
Nicholas Tinsley dd1a15c249 fixup! Refactor video testapp. 2024-01-10 11:52:20 -05:00
Nicholas Tinsley 8b24498fa7 Refactor video testapp. 2024-01-10 11:50:54 -05:00
Nicholas Tinsley 3673fa4908 Null safety for TransformProperties during attachment compression. 2024-01-09 16:59:20 -05:00
Nicholas Tinsley 960c1df5e7 Apply faststart to videos transcoded using Streams. 2024-01-09 16:59:20 -05:00
Greyson Parrelli 8c3c7c18ad Fix backup restore issue with new attachment table name. 2024-01-09 16:17:14 -05:00
Greyson Parrelli b96a5af133 Fix issue when opening view-once messages. 2024-01-09 15:24:47 -05:00
Alex Hart d0d4008100 Add cleanup job for group ringing. 2024-01-09 13:40:50 -04:00
Alex Hart 17a6fcafa1 Add ability to set custom username discriminators. 2024-01-09 11:37:39 -04:00
Bernie Dolan fb75440769 Update payments to 6.0.1 2024-01-09 11:12:48 -04:00
Greyson Parrelli fe39b5e4e2 Clean up AttachmentTable schema. 2024-01-09 11:12:48 -04:00
Alex Hart 62b142cdeb Add new state transitions for group call disposition. 2024-01-09 11:12:48 -04:00
Nicholas Tinsley ffce7213b4 fixup! Fix width of attachment download status text. 2024-01-09 11:12:48 -04:00
Nicholas Tinsley 4205934806 fixup! Fix width of attachment download status text. 2024-01-09 11:12:48 -04:00
Nicholas Tinsley 7aab86643a Fix width of attachment download status text. 2024-01-09 11:12:48 -04:00
Alex Hart 1bb0c55d88 Cleanup unused imports in AttachmentTable. 2024-01-09 11:12:48 -04:00
Jim Gustafson d22ac9ee00 Update to RingRTC v2.36.0 2024-01-09 11:12:48 -04:00
Greyson Parrelli 80a7db2511 Fix crash when sending trimmed videos. 2024-01-09 11:12:48 -04:00
Nicholas Tinsley e0fb102572 Prevent back gesture during video trimming. 2024-01-09 11:12:48 -04:00
Cody Henthorne 8d1a16dcd6 Remove story references from multi-recipient saftey number change sheet. 2024-01-09 11:12:48 -04:00
Cody Henthorne 0b4bbd5db2 Fix unread decorator position when read follow unread. 2024-01-09 11:12:48 -04:00
Jim Gustafson 78b714e019 Remove legacy call message fields 2024-01-09 11:12:48 -04:00
Nicholas Tinsley 5022d81d9a Allow canceling media attachment send. 2024-01-09 11:12:48 -04:00
Nicholas Tinsley deacf28d77 Make entire video preview file size bubble clickable. 2024-01-09 11:12:48 -04:00
Cody Henthorne 5e8d324860 Fix large balance issues. 2024-01-09 11:12:48 -04:00
Greyson Parrelli 3554f82ea3 Convert AttachmentTable and models to kotlin. 2024-01-09 11:12:48 -04:00
Alex Hart 888a40a5c4 Bump version to 6.43.2 2024-01-09 10:50:04 -04:00
Alex Hart 363953a0a4 Updated baseline profile. 2024-01-09 10:35:17 -04:00
Alex Hart e599d9b14e Updated baseline profile. 2024-01-09 10:29:00 -04:00
Alex Hart a33be1fad3 Update translations and other static files. 2024-01-09 10:22:14 -04:00
Greyson Parrelli b6528e843e Fix profile fetches for empty groups. 2024-01-08 10:43:49 -05:00
Clark Chen 10c31e6591 Bump version to 6.43.1 2024-01-05 16:34:19 -05:00
Clark Chen 8fba64cb8f Update translations and other static files. 2024-01-05 16:21:05 -05:00
Nicholas Tinsley c2fd08ca80 Display attachment download progress in MB. 2024-01-05 15:34:12 -05:00
Nicholas Tinsley 940bf0603e Only play consecutive voice notes. 2024-01-04 16:52:35 -05:00
Nicholas Tinsley 4d8a3dafe0 Do not play end tone for individual voice memo. 2024-01-04 16:23:40 -05:00
Nicholas Tinsley d237bb0136 WebRtcCallView cleanups 2024-01-04 15:52:56 -05:00
Nicholas Tinsley d42dfd3edd Don't hide call controls when interacting with PiP. 2024-01-04 15:50:31 -05:00
Alex Hart ab4f17d55f Fix contentColor in dark mode on bottom sheets. 2024-01-04 15:54:14 -04:00
Alex Hart 07968febe8 Fix color tinting of icons in conversation header view. 2024-01-04 11:36:40 -04:00
Alex Hart 67ff0892d5 Fix bug where description would overwrite subtitle of conversation header. 2024-01-04 11:32:40 -04:00
Alex Hart f1ee168657 Hide header decoration when in the release notes chat. 2024-01-04 11:18:28 -04:00
Nicholas Tinsley 5fef60c2b0 Only play raise hand sound if no other hands are raised. 2024-01-04 09:59:01 -05:00
Clark Chen 4afffc7dd3 Bump version to 6.43.0 2024-01-03 15:54:16 -05:00
Clark Chen 7e6346a694 Update translations and other static files. 2024-01-03 15:33:59 -05:00
Cody Henthorne abf22eff44 Fix gift donation getting stuck in pending. 2024-01-03 15:12:45 -05:00
Cody Henthorne 3fa3b93c85 Fix improper notifications when delaying for linked device activity. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley 549ef9dabc Revert "Merge database writes for attachment compression."
This reverts commit c50993bbf7c4a9e10a253f8d234c621ededffc47.
2024-01-03 15:12:45 -05:00
Nicholas Tinsley 59c75663b1 Adjust download size margin. 2024-01-03 15:12:45 -05:00
Greyson Parrelli 820a5bc363 Add extra guard during db migration.
Relates to #13183
2024-01-03 15:12:45 -05:00
Nicholas Tinsley 1b9cf631be Play sound for raised hand. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley b4f2208bae Merge database writes for attachment compression. 2024-01-03 15:12:45 -05:00
Greyson Parrelli f4bcfca323 Add upload support for the main backup file in backupV2. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley f93a9a0f22 Sort call participants by raised hand. 2024-01-03 15:12:45 -05:00
Clark Chen e5652197eb Fix content description for send message button. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley 4a102d44cb Show raise hand on each particpiant. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley c837840e04 Properly hide toolbar gradient in calling view. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley b280ff7495 Instant video processing metadata. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley f6d8dcf6fd Accurate remaining download size for partially downloaded galleries. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley fa0661f58a Instant video playback for very small video files. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley e2fe137b05 Attachment download progress view fixes. 2024-01-03 15:12:45 -05:00
Alex Hart b434e955ac Fix dropped gradient background from text stories sent from desktop. 2024-01-03 15:12:45 -05:00
Clark d74b302edb Add remaining non-group update messages for backup. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley 0200430346 TransferControlView padding tweak. 2024-01-03 15:12:45 -05:00
Alex Hart d70ebc2398 Update style for conversation header view. 2024-01-03 15:12:45 -05:00
Alex Hart 2b606a2dec Add update-tick for call log timestamps. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley 9c7f2250b9 Don't set attachment progress to 1 if it's complete. 2024-01-03 15:12:45 -05:00
Greyson Parrelli c2ee621f64 Move maybeMarkRegistrationComplete to be non-blocking. 2024-01-03 15:12:45 -05:00
Greyson Parrelli b2cdb46c84 Remove now-unnecessary data from prekey upload request. 2024-01-03 15:12:45 -05:00
Haris Dautovic 6d150aa5cb Move attachment constraints check to a background thread.
Fixes #13296
Closes #13306
2024-01-03 15:12:45 -05:00
Greyson Parrelli 62ece66f36 Fix bug where scheduled messages don't update snippets. 2024-01-03 15:12:45 -05:00
Nicholas Tinsley 628cd3896c Don't attempt to launch viewer for deleted view-once media. 2024-01-03 15:12:45 -05:00
Greyson Parrelli f10418face Convert RetrieveProfileJob to kotlin. 2024-01-03 15:12:45 -05:00
Alex Hart ca9a629804 Restyling review banner and cards. 2024-01-03 15:12:45 -05:00
Greyson Parrelli bb30535afb Respect the phoneNumberSharing setting on the profile. 2024-01-03 15:12:44 -05:00
Cody Henthorne 624f863da4 Ensure call links UX is still available post new calling features. 2024-01-03 15:12:44 -05:00
Nicholas Tinsley b55a9f253e Improve animations for reactions feed. 2024-01-03 15:12:44 -05:00
Nicholas Tinsley 5b9ef5b6b6 Separate string resources for edited message footer. 2024-01-03 15:12:44 -05:00
Cody Henthorne e7c8ecbd31 Fix storage sync validation crash with local only unknown ids. 2024-01-03 15:12:44 -05:00
Cody Henthorne 592dfec8db Fix spacing between donate gateway buttons. 2024-01-03 15:12:44 -05:00
Cody Henthorne 23ebccc041 Fix notification profile toast crash. 2024-01-03 15:12:44 -05:00
Greyson Parrelli 036bd51298 Update libsignal-client to 0.37.0 2024-01-03 15:12:44 -05:00
Nicholas Tinsley 6d9a66cc41 Send download job cancellations upon remote delete. 2024-01-03 15:12:44 -05:00
Nicholas Tinsley 1923b84a01 Expand forwarding search touch target. 2024-01-03 15:12:44 -05:00
Alex Hart 9924e293c9 Implement new about sheet. 2024-01-03 15:12:44 -05:00
Cody Henthorne 490d3549e2 Attempt to fix message replies bottom sheet overlap. 2024-01-03 15:12:44 -05:00
Nicholas Tinsley 45d2a5d0b6 Make emoji burst more "out of the reaction". 2023-12-19 11:14:04 -05:00
Jim Gustafson 4d3929948c Update to RingRTC v2.35.0 2023-12-19 11:14:04 -05:00
Nicholas Tinsley 56ea09431f Make time duration dialog scrollable.
Addresses #13202.
2023-12-19 11:14:04 -05:00
Nicholas Tinsley a53a5f4685 Calling 2.1 Improvements 2023-12-19 11:14:04 -05:00
Clark 52f3ff5ff6 Fix case where we delete unknown remote records but also handle unknown ids. 2023-12-19 11:14:04 -05:00
Alex Hart 7150783848 Revert "Fix case where we delete unknown remote records. "
This reverts commit ab29d194bb677ac51c2ad225e894e37f10cf6599.
2023-12-19 11:14:04 -05:00
Nicholas Tinsley c03d3520d6 Raise hand polish. 2023-12-19 11:14:02 -05:00
Clark d2e19c5129 Fix case where we delete unknown remote records. 2023-12-19 11:12:57 -05:00
Alex Hart a829165f2d Clone overflow spannable in attempt to reduce flickering. 2023-12-19 11:12:57 -05:00
Alex Hart f2707d053d Fix ANR when deleting a video during story creation. 2023-12-19 11:12:57 -05:00
Haris Dautovic 2a4ccf69b2 Use ViewCompat.setTransitionName in a safe way.
Fixes #13307
2023-12-19 11:12:57 -05:00
Alex Hart 818356dfed Add gift badge title to row item to mirror iOS. 2023-12-19 11:12:57 -05:00
Alex Hart 49d6743cbb Fix conversation list jank after returning from calls tab. 2023-12-19 11:12:57 -05:00
Nicholas Tinsley 9ed80d46b6 Add confirmation dialog for lowering a raised hand. 2023-12-19 11:12:57 -05:00
Nicholas Tinsley c2f5a6390e Initial raise hand support. 2023-12-19 11:12:57 -05:00
Greyson Parrelli f2a7824168 Fix error message interaction on text-only bubbles. 2023-12-19 11:12:57 -05:00
Greyson Parrelli 3439861f74 Stop writes to the deprecated SVR2 enclave. 2023-12-19 11:12:57 -05:00
Alex Hart 06ee096746 Fix crash when launching 'turn off contact joined' option via activity. 2023-12-19 11:12:56 -05:00
Greyson Parrelli 6230a7553d Add some initial backupV2 network infrastructure. 2023-12-19 11:12:56 -05:00
Nicholas Tinsley e17b07bb12 Bump version to 6.42.3 2023-12-18 22:57:23 -05:00
Nicholas Tinsley ea2d4e9206 Updated baseline profile. 2023-12-18 22:53:07 -05:00
Nicholas Tinsley 2fffc86a5a Update translations and other static files. 2023-12-18 22:49:43 -05:00
Nicholas Tinsley 1bb0af72ee Fix static IP resolver for macOS. 2023-12-18 21:34:55 -05:00
Cody Henthorne 23a58ac064 Fix showing control bottom sheet on incoming rings. 2023-12-15 12:04:33 -05:00
Nicholas Tinsley af9d16852e Fix speaker icon colors for "small". 2023-12-15 11:58:01 -05:00
Cody Henthorne ee47c1ea10 Fix calling controll visibility bugs. 2023-12-15 11:41:47 -05:00
Cody Henthorne 2b318152fa Fix call buttons overflowing bottom sheet. 2023-12-14 10:29:53 -05:00
Greyson Parrelli 10a363248e Bump version to 6.42.2 2023-12-13 18:22:38 -05:00
Greyson Parrelli 11d4bde18a Update translations and other static files. 2023-12-13 18:21:49 -05:00
Greyson Parrelli b88b992cb6 Fix reading from the deprecated SVR2 enclave during the reglock flow. 2023-12-13 18:12:50 -05:00
Cody Henthorne 6e3e1b56fb Fix toolbar showing incorrectly bug. 2023-12-13 11:15:22 -05:00
Cody Henthorne 7dfda2598d Fix audio level background showing during ringing. 2023-12-13 10:52:40 -05:00
Cody Henthorne 853862c475 Fix call info sheet scroll position after dismissing. 2023-12-13 10:24:53 -05:00
Cody Henthorne 5627bb6bed Fix switch camera tooltip showing incorrectly. 2023-12-13 10:17:28 -05:00
Cody Henthorne b646e69b6b Fix self-PIP boundaries in calls. 2023-12-13 10:15:28 -05:00
Cody Henthorne 632aeed00b Bump version to 6.42.1 2023-12-08 12:00:38 -05:00
Cody Henthorne 3f5b4bad62 Update translations and other static files. 2023-12-08 11:46:29 -05:00
Cody Henthorne 7bba4ed820 Move switch camera button to self pip. 2023-12-08 11:40:02 -05:00
Nicholas Tinsley e22ff1bbfe Preserve lobby audio device choice. 2023-12-08 11:40:02 -05:00
Nicholas Tinsley ab66567db6 Instant Video design improvements. 2023-12-08 11:40:02 -05:00
Cody Henthorne a763e1729c Update audio indicator for new designs. 2023-12-08 11:40:02 -05:00
Nicholas Tinsley 6aac250990 Send reactions. 2023-12-07 15:18:05 -05:00
Greyson Parrelli a749b97707 Migrate to a new SVR2 enclave. 2023-12-07 15:14:44 -05:00
Greyson Parrelli f966b23f3a Update libsignal-client to 0.36.1 2023-12-07 14:23:33 -05:00
Greyson Parrelli 763025d19b Disable notification thumbnails on some devices.
Relates to #13287
2023-12-07 13:56:30 -05:00
Cody Henthorne 0bf2ae6075 Fix various UI quirks with new calling controls.
- Change nav bar color
- Fix padding in info list
2023-12-07 10:41:12 -05:00
Cody Henthorne 71f947484e Bump version to 6.42.0 2023-12-06 17:11:11 -05:00
Cody Henthorne 5160164111 Updated baseline profile. 2023-12-06 16:54:43 -05:00
Cody Henthorne 7501e029ab Update translations and other static files. 2023-12-06 16:49:31 -05:00
Cody Henthorne a678555d8d Receive calling reactions support and control ux refactor.
Co-authored-by: Nicholas <nicholas@signal.org>
2023-12-06 16:42:04 -05:00
Clark 7ce2991b0f Do not turn screen on automatically for calls. 2023-12-06 08:37:33 -05:00
Greyson Parrelli befa396e82 Export backupV2 using actual desired file format. 2023-12-04 16:18:56 -05:00
Clark Chen fb69fc5af2 Add backupV2 support for simple update messages. 2023-12-04 16:18:56 -05:00
Greyson Parrelli b540b5813e Setup backupV2 infrastructure and testing.
Co-authored-by: Clark Chen <clark@signal.org>
2023-12-04 16:18:56 -05:00
Greyson Parrelli feb74d90f6 Update libsignal-client to 0.35.0 2023-12-04 16:18:56 -05:00
Greyson Parrelli a0de2577e8 Add extra data to the provisioning proto. 2023-12-04 16:18:56 -05:00
Greyson Parrelli dbc5112ada Move send requirement calculations to a background thread. 2023-12-04 16:18:56 -05:00
Greyson Parrelli 9f8335810c Do not resort the chat list based on identity verification updates. 2023-12-04 16:18:56 -05:00
Greyson Parrelli c54e2388ce Fix potential stack overflow during thread deletion. 2023-12-04 16:18:56 -05:00
Greyson Parrelli a8a7019411 Fix marking crashes as prompted. 2023-12-04 16:18:56 -05:00
Greyson Parrelli 098da3c3dd Attempt to address a search crash. 2023-12-04 16:18:56 -05:00
Cody Henthorne 71b5645801 Do not show donate megaphone if currently awaiting a donation to clear. 2023-12-04 16:18:56 -05:00
Cody Henthorne f5d9fbe91c Allow deeplinks back into Signal from iDEAL banking apps. 2023-12-04 16:18:56 -05:00
Clark 420e15c179 Fix infinite identity key storage service clash. 2023-12-04 16:18:56 -05:00
Greyson Parrelli 74619f6f8d Prevent nested SQL error handlers. 2023-12-04 16:18:56 -05:00
Greyson Parrelli 1355a4a28d Fix bug where username may be put in e164 column. 2023-12-04 16:18:56 -05:00
Greyson Parrelli 97c34b889a Update logging format. 2023-12-04 16:18:53 -05:00
Cody Henthorne 0b0c54d874 Perform client side checks on name and email for donation flows. 2023-12-04 16:18:53 -05:00
Jim Gustafson 1005be006f Update to RingRTC v2.34.5 2023-12-04 16:18:53 -05:00
Greyson Parrelli 8db113a19b Fix potential crash in username share sheet. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 075df8a26d Fix crash if you search for a malformed username. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 38cf3f40e1 Fix various places where we should show the username. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 4a0abbbee7 Ensure ACI/PNI are associated after processing a PNI promotion message. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 15f1201a76 Remove leftover deprecated gv1 code. 2023-12-04 16:18:53 -05:00
Nicholas Tinsley b152723ed2 Restrict StreamingTranscoder usage to feature flag. 2023-12-04 16:18:53 -05:00
Clark 84a2832a65 Fix getAndPossiblyMerge to run after successful transaction in case of nested transactions. 2023-12-04 16:18:53 -05:00
Clark 8037494f7a Stop throwing an assertion error when getting attachment TransformationProperties. 2023-12-04 16:18:53 -05:00
Nicholas Tinsley 97c1ace020 Do not display stop icon on uncancelable progress. 2023-12-04 16:18:53 -05:00
Nicholas 64457b0235 Unique string resource for "edited now". 2023-12-04 16:18:53 -05:00
Nicholas 67ef831681 Only generate incremental mac for faststart videos. 2023-12-04 16:18:53 -05:00
Nicholas Tinsley 1fd6aae3d9 Make "Retry" text clickable when downloading attachment. 2023-12-04 16:18:53 -05:00
Clark 61810cc977 Re-use session objects during multi-recipient encryption. 2023-12-04 16:18:53 -05:00
Nicholas Tinsley 59401e18ed Prevent crash on audio focus permission denied.
Addresses #13283.
2023-12-04 16:18:53 -05:00
Nicholas Tinsley 30eff93fa1 Fix donation FAQ URL. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 7c5bae3b53 Remove unnecessary jcenter repository. 2023-12-04 16:18:53 -05:00
Greyson Parrelli ee16e4236e Convert the topmost build.gradle to .gradlew.kts. 2023-12-04 16:18:53 -05:00
Greyson Parrelli 30e9cf9dc8 Convert settings and dependencies to .gradle.kts. 2023-12-04 16:18:53 -05:00
Greyson Parrelli ac5d0bf8a3 Convert main app build.gradle to .gradle.kts. 2023-12-04 16:18:45 -05:00
Greyson Parrelli 923eb05e59 Converted libsignal-service to .gradle.kts. 2023-12-04 16:18:11 -05:00
Greyson Parrelli 8f59e51445 Move test into proper directory. 2023-12-04 16:18:11 -05:00
Greyson Parrelli 766733617e Converted all minor modules to .gradle.kts. 2023-12-04 16:18:11 -05:00
Nicholas Tinsley d77744c562 Additional logging around retry button. 2023-12-04 16:18:11 -05:00
Nicholas Tinsley 0d6db1305e Don't check recorded voice note size if discarding. 2023-12-04 16:18:11 -05:00
Nicholas 61c2e59f41 Only update profiles if their contents has changed. 2023-12-04 16:18:11 -05:00
Clark 47dd7adf4b Use libsignal to derive access key during group send. 2023-12-04 16:18:11 -05:00
Nicholas 016736c455 Encrypting for multiple senders benchmark. 2023-12-04 16:18:11 -05:00
Cody Henthorne 6d3924ba43 Add group call NOT_ACCEPTED sync handling. 2023-12-04 16:18:10 -05:00
Greyson Parrelli 428f963243 Remove unique constraint from dlist table. 2023-12-04 16:18:10 -05:00
Mridul Barman dd871b64ea Remove duplicate permission filtering.
Closes #12987
2023-12-04 16:18:10 -05:00
Greyson Parrelli 38863f618a Fix back navigation in username link settings screen. 2023-12-04 16:18:10 -05:00
Greyson Parrelli 8023285b9d Only mark username corrupted after repeated failures. 2023-12-04 16:18:10 -05:00
Greyson Parrelli 1aa7175006 Update order for attachment menu options. 2023-12-04 16:18:10 -05:00
Cody Henthorne 1222c30738 Bump version to 6.41.3 2023-12-04 16:12:20 -05:00
Cody Henthorne 0c8e62add9 Update translations and other static files. 2023-12-04 16:07:13 -05:00
Cody Henthorne eb1d06b4a6 Fix thumbnail info generation bug in notifications. 2023-12-04 16:01:43 -05:00
Greyson Parrelli d58c3292d7 Only use apk uploadTimestamp for non-website builds.
Relates to #13273
2023-12-04 15:54:14 -05:00
Greyson Parrelli 4320d26a3d Do not read PNP FF in job. 2023-12-04 15:12:20 -05:00
Cody Henthorne 3ca4e33d94 Fix sepa badge redemption job. 2023-12-04 15:12:20 -05:00
Greyson Parrelli 19e726a630 Bump version to 6.41.2 2023-11-17 15:10:15 -05:00
Greyson Parrelli 96dddef271 Update translations and other static files. 2023-11-17 15:09:35 -05:00
Cody Henthorne 34a228f85e Remove GV1 migration support. 2023-11-17 14:25:47 -05:00
Greyson Parrelli 213d996168 Fix issues with some japanese numbers being detected as shortcodes. 2023-11-17 14:25:47 -05:00
Greyson Parrelli 5a159ce01f Update libphonenumber to 8.13.23 2023-11-17 14:25:47 -05:00
Cody Henthorne fed9c64113 Fix false-positive CVC errors in credit card donation flow. 2023-11-17 14:25:47 -05:00
Nicholas Tinsley 2d835581a5 Set audio picker bottom sheet text color to onSurface. 2023-11-17 14:25:47 -05:00
Nicholas Tinsley c8f1ebdf4c Fix speakerphone drawables for selection. 2023-11-17 14:25:47 -05:00
Greyson Parrelli 98e3530acd Bump version to 6.41.1 2023-11-16 17:12:19 -05:00
Greyson Parrelli 1a5b216dd5 Update translations and other static files. 2023-11-16 17:11:47 -05:00
Cody Henthorne ae98d5e3bd Fix NPE in wifi direct connection establishment. 2023-11-16 16:37:38 -05:00
Greyson Parrelli 750825b3c3 Fix potential bug with the in-app updater. 2023-11-16 16:19:50 -05:00
Cody Henthorne 8c255256c9 Remove mms_config xmls. 2023-11-16 16:19:50 -05:00
Cody Henthorne 19626361ec Fix bug allowing creation of new and sending in existing MMS groups. 2023-11-16 16:19:50 -05:00
Cody Henthorne df4bd1fa4a Replace monthly badge expires with cancellation dialogs. 2023-11-16 10:22:01 -05:00
Greyson Parrelli 62bf5abd8d Bump version to 6.41.0 2023-11-15 17:32:01 -05:00
Greyson Parrelli cd9ec9f346 Update translations and other static files. 2023-11-15 17:30:04 -05:00
Greyson Parrelli cf7d5b3481 Remove deprecated storage service fields. 2023-11-15 17:02:44 -05:00
Cody Henthorne 12f9ac3aa4 Use shorter string for tab for better localization. 2023-11-15 17:02:44 -05:00
Greyson Parrelli 4519cdb49c Remove some unnecessary transactions in MessageContentProcessor. 2023-11-15 17:02:28 -05:00
Jim Gustafson d20b6f355c Enable opus low bitrate redundancy for internal testing. 2023-11-15 17:02:21 -05:00
Greyson Parrelli 70e64003f9 Unconditionally enable the PNI capability. 2023-11-15 17:02:21 -05:00
Nicholas Tinsley 0a4644e743 Update conversation shortcuts onPause. 2023-11-15 17:02:21 -05:00
Greyson Parrelli c428d23d8b Install prompt notification should dismiss failures and vice-versa. 2023-11-15 17:02:21 -05:00
Greyson Parrelli d6b189badc Fix potential binding crash. 2023-11-15 17:02:21 -05:00
Greyson Parrelli 6e899391c0 Add back the foreign key transaction dance. 2023-11-15 17:02:21 -05:00
Greyson Parrelli e0acbcc32d Perform one database upgrade at a time, saving progress as we go. 2023-11-15 17:02:21 -05:00
Cody Henthorne 95fb9ea117 Remove old remote configs. 2023-11-15 17:02:21 -05:00
Greyson Parrelli e80b7cf0a2 Store receipt fields as booleans instead of counts. 2023-11-15 17:02:21 -05:00
Cody Henthorne 5e70c06075 Rotate ideal and sepa flags. 2023-11-15 17:02:21 -05:00
Cody Henthorne 1413b74f76 Add 'Add remote donate megaphone' to internal settings. 2023-11-15 17:02:21 -05:00
Cody Henthorne bf0548e802 Fix donation-based remote config region checks. 2023-11-15 17:02:21 -05:00
Clark b7e1863526 Fix timezone weirdness with scheduled messages. 2023-11-15 17:02:21 -05:00
Cody Henthorne f189188563 Fix snackbar colors on older api verisons. 2023-11-15 17:02:21 -05:00
Greyson Parrelli 2f52664820 Merge MediaMmsMessageRecord into MmsMessageRecord. 2023-11-15 17:02:21 -05:00
Greyson Parrelli 5f6fa73be9 Delete NotificationMmsMessageRecord. 2023-11-15 17:02:21 -05:00
Greyson Parrelli b7ec913cb9 Improve receipt perf by caching the pending PNI signature table. 2023-11-15 17:02:21 -05:00
Greyson Parrelli ebef4b079c Fix LRUCache to be ordered by access time. 2023-11-15 17:02:21 -05:00
Greyson Parrelli a81e5c4e6b Improve receipt processing via faster thread updates. 2023-11-15 17:02:21 -05:00
Greyson Parrelli b0733dcd51 Reduce transactions during getAndPossiblyMerge. 2023-11-15 17:02:21 -05:00
Greyson Parrelli e9bd35619d Add migration to fix registration state of some users. 2023-11-11 13:34:48 -05:00
Greyson Parrelli 6528b34152 Fix username education layout when text is long. 2023-11-11 13:34:48 -05:00
Rashad Sookram b60c02e0c7 Update to RingRTC v2.34.4 2023-11-11 13:34:48 -05:00
Greyson Parrelli a0792d166b Add additional logging around apk updates. 2023-11-11 13:34:48 -05:00
Greyson Parrelli fcf36c4bc0 Fix color of x in color picker. 2023-11-11 13:34:48 -05:00
Greyson Parrelli e5b617cd16 Fix text color in username link sharing bottom sheet. 2023-11-11 13:34:48 -05:00
Greyson Parrelli 0acefb4521 Fix storage sync issues with usernames. 2023-11-11 13:34:48 -05:00
Greyson Parrelli 111c8367a9 Fix discoverability setting persistence during registration. 2023-11-11 13:34:48 -05:00
Greyson Parrelli ead8f209b6 Fix 'next' button alignment during registration. 2023-11-11 13:34:48 -05:00
Greyson Parrelli 96333b616b Add username link share sheet. 2023-11-11 13:34:48 -05:00
Greyson Parrelli 5698e0deda Bump version to 6.40.4 2023-11-11 12:38:36 -05:00
Greyson Parrelli df2ddebf6c Bump version to 6.40.3 2023-11-11 12:05:20 -05:00
Greyson Parrelli 71ab7528e7 Fix shared group membership check. 2023-11-11 12:04:55 -05:00
Cody Henthorne b4e459d831 Bump version to 6.40.2 2023-11-10 15:44:59 -05:00
Cody Henthorne a57d3fdf3f Updated baseline profile. 2023-11-10 15:39:20 -05:00
Cody Henthorne 2c207873be Update translations and other static files. 2023-11-10 15:34:43 -05:00
Cody Henthorne fc8385113f Fix system ANR when loading avatars for system UI. 2023-11-10 15:27:57 -05:00
Cody Henthorne 95d7d26f11 Add SEPA max amount exceeded dialog. 2023-11-10 15:27:57 -05:00
AsamK 43a13964bd Fix leaking okhttp response in error case.
Closes #13246
2023-11-10 15:27:57 -05:00
Cody Henthorne d2053d2db7 Bump version to 6.40.1 2023-11-09 16:52:42 -05:00
Cody Henthorne 8ba2bcaa53 Updated baseline profile. 2023-11-09 16:30:25 -05:00
Cody Henthorne f7abdbe97f Update translations and other static files. 2023-11-09 16:27:36 -05:00
Greyson Parrelli 91af3e60ba Fix potential NPE when building an account record. 2023-11-09 16:13:46 -05:00
Clark 8fe196cd7a Don't renotify every single message on new message. 2023-11-09 12:29:59 -05:00
Cody Henthorne 66d7241c03 Update donation learn more urls in error states. 2023-11-09 12:06:27 -05:00
Cody Henthorne 89d7c0b0d0 Bump version to 6.40.0 2023-11-08 20:11:41 -05:00
Cody Henthorne d2ec62d681 Updated baseline profile. 2023-11-08 20:02:59 -05:00
Cody Henthorne b6d38fe8f1 Update translations and other static files. 2023-11-08 19:57:56 -05:00
Cody Henthorne 1edc256148 Rotate ideal and sepa flags. 2023-11-08 19:51:46 -05:00
Cody Henthorne 24ac385898 Fix dark theme issues with compose bottom sheets and donation bank name typo. 2023-11-08 19:51:46 -05:00
Cody Henthorne f062e58f7b Flesh out monthly iDEAL donation flow. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 96aec401b9 Fix username link settings navigation. 2023-11-08 19:51:46 -05:00
Nicholas Tinsley 7ff0b7aa3c Increase clickable area of media download button. 2023-11-08 19:51:46 -05:00
Greyson Parrelli e5ab5241d5 Centralize username logic in UsernameRepository. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 0f4f87067e Add some detailed username docs to UsernameRepository. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 3f32f816b0 Convert the UsernameRepository to an object. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 73de2dfda7 Fix opening username links. 2023-11-08 19:51:46 -05:00
Nicholas d6fd6cb5a3 Optimize thread ID DB query. 2023-11-08 19:51:46 -05:00
Nicholas 39fbbe896f Batch insert group receipts. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 29c70acf4e Leave attachment insert early if there are no attachments. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 5cd2568776 Fix foreground service crash with state tracking. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 60a6535a12 Add internal test buttons to corrupt username state. 2023-11-08 19:51:46 -05:00
Greyson Parrelli f48b389449 Fix padding in edit profile screen. 2023-11-08 19:51:46 -05:00
Greyson Parrelli 316dd210a0 Minor improvements to username tooltip. 2023-11-07 22:11:08 -05:00
Greyson Parrelli a60712c09d If both usernames hashes are empty, consider valid. 2023-11-07 14:44:46 -05:00
Nicholas Tinsley 482cd564ff Lower priority of ConversationShortcutUpdateJob. 2023-11-07 13:37:21 -05:00
Greyson Parrelli ac1171d43b Allow install of nightlies with the same version code but newer upload dates. 2023-11-07 12:51:09 -05:00
Greyson Parrelli ed8953c430 Fix logging around username link reset failures. 2023-11-07 12:11:22 -05:00
Cody Henthorne 9a8aecaf3f Improve donation strings localization. 2023-11-07 11:56:01 -05:00
Greyson Parrelli 423719e7bc Fix username QR code sharing. 2023-11-07 11:43:40 -05:00
Cody Henthorne 7f2b6a874e Flesh out iDEAL sad path UX and address UI polish feedback. 2023-11-07 11:04:36 -05:00
Greyson Parrelli cfe5ea3f9b Add the ability to download the current perfetto trace in Spinner. 2023-11-07 09:07:59 -05:00
Greyson Parrelli 07aa058a46 Update username consistency error handling. 2023-11-06 14:49:51 -05:00
Nicholas Tinsley 6cadf93c43 Forward touch events in timestamp of text message. 2023-11-06 14:48:35 -05:00
Cody Henthorne 60eb1332d2 Fix lifespan typo for ExternalLaunchDonationJob. 2023-11-06 11:04:24 -05:00
Nicholas Tinsley a9ee7e93fd Increase IdentityKey cache size. 2023-11-06 10:46:53 -05:00
Clark 2782216e52 Remove slow getResourceAsStream when loading the Conscrypt provider. 2023-11-06 09:56:11 -05:00
Nicholas Tinsley d22537c5f2 Fix LocalMetrics for text sends. 2023-11-03 15:24:36 -04:00
Nicholas Tinsley 57aa6c19e1 Set silent group updates to low job priority. 2023-11-03 15:20:38 -04:00
Nicholas Tinsley 761553d392 Avoid unnecessary lock acquisition. 2023-11-03 15:12:29 -04:00
Greyson Parrelli 29350ab7b0 Add a QR code link and tooltip in the profile settings. 2023-11-03 14:33:07 -04:00
Cody Henthorne 528ccc1e9d Navigate to main donation screen if user leaves for external app. 2023-11-03 12:56:03 -04:00
Cody Henthorne 20d26ad7ca Expand spinner timestamp conversion to job tables. 2023-11-03 12:51:17 -04:00
Cody Henthorne 5d23c5c902 Increase sepa receipt request lifespan to cover at least 14 business days. 2023-11-03 12:49:19 -04:00
Greyson Parrelli 145794bf04 Add the ability to set job priority. 2023-11-03 12:21:27 -04:00
Greyson Parrelli d00f2aa8d0 Convert EditProfileFragment to kotlin. 2023-11-03 10:40:13 -04:00
Greyson Parrelli 3a20375567 Update profile edit screen to remove subtitles. 2023-11-03 09:25:09 -04:00
Greyson Parrelli 7be93a8a44 Rename profile fragments so they make sense. 2023-11-03 09:14:17 -04:00
Jim Gustafson b5e4c4e92a Update to RingRTC v2.34.3 2023-11-02 21:30:07 -04:00
Greyson Parrelli 20285796bd Fix username link sharing toolbar. 2023-11-02 19:19:00 -04:00
Greyson Parrelli 7826ff94e3 Also check PNI prekey age on message send. 2023-11-02 19:19:00 -04:00
Greyson Parrelli f1dccbb64d Consider empty usernames as absent. 2023-11-02 19:19:00 -04:00
Greyson Parrelli 528e301ce4 Improve username creation error debouncing. 2023-11-02 19:19:00 -04:00
Greyson Parrelli af016a9c79 Fix username error message text wrapping. 2023-11-02 19:19:00 -04:00
Greyson Parrelli cbd5738543 Fix some username creation tinting issues in dark theme. 2023-11-02 19:19:00 -04:00
Greyson Parrelli 2dd0899a3d Fix nightly updates. 2023-11-02 19:19:00 -04:00
Greyson Parrelli e486a4baef Bump version to 6.39.1 2023-11-02 19:18:37 -04:00
Greyson Parrelli 5fc11baf9e Update translations and other static files. 2023-11-02 19:18:37 -04:00
Nicholas 157777cac1 Batch update DB upon group receipt. 2023-11-02 19:18:37 -04:00
Greyson Parrelli 99d0ee6725 Fix cursor crash in ConversationSettings.
Best way to fix a cursor crash it to... stop using cursors.

Fairly confident the crash was caused by us closing the cursor while it
was read. And there just isn't a good way to avoid that with how it was
written. So this ended up being a great excuse to move over to models.
2023-11-02 11:58:23 -04:00
Greyson Parrelli b5c1051506 Attempt to fix AccountRecord restore crash.
My guess is that we're seeing a crash when updating because we're using
an out-of-date recipient snapshot that has an old/invalid storageId.

This commit uses a fresher recipient, and it prefers using the raw
record (what's in the DB) instead.
2023-11-02 10:25:17 -04:00
Greyson Parrelli bba3334df5 Bump version to 6.39.0 2023-11-01 20:45:16 -04:00
Greyson Parrelli 74488feec2 Update translations and other static files. 2023-11-01 20:45:16 -04:00
Greyson Parrelli 54953abc67 Reduce nightly update check interval to 2 hours. 2023-11-01 20:45:16 -04:00
Cody Henthorne 117bbdbcdf Show dialog when attempting to donate again while still processing previous donation. 2023-11-01 20:45:16 -04:00
Nicholas Tinsley b96b99c1c4 Swallow touch events in forwarding sheet overlay.
Addresses #13239.
2023-11-01 20:45:16 -04:00
Cody Henthorne 6e856a7648 Update bank mandate CTA UX. 2023-11-01 20:45:16 -04:00
Greyson Parrelli 0659edb762 Add a new foreground service for attachment progress. 2023-11-01 20:45:16 -04:00
Greyson Parrelli dcb870c432 Only show ACI SN's. 2023-11-01 20:45:16 -04:00
Greyson Parrelli 772bafbe43 Inline feature flag to show ACI SN by default. 2023-11-01 20:45:16 -04:00
Greyson Parrelli a9be6aff44 Fix delete crash. 2023-11-01 20:45:16 -04:00
Cody Henthorne dcd7ec7383 Treat pnp builds also as staging builds. 2023-11-01 20:45:16 -04:00
Greyson Parrelli c69a4dda00 Convert GenericForegroundService to kotlin. 2023-11-01 20:45:16 -04:00
Greyson Parrelli a911926119 Always for a full contact sync via ContactDiscovery.refreshAll(). 2023-11-01 20:45:15 -04:00
Greyson Parrelli 6f30aec4f2 Improve LocalMetrics logging. 2023-11-01 20:45:15 -04:00
Greyson Parrelli 5a005fb809 Build a simple ANR detector. 2023-11-01 20:45:15 -04:00
Cody Henthorne 776a4c5dce Fix string issues. 2023-10-31 10:19:34 -04:00
Jim Gustafson c53c316303 Update to RingRTC v2.34.2 2023-10-31 09:50:07 -04:00
Greyson Parrelli 622aa844e4 Clear glide memory cache on attachment delete. 2023-10-31 09:50:07 -04:00
Greyson Parrelli de2cf6026e Fix nightly build. 2023-10-30 18:09:17 -04:00
Greyson Parrelli a8e02b9ced Move envelope follow-up operations outside of the transaction. 2023-10-30 18:09:17 -04:00
Nicholas Tinsley 297308ad76 Only suggest scheduled message times in the future.
Addresses #13139
2023-10-30 18:09:17 -04:00
Greyson Parrelli ea0c3dbe5a Add logging around database transactions and group recipient creation. 2023-10-30 18:09:17 -04:00
Greyson Parrelli b8d229e58e Enable auto-updates for nightly builds. 2023-10-30 18:09:17 -04:00
Greyson Parrelli c4f5110148 Stop falling back to CDN0 for attachments. 2023-10-30 18:09:17 -04:00
Jim Gustafson 7fdd7e89bd Update to RingRTC v2.34.1 2023-10-30 18:09:17 -04:00
Greyson Parrelli 2378346537 Bump version to 6.38.2 2023-10-30 17:54:17 -04:00
Greyson Parrelli 72fc5fc3b1 Update translations and other static files. 2023-10-30 17:53:56 -04:00
Greyson Parrelli c063c99ba6 Fix contact joined messages. 2023-10-30 17:44:25 -04:00
Nicholas Tinsley 90341f0a6e Finish updating audio output assets. 2023-10-30 11:48:13 -04:00
Nicholas Tinsley cdb9df5aba Bump version to 6.38.1 2023-10-27 19:26:48 -04:00
Nicholas Tinsley 1f6d9d6422 Updated baseline profile. 2023-10-27 19:26:28 -04:00
Nicholas Tinsley ffbda7e521 Update translations and other static files. 2023-10-27 19:23:15 -04:00
Nicholas Tinsley 3b5ef29047 Update IncomingMessage in benchmark. 2023-10-27 18:32:35 -04:00
Nicholas Tinsley 14cf6ceb84 Change audio output assets. 2023-10-26 11:59:20 -04:00
Nicholas Tinsley 5fb940ff2a Update speaker view hint's legibility. 2023-10-26 11:29:26 -04:00
Nicholas Tinsley f446e18289 Require attachment data to be shown in "All" list. 2023-10-26 11:23:47 -04:00
Cody Henthorne 84f26b32d6 Fix snc causing thread reordering. 2023-10-26 10:43:44 -04:00
Nicholas Tinsley f7690245aa Bump version to 6.38.0 2023-10-25 15:51:26 -04:00
Nicholas Tinsley f44e32fd6a Update translations and other static files. 2023-10-25 15:50:48 -04:00
Nicholas Tinsley 8bac34238e Prevent crash on reaction animation end. 2023-10-25 15:44:13 -04:00
Nicholas Tinsley 6d2f6ce2f9 Hide safety verification in bottom sheet for null senders. 2023-10-25 15:44:13 -04:00
Alex Hart 3a465cc56b Account for horizontal padding when calculating available footer space. 2023-10-25 15:44:13 -04:00
Greyson Parrelli 617369dbc0 Make type a mandatory param on IncomingMessage. 2023-10-25 15:44:13 -04:00
Alex Hart c0fed1498e Utilze visibility instead of isVisible for restoration of view visibility after long press. 2023-10-25 15:44:13 -04:00
Alex Hart 5bdd3ce47a Add background to sticky year header for donation receipts. 2023-10-25 14:30:23 -04:00
Greyson Parrelli 6b3f41d675 Merge IncomingTextMessages into IncomingMessage. 2023-10-25 14:30:23 -04:00
Alex Hart 23b696c9cf Rotate ideal and sepa flags. 2023-10-25 14:30:23 -04:00
Alex Hart 079400f89e Donation error sheet wiring and UI. 2023-10-25 14:30:23 -04:00
Alex Hart e12d467627 Add ordering strategy for netherlands donation gateways. 2023-10-25 14:30:23 -04:00
Alex Hart 162ca3e21e Add locale based feature flags for iDEAL / SEPA donations. 2023-10-25 14:30:23 -04:00
Alex Hart dddd0e7b71 Pipe in bank mandate parameter. 2023-10-25 14:30:23 -04:00
Cody Henthorne 95d68e09da Cycle hide contacts remote config. 2023-10-25 14:30:23 -04:00
Alex Hart aaf0cf53d8 Remove number suffix of iban text as it is redundant. 2023-10-25 14:30:23 -04:00
Cody Henthorne 9c8f759732 Fix group call not ringing/notifying bug when starting a call. 2023-10-25 14:30:23 -04:00
Nicholas Tinsley a45c685893 Increase logging during registration. 2023-10-25 14:30:23 -04:00
Jordan Rose 87bdebb21c Remove dependency on presentations being present in AddMemberAction. 2023-10-25 14:30:00 -04:00
Greyson Parrelli 4f754ae309 Centralize media message inserts. 2023-10-23 14:31:39 -04:00
Greyson Parrelli 4b004f70ec Update website build to use PackageInstaller. 2023-10-23 14:30:37 -04:00
Greyson Parrelli d468d4c21b Remove sms/mms receive code.
Simplifying incoming message insert. Removing this dead path as part of
it.
2023-10-23 13:29:07 -04:00
Alex Hart a4df433d80 Add proper endpoint for setting iDEAL default payment method. 2023-10-23 14:13:13 -03:00
Alex Hart 10eec025d2 Implement pending one-time donation error handling. 2023-10-23 13:50:54 -03:00
Alex Hart d497ed4195 Handle launch to external bank application. 2023-10-23 09:26:31 -03:00
Alex Hart e63137d293 Add bank icons and ideal logo. 2023-10-20 15:28:10 -04:00
Cody Henthorne c744743913 Bump version to 6.37.2 2023-10-20 14:44:28 -04:00
Cody Henthorne 42493c8eb6 Updated baseline profile. 2023-10-20 14:34:45 -04:00
Cody Henthorne 391839028f Update translations and other static files. 2023-10-20 14:29:31 -04:00
Cody Henthorne d9ecfeadc0 Add prompt to re-enable full screen intent notifications. 2023-10-20 14:22:08 -04:00
Greyson Parrelli d866646f66 Update enum for phone number sharing mode. 2023-10-20 14:22:08 -04:00
Alex Hart 6295041341 Fix paypal one-time donation handling. 2023-10-20 14:22:08 -04:00
Alex Hart 8c7556427a Fix temporary screenshot security functionality. 2023-10-20 14:22:08 -04:00
Alex Hart 82c91db78c Fix SaveStateHandler viewModel delegate. 2023-10-20 11:26:37 -03:00
Alex Hart 2d969f4fff Reset scroll position to 0 on contact selection list commit. 2023-10-20 10:40:39 -03:00
Alex Hart e84d46dae7 Add check for call link prefix before parsing. 2023-10-20 10:33:01 -03:00
Alex Hart b6828b54ca Fix group calling update messages. 2023-10-20 10:17:31 -03:00
Cody Henthorne f9bd1bac36 Revert "Upgrade eventbus to 3.3.1"
This reverts commit 89199b81ab.
2023-10-19 13:11:13 -04:00
Cody Henthorne 22e2bfacae Bump version to 6.37.1 2023-10-19 10:53:15 -04:00
Cody Henthorne c446d4bb54 Fix crash in pni typing migration. 2023-10-19 10:39:08 -04:00
Cody Henthorne 23c7e5dc3f Bump version to 6.37.0 2023-10-18 17:08:26 -04:00
Cody Henthorne 661f1e624c Updated baseline profile. 2023-10-18 16:29:37 -04:00
Cody Henthorne 81ff5ef899 Update translations and other static files. 2023-10-18 16:23:23 -04:00
Cody Henthorne e79364cb03 Fix pni decryption error. 2023-10-18 16:14:58 -04:00
Nicholas d750e2fe7a Do not update media preview fragment state upon window transition. 2023-10-18 16:14:58 -04:00
Alex Hart 5e1025453a Implement beginnings of support for iDEAL payments. 2023-10-18 16:14:58 -04:00
Alex Hart 280da481ee Implement Stripe Failure Code support. 2023-10-18 16:14:58 -04:00
Jim Gustafson 9da5f47623 Update to RingRTC v2.34.0 2023-10-18 16:14:58 -04:00
Cody Henthorne 45f1f419e1 Add internal setting to log prekey ids. 2023-10-18 16:14:58 -04:00
Alex Hart 92f2ac67d5 Add proguard keep entry for org.signal.donations.json.** 2023-10-18 16:14:58 -04:00
Jordan Rose d28a62d70b Improve signalwebp JNI. 2023-10-18 16:14:58 -04:00
Alex Hart f9336f2a28 Rename DonationErrorSource value to MONTHLY. 2023-10-17 11:15:56 -04:00
Alex Hart 940e67b1ca Rename DonationErrorSource value to ONE_TIME and document. 2023-10-17 11:15:56 -04:00
Alex Hart 073e138ab2 Trim IBAN input before validating value. 2023-10-17 11:15:56 -04:00
Alex Hart 5aec4b4571 Remove alpha from pending badge states. 2023-10-17 11:15:56 -04:00
Alex Hart f9cd3decb1 Fix several issues with proper pending state routing. 2023-10-17 11:15:56 -04:00
Alex Hart 627c47b155 Implement donations one-time pending state. 2023-10-17 11:15:56 -04:00
Greyson Parrelli 57135ea2c6 Add more logging to forwarding bottom sheet. 2023-10-17 11:15:56 -04:00
Greyson Parrelli 609e9fcdb0 Remove all unused KBS/SVR1 code. 2023-10-17 11:15:56 -04:00
Cody Henthorne 5b0e71b680 Fix dialog dismiss crash in debuglog prompt. 2023-10-17 11:15:56 -04:00
Cody Henthorne 9c2d478797 Skip sends to users with prekey failures. 2023-10-17 11:15:56 -04:00
Greyson Parrelli c55fa13038 Add some new PNP merge tests. 2023-10-17 11:15:56 -04:00
Alex Hart 27b9565d2f Update TextInputLayout Style and Naming. 2023-10-17 11:15:56 -04:00
Greyson Parrelli 4fe6d79fff Unify our Base64 utilities. 2023-10-17 11:15:56 -04:00
Cody Henthorne e636e38ba1 Fix NPE in contact attachment processing. 2023-10-17 11:15:56 -04:00
Alex Hart ebc6665224 Implement small screen support for BankTransferMandateFragment. 2023-10-17 11:15:56 -04:00
Alex Hart 7001cedbc7 Add lifecycle aware temporary screenshot security component. 2023-10-17 11:15:56 -04:00
Alex Hart b14209d5cf Add new styling for active subscription pref item. 2023-10-17 11:15:56 -04:00
Alex Hart 5150564fe2 Reduce donation configuration TTL to 1 hour. 2023-10-17 11:15:56 -04:00
Lakshay Bomotra b7eaa9e353 Fix issue with new group members count. 2023-10-17 11:15:56 -04:00
Greyson Parrelli c00943591d Remove PNP flag from reading some settings. 2023-10-17 11:15:56 -04:00
Cody Henthorne 1f9320200a Sync keys with linked devices. 2023-10-17 11:15:56 -04:00
Cody Henthorne 6a6b80cce2 Decrease db thrashing when starting expiration timers for messages. 2023-10-17 11:15:56 -04:00
Alex Hart 05296e3d9b Add proper text for pending sheet. 2023-10-17 11:15:56 -04:00
Alex Hart 7e68050e0a Add proper pending bank transfer urls. 2023-10-17 11:15:56 -04:00
Alex Hart ab928be1b3 Suppress checking for messages on application foreground. 2023-10-17 11:15:56 -04:00
Alex Hart 65d26d753d Disable SEPA Debit for gifts. 2023-10-17 11:15:56 -04:00
Alex Hart bf37c09ba0 Implement bank transfer completed sheet. 2023-10-17 11:15:56 -04:00
Grzegorz Bobryk 89199b81ab Upgrade eventbus to 3.3.1 2023-10-17 11:15:56 -04:00
Alex Hart 0dd17673f5 Implement bank transfer pending sheet. 2023-10-17 11:15:56 -04:00
Alex Hart c17d6c2334 Implement gateway ordering. 2023-10-17 11:15:56 -04:00
Cody Henthorne 5285dd1665 Fix NPE in account record proto parsing. 2023-10-17 11:15:56 -04:00
Alex Hart 046ce30e08 Fix SGNL schema link for call links. 2023-10-17 11:15:56 -04:00
Alex Hart 1601fa5608 Update SEPA mandate acceptance parameters. 2023-10-17 11:15:56 -04:00
Alex Hart 5f7099184d Add new credit card and bank transfer glyphs. 2023-10-17 11:15:56 -04:00
Alex Hart 8425bb4f59 Update IBAN character limit in information string. 2023-10-17 11:15:56 -04:00
Bernie Dolan e44006f531 Update MobileCoin SDK to 5.0.1 2023-10-17 11:15:56 -04:00
Alex Hart 3423e24de6 Add donation pending sheet for SEPA transfers. 2023-10-17 11:15:56 -04:00
Alex Hart 5ac363232f Implement isLongRunning wiring for receipt redemption jobs. 2023-10-17 11:15:56 -04:00
Greyson Parrelli 9cc020a2c7 Move the video lib to the proper directory. 2023-10-17 11:15:56 -04:00
Alex Hart d2240f07d8 Add privacy and accounts sheets for SEPA. 2023-10-17 11:15:56 -04:00
Greyson Parrelli 4968db750b Move libsignal-service up a directory. 2023-10-17 11:15:55 -04:00
Alex Hart 6134244244 Update radii and margins of one-time-donation selection grid. 2023-10-17 11:15:55 -04:00
Cody Henthorne 4559ca9f2b Bump version to 6.36.5 2023-10-17 11:12:12 -04:00
Cody Henthorne 9a38920cb8 Updated baseline profile. 2023-10-17 11:02:21 -04:00
Cody Henthorne 2b771931e6 Update translations and other static files. 2023-10-17 10:57:35 -04:00
Cody Henthorne d72e003f8c Fix delete account bug. 2023-10-17 10:33:30 -04:00
Cody Henthorne 097988e046 Bump version to 6.36.4 2023-10-16 12:58:21 -04:00
Cody Henthorne 4d15bc7ea0 Updated baseline profile. 2023-10-16 12:43:58 -04:00
Cody Henthorne 26f49e2877 Update translations and other static files. 2023-10-16 12:39:05 -04:00
Alex Hart 10aba86e70 Remove clear of chat color in onDestroy. 2023-10-16 10:54:57 -03:00
Alex Hart 9e3d100599 Bump version to 6.36.3 2023-10-13 14:37:13 -03:00
Alex Hart a7193e321c Updated baseline profile. 2023-10-13 14:24:26 -03:00
Alex Hart fa15469696 Update translations and other static files. 2023-10-13 14:19:43 -03:00
Cody Henthorne 58b9cdf28f Fix deadlock in JobManager initialization. 2023-10-13 13:02:03 -04:00
Nicholas 8e05fe3b0c Rotate incremental MAC proto field. 2023-10-13 11:43:42 -04:00
Nicholas af063b2e9e Transfer Control View Improvements. 2023-10-13 10:03:42 -04:00
Alex Hart 5cc85cc860 Fix issue with chat colors not updating properly. 2023-10-13 10:37:09 -03:00
Nicholas Tinsley eafa1eabee Adjust transfer control view insets. 2023-10-11 16:01:55 -04:00
Nicholas Tinsley 34a1838668 Make blurred thumbnails fill the view. 2023-10-11 16:00:15 -04:00
Alex Hart df83c94180 Bump version to 6.36.2 2023-10-11 16:28:39 -03:00
Alex Hart e102b60923 Updated baseline profile. 2023-10-11 16:24:06 -03:00
Alex Hart 02900eaa6d Update translations and other static files. 2023-10-11 16:18:23 -03:00
Nicholas Tinsley 5ed4c51582 Do not check incremental MAC in Glide. 2023-10-11 16:11:30 -03:00
Nicholas Tinsley 81e928f94e Disable incremental MAC changes. 2023-10-11 14:31:06 -04:00
Alex Hart 985b569d29 Fix wacky layout while scrolling in thread. 2023-10-11 14:52:30 -03:00
Nicholas Tinsley d2d000ef16 Log device type when failing to set audio device. 2023-10-11 10:25:16 -04:00
Alex Hart 520b3a14bc Handle donation-driven 440 errors more gracefully. 2023-10-11 09:56:09 -03:00
Nicholas Tinsley 157d194cc5 Fix downloading outgoing media view. 2023-10-10 17:52:54 -04:00
Cody Henthorne 2785609481 Fix bug with dangling notification clear. 2023-10-10 12:06:15 -04:00
Nicholas Tinsley 6e5e60173b Bump version to 6.36.1 2023-10-06 19:30:50 -04:00
Nicholas Tinsley f37e938f17 Update translations and other static files. 2023-10-06 19:30:39 -04:00
Nicholas Tinsley da645acd1c Updated baseline profile. 2023-10-06 19:22:31 -04:00
Nicholas Tinsley 17205b2baf Remove vestigial relayout calls. 2023-10-06 18:24:58 -04:00
Greyson Parrelli b5ba4d3570 Fix progress text wrapping in TransferControlView. 2023-10-06 17:05:40 -04:00
Alex Hart 17b24d3c24 Add handling for no-bubble outgoing messages without wallpaper. 2023-10-06 16:13:18 -03:00
Alex Hart 044454dca2 Fix story start position when in a mixed read/unread state. 2023-10-06 10:32:47 -03:00
Nicholas Tinsley 88bff9ab6c Bump version to 6.36.0 2023-10-05 19:23:24 -04:00
Nicholas Tinsley 203fde60d6 Update translations and other static files. 2023-10-05 19:23:01 -04:00
Nicholas 82956c4149 New attachment download UI. 2023-10-05 19:13:19 -04:00
Greyson Parrelli 1f41b9e481 Include microbenchmark compilation check in qa. 2023-10-05 19:13:19 -04:00
Greyson Parrelli 945921fa9a Fix compilation of microbenchmarks. 2023-10-05 19:13:19 -04:00
Greyson Parrelli 7d5786ea93 Add a core-util-jvm module.
This is basically a location where we can put common utils that can also
be imported by libsignal-service (which is java-only, no android
dependency).
2023-10-05 19:13:19 -04:00
Cody Henthorne 6be1413d7d Fix link preview overriding edit message with media bug. 2023-10-05 19:13:19 -04:00
Cody Henthorne fd07ab10ee Fix ISE crash in compose text watcher. 2023-10-05 19:13:19 -04:00
Cody Henthorne 6232656ad4 Fix dangling notifications after clear message history. 2023-10-05 19:13:19 -04:00
Cody Henthorne 8493c7ffe5 Enable split-window support for key activites.
Fixes #13182
2023-10-05 19:13:19 -04:00
Alex Hart 15700b85cb Implement underpinnings of SEPA debit transfer support for donations. 2023-10-05 19:13:19 -04:00
Cody Henthorne 3dfd1c98ba Re-download profile avatars if they fail to load. 2023-10-04 15:00:52 -04:00
Nicholas Tinsley 9a249b0dec Make voice note playback log statement more readable. 2023-10-04 10:32:23 -04:00
Cody Henthorne b74a431ac9 Prevent incorrect state changes during vanity camera switchover. 2023-10-03 11:27:33 -04:00
Cody Henthorne 880ce18fd0 Pluralize chat length limit custom setting. 2023-10-03 10:16:05 -04:00
Alex Hart 6279149cb8 Add SEPA API endpoints. 2023-10-03 10:00:42 -04:00
Alex Hart f5c5a34798 CallLink profile sharing via ProfileKeySendJob. 2023-10-03 10:00:42 -04:00
Alex Hart e9a616c68d Add error handling for PayPal decline codes. 2023-10-03 10:00:42 -04:00
Nicholas Tinsley f5ee7160cb Bump version to 6.35.3 2023-10-02 21:33:58 -04:00
Nicholas Tinsley cea671aab5 Update translations and other static files. 2023-10-02 21:21:18 -04:00
Nicholas da84cde6da Read first frame of backup to validate before proceeding.
Addresses #11952.
2023-10-02 20:30:39 -04:00
Cody Henthorne e9fbce4e28 Add missing GV2 state update on conversation open. 2023-10-02 14:49:52 -04:00
Alex Hart 913605a065 Fix state snapshot in LinkPreviewViewModelV2. 2023-10-02 13:59:07 -03:00
Alex Hart 4bf49df6fa Fix horizontal ReactionView margins. 2023-10-02 13:53:24 -03:00
Cody Henthorne 91a9d6c68f Fix NPE in group access control. 2023-10-02 12:01:56 -04:00
Cody Henthorne a477c3c4d9 Fix incorrect assertion for syncing pni only contacts. 2023-10-02 12:00:15 -04:00
Cody Henthorne 0cdd56e0ac Bump version to 6.35.2 2023-09-30 09:24:52 -04:00
Cody Henthorne abefb894cc Updated baseline profile. 2023-09-30 09:23:24 -04:00
Cody Henthorne 97d482c1ad Update translations and other static files. 2023-09-30 09:20:50 -04:00
Cody Henthorne d3e9303d6d Fix incorrect data migration. 2023-09-30 09:15:37 -04:00
Cody Henthorne df7bb13752 Bump version to 6.35.1 2023-09-29 20:27:48 -04:00
Cody Henthorne d28f6f5922 Updated baseline profile. 2023-09-29 20:23:45 -04:00
Cody Henthorne c90ad7c1e2 Fix bugs around PNI only contacts and storage service. 2023-09-29 20:15:34 -04:00
Alex Hart 7fbdcb8a88 Add several SavedStateHandle delegates. 2023-09-29 11:28:36 -03:00
Alex Hart d46daed49a Add SavedStateHandle support to LinkPreviewViewModelV2. 2023-09-29 09:25:17 -03:00
Nicholas Tinsley f18a03ee6d Add incremental mac chunk size to attachment pointer. 2023-09-28 21:12:05 -04:00
Cody Henthorne 1d052e7c1b Bump version to 6.35.0 2023-09-28 20:10:05 -04:00
Cody Henthorne 2611165f21 Updated baseline profile. 2023-09-28 20:05:46 -04:00
Cody Henthorne f059aa7407 Update translations and other static files. 2023-09-28 20:01:19 -04:00
Jim Gustafson ac27df1f0e Update to RingRTC v2.33.0 2023-09-28 19:57:33 -04:00
Alex Hart 76b28593ea Suppress dialog if error is regarding user cancellation. 2023-09-28 19:57:33 -04:00
Alex Hart 0940c88c20 CallLink NullMessage sending. 2023-09-28 19:57:33 -04:00
Clark Chen c3408040fc Skip optimized notifications check if flag disabled. 2023-09-28 19:57:33 -04:00
Alex Hart d2ffc11749 Allow MediaStore permission check to function with only images enabled. 2023-09-28 19:57:32 -04:00
Alex Hart 4d640ec467 Donation CreatePaymentMethod 409 error recovery. 2023-09-28 19:57:32 -04:00
Alex Hart c409d49f14 Hide call link warning card when entering call. 2023-09-28 19:57:32 -04:00
Nicholas Tinsley 2c0dbf1062 Condense BubbleUtil debug info to a single line. 2023-09-28 19:57:32 -04:00
Alex Hart 25f0208e61 Upgrade AndroidX Core to 1.12.0 2023-09-28 19:57:32 -04:00
Nicholas d063cfe36a Upgrade libsignal to 0.32.1 2023-09-28 19:57:32 -04:00
Cody Henthorne 5c089e1d77 Fix crash on poorly formatted group change update. 2023-09-28 19:44:46 -04:00
Nicholas Tinsley 867006d29c Increase Bubble diagnostic logging. 2023-09-28 19:44:46 -04:00
Greyson Parrelli 6a974c48ef Add a log viewer to Spinner.
This is more of a proof-of-concept/demo for using a websocket with
Spinner. Gives an example of how we could push live updates to the
webapp.

Also, the logger is actually nice. Guaranteed to never get cluttered
with system logs. Looks basically identical to our other log viewers.
Filtering is basic but fast. And we could build much better tooling on
top of this.
2023-09-28 19:44:46 -04:00
Jim Gustafson c314918c6b Update to RingRTC v2.32.0 2023-09-28 19:44:46 -04:00
Greyson Parrelli e2e2a076c7 Fix error log in Spinner console. 2023-09-28 19:44:46 -04:00
Greyson Parrelli 8ee12b9f26 Fix compile issue with some sample apps. 2023-09-28 19:44:46 -04:00
Cody Henthorne 7377293f81 Bump version to 6.34.5 2023-09-28 19:43:18 -04:00
Cody Henthorne 29ae49b5f1 Updated baseline profile. 2023-09-28 19:40:19 -04:00
Cody Henthorne 195d967b3f Update translations and other static files. 2023-09-28 19:37:34 -04:00
Cody Henthorne eac74bf9c1 Fix NPE crash in group permissions screen. 2023-09-28 19:32:40 -04:00
Alex Hart 9f2dbf7b6c Fix context usage in ConversationDataSource. 2023-09-28 19:24:12 -04:00
Cody Henthorne 9e836ba586 Bump version to 6.34.4 2023-09-26 20:03:14 -04:00
Cody Henthorne cc6dc1b3a2 Updated baseline profile. 2023-09-26 19:57:24 -04:00
Cody Henthorne f49da2c9bf Update translations and other static files. 2023-09-26 19:52:35 -04:00
Cody Henthorne 96c1077238 Revert "Add more logging to forwarding bottom sheet."
This reverts commit 3fc26733ad.
2023-09-26 19:43:48 -04:00
Cody Henthorne 8d72b27e1d Fix gboard gif playback. 2023-09-26 19:41:49 -04:00
Cody Henthorne 0ea0d139dd Fix odd scaling issues during decoding. 2023-09-26 19:34:55 -04:00
Nicholas Tinsley b81ff4d672 Increase prominence of network errors during re-registration. 2023-09-26 10:43:57 -04:00
Alex Hart f380ac5e43 Fix username search issue for non-alpha-underscore characters. 2023-09-26 10:05:38 -03:00
Alex Hart 962d42292d Remove deprecated API endpoint call for setting the default payment method. 2023-09-26 09:11:42 -03:00
Alex Hart 15df15556d Always display footer underneath if text has mixed directions. 2023-09-26 09:08:22 -03:00
Cody Henthorne 6b29841cc8 Bump version to 6.34.3 2023-09-25 21:42:21 -04:00
Cody Henthorne 4f4c1a9bb8 Updated baseline profile. 2023-09-25 21:36:27 -04:00
Cody Henthorne 5f7630b906 Update translations and other static files. 2023-09-25 21:31:44 -04:00
Cody Henthorne 8a831889f9 Decode using aspect ratio preserving scaling. 2023-09-25 21:25:00 -04:00
Nicholas Tinsley bce133ac28 Add more logging around missing RecipientId. 2023-09-25 21:25:00 -04:00
Alex Hart f5215d715a Utilize timestamp from table instead of relying on getCurrentMillis. 2023-09-25 21:25:00 -04:00
Alex Hart fde0f3bba1 Fix call log clear history error handling. 2023-09-25 21:25:00 -04:00
Alex Hart e7b18bd3a2 Tie CallLogViewModel lifecycle to the activity. 2023-09-25 21:25:00 -04:00
Alex Hart e5e86e639a Update getAdapterPosition to utilize the binding adapter position instead of absolute. 2023-09-25 21:25:00 -04:00
Alex Hart f44b44a354 Fix timestamp update on conversation re-entry from background. 2023-09-25 21:25:00 -04:00
Alex Hart b3399b5242 Fix RTL display of CIV2 bubble corners. 2023-09-25 21:25:00 -04:00
Alex Hart 7d4ebd9d3b Fix strange padding on some CIV2 items. 2023-09-25 10:16:03 -03:00
Cody Henthorne 3bb2131375 Fix crash when prompting for debuglogs. 2023-09-24 21:29:01 -04:00
Cody Henthorne d7314ec2a4 Fix NPE in group change message processing. 2023-09-24 21:20:54 -04:00
Cody Henthorne cc6c724ee8 Fix crash if pixels are null. 2023-09-24 20:57:24 -04:00
Cody Henthorne d3b0559b72 Fix link preview processing when missing a date. 2023-09-23 23:06:50 -04:00
Cody Henthorne 1e24caec31 Fix SignalServiceGroupV2 proto parsing. 2023-09-23 23:00:36 -04:00
Cody Henthorne 65cdc143da Fix incorrect handling of hangup message. 2023-09-23 22:40:46 -04:00
Cody Henthorne 5d612f020c Bump version to 6.34.2 2023-09-22 16:19:23 -04:00
Cody Henthorne ccef2cc178 Use https for submodule. 2023-09-22 16:16:21 -04:00
Alex Hart 9337160583 Bump version to 6.34.1 2023-09-22 17:02:41 -03:00
Alex Hart bf9d570c3d Updated baseline profile. 2023-09-22 17:02:20 -03:00
Alex Hart 306b0096be Update translations and other static files. 2023-09-22 16:56:56 -03:00
Alex Hart 45583ea469 Revert "Instant Video Playback UI"
This reverts commit f8283acfae.
2023-09-22 16:50:20 -03:00
Cody Henthorne 15c6c372ba Fix quoted mentioned showing in regular message bug. 2023-09-22 16:50:20 -03:00
Ehren Kret 770a89507a Fix background color on internal search fragment. 2023-09-22 16:50:20 -03:00
Alex Hart ddc9aa7506 Remove unused padding in ContactSelectionListFragment. 2023-09-22 16:50:20 -03:00
Cody Henthorne a7d9fd19d9 Enable WebP decoding in Signal using libwebp v1.3.2
Co-authored-by: Greyson Parrelli <greyson@signal.org>
Co-authored-by: Greyson Parrelli <greyson@pop-os.localdomain>
2023-09-22 16:50:20 -03:00
Alex Hart 091f7c49ab Fix issue where story contact list would reset when selecting contacts.
Fixes #13174
2023-09-22 16:50:20 -03:00
Cody Henthorne b443f59078 Rebuild wire-handler-1.0.0.jar without extra logging. 2023-09-22 16:50:20 -03:00
Clark Chen 27bcf92e9b Update remote delete send threshold. 2023-09-22 16:50:20 -03:00
Alex Hart 31100c3d82 Fix bug causing WifiDirect transfers to not initialize.
Fixes #13173
2023-09-22 16:50:20 -03:00
Alex Hart 119da2e76e Fix crash in welcome fragment click handling. 2023-09-22 16:50:20 -03:00
Greyson Parrelli 588a6cf74f Remove PNP flag checks in some areas. 2023-09-22 16:50:20 -03:00
Greyson Parrelli eb6394eb6a Fix SSE event bug and make the assertion guarded by a separate flag. 2023-09-21 15:56:03 -04:00
Alex Hart 76de183ec2 Bump version to 6.34.0 2023-09-21 16:29:16 -03:00
Alex Hart ba31ceb3e7 Updated baseline profile. 2023-09-21 16:24:23 -03:00
Alex Hart e94e0f8a6b Update translations and other static files. 2023-09-21 16:19:36 -03:00
Nicholas f8283acfae Instant Video Playback UI 2023-09-21 15:12:11 -04:00
Alex Hart f8cb26ca74 Replace TypingIndicatorItemDecoration with TypingIndicatorAdapter. 2023-09-21 14:05:49 -03:00
Alex Hart 190b9da6c7 Fix icon alignment in CIV2 footer. 2023-09-21 13:59:52 -03:00
Alex Hart f84b46148c Show delivery status in forced footers for CIV2. 2023-09-21 13:59:52 -03:00
Alex Hart 12db8b5ee1 Fix swipe to reply positioning in CIV2. 2023-09-21 13:59:52 -03:00
Alex Hart 05b5078aa9 Hide footer end pad in CIV2 non-end items. 2023-09-21 13:59:52 -03:00
Alex Hart 85b7ee85f3 Display date in forced footer for CIV2. 2023-09-21 13:59:52 -03:00
Alex Hart 326b728d4b Always show expiration timer if there is one. 2023-09-21 13:59:52 -03:00
Alex Hart 2e45e131b1 Fix tinting of CIV2 expiration icon. 2023-09-21 13:59:52 -03:00
Alex Hart 1aa95c057b Fix edit message label. 2023-09-21 13:59:52 -03:00
Alex Hart 6de7a849b3 Increment CIV2 feature flag. 2023-09-21 13:59:52 -03:00
Nicholas 268091b10e Close media preview upon remote delete. 2023-09-21 13:59:52 -03:00
Alex Hart 3920c85ab7 Increment edit message feature flag. 2023-09-21 13:59:52 -03:00
Alex Hart 524565f0bb Add animations for add name fragment in call links. 2023-09-21 13:59:52 -03:00
Nicholas Tinsley 69c1c856d9 Prevent crash from toolbar subtitle in call view. 2023-09-21 13:59:52 -03:00
Nicholas Tinsley dd62d92ffb Don't stop playback on seek. 2023-09-21 13:59:52 -03:00
Nicholas Tinsley f7e89d75a4 Deduplicate audio devices by name. 2023-09-21 13:59:52 -03:00
Nicholas Tinsley 023f31eadd Set recipients name in safety number verification screen.
Addresses #13171.
2023-09-21 13:59:52 -03:00
Alex Hart da8df5beac Avoid triggering requestLayout during measure pass for setBubbleWidth. 2023-09-21 13:59:52 -03:00
Alex Hart f3a8825cb9 Revert "Add proper tinting to delivery status icon."
This reverts commit c4ac63ea7a89e44f478b0321901eaf43e2745502.
2023-09-21 13:59:52 -03:00
Cody Henthorne 835fd47482 Fix crashes related to activity starts. 2023-09-21 13:59:52 -03:00
Cody Henthorne efbd5cab85 Convert SignalService, Database, Group, Payment, and other remaining protos to wire. 2023-09-21 13:59:52 -03:00
Alex Hart a6b7d0bcc5 Set outgoing download tint to onCustom. 2023-09-21 13:59:52 -03:00
Alex Hart e06126d889 Fix pulse on quote press. 2023-09-21 13:59:51 -03:00
Alex Hart 4bf8e2c488 Fix auto-update timestamps. 2023-09-21 13:59:51 -03:00
Alex Hart 1c55ad21a3 Add background to group sender name in CIV2. 2023-09-21 13:59:51 -03:00
Alex Hart 3a601e1e65 Rename binding fields for CIV2. 2023-09-21 13:59:51 -03:00
Alex Hart c953003c2f Fix footer background sizing. 2023-09-21 13:59:51 -03:00
Alex Hart 18de51a531 Add proper tinting to delivery status icon. 2023-09-21 13:59:51 -03:00
Alex Hart ab6d3b5e8d Set bubble width in onMeasure. 2023-09-21 13:59:51 -03:00
Alex Hart 151980c6de Bump version to 6.33.3 2023-09-21 13:51:58 -03:00
Alex Hart 375527b765 Updated baseline profile. 2023-09-21 13:42:50 -03:00
Alex Hart 2978e567d4 Update translations and other static files. 2023-09-21 13:37:48 -03:00
Alex Hart 8ad50ab61c Check for database initialisation in AvatarProvider#openFile. 2023-09-21 13:13:52 -03:00
Cody Henthorne 2145ded2f2 Improve network reliability. 2023-09-21 12:10:27 -04:00
Clark Chen 29c4d9f4d6 Bump version to 6.33.2 2023-09-18 14:09:36 -04:00
Clark Chen c7de3d299a Update translations and other static files. 2023-09-18 13:57:13 -04:00
Greyson Parrelli 8bad476315 Fix backoff interval scheduling for jobs. 2023-09-18 12:06:42 -04:00
Clark Chen bc8eb44a53 Bump version to 6.33.1 2023-09-15 15:15:43 -04:00
Clark Chen f98e22cb76 Update translations and other static files. 2023-09-15 15:03:38 -04:00
Cody Henthorne 5b6326e462 Fix thread body ellipsizing in Conversation List. 2023-09-15 14:20:22 -04:00
Greyson Parrelli 342f249fab Fix possible crash during JobDatabase upgrade.
This seems to be a SQLite/SQLCipher caching issue.

Fixes #13172
2023-09-14 17:02:52 -04:00
Greyson Parrelli 09ba6d834a Ensure signed prekeys are rotated even if someone turns their clock back. 2023-09-14 13:20:55 -04:00
Alex Hart 61654f815d Bump version to 6.33.0 2023-09-13 14:25:11 -03:00
Alex Hart bf450766b2 Updated baseline profile. 2023-09-13 13:40:12 -03:00
Alex Hart 2f813f3d91 Update translations and other static files. 2023-09-13 13:35:45 -03:00
Clark 51e46db42d Fail websocket drain if keepalive token is removed. 2023-09-13 13:28:43 -03:00
Cody Henthorne 11e0dd18d3 Remove use of legacy hangup in sending flow. 2023-09-13 13:28:43 -03:00
Nicholas Tinsley ff5b024074 Fix crash when closing app during voice memo. 2023-09-13 13:28:43 -03:00
Nicholas 2f53c1a860 Display error when initial attachment selection is too large. 2023-09-13 13:28:43 -03:00
Alex Hart 53b1544b58 Fix bad feeling on-back behavior for in-conversation search. 2023-09-13 13:28:43 -03:00
Alex Hart 846fc9008c Fix issue on lower API devices with insets.
Fixes #13156
2023-09-13 13:28:43 -03:00
Alex Hart cf7455c661 Do not display clear filter when no filter is applied in call log. 2023-09-12 10:58:39 -03:00
Alex Hart ea52bbea42 Fix footer padding for very narrow bodies. 2023-09-12 10:48:00 -03:00
Alex Hart 712c41d927 Remove busy log line from call link processing. 2023-09-12 10:31:44 -03:00
Greyson Parrelli c33da4a5ae Do not block CDS if PIN creation failed. 2023-09-12 09:28:13 -04:00
Alex Hart 10aecb9390 Order call links in reverse chron on calls tab. 2023-09-12 10:16:49 -03:00
Alex Hart a1eafe311e Set correct color on join button in SignalCallRow. 2023-09-12 10:13:46 -03:00
Alex Hart df416be43e Rotate CIV2 flag. 2023-09-12 10:01:10 -03:00
Alex Hart 08035bf8a5 Rotate edit message flag. 2023-09-12 10:00:25 -03:00
Alex Hart 05a990e228 Add CIV2 callback for clicking an edited message footer. 2023-09-12 10:00:02 -03:00
Nicholas Tinsley 4d7d1699f9 Simplify VoiceNote playback logic. 2023-09-11 17:12:26 -04:00
Alex Hart 92b0ebb6f6 Call Link single-user request sheet. 2023-09-11 17:07:12 -03:00
Greyson Parrelli e41accf52d Fix bug where name wouldn't refresh when getting a message request. 2023-09-11 17:07:12 -03:00
Greyson Parrelli 1cca60fa53 Include decryption time in success log. 2023-09-11 17:07:12 -03:00
Alex Hart 69f489ffc5 Fix reactions position when parent isn't re-laid-out. 2023-09-11 17:07:12 -03:00
Alex Hart 903e305519 Drop verbose logs from ConversationItemAnimator. 2023-09-11 17:07:12 -03:00
Cody Henthorne 9ed3e8befb Fix infinite spinner on scheduled link preview bug. 2023-09-11 17:07:12 -03:00
Greyson Parrelli cd38c99f7e Reduce websocket timeout if we have no network. 2023-09-11 17:07:12 -03:00
Greyson Parrelli 3fc26733ad Add more logging to forwarding bottom sheet. 2023-09-11 17:07:12 -03:00
Greyson Parrelli e24134ff6f Reduce AvatarProvider logging. 2023-09-11 17:07:12 -03:00
Greyson Parrelli 901063f4c9 Improve third-party license display. 2023-09-11 17:07:12 -03:00
Cody Henthorne be8742f69e Add promote PNI change profile keys to change processing. 2023-09-11 17:07:12 -03:00
Alex Hart dbd6b4bd52 Update recipient after related call link has a state change. 2023-09-11 17:07:12 -03:00
Alex Hart 8a39e8094c Force CallLinkInfoSheet to always be dark mode. 2023-09-11 17:07:12 -03:00
Alex Hart a154a6cce5 Add 'edit' state for call name button. 2023-09-11 17:07:12 -03:00
Alex Hart 052ec14a6b Rotate ad-hoc calling flag. 2023-09-11 17:07:12 -03:00
Alex Hart fa9034d57b Add logic for handling disconnect reason while connected to call link. 2023-09-11 17:07:12 -03:00
Alex Hart 266adf788c Add support for received call link previews. 2023-09-11 17:07:12 -03:00
Greyson Parrelli b19aedd17c Upgrade CameraX to 1.3.0-rc01 2023-09-11 17:07:12 -03:00
Greyson Parrelli f959543c19 Add UI for prompting about crashes. 2023-09-11 17:07:11 -03:00
Alex Hart 0a6c3baf24 Bump version to 6.32.4 2023-09-11 17:03:34 -03:00
Alex Hart 5a33c1eed6 Updated baseline profile. 2023-09-11 16:59:24 -03:00
Alex Hart ce1196e17a Updated language translations. 2023-09-11 16:56:56 -03:00
Nicholas Tinsley a9fd5a3162 Fix DB query for incremental media. 2023-09-11 14:56:49 -04:00
Alex Hart 18b33a7776 Ensure lower api levels do not try to use Uri based IconCompat. 2023-09-11 15:27:58 -03:00
Cody Henthorne b72fe0d7a2 Fix unread mention indicator showing incorrectly bug. 2023-09-11 13:53:42 -04:00
Nicholas Tinsley 551e5a0a25 Bump version to 6.32.3 2023-09-09 13:08:03 -04:00
Nicholas Tinsley 92d4a580c1 Updated language translations. 2023-09-09 13:07:48 -04:00
Greyson Parrelli b367701a96 Fix bug where default reactions were dropped in AccountRecord. 2023-09-08 19:42:04 -04:00
Nicholas Tinsley 8595863afe Bump version to 6.32.2 2023-09-08 17:54:55 -04:00
Nicholas Tinsley 21492ed88e Updated language translations. 2023-09-08 17:54:36 -04:00
Nicholas Tinsley 4dc14ab7f9 Fix translation tool postprocessing bug. 2023-09-08 17:50:50 -04:00
Nicholas Tinsley 5caf3409db Match incremental MAC calculation. 2023-09-08 17:50:50 -04:00
Greyson Parrelli 1565c32162 Fix crash when opening license screen. 2023-09-07 13:51:55 -04:00
Nicholas Tinsley 45edb4e5da Bump version to 6.32.1 2023-09-07 11:06:31 -04:00
Nicholas Tinsley 5bf1c4f433 Updated baseline profile. 2023-09-07 11:04:46 -04:00
Nicholas Tinsley 3cc692d3fb Rotate the edit message feature flag. 2023-09-07 10:28:54 -04:00
Alex Hart e42b2490f0 Rotate flag for civ2-text-only views. 2023-09-07 11:27:34 -03:00
Alex Hart 454b1f69ed Suppress LayoutTransition during scroll events. 2023-09-07 11:26:45 -03:00
Greyson Parrelli b410756dfd Remove dashes from help option. 2023-09-07 10:17:09 -04:00
Nicholas 1458919549 Voice note playlist improvements. 2023-09-07 10:02:16 -04:00
Alex Hart 48ae8c2465 Utilize Bitmap shortcut on API29 and under. 2023-09-07 10:42:39 -03:00
Alex Hart 0a78bcb374 Remove payments beta tag. 2023-09-06 16:27:51 -04:00
Greyson Parrelli 61cdb48273 Fix issue where notification settings were slow to open. 2023-09-06 16:23:14 -04:00
Nicholas Tinsley b3350b22b6 Remove extraneous "Learn more" from Payments screen. 2023-09-06 15:12:42 -04:00
Nicholas Tinsley d35d22c7d8 Fix voice note playback for single voice notes. 2023-09-06 11:53:41 -04:00
Alex Hart 24cd11152b Prevent several re-layout calls in ConversationItem. 2023-09-06 11:18:36 -03:00
Nicholas Tinsley d21254ac02 Bump version to 6.32.0 2023-09-06 10:15:12 -04:00
Nicholas Tinsley 70f08c806a Updated baseline profile. 2023-09-06 10:08:51 -04:00
Nicholas Tinsley e7c3fb02e8 Updated language translations. 2023-09-06 09:57:00 -04:00
Alex Hart 3d3cf1d76e Add logging for slide enqueue and move dropped animation message to verbose. 2023-09-06 09:34:49 -04:00
Nicholas 2bf385fe38 Upgrade libsignal to 0.32.0 2023-09-06 09:34:49 -04:00
Nicholas 7ba595be55 Ignore Bluetooth devices with Watch in their product name.
Addresses #13141
2023-09-06 09:34:49 -04:00
Alex Hart c45e79c588 Split reaction view updates to separate width from adding views. 2023-09-06 09:34:49 -04:00
Alex Hart f37568b050 Stopgap for reaction display in conversation item v2. 2023-09-06 09:34:49 -04:00
Nicholas Tinsley b5afc1cd1c Fix AttachmentCipherTest 2023-09-06 09:34:49 -04:00
Alex Hart e9777ccfc6 Fix scroll button when only one giant message is displayed. 2023-09-06 09:34:49 -04:00
Alex Hart 898404fc65 Fix poor spacing of footer in short group text messages. 2023-09-06 09:34:49 -04:00
Alex Hart 131212b158 Fix improper bubble spacing caused by swipe to reply icon. 2023-09-06 09:34:49 -04:00
Greyson Parrelli 3f1d3149e9 Attempt to open db as read-write during error recovery.
Relates to #13034
2023-09-06 09:34:49 -04:00
Nicholas bfc8b199b6 Hopefully prevent VoiceNotePlaybackService startup crash.
Addresses #13140
2023-09-06 09:34:49 -04:00
Alex Hart 6d4b487428 Update shortcut drawable to use content id. 2023-09-06 09:34:49 -04:00
Cody Henthorne 9337201ffb Prevent okhttp from auto-retrying attachment uploads. 2023-09-06 09:34:49 -04:00
Greyson Parrelli 494b2c6786 Add an index specifically for improving thread count perf. 2023-09-06 09:34:49 -04:00
Alex Hart bc1c8032c1 Add support for shade and arbitrary overlay drawables to CIV2 Media items. 2023-09-06 09:34:49 -04:00
Alex Hart 21b0a4d370 Fix UriChatWallpaper loading issue where wrong thread was used for setting the imageView resource. 2023-09-06 09:34:49 -04:00
Alex Hart 133effccfc Move delegate creation to a lazy field. 2023-09-06 09:34:49 -04:00
Cody Henthorne 62b4ebc4a9 Fix mention excessive haptic feedback bug. 2023-09-06 09:34:49 -04:00
Cody Henthorne 12941ea19e Fix attachment editor and schedule message bar UI overlap bug. 2023-09-06 09:34:49 -04:00
Alex Hart f94bd706a4 Fix sender name color. 2023-09-06 09:34:48 -04:00
Greyson Parrelli 3cbbc29c00 Rotate the edit message feature flag. 2023-09-06 09:34:48 -04:00
Cody Henthorne 0827c18eeb Update edit message awareness bottom sheet copy. 2023-09-06 09:34:48 -04:00
Cody Henthorne 6c4ebc9f58 Fix incorrect type value being used for unknown storage records. 2023-09-06 09:34:48 -04:00
Alex Hart 1f2bfe8245 Replace internal setting for CIV2 TextOnly with a FeatureFlag. 2023-09-06 09:34:48 -04:00
Jim Gustafson 305d7485c1 Update to RingRTC v2.31.2 2023-09-06 09:34:48 -04:00
Alex Hart 4ded05bbd1 Implement groundwork for proper ConversationItemV2 payload processing. 2023-09-06 09:34:48 -04:00
Alex Hart 540a2b1876 ConversationItemV2 Quote support and various fixes. 2023-09-06 09:34:48 -04:00
Cody Henthorne 153d3ad388 Fix story group replies layout in RTL. 2023-09-06 09:34:48 -04:00
Alex Hart a3e36d2453 Update target API to 33 2023-09-06 09:34:48 -04:00
Nicholas Tinsley b9449a798b Increase Glide exception coverage. 2023-09-06 09:34:48 -04:00
Greyson Parrelli 9da149a868 Convert DateUtils to kotlin, improve perf with caching. 2023-09-06 09:34:48 -04:00
Cody Henthorne d505c00403 Add CDN3 upload and download support. 2023-09-06 09:34:48 -04:00
Nicholas Tinsley 4d7a0a361f Dismiss Voice Note player notification upon completion. 2023-09-06 09:34:48 -04:00
Greyson Parrelli e08e02ae80 Update Stopwatch to log fractional milliseconds. 2023-09-06 09:34:48 -04:00
Greyson Parrelli 95c6f569d6 Fetch data in ConversationDataSource in parallel. 2023-09-06 09:34:48 -04:00
Nicholas Tinsley e46759f436 Update view-once Toast string. 2023-09-06 09:34:48 -04:00
Greyson Parrelli b42dd5289b Remove unnecessary context args in slide creation. 2023-09-06 09:34:48 -04:00
Greyson Parrelli a911a007d2 Change job scheduling to be relative rather than absolute. 2023-09-06 09:34:48 -04:00
Nicholas 64babe2e42 Streamable Video. 2023-09-06 09:34:48 -04:00
Greyson Parrelli 099c94c215 Fix handling of some PNI initial contact flows. 2023-08-31 14:33:54 -04:00
Alex Hart 75b81a0fd2 Add the groundwork for the ConversationItemV2 Media item. 2023-08-31 14:33:54 -04:00
Greyson Parrelli f9ab5d4013 Fix SVR2 typo. 2023-08-31 14:33:53 -04:00
Cody Henthorne b83080e2d7 Fix payments spinning forever. 2023-08-31 14:33:53 -04:00
Cody Henthorne 6a21106347 Convert StorageService protos to wire. 2023-08-31 14:33:53 -04:00
Greyson Parrelli 9a7d8c858d Convert JobDatabase to Kotlin. 2023-08-31 14:33:53 -04:00
Greyson Parrelli 8339c0d8de Convert JobManager tests to kotlin. 2023-08-29 09:33:45 -04:00
Greyson Parrelli 2b1136ea02 Fix loading states for username editing. 2023-08-29 09:33:45 -04:00
Greyson Parrelli 84b4d69913 Fix error display when entering invalid username characters.
Also convert UsernameEditViewModel to kotlin.
2023-08-29 09:33:45 -04:00
Alex Hart 3fe9ce378e Mock out glideRequests dependency for instrumented test. 2023-08-29 09:33:45 -04:00
Greyson Parrelli 57b9571d86 Don't store blank usernames. 2023-08-29 09:33:45 -04:00
Alex Hart ae3071d318 Fix bottom constraint of sender photo in civ2. 2023-08-29 09:33:45 -04:00
Greyson Parrelli 8a93814bac Update to the new username link spec. 2023-08-29 09:33:45 -04:00
Alex Hart a6dd4345ab Rewrite quote view using constraint layout and stubs. 2023-08-29 09:33:45 -04:00
Greyson Parrelli c71456444f Bump version to 6.31.2 2023-08-28 18:58:47 -04:00
Greyson Parrelli b916605a24 Updated language translations. 2023-08-28 18:58:23 -04:00
Alex Hart 553da1e7e8 Speed up AvatarProvider. 2023-08-28 18:51:43 -04:00
Cody Henthorne 847651ead7 Revert "Update to RingRTC v2.31.1"
This reverts commit 4ab82c99a8.
2023-08-28 11:55:41 -04:00
Alex Hart f977f261d6 Utilize iconless person objects until we can fix AvatarProvider. 2023-08-28 12:52:45 -03:00
Greyson Parrelli 3fa9e89e8e Fix typo when reading feature flag. 2023-08-28 10:01:58 -04:00
Cody Henthorne 0662959e1d Bump version to 6.31.1 2023-08-25 16:34:48 -04:00
Cody Henthorne e5e03f9693 Updated baseline profile. 2023-08-25 16:27:39 -04:00
Cody Henthorne 4203900365 Updated language translations. 2023-08-25 16:24:57 -04:00
Greyson Parrelli eb7794ba47 Fix flag for battery saver prompt, enable for internal users. 2023-08-25 11:22:29 -04:00
Alex Hart 9626f33768 Fix viewer count in story viewer. 2023-08-25 11:57:28 -03:00
Cody Henthorne cfc0ace41e Bump version to 6.31.0 2023-08-24 16:05:00 -04:00
Cody Henthorne ce2947c756 Updated baseline profile. 2023-08-24 15:58:30 -04:00
Cody Henthorne 87fc10ad24 Updated language translations. 2023-08-24 15:34:07 -04:00
Greyson Parrelli cae71559a0 Updated libphonenumber to 8.13.19 2023-08-24 15:11:54 -04:00
Cody Henthorne 3cf7920a22 Fix various media send failed to compress bugs. 2023-08-24 15:11:54 -04:00
Cody Henthorne fba9b46fe9 Convert Provisioning, ResumeableUploads, and StickerResources protos to wire. 2023-08-24 15:11:54 -04:00
Alex Hart 611f074a9d Add main thread assertion for setting call status. 2023-08-24 15:11:54 -04:00
Cody Henthorne 7909703f4c Convert CDSI, KBS, and WebSocket protos to wire. 2023-08-24 15:11:54 -04:00
Cody Henthorne dcbf4b8faf Prevent empty message sends with enter-key sends enabled. 2023-08-24 15:11:54 -04:00
Cody Henthorne c5edcf47bd Rotate edit message flag. 2023-08-24 15:11:54 -04:00
Alex Hart 02e6b89fdd Fix message clustering for CIV2. 2023-08-24 15:11:54 -04:00
Alex Hart c4109a19d6 Extract V2TextOnlyViewHolder to its own file. 2023-08-24 15:11:54 -04:00
Alex Hart 630d9492cd Add proper context menu positioning for CIV2. 2023-08-24 15:11:54 -04:00
Alex Hart b762d95622 Fix issue where StoryPostFragment tries to post updates after fragment is detached from Context. 2023-08-24 15:11:54 -04:00
Alex Hart 3738997832 Add proper click handling support to ConversationItem V2. 2023-08-24 15:11:54 -04:00
Alex Hart 21c70039f4 Upgrade Compose BOM to 23.08.00 2023-08-23 09:29:48 -04:00
Cody Henthorne 23e3385290 Remove unused resources. 2023-08-23 09:29:48 -04:00
Jim Gustafson 4ab82c99a8 Update to RingRTC v2.31.1 2023-08-23 09:29:48 -04:00
Alex Hart f4df37da23 Compute ConversationItem dates in the background. 2023-08-23 09:29:48 -04:00
Alex Hart 4494d8652d Add several performance improvements to ConversationItemV2. 2023-08-23 09:29:48 -04:00
Alex Hart 32ae4393e2 Fix issue with CIV2 where avatars would not load. 2023-08-23 09:29:48 -04:00
Alex Hart ea5c3a7c5e Update compileSdk to 34. 2023-08-23 09:29:48 -04:00
Cody Henthorne f9d9af4fe9 Bump version to 6.30.4 2023-08-22 20:30:44 -04:00
Cody Henthorne 098ef61b5d Updated baseline profile. 2023-08-22 20:24:11 -04:00
Cody Henthorne e926f56f6b Updated language translations. 2023-08-22 20:21:02 -04:00
Cody Henthorne 9b1da3cfa0 Revert "Do not manually handle orientation changes in ConversationActivity."
This reverts commit 8b2a535f19.
2023-08-22 19:49:39 -04:00
Alex Hart 1fbcd9b362 Fix possible threading issue causing issues in group calls. 2023-08-22 15:15:48 -03:00
Alex Hart 38940e0111 Hopeful fix for remote notification crash. 2023-08-22 15:02:16 -03:00
Cody Henthorne 4fa3570d1e Bump version to 6.30.3 2023-08-22 11:49:43 -04:00
Cody Henthorne d1c78d5062 Updated baseline profile. 2023-08-22 11:41:23 -04:00
Cody Henthorne c4862bdddf Updated language translations. 2023-08-22 11:38:36 -04:00
Nicholas Tinsley 2b8018727c Fix voice note earpiece playback. 2023-08-22 11:30:43 -04:00
Greyson Parrelli e3be279f1f Do not allow the sending of whitespace-only messages. 2023-08-22 11:30:43 -04:00
Greyson Parrelli 1e6126d5be Downgrade some logs and add a missing return. 2023-08-22 11:30:43 -04:00
Alex Hart 9a09708842 Use M3 Switch on EditProxyFragment. 2023-08-22 11:30:43 -04:00
Nicholas Tinsley e861204cb0 Additional logging around incrementally digested uploads. 2023-08-22 11:30:43 -04:00
Alex Hart afd3afcf0d Utilize M3 switch on chat color and wallpaper screen. 2023-08-22 11:30:43 -04:00
Alex Hart 5055b0c75d Fix rendering issue when opening the story info sheet too fast. 2023-08-22 11:30:43 -04:00
Alex Hart 372104cdfe Fix typing indicator rendering. 2023-08-21 14:22:08 -03:00
Cody Henthorne acb24fd265 Bump version to 6.30.2 2023-08-18 17:07:03 -04:00
Cody Henthorne 5b7420ba90 Updated baseline profile. 2023-08-18 16:51:07 -04:00
Cody Henthorne e73dbd5c15 Updated language translations. 2023-08-18 16:46:34 -04:00
Nicholas Tinsley b5f82beb46 Revert "Fix contact photo upload failure."
This reverts commit 06dc8ccbdd.
2023-08-18 16:40:37 -04:00
Nicholas Tinsley 61b97fd09b Fix MediaController connection exception. 2023-08-18 16:40:37 -04:00
Cody Henthorne 99e34860d4 Increase vertial tap space for compose text to match bubble. 2023-08-18 16:40:37 -04:00
Cody Henthorne 5d44bbe956 Fix scroll jump when reacting with keyboard open. 2023-08-18 16:40:37 -04:00
Cody Henthorne e7d0b575bb Reshow IME keyboard if it was showing prior to opening attachment keyboard. 2023-08-18 13:00:54 -04:00
Cody Henthorne 8b2a535f19 Do not manually handle orientation changes in ConversationActivity. 2023-08-18 12:34:50 -04:00
Alex Hart a242dba345 Fix crash with improper fallback size. 2023-08-18 13:24:48 -03:00
Greyson Parrelli 587cb5de16 Fix unexpected SSE's.
Fixes #13115
2023-08-18 11:07:14 -04:00
Greyson Parrelli e93c6957ac Fix crash in RecipientTable.getAllPnis() 2023-08-18 09:59:12 -04:00
Cody Henthorne f644115b54 Bump version to 6.30.1 2023-08-17 16:45:36 -04:00
Cody Henthorne 0c753d22b6 Updated baseline profile. 2023-08-17 16:36:36 -04:00
Cody Henthorne ec7f2c33e7 Updated language translations. 2023-08-17 16:31:38 -04:00
Cody Henthorne 39c1c1e371 Fix ANR-like bug when resuming MainActivity. 2023-08-17 15:02:16 -04:00
Greyson Parrelli 74d5faf3fa Allow PNI-only contact inserts. 2023-08-17 14:51:11 -04:00
Cody Henthorne 15204a2c84 Remove SignalServiceContent. 2023-08-17 14:43:42 -04:00
Nicholas Tinsley 2397cb5428 Fix play-pause button in video player. 2023-08-17 14:34:19 -04:00
Greyson Parrelli 4b6b87d632 Make ACI's optional on ContactRecords. 2023-08-17 14:33:18 -04:00
Alex Hart 2492b8de34 Fix AvatarProvider crash when user does not have a profile photo set. 2023-08-17 15:30:55 -03:00
Greyson Parrelli 635987a420 Add improved error logging for SSE issues. 2023-08-17 13:42:22 -04:00
Alex Hart 51602ed231 Wrap thread get/create into a transaction. 2023-08-17 14:38:45 -03:00
Alex Hart 25aab0f702 Clean up threadId -1 checks in Conversation code. 2023-08-17 14:18:32 -03:00
Greyson Parrelli 23b3c7f1fd Use a consistent SSE condition and use more breadcrums in logs. 2023-08-17 12:51:40 -04:00
Nicholas Tinsley 451ce74fa4 Safely run VoiceNoteProximityWakeLockManager cleanup. 2023-08-17 11:17:19 -04:00
Greyson Parrelli 1fd9609810 Improve logging around SSE exceptions. 2023-08-17 10:23:03 -04:00
Greyson Parrelli 29804e0a2b Add more logging to SVR2 failures. 2023-08-17 09:54:20 -04:00
Clark Chen 26aa7e8332 Bump version to 6.30.0 2023-08-16 17:49:40 -04:00
Clark Chen e4e00be119 Updated language translations. 2023-08-16 17:33:09 -04:00
Clark Chen de6b71528b Rotate edit message flag. 2023-08-16 17:06:04 -04:00
Greyson Parrelli d005ace383 Add some more getAndPossiblyMerge tests. 2023-08-16 17:06:04 -04:00
Cody Henthorne f566e10710 Drop V2 suffix from MCPv2 classes. 2023-08-16 17:06:04 -04:00
Alex Hart 18f9c6b1f0 Consolidate some constants and add kotlin target JVM version. 2023-08-16 15:29:45 -03:00
Cody Henthorne fbf4de0ec5 Remove job-based decryption support and MCPv1. 2023-08-16 14:28:14 -04:00
Nicholas Tinsley 3d94122abc Null check for current audio device. 2023-08-16 12:40:35 -04:00
Greyson Parrelli 442a66df2e Update the groups tables to use foreign keys. 2023-08-16 12:23:54 -04:00
Clark 3be5d61ced Fix wrong thread crash when revoking message while editing. 2023-08-16 10:48:51 -04:00
Greyson Parrelli f137e23b43 Split usernames into it's own feature flag for internal testing. 2023-08-16 10:46:07 -04:00
Greyson Parrelli f00178cc0d Don't show the safety number and badges sections in note-to-self settings. 2023-08-16 10:26:32 -04:00
Greyson Parrelli e33c5b055d Fix FTS searches for punctuation and emoji.
Fixes #13047
2023-08-16 10:26:32 -04:00
Greyson Parrelli f2237a385e Don't show safety number item for the release notes chat. 2023-08-16 10:26:32 -04:00
Nicholas a9c45f7e78 Video streaming sample app. 2023-08-16 10:26:32 -04:00
Nicholas 11cfe5ee82 Upgrade to AndroidX Media3. 2023-08-16 10:26:32 -04:00
Clark 4cbcee85d6 Add prompt to help troubleshoot slow notifications. 2023-08-16 10:26:32 -04:00
Alex Hart 98ec2cceb4 Add content description to DeliveryStatusView. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 8ce05c8bbe Include urgent flag in delivery latency log. 2023-08-16 10:26:32 -04:00
Greyson Parrelli a7019b2e60 Rename PushNotificationReceiveJob -> MessageFetchJob. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 0facdc0497 Fix foreground service in PushNotificationReceiveJob. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 25a7560e2e Always attempt to clear FTS index for DB issues. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 063d909572 Log some debug info about image compression. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 2f8e112f3a Rename MessageProcessReceiver -> RoutineMessageFetchReceiver. 2023-08-16 10:26:32 -04:00
Alex Hart 99abfd0d98 Share to signal from CallSheet. 2023-08-16 10:26:32 -04:00
Greyson Parrelli 5fa9a27ee0 Convert WebSocketStrategy.java -> WebSocketDrainer.kt 2023-08-16 10:26:32 -04:00
Greyson Parrelli b07d675bb4 Remove BackgroundMessageRetriever and clean up old code. 2023-08-16 10:26:32 -04:00
Alex Hart 9f75c37331 Upgrade Glide to 4.15.1 2023-08-16 10:26:31 -04:00
Greyson Parrelli df96b05863 Improve table display in Spinner. 2023-08-16 10:26:31 -04:00
Greyson Parrelli d6adfea9b1 Clean up old one-time prekeys. 2023-08-16 10:26:31 -04:00
Greyson Parrelli 389b439e9a Log ServiceId parsing failures. 2023-08-16 10:26:31 -04:00
Greyson Parrelli 046b89fa21 Update libsignal to 0.31.0 2023-08-16 10:26:31 -04:00
Greyson Parrelli 72e5532c6c Perform a legacy session reset if you fail to decrypt a sync message. 2023-08-16 10:26:31 -04:00
Greyson Parrelli 5688d85789 Do not send retry receipts for messages sent to our PNI. 2023-08-16 10:26:31 -04:00
Clark Chen 28b63e08f1 Bump version to 6.29.2 2023-08-15 15:45:35 -04:00
Clark Chen 951ce77853 Updated language translations. 2023-08-15 15:31:11 -04:00
Clark b37ba63018 Revert remote delete/edit send threshold to 3 hours. 2023-08-15 12:28:59 -04:00
Clark 251d251661 Send read receipts per edit message revision. 2023-08-14 17:20:04 -04:00
Clark Chen e11750fb75 Bump version to 6.29.1 2023-08-14 16:33:01 -04:00
Clark Chen 1634ddeb25 Updated language translations. 2023-08-14 16:14:17 -04:00
Clark 7d4bcd7f15 Ignore message_fts table if needed in v175 migration. 2023-08-14 15:59:50 -04:00
Cody Henthorne 13d9b6cc5a Fix incorrect unread counts. 2023-08-14 15:59:50 -04:00
Clark 8d0c41baa0 Update edit message and remote delete send/receive thresholds. 2023-08-14 15:59:50 -04:00
Alex Hart 0303c96ee1 Fix usage of setAvatar in ConversationListItem. 2023-08-14 15:59:50 -04:00
Alex Hart fde6d7921e Bounce message request state update if needed. 2023-08-14 15:59:50 -04:00
Alex Hart c632d8ebec Remove group calling tooltip. 2023-08-14 15:59:50 -04:00
Alex Hart 31b43e8754 Fix thread set query during row deletion. 2023-08-14 15:59:50 -04:00
Alex Hart 195360a0f9 Fix quote reply scroll-to-bottom behavior. 2023-08-11 14:06:11 -03:00
Alex Hart f293f88958 Fix strange RTL white screen behavior. 2023-08-11 13:37:16 -03:00
Alex Hart 6ccfab4087 Bump version to 6.29.0 2023-08-10 15:38:34 -03:00
Alex Hart a45ce55808 Updated language translations. 2023-08-10 15:32:05 -03:00
Greyson Parrelli c7dabe1b6f Ensure all group recipients have group records. 2023-08-10 15:29:02 -03:00
Alex Hart ec51268439 Update Fragment and RecyclerView libraries.
Update Fragment to 1.6.1
Update RecyclerView to 1.3.1
2023-08-10 15:29:02 -03:00
Clark 7543b9fa37 Fix hidden recipients instrumentation tests. 2023-08-10 15:29:02 -03:00
Greyson Parrelli ca3187d0b8 Ungate some PNP receive-side behavior. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 327cd93e3c Save PNI's from CDSv2 for all users. 2023-08-10 15:29:02 -03:00
Alex Hart 13853c708e Implement proper in-call status for call links. 2023-08-10 15:29:02 -03:00
Greyson Parrelli ee1291c816 Improve logging of (ACI, PNI, E164) tuples. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 6d2d3ae528 Improve ServiceId parsing functions. 2023-08-10 15:29:02 -03:00
Alex Hart 784f94ecdb Fix missed call icon for groups. 2023-08-10 15:29:02 -03:00
Alex Hart 93bf853b5e Fix threading in call link creation sheet. 2023-08-10 15:29:02 -03:00
Clark bb83ddfe28 Prompt user for debug logs with slow notifications. 2023-08-10 15:29:02 -03:00
Clark b51ec53e33 Light battery optimizations cleanup. 2023-08-10 15:29:02 -03:00
Alex Hart ca210f2b6d Add denial dialogs for call links. 2023-08-10 15:29:02 -03:00
Alex Hart 38bddec4ba Fix call deletion sync message sending. 2023-08-10 15:29:02 -03:00
Alex Hart b866d57814 Hide admin options if user is not a call admin. 2023-08-10 15:29:02 -03:00
Alex Hart 3c9004d87d Remove maximum denial tracking. 2023-08-10 15:29:02 -03:00
Alex Hart c479dd404c Add invalid call link dialog. 2023-08-10 15:29:02 -03:00
Alex Hart 748667a0b4 Fix bad ad hoc calling flag check. 2023-08-10 15:29:02 -03:00
Alex Hart 6898595f8a Add GroupCall.JoinState.PENDING support. 2023-08-10 15:29:02 -03:00
Alex Hart 30d0b6fd0e Add additional call links moderation ui. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 7c209db146 Fix logging for 'restricted' power bucket. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 49c8c88a22 Put message latency time in decryption log. 2023-08-10 15:29:02 -03:00
Alex Hart 88e530c96c Rotate edit message flag. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 14f3fb5a94 Break message-latency into high/low priority. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 7ac479b78a Log server time offset in FCM log. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 10b356e642 Stop reading the giftBadges capability. 2023-08-10 15:29:02 -03:00
Greyson Parrelli 7f92482d7a Stop reading the stories capability. 2023-08-10 15:29:02 -03:00
Alex Hart b79a7309aa Allow input panel to grow as text is entered. 2023-08-10 15:29:02 -03:00
Alex Hart b54781ff56 Utilize AlertDialog.Builder for displayInDialogAboveAnchor. 2023-08-10 15:29:02 -03:00
Clark 6a87495a6d Update contact hiding to spec. 2023-08-10 15:29:02 -03:00
Greyson Parrelli c5d9346370 Convert all group code to be based on ServiceIds. 2023-08-10 15:05:18 -03:00
Alex Hart d247e2c111 Implement several parts of the call links admin UX. 2023-08-10 15:05:18 -03:00
Cody Henthorne b30f47bac4 Remove ComposeText and SendButton sms/mms transport complexity. 2023-08-10 15:05:18 -03:00
Cody Henthorne 2f9498e137 Refactor input panel to use constraint layout. 2023-08-10 15:05:18 -03:00
Alex Hart 067b3513b7 Add content descriptions to call log row item buttons. 2023-08-10 15:05:18 -03:00
Alex Hart e50ed22c85 Bump version to 6.28.5 2023-08-10 14:47:22 -03:00
Alex Hart 7cf17f3cc4 Updated language translations. 2023-08-10 14:42:19 -03:00
Greyson Parrelli 9f52ecab5c Ensure that inbound messages mark threads as active. 2023-08-10 13:33:23 -04:00
Alex Hart c8a56d4f78 Bump version to 6.28.4 2023-08-09 14:02:20 -03:00
Alex Hart 71d482ab29 Updated language translations. 2023-08-09 14:00:24 -03:00
Greyson Parrelli 1cc7b46555 Fix PNI prefixing in provisioning message. 2023-08-09 13:57:32 -03:00
Alex Hart f56a65d30d Bump version to 6.28.3 2023-08-09 09:42:28 -03:00
Alex Hart aff813b284 Updated language translations. 2023-08-09 09:29:37 -03:00
Alex Hart 181c0e8a60 Revert "Fix unread state using last seen timestamp."
This reverts commit a5e30bc818.
2023-08-09 09:24:40 -03:00
Alex Hart cf7f614296 Revert "Use local timestamps for in-chat unread counter."
This reverts commit c501a417bb.
2023-08-09 09:24:19 -03:00
Alex Hart 351e37bcee Bump version to 6.28.2 2023-08-07 15:52:47 -03:00
Alex Hart cc1f27f588 Updated language translations. 2023-08-07 15:49:02 -03:00
Alex Hart 859905c3e4 Fix sticker insertion from system keyboard. 2023-08-07 15:41:49 -03:00
Alex Hart 8af91bffb5 Fix expanding captions. 2023-08-07 15:41:49 -03:00
Alex Hart 06dc8ccbdd Fix contact photo upload failure. 2023-08-07 15:28:22 -03:00
Alex Hart c501a417bb Use local timestamps for in-chat unread counter. 2023-08-07 13:28:19 -03:00
Alex Hart 0021e229d8 Add virtual file support to file sharing. 2023-08-07 13:27:55 -03:00
Alex Hart b4ef95a9b4 Add ActivityNotFoundException handling to ConversationFragment. 2023-08-04 15:17:47 -03:00
Greyson Parrelli f25a716d62 Bump version to 6.28.1 2023-08-04 12:47:40 -04:00
Greyson Parrelli a9739ed500 Updated language translations. 2023-08-04 12:47:04 -04:00
Alex Hart a131eeaa4a Add recaptcha triggers to CFV2. 2023-08-04 13:30:05 -03:00
Alex Hart 9382bbd8fd Add first time in group check. 2023-08-04 13:24:00 -03:00
Greyson Parrelli adb1e292bf Convert Scrubber to kotlin. 2023-08-04 12:17:05 -04:00
Greyson Parrelli ca79929141 Add additional Scrubber tests. 2023-08-04 11:43:33 -04:00
Greyson Parrelli ae2998bcf2 Actually use db reference passed into SearchTable.fullyResetTables. 2023-08-04 11:04:52 -04:00
Alex Hart 1e8e09d5c4 Add multiselect callback to conversation fragment. 2023-08-04 11:57:03 -03:00
Alex Hart a5e30bc818 Fix unread state using last seen timestamp. 2023-08-04 11:53:16 -03:00
Alex Hart 72edf5c08b Ignore call links check if ff is disabled. 2023-08-04 11:34:43 -03:00
Alex Hart 192154a11c Update reminder on ReminderUpdateEvent broadcast. 2023-08-04 10:21:45 -03:00
Cody Henthorne c3700cf6d9 Fix incorrect read state causing stale notifications and tweak scroll to bottom behavior. 2023-08-04 09:31:03 -03:00
Greyson Parrelli 78bdee61ef Bump version to 6.28.0 2023-08-02 18:00:00 -04:00
Greyson Parrelli b84eea9620 Updated language translations. 2023-08-02 17:59:21 -04:00
Greyson Parrelli 5f289fa400 Refactor RecipientTable with a PNI constraint. 2023-08-02 17:49:53 -04:00
Cody Henthorne 67b8f468e4 Remove most of Conversation Fragment V1 and friends. 2023-08-02 17:49:53 -04:00
Alex Hart 9c49c84306 Fix poor linkpreview popin behavior in cfv2. 2023-08-02 17:49:53 -04:00
Jon Chambers b31ee802fc Update KBS service ID in staging. 2023-08-02 12:13:58 -04:00
Clark b893a0eb76 Refresh contact list after hiding a contact. 2023-08-02 10:21:40 -04:00
Clark 6f1a04abce Add dialog for when you hit edit message limit. 2023-08-02 09:47:38 -04:00
Clark 041ba27efe Show hidden contacts with chats when searching. 2023-08-01 15:51:31 -04:00
Alex Hart e239036d8b Send 'clear history' event when clearing the call log. 2023-08-01 15:51:31 -04:00
Clark d3f073e573 Fix edit message when sending via legacy path. 2023-08-01 15:51:31 -04:00
Clark 0b7490dc06 Update edit message history items to match design. 2023-08-01 15:51:31 -04:00
Clark a0e514dac9 Enqueue download jobs for edit messages. 2023-08-01 15:51:31 -04:00
Alex Hart 2f1eaf7d6b Fix coloring of media overview toolbar in dark mode. 2023-08-01 15:51:31 -04:00
Alex Hart 6cb8b1c439 Fix call link icon tint in call log. 2023-08-01 15:51:31 -04:00
Alex Hart 5363208e4e Fix typo in method name. 2023-08-01 15:51:31 -04:00
Greyson Parrelli 510ff51198 Rotate the edit message feature flag. 2023-08-01 15:51:31 -04:00
Greyson Parrelli 6dde3d55ef Fix Spinner JS imports. 2023-08-01 15:51:31 -04:00
Greyson Parrelli e3ec53c2d0 Remove deprecated SMS fields from recipient table. 2023-08-01 15:51:31 -04:00
Cody Henthorne e7972d4903 Update request and response properties for batch identity checks. 2023-08-01 15:51:31 -04:00
Jordan Rose a2c3b5d64e Adopt libsignal 0.30.0 and ServiceIds for group members.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-08-01 15:51:31 -04:00
Clark b11d653fc0 Exit edit message mode on message send. 2023-08-01 15:51:31 -04:00
Clark 66792f2d56 Add heuristics for delayed notifications. 2023-08-01 15:51:31 -04:00
Greyson Parrelli c012ead143 Validate ServiceIds on envelopes. 2023-08-01 15:51:31 -04:00
Greyson Parrelli 82906aee58 Use strongly-typed ACIs and PNIs everywhere. 2023-08-01 15:51:31 -04:00
Nicholas 7ff4a82755 Show popup on switching to/from speakerphone. 2023-08-01 15:51:31 -04:00
Jordan Rose 8ca49c1e18 Update to RingRTC v2.30.0 2023-08-01 15:51:31 -04:00
Nicholas 7d68a57f53 Fall back to AudioCodec if MediaRecorderWrapper fails. 2023-08-01 15:51:31 -04:00
Greyson Parrelli c68487c0c7 Disable ktlint rule around class naming. 2023-08-01 15:51:31 -04:00
Clark 4adc660705 Stop content provider handler threads on release. 2023-08-01 15:51:31 -04:00
Clark d78e73bd6f Fix search showing received mention messages as note to self. 2023-08-01 15:51:31 -04:00
Clark Chen 9d33690f34 Show read more for super long scheduled messages. 2023-08-01 15:51:31 -04:00
Greyson Parrelli 4c428e5b5b Update to new CDS flag. 2023-08-01 15:51:31 -04:00
Greyson Parrelli 4c3882689f Let PNP feature flag override CDS compat flag. 2023-08-01 15:51:31 -04:00
Greyson Parrelli c82ed473fc Bump version to 6.27.10 2023-08-01 15:49:51 -04:00
Greyson Parrelli 06664b4c58 Updated language translations. 2023-08-01 15:49:15 -04:00
Cody Henthorne 5e68388b01 Fix crash when on recipient change called after requesting to remove observer. 2023-08-01 15:31:34 -04:00
Greyson Parrelli e14fcf8577 Bump version to 6.27.9 2023-07-31 18:30:04 -04:00
Greyson Parrelli 0219c5253b Updated language translations. 2023-07-31 18:29:37 -04:00
Cody Henthorne adf3d74d91 Fix attachment keyboard not showing. 2023-07-31 15:44:51 -04:00
Cody Henthorne 3acd68e0b3 Fix quoted reply being dropped from voice notes. 2023-07-31 15:44:51 -04:00
Cody Henthorne e5eccd732d Fix gifs rendering behind compose bar. 2023-07-31 15:44:51 -04:00
Cody Henthorne eb0df5791a Fix scroll date header going blank at top of conversation. 2023-07-31 15:44:51 -04:00
Cody Henthorne f5371123da Fix send location not including description location. 2023-07-31 15:44:51 -04:00
Cody Henthorne 6aa723bc22 Fix lifecycle crashes when fragment is destroy before async callbacks. 2023-07-31 11:38:06 -04:00
Cody Henthorne 9ba34df4ae Fix memory leaks and potentially gif playback issues with conversation items. 2023-07-31 11:38:06 -04:00
Cody Henthorne 6194515f8e Fix invalid message request state being used. 2023-07-31 11:16:51 -04:00
Cody Henthorne 39289715bc Fix incorrect text on remote delete dialog in Note to Self. 2023-07-31 11:12:49 -04:00
Cody Henthorne 5fdd2430ca Bump version to 6.27.8 2023-07-28 20:14:40 -04:00
Cody Henthorne a7f3f485ad Updated baseline profile. 2023-07-28 20:07:37 -04:00
Cody Henthorne 69a76fa1b7 Updated language translations. 2023-07-28 20:04:55 -04:00
Cody Henthorne 2e5e64b05d Fix crash when opening non-gv2 group. 2023-07-28 20:00:09 -04:00
Cody Henthorne 933e3233a7 Show correct note to self delete dialog options in CFv2. 2023-07-28 19:55:43 -04:00
Cody Henthorne a54df29542 Protected against crash with unread counter that exceeds thread size. 2023-07-28 19:40:24 -04:00
Greyson Parrelli cdce802b32 Do not retry auth failures in Svr2MirrorJob. 2023-07-28 17:25:14 -04:00
Greyson Parrelli 2abf30e94b Limit RefreshSvrCredentialsJob to registered users. 2023-07-28 17:14:27 -04:00
Cody Henthorne 148cff1b48 Fix missed menu invalidation after opening search. 2023-07-28 15:00:33 -04:00
Cody Henthorne ce2a21c438 Fix disabled input state for Release Notes Channel. 2023-07-28 13:27:11 -04:00
Clark d77c0198d1 Make CFv2 date header visible again. 2023-07-28 12:18:11 -04:00
Cody Henthorne f58a1acff5 Bump version to 6.27.7 2023-07-27 17:54:06 -04:00
Cody Henthorne aadbddd7e9 Updated baseline profile. 2023-07-27 17:11:48 -04:00
Cody Henthorne 689fe3947b Updated language translations. 2023-07-27 17:09:06 -04:00
Cody Henthorne fff0b8b187 Fix crash when tapping re-register banner view. 2023-07-27 17:05:29 -04:00
Cody Henthorne 74562432cf Disable compose input when opening conversation with unregistered recipient. 2023-07-27 17:05:29 -04:00
Clark 8e3027642b Remove self from mention picker. 2023-07-27 16:28:39 -04:00
Cody Henthorne 39f96bb12c Revamp group name color generation. 2023-07-27 16:07:38 -04:00
Clark 938309d125 Remove SMS popup from CFv2. 2023-07-27 16:04:07 -04:00
Cody Henthorne f740b69ffe Fix lifecycle crashes with keyboard fragment and media sending. 2023-07-27 15:36:38 -04:00
Nicholas Tinsley 0a4147aa0e Add margin to the Note to Self bottom sheet. 2023-07-27 14:58:12 -04:00
Greyson Parrelli dcffc13843 Fix a RRP recovery path. 2023-07-26 20:15:57 -04:00
Cody Henthorne 1e9a0cdc16 Attempt to swallow erroneous cancel alarm security exceptions. 2023-07-26 14:07:10 -04:00
Cody Henthorne 82e7050864 Fix various lifecycle crashes. 2023-07-26 13:51:19 -04:00
Nicholas 72d1e55373 Re-enable bubble menu shortcut on CFv2. 2023-07-26 13:21:00 -04:00
Cody Henthorne fe5d5df2d7 Bump version to 6.27.6 2023-07-26 12:39:20 -04:00
Cody Henthorne 9bc337373e Updated baseline profile. 2023-07-26 12:32:52 -04:00
Cody Henthorne 5aad879a95 Updated language translations. 2023-07-26 12:27:54 -04:00
Cody Henthorne a3798dba68 Properly support group calls in CFv2. 2023-07-26 11:59:03 -04:00
Cody Henthorne b9f7ef5cbd Fix bug with search UI not showing when searching from settings. 2023-07-26 11:20:06 -04:00
Cody Henthorne 0c3b541031 Fix odd keyboard open state when viewing media. 2023-07-26 11:05:18 -04:00
Cody Henthorne a09bc53b99 Show mention picker after only typing @. 2023-07-26 10:53:21 -04:00
Cody Henthorne 3731723472 Improve group name coloring performance. 2023-07-25 19:12:04 -04:00
Cody Henthorne ded29619cd Add payload support to CFv2. 2023-07-25 16:53:45 -04:00
Cody Henthorne a5b39a8f17 Fix conversation banner animation bugs.
When in doubt, put it in a FrameLayout.
2023-07-25 16:53:11 -04:00
Cody Henthorne 26866a7b2c Reduce conversation transition animations to 200ms. 2023-07-25 16:36:33 -04:00
Cody Henthorne 7837f3999f Bump version to 6.27.5 2023-07-25 12:12:38 -04:00
Cody Henthorne b25f658647 Updated baseline profile. 2023-07-25 12:06:36 -04:00
Cody Henthorne 49625619fe Updated language translations. 2023-07-25 12:04:15 -04:00
Cody Henthorne 912299bcfd Fix invalid type crash when attempting to recover keyboard landscape height.
A bug with setter using long means it easier to just use long going
forward.
2023-07-25 12:01:14 -04:00
Clark 5648fd2e91 Fix avatar provider buffer underflow. 2023-07-25 12:01:14 -04:00
Clark 557ef5820e Fix bad notification for start foreground when hanging up. 2023-07-25 12:01:14 -04:00
Cody Henthorne 4ce512d259 Fix crash when setting starting scroll position. 2023-07-25 12:01:14 -04:00
Cody Henthorne a68319dae4 Bump version to 6.27.4 2023-07-24 19:36:29 -04:00
Cody Henthorne 080ecf51d3 Updated baseline profile. 2023-07-24 19:31:55 -04:00
Cody Henthorne 657109dae1 Updated language translations. 2023-07-24 19:27:13 -04:00
Greyson Parrelli 019ef02be8 Ensure we use SVR2 endpoint for checking RRP. 2023-07-24 19:22:07 -04:00
Nicholas Tinsley d4774c963d Clear click listener after view is recycled.
Fixes #13070
2023-07-24 16:48:57 -04:00
Clark 34cb4c579c Fix for intermittent date header in CFv2. 2023-07-24 14:50:19 -04:00
Clark 74c261f913 Do not regenerate url preview if url has not changed. 2023-07-24 14:43:01 -04:00
Cody Henthorne 7420123519 Fix input panel moving behind navigation bar. 2023-07-24 14:29:31 -04:00
Cody Henthorne 374910736e Fix crash when data observer called after fragment destroy. 2023-07-24 13:43:35 -04:00
Clark 3a71696a49 Fix CFv2 Voice Note Drafts. 2023-07-24 13:34:58 -04:00
Cody Henthorne 73792905a2 Clear compose input immediately on send to match behavior of v1. 2023-07-24 13:32:16 -04:00
Cody Henthorne 05fc30e6e8 Fix CFv2 initial scrolling bugs. 2023-07-24 12:46:17 -04:00
Nicholas 9c308588b5 Bump version to 6.27.3 2023-07-21 22:30:59 -04:00
Nicholas 2f53200096 Updated language translations. 2023-07-21 22:30:36 -04:00
Cody Henthorne 8c1f2c6064 Fix attachment pointer crash when missing incremental digest. 2023-07-21 19:54:57 -04:00
Cody Henthorne f5fc2acf50 Prevent attachment send of duplicate data with different transforms from failing. 2023-07-21 19:43:31 -04:00
Alex Hart 306fa24d6b Fix crash when text draft save debouncer fires after fragment is destroyed. 2023-07-21 16:32:28 -03:00
Fynn Godau f5ee9d4a3b Call all lifecycle methods on snapshot mapView 2023-07-21 15:22:02 -04:00
Alex Hart e7a5f64fe5 Fix scroll to bottom behavior during fast fling. 2023-07-21 16:10:37 -03:00
Alex Hart 6191e003fc Ensure edit history always starts scrolled to top. 2023-07-21 16:03:41 -03:00
Clark fad401941e Hide old edit revisions from media preview gallery. 2023-07-21 15:00:27 -04:00
Alex Hart 1e0733bd46 Add log-line to see how often setTypists is called. 2023-07-21 15:43:16 -03:00
Clark d0a44c3f14 Small screen fixes for ACI safety number screen. 2023-07-21 14:41:07 -04:00
Alex Hart 3cee0c1bd5 Fix possible data race in ThumbnailView after image send. 2023-07-21 13:38:49 -03:00
Clark Chen f5d403e97d Fix tap to scan not wrapping to next line. 2023-07-21 11:11:43 -04:00
Alex Hart 4f1d021aa8 Fix crash when accessing message edit history in details fragment. 2023-07-21 10:11:27 -03:00
Nicholas dca8c042ab Bump version to 6.27.2 2023-07-20 17:09:05 -04:00
Nicholas ae1ccadcc8 Updated language translations. 2023-07-20 17:08:55 -04:00
Clark 9ac12c2532 Update safety number screen to be in line with design. 2023-07-20 16:53:46 -04:00
Clark 18337c97e2 Remove underline from safety number learn more. 2023-07-20 16:50:34 -04:00
Clark 1e652d497e Dont allow editing failed messages. 2023-07-20 16:50:21 -04:00
Cody Henthorne b53cad2808 Fix various CFv2 scrolling issues. 2023-07-20 16:50:10 -04:00
Alex Hart 4520ff78ff Fix issue with icon pop in attachment keyboard. 2023-07-20 16:44:54 -03:00
Cody Henthorne b887129cd7 Fix crash when leaving conversation. 2023-07-20 13:52:12 -04:00
Cody Henthorne ec25831a37 Fixes for CFv2.
- Status bar color being incorrect when entering a screen that changes it and then returning (e.g., Message Details)
- Fix crash in enter sends mode
- Fix warning about non-closed cursor
- Prevent message abandonment (via trim thread) when it's the first in an inactive thread
- Fix payment attachment button flashing on attachment keyboard open if payments disabled
- Fix reactionDelegate crash
- Fix attachment preview (file, mp3, location, etc) not getting cleared on send
2023-07-20 13:50:32 -04:00
Clark 744f74b498 Address UI issues on safety number verification screen. 2023-07-20 13:09:37 -04:00
Clark Chen 52aaf93f37 Fix copy of the safety number fragment. 2023-07-20 12:53:22 -04:00
Cody Henthorne 2d92d4ad87 Fix jumbo emoji having bubbles bugs. 2023-07-19 19:54:12 -04:00
Cody Henthorne 7617cc0a80 Remove Phase 2 in preparation for CFv2. 2023-07-19 19:46:45 -04:00
Cody Henthorne dc69bcf6f2 Fix view once media send in CFv2. 2023-07-19 19:41:17 -04:00
Nicholas 0775fc7ead Bump version to 6.27.1 2023-07-19 17:48:36 -04:00
Nicholas 034aef483b Updated language translations. 2023-07-19 17:48:06 -04:00
Nicholas Tinsley ee5b99fed4 Rotate edit message feature flag. 2023-07-19 17:40:41 -04:00
Nicholas e031da1337 Bump version to 6.27.0 2023-07-19 17:23:29 -04:00
Nicholas 8f1514642c Updated language translations. 2023-07-19 17:22:58 -04:00
Clark 5aa304ea9a Always show verify safety numbers option. 2023-07-19 17:12:19 -04:00
Alex Hart c5a27b2cc7 Fix remote story deletion syncing. 2023-07-19 17:12:19 -04:00
Clark 0fde404da8 Add you may have messages notification. 2023-07-19 17:12:19 -04:00
Cody Henthorne 5242b9af39 Rotate CFv2 feature flag. 2023-07-19 17:12:19 -04:00
Cody Henthorne 5e2d6fc05f Fix incorrect unread divider behavior when receiving new messages. 2023-07-19 17:12:18 -04:00
Clark Chen 7e08a1f321 Only show one safety number education dialog at a time. 2023-07-19 17:12:18 -04:00
Cody Henthorne 076295eae8 Fix rendering bug when scrolling a chat with a background in CFv2.
"When in doubt, put it in a FrameLayout. - Wayne Gretzky" - MiCHAELSCOTT
2023-07-19 17:12:18 -04:00
Cody Henthorne c13339ca52 Fix scroll to bottom on send bug in CFv2. 2023-07-19 17:12:18 -04:00
Nicholas 627657e1de Update to the final ExoPlayer release. 2023-07-19 17:12:18 -04:00
Alex Hart a8349671d0 Add Receive support for the new CallLogEvent proto messages. 2023-07-19 17:12:18 -04:00
Clark 461875b0e4 Add support for displaying both ACI and e164 safety numbers. 2023-07-19 17:12:18 -04:00
Cody Henthorne 00bbb6bc6e Fix spoiler display bug in long message view. 2023-07-19 17:12:18 -04:00
Greyson Parrelli e1f1181a07 Specifiy SHA256 for docker base image. 2023-07-19 17:12:18 -04:00
Nicholas Tinsley 0e1de39192 Remove Bluetooth mic voice message recording. 2023-07-19 17:12:18 -04:00
Cody Henthorne 05bbeb10da Revert "Attempt to fix crash on call hangup."
This reverts commit 025411c9fb.
2023-07-19 17:12:18 -04:00
Cody Henthorne 7375a9e06b Do not jumbo styled emojis. 2023-07-19 17:12:18 -04:00
Cody Henthorne 4910050891 Fix bubbles jumping around when entering selection mode. 2023-07-19 17:12:18 -04:00
Cody Henthorne 67d4f666ce Add share highwater timestamp protection to CFv2. 2023-07-19 17:12:18 -04:00
Cody Henthorne e6c9449e3c Fix voice note playback and wave form generation in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart b8effba497 Fix crash in hasHeader via range check. 2023-07-19 17:12:18 -04:00
Cody Henthorne 8fcdd7cb8a Update attachment keyboard based on payment availability in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart f3fb5ccc3b CFV2 handle keyboard images and gifs. 2023-07-19 17:12:18 -04:00
Alex Hart b8f55f982f Fix toggle in AdvancedPrivacySettingsFragment. 2023-07-19 17:12:18 -04:00
Cody Henthorne 6db59cb896 Prevent menu creation slowing data load performance in CFv2. 2023-07-18 10:19:17 -04:00
Cody Henthorne 3db83c1602 Fix multiple issues in CFv2. 2023-07-18 10:01:48 -04:00
Nicholas Tinsley 6be9225fbd Include incremental digest when sending attachments. 2023-07-18 09:55:02 -04:00
Nicholas Tinsley 653eff403c Prevent overlap of backup icon on small screens.
Fixes #13064.
2023-07-18 09:55:02 -04:00
Alex Hart ab410ec0cf CFV2 Message Request state adapter update. 2023-07-18 09:55:02 -04:00
Cody Henthorne 7b75a32394 Clean up remaining CFv2 todos. 2023-07-18 09:55:02 -04:00
Cody Henthorne daf077b3c9 Fix overlapping date and unread decorations. 2023-07-18 09:55:02 -04:00
Alex Hart f6bbb59400 Fix crash when accessing binding via delayed runnable. 2023-07-18 09:55:02 -04:00
Cody Henthorne 09813d5dbd Fix crash when dismissing mention picker late. 2023-07-18 09:55:02 -04:00
Cody Henthorne fe509838f4 Add CFv2 feature flag. 2023-07-18 09:55:02 -04:00
Alex Hart 6a443d0074 Fix clipping around incoming V2 conversation items. 2023-07-18 09:55:02 -04:00
Greyson Parrelli 8fc1065dd6 Rename some protos. 2023-07-18 09:55:02 -04:00
Cody Henthorne 1af50ba0f5 Perform safety number check pre-send in CFv2. 2023-07-18 09:55:02 -04:00
Alex Hart 82b3036b77 Add handling for text slide deck in sendMessage. 2023-07-18 09:55:02 -04:00
Alex Hart 676412019c CFV2 Add search bottom bar to bottom panel barrier ids. 2023-07-18 09:55:02 -04:00
Alex Hart 980f4e00e2 Scroll date header in CFV2. 2023-07-18 09:55:01 -04:00
Cody Henthorne 5731bf023a Add unread divider decoration to CFv2. 2023-07-18 09:55:01 -04:00
Alex Hart 2511ca17aa Add onRequestPermissionsResult to CFV2. 2023-07-18 09:55:01 -04:00
Nicholas Tinsley fae653540b Make Safety Number Changed dialog scrollable.
This helps with smaller screens.
2023-07-18 09:55:01 -04:00
Alex Hart b0ca66cc1a Add new active column to ThreadTable. 2023-07-18 09:55:01 -04:00
Nicholas a65e9c76bc Bump version to 6.26.3 2023-07-17 23:31:59 -04:00
Nicholas adbac4c557 Updated language translations. 2023-07-17 23:31:48 -04:00
Nicholas fc7b024e96 Fix benchmark test user generation. 2023-07-17 22:06:00 -04:00
Clark acee65ba25 Defer TooltipPopup show till anchor has been laid out. 2023-07-17 16:40:08 -04:00
Clark Chen 244902ecfc Bump version to 6.26.2 2023-07-14 18:15:18 -04:00
Clark Chen 4b1a678af2 Updated language translations. 2023-07-14 17:57:49 -04:00
Greyson Parrelli 59dd72b5c0 Fix issue with syncing remote deletes in note to self. 2023-07-14 17:46:40 -04:00
Greyson Parrelli 44c393f11a Fix possible ISE in registration. 2023-07-14 17:46:40 -04:00
Nicholas Tinsley fddfbd8d2d Fix changing number flow in scenarios where service requires additional verification.
Fixes #12985, #13059.
2023-07-14 17:46:40 -04:00
Clark e7e00bd428 Disable sticker suggestions when editing message. 2023-07-14 17:46:40 -04:00
Cody Henthorne 07702e69ad Improve spoiler render performance. 2023-07-14 14:41:36 -04:00
Cody Henthorne e5c3757629 Remove Phase 1 in preparation for CFv2. 2023-07-14 13:51:30 -04:00
Greyson Parrelli 7031bbae43 Close the SVR2 socket when we're done. 2023-07-14 13:11:08 -04:00
Nicholas Tinsley 4a6dfed676 Even more Change Number logging. 2023-07-14 09:56:49 -04:00
Clark Chen bb0b414d71 Bump version to 6.26.1 2023-07-13 18:11:30 -04:00
Nicholas Tinsley 95e25652c1 Add change number screen logging. 2023-07-13 17:59:53 -04:00
Nicholas Tinsley 58155b0859 Restore previous Registration session handler.
Fixes #12839, #13059.
2023-07-13 15:20:15 -04:00
Clark f579b79d2e Change websocket keepalive response time to 20s. 2023-07-13 14:22:10 -04:00
Greyson Parrelli 47673be4e0 Bump version to 6.26.0 2023-07-12 16:09:59 -04:00
Clark Chen f67e6a9e9f Updated language translations. 2023-07-12 15:56:38 -04:00
Greyson Parrelli 110d8259fa Explicity declare exported status in manifest entries.
This shouldn't change any behavior. We're just explicitly declaring the
exported field to be what it would otherwise get set to by default.
2023-07-12 15:48:52 -04:00
Alex Hart 8f253ffc43 Add lazy thread creation throughout in preparation for CFV2. 2023-07-12 15:48:52 -04:00
Greyson Parrelli 6ca9cb6da1 Add migration to cleanup some inconsistent DB state. 2023-07-12 15:48:52 -04:00
Greyson Parrelli 1b63bdec12 Control CDS compat mode with it's own remote config. 2023-07-12 15:48:52 -04:00
Greyson Parrelli bb52172516 Fix validation of NullMessage types. 2023-07-12 15:48:52 -04:00
Alex Hart 1640495f34 Fix CFV2 selection. 2023-07-12 15:48:52 -04:00
Cody Henthorne 64415a980f Fix text cutoff in device transfer lock dialog.
Fixes #13040
2023-07-12 15:48:52 -04:00
Cody Henthorne e06d141823 Tweak SpoilerPaint settings to reduce wave effect.
Closes #13041
2023-07-12 15:48:52 -04:00
Clark ac4d8679a1 Add local metrics for message processing. 2023-07-12 15:48:52 -04:00
Clark 8fc03a67b9 Fix failing AttachmentCipherTest for incremental mac. 2023-07-12 15:48:52 -04:00
Cody Henthorne 648506fe04 Fix empty emoji search index. 2023-07-12 15:48:52 -04:00
Cody Henthorne 2c74ac8bfa Fix spoilers not working in story replies. 2023-07-12 15:48:52 -04:00
Cody Henthorne 963709c552 Fix draft message lost during media send flow. 2023-07-12 15:48:52 -04:00
Clark 9af888a595 Refactor FcmFetchManager to make foreground service clearer. 2023-07-12 15:48:51 -04:00
Cody Henthorne ec373b5b4d Fix attachment dedupe race where original may be deleted before new usage is recorded. 2023-07-12 15:48:51 -04:00
Cody Henthorne bfcd57881e Add test to verify sync behavior during VERIFIED to DEFAULT change. 2023-07-12 15:48:51 -04:00
Alex Hart b277a8c5e0 CFV2 fix possible race when initializing menu. 2023-07-12 15:48:51 -04:00
Cody Henthorne 979a50716e Fix most android tests.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-07-12 15:48:51 -04:00
Jim Gustafson c5f0da8151 Update to RingRTC v2.29.0 2023-07-12 15:48:51 -04:00
Cody Henthorne aee0b5268f Improve conversation open benchmark test. 2023-07-12 15:48:51 -04:00
Alex Hart 7e909f2bee Add InternalSettings option for ConversationItem V2. 2023-07-12 15:48:51 -04:00
Cody Henthorne 584c90521a Polish voice notes in CFv2. 2023-07-12 15:48:51 -04:00
Yuval Razieli 23ef8c78bd Fix an issue where the charset in the link preview of some pages was not identified correctly. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 5ca025544e Improve logging around memory usage. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 500ae0c72e Add Spinner support for kyber keys. 2023-07-12 15:48:51 -04:00
Greyson Parrelli c359207f1f Fix potential NPE in MediaOverviewPageFragment. 2023-07-12 15:48:51 -04:00
Alex Hart 159f2ebec0 Don't crash on invalid window token in tooltip popup. 2023-07-12 15:48:51 -04:00
Alex Hart a0db812606 Fix action bar background for multiselect in CFV2. 2023-07-12 15:48:51 -04:00
Alex Hart d4c6a433d7 Add documentation to DeliveryStatusView. 2023-07-12 15:48:51 -04:00
Alex Hart 3a7cde9239 Fix pending rotation pivot point. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 4b6c308ae9 Stop HEAD requests for possibly-unlisted contacts during CDS refresh. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 002279f6a7 Ignore AccountRecord.e164 if PNP-capable. 2023-07-12 15:48:51 -04:00
Greyson Parrelli c807e52ad9 Support CDSI ignore the useCompat flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 9557e3b910 Remove unused feature flag constant. 2023-07-12 15:48:51 -04:00
Greyson Parrelli ddc77884bd Inline the credit card payments feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 9d6337d5a8 Inline the chat filters feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 117dd17215 Rotate edit message feature flag. 2023-07-12 15:48:51 -04:00
Alex Hart f9eed0f6d0 Fix slide in animation for new messages in CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 4429145cdf Remove deprecated ignoreResults parameter. 2023-07-12 15:48:51 -04:00
Alex Hart 5ea4cbf9ca CFV2 Add proper body presentation code. 2023-07-12 15:48:51 -04:00
Ehren Kret c6473ca9e6 Minor improvement to Android Backup file format. 2023-07-12 15:48:51 -04:00
Alex Hart 38b2a2f5b7 Add multi-select support to CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli ebaa445bee Save last-known server time offset. 2023-07-12 15:48:51 -04:00
Greyson Parrelli 8372c699f7 Update username link QR code styling. 2023-07-12 15:48:51 -04:00
Greyson Parrelli e1570e9512 Start mirroring to SVR2. 2023-07-12 15:48:51 -04:00
Alex Hart dfb7304626 Fix external shares in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart 919531a82b Enable quick toggle camera in CFV2 input panel. 2023-07-11 17:58:09 -04:00
Alex Hart 81b2e9ccd2 Hook up reaction callback. 2023-07-11 17:58:09 -04:00
Alex Hart 4ef2aba4e2 Ensure text-only entries are cached. 2023-07-11 17:58:09 -04:00
Alex Hart 4590655dc5 Fix CI V2 layout bounds when item has a reaction. 2023-07-11 17:58:09 -04:00
Alex Hart 3040b70100 Add initial instrumentation testing for V2 ConversationItem shapes. 2023-07-11 17:58:09 -04:00
Alex Hart 47b97aafc6 Add TypingIndicatorDecoration to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart 27e7383db6 Add compose divider to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart 42fe827cb3 Add proper navigation bottom bar color. 2023-07-11 17:58:09 -04:00
Alex Hart 3fa3e8357c Handle video calls for 1:1 conversations in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart 6260607e1b Launch settings on toolbar press in CFV2. 2023-07-11 17:58:09 -04:00
Rashad Sookram 3c1666e874 Update verification metadata for aapt2. 2023-07-11 17:58:09 -04:00
Clark f4a082584c Add upload/download size restrictions for attachments based on remote config. 2023-07-11 17:58:09 -04:00
Ehren Kret 87d4dba32b remove whispersystems.org reference 2023-07-11 17:58:09 -04:00
Alex Hart 329f68d167 Upgrade to Gradle 8.0.2 and AGP 8.0.2 2023-07-11 17:58:09 -04:00
Alex Hart 2053cf085a Add retries to CallEventSyncJob. 2023-07-11 17:58:09 -04:00
Iñaqui 16d48984c5 Remove calling stun fallback. 2023-07-11 17:58:09 -04:00
Cody Henthorne a17800283a Plumb schedule message and edit message send flows for CFv2. 2023-07-11 17:58:09 -04:00
Alex Hart 9b1917cbdc Add story ring to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart e1e3d7a85b Small tweaks for footer positioning. 2023-07-11 17:58:09 -04:00
Alex Hart dc37d1f029 Apply proper background to input panel when wallpaper is enabled. 2023-07-11 17:58:09 -04:00
Alex Hart 53e62f2be0 Add new text-only conversation item. 2023-07-11 17:58:09 -04:00
Alex Hart e6cc789c6f Add conversation test springboard fragment. 2023-07-11 17:58:09 -04:00
Cody Henthorne cfaef77b21 Properly plumb attachment keyboard in CFv2. 2023-07-11 17:58:09 -04:00
Clark Chen 36fc9aa82a Add 10s timeout to user facing CDSI requests. 2023-07-11 17:58:09 -04:00
Alex Hart 8d20669e46 Rewrite AlertView to be a single ImageView. 2023-07-11 17:58:09 -04:00
Greyson Parrelli 9d8501cd64 Bump version to 6.25.5 2023-07-11 14:21:30 -04:00
Greyson Parrelli 88827e94f5 Updated language translations. 2023-07-11 14:21:30 -04:00
Alex Hart 99d2a0c0b6 Wrap dialog content in a scroll view. 2023-07-11 14:21:30 -04:00
Alex Hart 1d0582867b Fix crashing when deleting a custom story. 2023-07-11 14:21:30 -04:00
Alex Hart 0ea6d9205d Fix rotation metrics for DeliveryStatusView. 2023-07-11 14:21:30 -04:00
Greyson Parrelli f438ef543b Fix prekey generation during registration. 2023-07-10 23:05:36 -04:00
Alex Hart 61cd9767c8 Ensure bitmaps are not recycled when getting them from the cache. 2023-07-10 17:08:27 -03:00
Alex Hart 5fbf0a98b9 Bump version to 6.25.4 2023-07-07 15:08:04 -03:00
Alex Hart 1671518ded Updated language translations. 2023-07-07 15:04:13 -03:00
Alex Hart f628ffca06 Fix avatar blurring during calls. 2023-07-07 14:53:23 -03:00
Alex Hart 1d6f4fd4e7 Bump version to 6.25.3 2023-07-06 16:27:55 -03:00
Alex Hart 9eedc0a36b Updated language translations. 2023-07-06 16:19:42 -03:00
Alex Hart a870fe9e1a Do not throw an ISE when we cannot start a foreground service from calling. 2023-07-06 16:12:09 -03:00
Alex Hart d3f779cea9 Fix crash when toggling stories. 2023-07-05 12:16:46 -03:00
Cody Henthorne fb4f41b996 Extend style to inserted text via paste or auto-correct regardless of content. 2023-06-30 14:05:35 -04:00
Cody Henthorne 11aac76fb6 Fix spoiler rendering in quote views. 2023-06-30 13:46:17 -04:00
Nicholas 98424f6cbb Bump version to 6.25.2 2023-06-30 13:36:03 -04:00
Nicholas 1cf7c59af9 Updated language translations. 2023-06-30 13:33:51 -04:00
Clark 13470fb0c3 Increase FCM push websocket timeout. 2023-06-30 12:41:00 -04:00
Nicholas Tinsley 3aa0fd1937 Reset continue button state when dismissing registration number confirmation dialog. 2023-06-30 12:23:47 -04:00
Clark 9e6f2336d1 Add push websocket fetch stats. 2023-06-30 11:07:05 -04:00
Nicholas Tinsley 8b8d62f598 Only close AttachmentCipher streams if using incremental MAC. 2023-06-30 11:06:51 -04:00
Nicholas 4572ae5886 Bump version to 6.25.1 2023-06-29 18:42:29 -04:00
Nicholas 0802d4beb4 Updated language translations. 2023-06-29 18:40:18 -04:00
Nicholas 9361aa700a Transcode video files in a streamable format. 2023-06-29 18:30:29 -04:00
Greyson Parrelli be5cad1cec Add support for Emoji v15.0 2023-06-29 15:55:49 -04:00
Greyson Parrelli fe20de2995 Improve logging around edit and sync messages. 2023-06-29 15:55:12 -04:00
Clark 8714e4298e Specify scale type for glide thumbnails. 2023-06-29 15:32:04 -04:00
Alex Hart 1baebe7475 Remove read log line from AvatarProvider. 2023-06-29 13:17:05 -03:00
Nicholas 6686ae43f3 Bump version to 6.25.0 2023-06-28 17:24:23 -04:00
Nicholas cc2c0e9561 Updated language translations. 2023-06-28 17:21:07 -04:00
Nicholas 34d252a4bd Add incremental digests to attachment sending. 2023-06-28 17:13:15 -04:00
Cody Henthorne 025411c9fb Attempt to fix crash on call hangup. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley 4edb66d2b9 Fix SimpleProgressDialog. 2023-06-28 17:13:15 -04:00
Alex Hart a17033dff4 Add ContentProvider for user avatars. 2023-06-28 17:13:15 -04:00
Cody Henthorne 04a5e56da7 Add mentions support to CFv2. 2023-06-28 17:13:15 -04:00
Bernie Dolan 0e6a3dd408 Fix MobileCoin test net config. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley 47f48a6a8c Put backup enable dialog into ScrollView for smaller screens.
Addresses #13033.
2023-06-28 17:13:15 -04:00
Cody Henthorne 2ef7fabade Add inline emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart 5c2b475c01 Add randomized testing for ConversationItem. 2023-06-28 17:13:15 -04:00
Clark 559f4bc0d3 Rotate edit message feature flag. 2023-06-28 17:13:15 -04:00
Clark 1357a4816b Update edit history dialog to new style. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley d1b8a56c0f Revert "Add more logging around Bubble eligibility."
This purely logs the state without affecting the return logic.
2023-06-28 17:13:15 -04:00
Cody Henthorne 7ea38298ea Fix gif playback in CFv2. 2023-06-28 17:13:15 -04:00
Clark c08f1355db Refresh isConnectionNecessary on network block changes. 2023-06-28 17:13:15 -04:00
Cody Henthorne b6589637fa Add emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart 2ee2d2883a Disallow reacting to pending or failed messages. 2023-06-28 17:13:15 -04:00
Alex Hart 029c8ba917 Fix text story keyboard in text stories. 2023-06-28 17:13:15 -04:00
Alex Hart 60e1ee21ed Prevent ANR from large call logs. 2023-06-28 17:13:15 -04:00
Alex Hart a96e9158c4 Change call notification flag to UPDATE_CURRENT and change request code. 2023-06-28 17:13:15 -04:00
Alex Hart e47765d7d5 Add logging to WebRtcActivity Intent. 2023-06-28 17:13:15 -04:00
Clark Chen e807435c8b Capitalize edited string. 2023-06-28 17:13:15 -04:00
Sgn-32 14b41a93e2 Fix Do not animate spoilers if system animations are disabled.
Closes #13016
2023-06-28 17:13:15 -04:00
Yuichi Araki f51fb9da29 Report shortcut usage when a message is sent to a group.
Fixes #13029
Close #13030
2023-06-28 17:13:15 -04:00
Sebastian Scheibner df3ca3d3cc Fix duplicate kyber pre key id in registration
The `PreKeyUtil.generateKyberPreKey` method doesn't update the `nextKyberPreKeyId` in the metadataStore,
so the two `metadataStore.getNextKyberPreKeyId()` calls in this method return the same id.
The first oneTimeKyberPreKey will have the same id as the lastResortKyberPreKey and overwrite it in the database.

Closes #13021
2023-06-28 17:13:15 -04:00
Clark 7786956b11 Fix conversation date header weirdness with edited messages. 2023-06-28 17:13:15 -04:00
Cody Henthorne c17d62aeab Update ktlint and gradle plugin. 2023-06-28 17:13:15 -04:00
Cody Henthorne 65255121de Add media keyboard support in CFv2. 2023-06-28 17:13:15 -04:00
Greyson Parrelli b042945fef Add check for internal users around group lock ordering. 2023-06-28 17:13:15 -04:00
Greyson Parrelli 9d979217fa Include 'dont keep activities' setting in debuglog. 2023-06-28 17:13:15 -04:00
Greyson Parrelli c177de2ec3 Add CDSI and SVR2 to static IP list. 2023-06-26 15:09:39 -04:00
Greyson Parrelli b4fd57d900 Fix possible NPEs with megaphone container. 2023-06-26 15:09:36 -04:00
Nicholas Tinsley 92339dfdcf Add one more logging statement to the change number flow. 2023-06-26 15:09:36 -04:00
Nicholas 8ae115028e Update PIN switch keyboard button to be more straightforward.
Addresses #12866.
2023-06-26 15:09:36 -04:00
Greyson Parrelli 2dd0221680 Fix issue where FTS recovery path didn't work during migrations.
There was a recursive getDatabase() call because it was happening during
a migration. Solution was to re-use the DB instance we already had.
2023-06-26 15:09:36 -04:00
Greyson Parrelli 1a1923c6c0 Fix docker build by using our own mirror.
snapshot.debian.org is super flaky and unreliable. At this point it's
easier to host our own mirror snapshot.
2023-06-26 15:09:36 -04:00
Cody Henthorne 5801ad4bdb Update FCM to 23.1.2 2023-06-26 15:09:36 -04:00
Cody Henthorne 388f2971e9 Allow libsignal-service to build with JDK17. 2023-06-26 15:09:36 -04:00
Greyson Parrelli 14c3a36ec0 Improve logging for GV2 validation error. 2023-06-26 15:09:36 -04:00
Alex Hart 1ad338ce31 Turn DelveryStatusView into a custom AppCompatImageView. 2023-06-26 15:09:36 -04:00
Greyson Parrelli 71981e8a27 Fix dockerfile for reproducible builds. 2023-06-26 15:09:36 -04:00
Nicholas 5cb10cd054 Improve voice note Bluetooth state handling. 2023-06-26 15:09:36 -04:00
Rashad Sookram ecf576e9b9 Don't localize audio output in logs. 2023-06-26 15:09:36 -04:00
Alex Hart 09d17659b9 Add media send support to CFV2. 2023-06-26 15:09:36 -04:00
Cody Henthorne 53673be5cb Update AGP to 8.0
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-06-26 15:09:36 -04:00
Alex Hart ed4a1d6ddd Use delete instead of delete for me in call log dialog. 2023-06-26 15:09:36 -04:00
Alex Hart e3044b8b85 Add preview treatment for call links. 2023-06-26 15:09:36 -04:00
Alex Hart 4ce05a064c Add CFV2 Sticker Suggestions. 2023-06-26 15:09:36 -04:00
Alex Hart 2fbcc23451 Add LinkPreview support to CFV2. 2023-06-26 15:09:36 -04:00
Alex Hart 3bdffed8c9 Add call link scrubbing for logs. 2023-06-26 15:09:36 -04:00
Nicholas 6cc8e87d46 Bump version to 6.24.4 2023-06-26 14:33:40 -04:00
Nicholas 7d8f549d97 Updated baseline profile. 2023-06-26 14:31:28 -04:00
Nicholas fb2ef265bd Updated language translations. 2023-06-26 14:16:45 -04:00
Nicholas 96e8256781 Fix TestUser constructor. 2023-06-26 14:10:13 -04:00
Cody Henthorne dd40517f12 Fix crash after hanging up call. 2023-06-26 14:10:13 -04:00
Cody Henthorne 6c95b766d6 Fix answer audio call and video starting bug. 2023-06-26 14:10:13 -04:00
Greyson Parrelli feffdcb71e Bump version to 6.24.3 2023-06-22 18:53:58 -04:00
Greyson Parrelli f33e2c49ca Updated language translations. 2023-06-22 18:53:29 -04:00
Clark 1037acd4a2 Load original message and use it for timestamps in Conversation. 2023-06-22 18:42:56 -04:00
Clark 68042fc755 No longer call startForeground in onCreate. 2023-06-22 14:09:23 -04:00
Alex Hart 1bdc77affe Resolve issue with incoming video call state. 2023-06-22 14:52:10 -03:00
Nicholas Tinsley 88f50da4fb Discard voice note recording on error. 2023-06-22 13:32:27 -04:00
Cody Henthorne bc1fbd9b6c Fix spoilers not animating after leaving and returning to conversation.
Fixes #13015
2023-06-22 11:57:59 -04:00
Cody Henthorne c1b7b7c95e Fix styling lost in draft state bug. 2023-06-22 11:16:27 -04:00
Greyson Parrelli b0688eed5c Bump version to 6.24.2 2023-06-21 20:34:12 -04:00
Greyson Parrelli e06ed03d33 Updated language translations. 2023-06-21 20:34:12 -04:00
Clark e2a79394ab Always background legacy FCM fallback. 2023-06-21 20:34:12 -04:00
Clark 5db770ca44 Use original message instead of edit message when checking if we can edit. 2023-06-21 17:52:33 -04:00
Cody Henthorne df8aaa2005 Provide better 'why' when an attachment is not auto-downloaded. 2023-06-21 16:18:16 -04:00
Cody Henthorne 882748f080 Allow formatting text from overflow menu. 2023-06-21 16:16:16 -04:00
Cody Henthorne 15035f4eb3 Fix rendering when editing a message with spoilers. 2023-06-21 13:08:00 -04:00
Cody Henthorne 1d0a87f52a Add ability to clear or toggle formatting. 2023-06-21 13:05:46 -04:00
Clark 59b2cc5f79 Fix edit message date header showing wrong date. 2023-06-21 12:44:22 -04:00
Nicholas Tinsley d6758fc264 Store PIN keyboard type in file backup. 2023-06-21 11:53:01 -04:00
Greyson Parrelli 36418bec59 Ensure SqlCipherDeletingErrorHandler runs delete. 2023-06-21 10:24:12 -04:00
Cody Henthorne 07bd8b2fa3 Fix NPE in spoiler renderer. 2023-06-21 10:10:01 -04:00
Greyson Parrelli d989d02af9 Bump version to 6.24.1 2023-06-20 14:26:35 -04:00
Greyson Parrelli 503ce13122 Updated language translations. 2023-06-20 14:24:16 -04:00
Alex Hart 8f77321adb Dispose notification disposable when stopping service. 2023-06-20 12:07:11 -03:00
Clark 60cdcea791 Revert "Call startForeground onCreate for generic foreground service. " 2023-06-20 10:53:28 -04:00
Clark 86cd4c5c30 Fix remote delete for edit messages. 2023-06-20 10:29:47 -04:00
Nicholas Tinsley 62d5f61a0b Allow verification with PIN rather than SMS when restoring from backup. 2023-06-16 16:31:46 -04:00
Clark 25860867bb Use correct receive timestamp for edit message handling. 2023-06-16 16:30:49 -04:00
Greyson Parrelli 272860f071 Do not animate spoilers if system animations are disabled. 2023-06-16 15:51:24 -04:00
Nicholas 767cfbc717 Fix atomic registrations when not using session ID. 2023-06-16 15:38:16 -04:00
Cody Henthorne 55af6ca84e Bump version to 6.24.0 2023-06-15 15:44:35 -04:00
Cody Henthorne 88933ae051 Updated baseline profile. 2023-06-15 15:36:09 -04:00
Cody Henthorne 4781beebee Updated language translations. 2023-06-15 15:36:09 -04:00
Clark Chen 0049c74323 Fix baseline profile generation benchmark. 2023-06-15 15:36:09 -04:00
Alex Hart 361727cec6 Add resend handler to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart 1b95177e0e Add handleViewPaymentDetails to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart 8d34c54de2 Add multiselect action support to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart 2fa0eba3db Add search call-through in CFV2 override. 2023-06-15 15:36:09 -04:00
Cody Henthorne 6cc41e95c6 Remove edit message receive feature flag. 2023-06-15 15:36:09 -04:00
Cody Henthorne b1523f5b91 Update text formatting feature flag. 2023-06-15 15:36:09 -04:00
Nicholas d16002546d Create account in single network request. 2023-06-15 15:36:09 -04:00
Clark 186a93f5d1 Use separate PNI key distribution endpoint instead of change number. 2023-06-15 15:36:09 -04:00
Alex Hart 3d4875bcfe Add sticker suggestion send support to CFV2. 2023-06-15 15:36:09 -04:00
Nicholas 441e30971a Add more logging around Bubble eligibility.
To help diagnose #12036.
2023-06-15 15:36:08 -04:00
Alex Hart ff115c2349 Add voice recording to CFV2. 2023-06-15 15:36:08 -04:00
Clark f23e5bdb44 Prepare edit message for beta run. 2023-06-15 15:36:08 -04:00
Jim Gustafson d0a232d86a Update to RingRTC v2.28.1 2023-06-15 15:36:08 -04:00
Alex Hart 9fef8386e6 Fix initial call state when starting from action. 2023-06-15 15:36:08 -04:00
Alex Hart b1680ba5c6 Add new call notification strings. 2023-06-15 15:36:08 -04:00
Alex Hart 6cd59daf0a Fix several issues with call notifications. 2023-06-15 15:36:08 -04:00
Greyson Parrelli 38f2b39ac4 Add common interface over SVR implementations. 2023-06-15 15:36:08 -04:00
Alex Hart 51222738df Remove avatar color from CallLink table. 2023-06-15 15:36:08 -04:00
Clark b9835584d8 Add sharing for PNP usernames badge. 2023-06-15 13:32:00 -04:00
Alex Hart cff01021c2 Ensure call links only ever have one call event associated with them. 2023-06-15 13:32:00 -04:00
Alex Hart d19b8a125c Do not enable ringing for call links. 2023-06-15 13:32:00 -04:00
Alex Hart 4caaa0033b Re-enable call delete sync events. 2023-06-15 13:32:00 -04:00
Alex Hart f3a0a059ea Add search and arbitrary jump support to CFV2. 2023-06-15 13:32:00 -04:00
Alex Hart 290b0fe46f Ensure owned call links are revoked on delete. 2023-06-15 13:32:00 -04:00
g1a55er 03a212eee4 Reschedule job if background web socket message retrieval fails.
Closes #12971
2023-06-15 13:31:59 -04:00
Alex Hart f3b629bc06 Add reaction support to CFV2. 2023-06-15 13:31:59 -04:00
Alex Hart b9e002f7b1 Update call links parsing. 2023-06-15 13:31:59 -04:00
Alex Hart c90779beea Fix jump position for quotes. 2023-06-15 13:31:59 -04:00
Alex Hart bc8c8a049f Add onViewsRevealed implementation. 2023-06-15 13:31:59 -04:00
Alex Hart 1d9dc66265 Update several androidx dependencies.
Navigation to 1.6.0
Fragment to 1.6.0
Compose BOM to 2023.05.01
Lifecycle to 2.6.1
Activity to 1.7.2
2023-06-15 13:31:59 -04:00
Alex Hart 886c149c3f Add in-call info sheet for call links. 2023-06-15 13:31:59 -04:00
Clark 369ca189d3 Disable stickers and gifs when editing message. 2023-06-15 13:31:59 -04:00
Cody Henthorne 02c4bbe816 Add date headers to CFv2. 2023-06-15 13:31:59 -04:00
Clark 523c9f6576 Add workflow for monitoring APK size changes. 2023-06-15 13:31:59 -04:00
Cody Henthorne c259430b09 Bump version to 6.23.5 2023-06-15 13:04:42 -04:00
Cody Henthorne fc94b90a03 Revert "Adopt new APIs for network connectivity check."
This reverts commit de4c6ab7b7.
2023-06-15 12:57:35 -04:00
Cody Henthorne 6af521130d Revert "Fix connectivity over VPN on older API versions."
This reverts commit 7e24252447.
2023-06-15 12:57:14 -04:00
Cody Henthorne 9c001e4f35 Bump version to 6.23.4 2023-06-15 11:30:05 -04:00
Cody Henthorne 9e8dee36a6 Updated baseline profile. 2023-06-15 11:25:25 -04:00
Cody Henthorne 26b17d8a3c Updated language translations. 2023-06-15 11:23:11 -04:00
Cody Henthorne 1c5e2e3359 Fix linkify for valid URLs with ... in the path. 2023-06-15 11:20:22 -04:00
Clark 0437d37f23 Fix pending retry receipt cache deadlock. 2023-06-15 11:20:22 -04:00
Cody Henthorne ec6b1a44de Add text formatting support to release notes channel. 2023-06-15 11:20:22 -04:00
Clark 08c661bb14 Fix foreground service start/stop race. 2023-06-14 14:42:08 -04:00
3879 changed files with 211655 additions and 139312 deletions
+6 -2
View File
@@ -1,5 +1,9 @@
root = true
[*.kt]
[*.{kt,kts}]
indent_size = 2
twitter_compose_allowed_composition_locals=LocalExtendedColors
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false
ktlint_code_style = intellij_idea
twitter_compose_allowed_composition_locals=LocalExtendedColors
ktlint_standard_class-naming = disabled
+5 -3
View File
@@ -18,19 +18,21 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
run: ./gradlew qa --parallel
run: ./gradlew qa
- name: Archive reports for failed build
if: ${{ failure() }}
+84
View File
@@ -0,0 +1,84 @@
name: APK Diff
on:
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
pull-requests: write # to comment on PR
jobs:
assemble-base:
if: ${{ github.repository != 'signalapp/Signal-Android' }}
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Cache base apk
id: cache-base
uses: actions/cache@v3
with:
path: diffuse-base.apk
key: diffuse-${{ github.event.pull_request.base.sha }}
- name: Build with Gradle
if: steps.cache-base.outputs.cache-hit != 'true'
run: ./gradlew assemblePlayProdRelease
- name: Copy base apk
if: steps.cache-base.outputs.cache-hit != 'true'
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-base.apk
- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'
- name: Build with Gradle
run: ./gradlew assemblePlayProdRelease
- name: Copy PR apk
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
- id: diffuse
uses: usefulness/diffuse-action@v1
with:
old-file-path: diffuse-base.apk
new-file-path: diffuse-new.apk
- uses: peter-evans/find-comment@v2
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Diffuse output
- uses: peter-evans/create-or-update-comment@v3
with:
body: |
Diffuse output:
${{ steps.diffuse.outputs.diff-gh-comment }}
edit-mode: replace
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v3
with:
name: diffuse-output
path: ${{ steps.diffuse.outputs.diff-file }}
+2 -1
View File
@@ -3,6 +3,7 @@ captures/
project.properties
keystore.debug.properties
keystore.staging.properties
nightly-url.txt
.project
.settings
bin/
@@ -28,4 +29,4 @@ jni/libspeex/.deps/
pkcs11.password
dev.keystore
maps.key
local/
local/
+3
View File
@@ -0,0 +1,3 @@
[submodule "libwebp"]
path = libwebp
url = https://github.com/webmproject/libwebp.git
+6
View File
@@ -15,6 +15,12 @@ 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.
## Building
1. You'll need to get the `libwebp` submodule after checking out the repository with `git submodule init && git submodule update`
1. Most things are pretty straightforward, and opening the project in Android Studio should get you most of the way there.
1. Depending on your configuration, you'll also likely need to install additional SDK Tool components, namely the versions of NDK and CMake we are currently using in our [Docker](https://github.com/signalapp/Signal-Android/blob/main/reproducible-builds/Dockerfile#L30) configuration.
## Issues
### Useful bug reports
+2 -2
View File
@@ -1,4 +1,4 @@
# Signal Android
# Signal Android
Signal is a simple, powerful, and secure messenger.
@@ -54,7 +54,7 @@ The form and manner of this distribution makes it eligible for export under the
## License
Copyright 2013-2023 Signal
Copyright 2013-2024 Signal Messenger, LLC
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
-2
View File
@@ -1,2 +0,0 @@
*.db
*.db.gz
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-106
View File
@@ -1,106 +0,0 @@
import sys
import re
import argparse
import sqlite3
import gzip
from progressbar import ProgressBar, Counter, Timer
from lxml import etree
parser = argparse.ArgumentParser(prog='apntool', description="""Process Android's apn xml files and drop them into an
easily queryable SQLite db. Tested up to version 9 of
their APN file.""")
parser.add_argument('-v', '--version', action='version', version='%(prog)s v1.1')
parser.add_argument('-i', '--input', help='the xml file to parse', default='apns.xml', required=False)
parser.add_argument('-o', '--output', help='the sqlite db output file', default='apns.db', required=False)
parser.add_argument('--quiet', help='do not show progress or verbose instructions', action='store_true', required=False)
parser.add_argument('--no-gzip', help="do not gzip after creation", action='store_true', required=False)
args = parser.parse_args()
def normalized(target):
o2_typo = re.compile(r"02\.co\.uk")
port_typo = re.compile(r"(\d+\.\d+\.\d+\.\d+)\.(\d+)")
leading_zeros = re.compile(r"(/|\.|^)0+(\d+)")
subbed = o2_typo.sub(r'o2.co.uk', target)
subbed = port_typo.sub(r'\1:\2', subbed)
subbed = leading_zeros.sub(r'\1\2', subbed)
return subbed
try:
connection = sqlite3.connect(args.output)
cursor = connection.cursor()
cursor.execute('SELECT SQLITE_VERSION()')
version = cursor.fetchone()
if not args.quiet:
print("SQLite version: %s" % version)
print("Opening %s" % args.input)
cursor.execute("PRAGMA legacy_file_format=ON")
cursor.execute("PRAGMA journal_mode=DELETE")
cursor.execute("PRAGMA page_size=32768")
cursor.execute("VACUUM")
cursor.execute("DROP TABLE IF EXISTS apns")
cursor.execute("""CREATE TABLE apns(_id INTEGER PRIMARY KEY, mccmnc TEXT, mcc TEXT, mnc TEXT, carrier TEXT,
apn TEXT, mmsc TEXT, port INTEGER, type TEXT, protocol TEXT, bearer TEXT, roaming_protocol TEXT,
carrier_enabled INTEGER, mmsproxy TEXT, mmsport INTEGER, proxy TEXT, mvno_match_data TEXT,
mvno_type TEXT, authtype INTEGER, user TEXT, password TEXT, server TEXT)""")
apns = etree.parse(args.input)
root = apns.getroot()
pbar = None
if not args.quiet:
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start()
count = 0
for apn in root.iter("apn"):
if apn.get("mmsc") is None:
continue
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
mccmnc = "%s%s" % (apn.get("mcc"), apn.get("mnc"))
normalized_mmsc = normalized(apn.get("mmsc"))
if normalized_mmsc != apn.get("mmsc"):
print("normalize MMSC: %s => %s" % (apn.get("mmsc"), normalized_mmsc))
apn.set("mmsc", normalized_mmsc)
if not apn.get("mmsproxy") is None:
normalized_mmsproxy = normalized(apn.get("mmsproxy"))
if normalized_mmsproxy != apn.get("mmsproxy"):
print("normalize proxy: %s => %s" % (apn.get("mmsproxy"), normalized_mmsproxy))
apn.set("mmsproxy", normalized_mmsproxy)
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + [mccmnc]
keys = apn.attrib.keys() + ["mccmnc"]
cursor.execute("SELECT 1 FROM apns WHERE mccmnc = ? AND apn = ?", [mccmnc, apn.get("apn")])
if cursor.fetchone() is None:
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
cursor.execute(statement, values)
count += 1
if not args.quiet:
pbar.update(count)
if not args.quiet:
pbar.finish()
connection.commit()
print("Successfully written to %s" % args.output)
if not args.no_gzip:
gzipped_file = "%s.gz" % (args.output,)
with open(args.output, 'rb') as orig:
with gzip.open(gzipped_file, 'wb') as gzipped:
gzipped.writelines(orig)
print("Successfully gzipped to %s" % gzipped_file)
if not args.quiet:
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.")
except sqlite3.Error as e:
if connection:
connection.rollback()
print("Error: %s" % e.args[0])
sys.exit(1)
finally:
if connection:
connection.close()
-3
View File
@@ -1,3 +0,0 @@
argparse>=1.2.1
lxml>=3.3.3
progressbar-latest>=2.4
-702
View File
@@ -1,702 +0,0 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.protobuf'
id 'androidx.navigation.safeargs'
id 'org.jlleitschuh.gradle.ktlint'
id 'org.jetbrains.kotlin.android'
id 'app.cash.exhaustive'
id 'kotlin-parcelize'
id 'com.squareup.wire'
id 'android-constants'
id 'translations'
}
apply from: 'static-ips.gradle'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.18.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir 'src/main/protowire'
}
}
ktlint {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.47.1"
}
def canonicalVersionCode = 1276
def canonicalVersionName = "6.23.3"
def postFixSize = 100
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
def selectableVariants = [
'nightlyProdSpinner',
'nightlyProdPerf',
'nightlyProdRelease',
'nightlyStagingRelease',
'nightlyPnpPerf',
'nightlyPnpRelease',
'playProdDebug',
'playProdSpinner',
'playProdCanary',
'playProdPerf',
'playProdBenchmark',
'playProdInstrumentation',
'playProdRelease',
'playStagingDebug',
'playStagingCanary',
'playStagingSpinner',
'playStagingPerf',
'playStagingInstrumentation',
'playPnpDebug',
'playPnpSpinner',
'playStagingRelease',
'websiteProdSpinner',
'websiteProdRelease',
]
android {
namespace 'org.thoughtcrime.securesms'
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions 'distribution', 'environment'
useLibrary 'org.apache.http.legacy'
testBuildType 'instrumentation'
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = ["-Xallow-result-return-type"]
}
signingConfigs {
if (keystores.debug != null) {
debug {
storeFile file("${project.rootDir}/${keystores.debug.storeFile}")
storePassword keystores.debug.storePassword
keyAlias keystores.debug.keyAlias
keyPassword keystores.debug.keyPassword
}
}
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests {
includeAndroidResources = true
}
managedDevices {
devices {
pixel3api30 (ManagedVirtualDevice) {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
test {
java.srcDirs += "$projectDir/src/testShared"
}
androidTest {
java.srcDirs += "$projectDir/src/testShared"
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility signalJavaVersion
targetCompatibility signalJavaVersion
}
packagingOptions {
resources {
excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/LICENSE.md', 'META-INF/NOTICE', 'META-INF/LICENSE-notice.md', 'META-INF/proguard/androidx-annotations.pro', 'libsignal_jni.dylib', 'signal_jni.dll']
}
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.2'
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
minSdkVersion signalMinSdkVersion
targetSdkVersion signalTargetSdkVersion
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders = [mapsKey:"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"]
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "SIGNAL_URL", "\"https://chat.signal.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.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_SVR2_URL", "\"https://svr2.signal.org\""
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"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 "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_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", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
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+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
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", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
buildConfigField "String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\""
buildConfigField "boolean", "TRACING_ENABLED", "false"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resConfigs autoResConfig()
splits {
abi {
enable !project.hasProperty('generateBaselineProfile')
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
testInstrumentationRunner "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
buildTypes {
debug {
if (keystores['debug'] != null) {
signingConfig signingConfigs.debug
}
isDefault true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard/proguard-firebase-messaging.pro',
'proguard/proguard-google-play-services.pro',
'proguard/proguard-jackson.pro',
'proguard/proguard-sqlite.pro',
'proguard/proguard-appcompat-v7.pro',
'proguard/proguard-square-okhttp.pro',
'proguard/proguard-square-okio.pro',
'proguard/proguard-rounded-image-view.pro',
'proguard/proguard-glide.pro',
'proguard/proguard-shortcutbadger.pro',
'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro',
'proguard/proguard-mobilecoin.pro',
'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro',
'proguard/proguard.cfg'
testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg'
manifestPlaceholders = [mapsKey:getMapsKey()]
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\""
}
instrumentation {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
applicationIdSuffix ".instrumentation"
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Instrumentation\""
}
spinner {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Spinner\""
}
release {
minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\""
}
perf {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Perf\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
benchmark {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Benchmark\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
canary {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Canary\""
}
}
productFlavors {
play {
dimension 'distribution'
isDefault true
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"play\""
}
website {
dimension 'distribution'
ext.websiteUpdateUrl = "https://updates.signal.org/android"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"website\""
}
nightly {
dimension 'distribution'
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\""
}
prod {
dimension 'environment'
isDefault true
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\""
}
staging {
dimension 'environment'
applicationIdSuffix ".staging"
buildConfigField "String", "SIGNAL_URL", "\"https://chat.staging.signal.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\""
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", "BUILD_ENVIRONMENT_TYPE", "\"Staging\""
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 ->
variant.outputs.each { output ->
if (output.baseName.contains('nightly')) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
def tag = getCurrentGitTag()
if (tag != null && tag.length() > 0) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
android.variantFilter { variant ->
def distribution = variant.getFlavors().get(0).name
def environment = variant.getFlavors().get(1).name
def buildType = variant.buildType.name
def fullName = distribution + environment.capitalize() + buildType.capitalize()
if (!selectableVariants.contains(fullName)) {
variant.setIgnore(true)
}
}
}
dependencies {
implementation libs.androidx.fragment.ktx
lintChecks project(':lintchecks')
coreLibraryDesugaring libs.android.tools.desugar
implementation (libs.androidx.appcompat) {
version {
strictly '1.6.1'
}
}
implementation libs.androidx.window.window
implementation libs.androidx.window.java
implementation libs.androidx.recyclerview
implementation libs.material.material
implementation libs.androidx.legacy.support
implementation libs.androidx.preference
implementation libs.androidx.legacy.preference
implementation libs.androidx.gridlayout
implementation libs.androidx.exifinterface
implementation libs.androidx.compose.rxjava3
implementation libs.androidx.constraintlayout
implementation libs.androidx.multidex
implementation libs.androidx.navigation.fragment.ktx
implementation libs.androidx.navigation.ui.ktx
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.common.java8
implementation libs.androidx.lifecycle.reactivestreams.ktx
implementation libs.androidx.camera.core
implementation libs.androidx.camera.camera2
implementation libs.androidx.camera.lifecycle
implementation libs.androidx.camera.view
implementation libs.androidx.concurrent.futures
implementation libs.androidx.autofill
implementation libs.androidx.biometric
implementation libs.androidx.sharetarget
implementation libs.androidx.profileinstaller
implementation libs.androidx.asynclayoutinflater
implementation libs.androidx.asynclayoutinflater.appcompat
implementation (libs.firebase.messaging) {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation libs.google.play.services.maps
implementation libs.google.play.services.auth
implementation libs.bundles.exoplayer
implementation libs.conscrypt.android
implementation libs.signal.aesgcmprovider
implementation project(':libsignal-service')
implementation project(':paging')
implementation project(':core-util')
implementation project(':glide-config')
implementation project(':video')
implementation project(':device-transfer')
implementation project(':image-editor')
implementation project(':donations')
implementation project(':contacts')
implementation project(':qr')
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation libs.libsignal.android
implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) {
exclude group: 'com.google.protobuf'
}
implementation libs.signal.ringrtc
implementation libs.leolin.shortcutbadger
implementation libs.emilsjolander.stickylistheaders
implementation libs.apache.httpclient.android
implementation libs.glide.glide
implementation libs.roundedimageview
implementation libs.materialish.progress
implementation libs.greenrobot.eventbus
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.google.flexbox
implementation (libs.subsampling.scale.image.view) {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation (libs.android.tooltips) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation (libs.android.smsmms) {
exclude group: 'com.squareup.okhttp', module: 'okhttp'
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
}
implementation libs.stream
implementation libs.lottie
implementation libs.signal.android.database.sqlcipher
implementation libs.androidx.sqlite
implementation (libs.google.ez.vcard) {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.freemarker'
}
implementation libs.dnsjava
implementation libs.kotlinx.collections.immutable
implementation libs.accompanist.permissions
spinnerImplementation project(":spinner")
canaryImplementation libs.square.leakcanary
testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core
testImplementation testLibs.mockito.core
testImplementation testLibs.mockito.kotlin
testImplementation testLibs.androidx.test.core
testImplementation (testLibs.robolectric.robolectric) {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
testImplementation testLibs.robolectric.shadows.multidex
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) {
force = true
}
testImplementation testLibs.hamcrest.hamcrest
testImplementation testLibs.mockk
testImplementation(testFixtures(project(":libsignal-service")))
androidTestImplementation testLibs.androidx.test.ext.junit
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.mockk.android
androidTestImplementation testLibs.square.okhttp.mockserver
instrumentationImplementation (libs.androidx.fragment.testing) {
exclude group: 'androidx.test', module: 'core'
}
testImplementation testLibs.espresso.core
implementation libs.kotlin.stdlib.jdk8
implementation libs.kotlin.reflect
implementation libs.jackson.module.kotlin
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
implementation libs.rxdogtag
androidTestUtil testLibs.androidx.test.orchestrator
implementation project(':core-ui')
ktlintRuleset libs.ktlint.twitter.compose
}
def getLastCommitTimestamp() {
if (!(new File('.git').exists())) {
return System.currentTimeMillis().toString()
}
new ByteArrayOutputStream().withStream { os ->
exec {
executable = 'git'
args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os
}
return os.toString() + "000"
}
}
def getGitHash() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
def getCurrentGitTag() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'tag', '--points-at', 'HEAD'
standardOutput = stdout
}
def output = stdout.toString().trim()
if (output != null && output.size() > 0) {
def tags = output.split('\n').toList()
return tags.stream().filter(t -> t.contains('nightly')).findFirst().orElse(tags.get(0))
} else {
return null
}
}
tasks.withType(Test) {
testLogging {
events "failed"
exceptionFormat "full"
showCauses true
showExceptions true
showStackTraces true
}
}
def loadKeystoreProperties(filename) {
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
return keystoreProperties
} else {
return null
}
}
static def getDateSuffix() {
def date = new Date()
def formattedDate = date.format('yyyy-MM-dd-HH:mm')
return formattedDate
}
def getMapsKey() {
def mapKey = file("${project.rootDir}/maps.key")
if (mapKey.exists()) {
return mapKey.readLines()[0]
}
return "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
+722
View File
@@ -0,0 +1,722 @@
import com.android.build.api.dsl.ManagedVirtualDevice
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Properties
plugins {
id("com.android.application")
id("kotlin-android")
id("androidx.navigation.safeargs")
id("org.jlleitschuh.gradle.ktlint")
id("org.jetbrains.kotlin.android")
id("app.cash.exhaustive")
id("kotlin-parcelize")
id("com.squareup.wire")
id("translations")
id("licenses")
}
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1408
val canonicalVersionName = "7.4.0"
val postFixSize = 100
val abiPostFix: Map<String, Int> = mapOf(
"universal" to 0,
"armeabi-v7a" to 1,
"arm64-v8a" to 2,
"x86" to 3,
"x86_64" to 4
)
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
val selectableVariants = listOf(
"nightlyProdSpinner",
"nightlyProdPerf",
"nightlyProdRelease",
"nightlyStagingRelease",
"playProdDebug",
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
"playStagingDebug",
"playStagingCanary",
"playStagingSpinner",
"playStagingPerf",
"playStagingInstrumentation",
"playStagingRelease",
"websiteProdSpinner",
"websiteProdRelease"
)
val signalBuildToolsVersion: String by rootProject.extra
val signalCompileSdkVersion: String by rootProject.extra
val signalTargetSdkVersion: Int by rootProject.extra
val signalMinSdkVersion: Int by rootProject.extra
val signalJavaVersion: JavaVersion by rootProject.extra
val signalKotlinJvmTarget: String by rootProject.extra
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir("src/main/protowire")
}
protoPath {
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
}
ktlint {
version.set("0.49.1")
}
android {
namespace = "org.thoughtcrime.securesms"
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions += listOf("distribution", "environment")
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
keystores["debug"]?.let { properties ->
signingConfigs.getByName("debug").apply {
storeFile = file("${project.rootDir}/${properties.getProperty("storeFile")}")
storePassword = properties.getProperty("storePassword")
keyAlias = properties.getProperty("keyAlias")
keyPassword = properties.getProperty("keyPassword")
}
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
unitTests {
isIncludeAndroidResources = true
}
managedDevices {
devices {
create<ManagedVirtualDevice>("pixel3api30") {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
getByName("test") {
java.srcDir("$projectDir/src/testShared")
}
getByName("androidTest") {
java.srcDir("$projectDir/src/testShared")
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = signalJavaVersion
targetCompatibility = signalJavaVersion
}
packagingOptions {
resources {
excludes += setOf("LICENSE.txt", "LICENSE", "NOTICE", "asm-license.txt", "META-INF/LICENSE", "META-INF/LICENSE.md", "META-INF/NOTICE", "META-INF/LICENSE-notice.md", "META-INF/proguard/androidx-annotations.pro", "libsignal_jni.dylib", "signal_jni.dll")
}
}
buildFeatures {
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
}
defaultConfig {
versionCode = canonicalVersionCode * postFixSize
versionName = canonicalVersionName
minSdk = signalMinSdkVersion
targetSdk = signalTargetSdkVersion
multiDexEnabled = true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders["mapsKey"] = "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
buildConfigField("long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L")
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
buildConfigField("String", "SIGNAL_URL", "\"https://chat.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.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_SVR2_URL", "\"https://svr2.signal.org\"")
buildConfigField("String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\"")
buildConfigField("String", "SIGNAL_STAGING_SFU_URL", "\"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("int", "CONTENT_PROXY_PORT", "443")
buildConfigField("String[]", "SIGNAL_SERVICE_IPS", rootProject.extra["service_ips"] as String)
buildConfigField("String[]", "SIGNAL_STORAGE_IPS", rootProject.extra["storage_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN_IPS", rootProject.extra["cdn_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN2_IPS", rootProject.extra["cdn2_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN3_IPS", rootProject.extra["cdn3_ips"] as String)
buildConfigField("String[]", "SIGNAL_SFU_IPS", rootProject.extra["sfu_ips"] as String)
buildConfigField("String[]", "SIGNAL_CONTENT_PROXY_IPS", rootProject.extra["content_proxy_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDSI_IPS", rootProject.extra["cdsi_ips"] as String)
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
buildConfigField("String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97\"")
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+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
buildConfigField("int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode")
buildConfigField("String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"")
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("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"unset\"")
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
buildConfigField("boolean", "TRACING_ENABLED", "false")
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "false")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
resourceConfigurations += listOf()
splits {
abi {
isEnable = !project.hasProperty("generateBaselineProfile")
reset()
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
isUniversalApk = true
}
}
testInstrumentationRunner = "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
buildTypes {
getByName("debug") {
if (keystores["debug"] != null) {
signingConfig = signingConfigs["debug"]
}
isDefault = true
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard/proguard-firebase-messaging.pro",
"proguard/proguard-google-play-services.pro",
"proguard/proguard-jackson.pro",
"proguard/proguard-sqlite.pro",
"proguard/proguard-appcompat-v7.pro",
"proguard/proguard-square-okhttp.pro",
"proguard/proguard-square-okio.pro",
"proguard/proguard-rounded-image-view.pro",
"proguard/proguard-glide.pro",
"proguard/proguard-shortcutbadger.pro",
"proguard/proguard-retrofit.pro",
"proguard/proguard-webrtc.pro",
"proguard/proguard-klinker.pro",
"proguard/proguard-mobilecoin.pro",
"proguard/proguard-retrolambda.pro",
"proguard/proguard-okhttp.pro",
"proguard/proguard-ez-vcard.pro",
"proguard/proguard.cfg"
)
testProguardFiles(
"proguard/proguard-automation.pro",
"proguard/proguard.cfg"
)
manifestPlaceholders["mapsKey"] = getMapsKey()
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Debug\"")
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
}
create("instrumentation") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
applicationIdSuffix = ".instrumentation"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Instrumentation\"")
}
create("spinner") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Spinner\"")
}
create("perf") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Perf\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("benchmark") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("canary") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Canary\"")
}
}
productFlavors {
create("play") {
dimension = "distribution"
isDefault = true
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"play\"")
}
create("website") {
dimension = "distribution"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"website\"")
}
create("nightly") {
val apkUpdateManifestUrl = if (file("${project.rootDir}/nightly-url.txt").exists()) {
file("${project.rootDir}/nightly-url.txt").readText().trim()
} else {
"<unset>"
}
dimension = "distribution"
versionNameSuffix = "-nightly-untagged-${getDateSuffix()}"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\"")
}
create("prod") {
dimension = "environment"
isDefault = true
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
buildConfigField("String", "SIGNAL_URL", "\"https://chat.staging.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.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_SVR2_URL", "\"https://svr2.staging.signal.org\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482\"")
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
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("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true")
}
}
lint {
abortOnError = true
baseline = file("lint-baseline.xml")
checkReleaseBuilds = false
disable += "LintError"
}
applicationVariants.all {
outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
if (output.baseName.contains("nightly")) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
var tag = getCurrentGitTag()
if (!tag.isNullOrEmpty()) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
val abiName: String = output.getFilter("ABI") ?: "universal"
val postFix: Int = abiPostFix[abiName]!!
if (postFix >= postFixSize) {
throw AssertionError("postFix is too large")
}
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
androidComponents {
beforeVariants { variant ->
variant.enable = variant.name in selectableVariants
}
}
val releaseDir = "$projectDir/src/release/java"
val debugDir = "$projectDir/src/debug/java"
android.buildTypes.configureEach {
val path = if (name == "release") releaseDir else debugDir
sourceSets.named(name) {
java.srcDir(path)
}
}
}
dependencies {
lintChecks(project(":lintchecks"))
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
implementation(project(":libsignal-service"))
implementation(project(":paging"))
implementation(project(":core-util"))
implementation(project(":glide-config"))
implementation(project(":video"))
implementation(project(":device-transfer"))
implementation(project(":image-editor"))
implementation(project(":donations"))
implementation(project(":contacts"))
implementation(project(":qr"))
implementation(project(":sticky-header-grid"))
implementation(project(":photoview"))
implementation(project(":core-ui"))
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.appcompat) {
version {
strictly("1.6.1")
}
}
implementation(libs.androidx.window.window)
implementation(libs.androidx.window.java)
implementation(libs.androidx.recyclerview)
implementation(libs.material.material)
implementation(libs.androidx.legacy.support)
implementation(libs.androidx.preference)
implementation(libs.androidx.legacy.preference)
implementation(libs.androidx.gridlayout)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.compose.rxjava3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.multidex)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.navigation.compose)
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.common.java8)
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.extensions)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.concurrent.futures)
implementation(libs.androidx.autofill)
implementation(libs.androidx.biometric)
implementation(libs.androidx.sharetarget)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
}
implementation(libs.google.play.services.maps)
implementation(libs.google.play.services.auth)
implementation(libs.bundles.media3)
implementation(libs.conscrypt.android)
implementation(libs.signal.aesgcmprovider)
implementation(libs.libsignal.android)
implementation(libs.mobilecoin)
implementation(libs.signal.ringrtc)
implementation(libs.leolin.shortcutbadger)
implementation(libs.emilsjolander.stickylistheaders)
implementation(libs.apache.httpclient.android)
implementation(libs.glide.glide)
implementation(libs.roundedimageview)
implementation(libs.materialish.progress)
implementation(libs.greenrobot.eventbus)
implementation(libs.google.zxing.android.integration)
implementation(libs.google.zxing.core)
implementation(libs.google.flexbox)
implementation(libs.subsampling.scale.image.view) {
exclude(group = "com.android.support", module = "support-annotations")
}
implementation(libs.android.tooltips) {
exclude(group = "com.android.support", module = "appcompat-v7")
}
implementation(libs.stream)
implementation(libs.lottie)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
implementation(libs.google.ez.vcard) {
exclude(group = "com.fasterxml.jackson.core")
exclude(group = "org.freemarker")
}
implementation(libs.dnsjava)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.accompanist.permissions)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlin.reflect)
implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
implementation(libs.rxdogtag)
"spinnerImplementation"(project(":spinner"))
"canaryImplementation"(libs.square.leakcanary)
"instrumentationImplementation"(libs.androidx.fragment.testing) {
exclude(group = "androidx.test", module = "core")
}
testImplementation(testLibs.junit.junit)
testImplementation(testLibs.assertj.core)
testImplementation(testLibs.mockito.core)
testImplementation(testLibs.mockito.kotlin)
testImplementation(testLibs.androidx.test.core)
testImplementation(testLibs.robolectric.robolectric) {
exclude(group = "com.google.protobuf", module = "protobuf-java")
}
testImplementation(testLibs.robolectric.shadows.multidex)
testImplementation(testLibs.bouncycastle.bcprov.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.bouncycastle.bcpkix.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.conscrypt.openjdk.uber)
testImplementation(testLibs.hamcrest.hamcrest)
testImplementation(testLibs.mockk)
testImplementation(testFixtures(project(":libsignal-service")))
testImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.ext.junit)
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.mockk.android)
androidTestImplementation(testLibs.square.okhttp.mockserver)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
fun assertIsGitRepo() {
if (!file("${project.rootDir}/.git").exists()) {
throw IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
}
fun getLastCommitTimestamp(): String {
assertIsGitRepo()
ByteArrayOutputStream().use { os ->
exec {
executable = "git"
args = listOf("log", "-1", "--pretty=format:%ct")
standardOutput = os
}
return os.toString() + "000"
}
}
fun getGitHash(): String {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "HEAD")
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
fun getCurrentGitTag(): String? {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "tag", "--points-at", "HEAD")
standardOutput = stdout
}
val output: String = stdout.toString().trim()
return if (output.isNotEmpty()) {
val tags = output.split("\n").toList()
tags.firstOrNull { it.contains("nightly") } ?: tags[0]
} else {
null
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
}
}
project.tasks.configureEach {
if (name.lowercase().contains("nightly") && name != "checkNightlyParams") {
dependsOn(tasks.getByName("checkNightlyParams"))
}
}
tasks.register("checkNightlyParams") {
doFirst {
if (project.gradle.startParameter.taskNames.any { it.lowercase().contains("nightly") }) {
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
throw GradleException("Cannot find 'nightly-url.txt' for nightly build! It must exist in the root of this project and contain the location of the nightly manifest.")
}
}
}
}
fun loadKeystoreProperties(filename: String): Properties? {
val keystorePropertiesFile = file("${project.rootDir}/$filename")
return if (keystorePropertiesFile.exists()) {
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
keystoreProperties
} else {
null
}
}
fun getDateSuffix(): String {
return SimpleDateFormat("yyyy-MM-dd-HH:mm").format(Date())
}
fun getMapsKey(): String {
val mapKey = file("${project.rootDir}/maps.key")
return if (mapKey.exists()) {
mapKey.readLines()[0]
} else {
"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
}
fun Project.languageList(): List<String> {
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct() + "en"
}
fun String.capitalize(): String {
return this.replaceFirstChar { it.uppercase() }
}
+9 -2
View File
@@ -2,8 +2,8 @@
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
@@ -11,4 +11,11 @@
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }
-keep class androidx.window.** { *; }
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

@@ -26,15 +26,27 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
}
override fun initializeLogging() {
persistentLogger = PersistentLogger(this)
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
SignalExecutors.UNBOUNDED.execute {
Log.blockUntilAllWritesFinished()
LogDatabase.getInstance(this).trimToSize()
LogDatabase.getInstance(this).logs.trimToSize()
}
}
override fun beginJobLoop() = Unit
/**
* Some of the jobs can interfere with some of the instrumentation tests.
*
* For example, we may try to create a release channel recipient while doing
* an import/backup test.
*
* This can be used to start the job loop if needed for tests that rely on it.
*/
fun beginJobLoopForTests() {
super.beginJobLoop()
}
}
@@ -0,0 +1,675 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2
import android.content.ContentValues
import android.database.Cursor
import androidx.core.content.contentValuesOf
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.junit.Before
import org.junit.Test
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
import org.signal.core.util.insertInto
import org.signal.core.util.readToList
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.signal.core.util.requireString
import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.backup.v2.database.clearAllDataForBackupRestore
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.EmojiSearchTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.subscription.Subscriber
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.io.ByteArrayInputStream
import java.util.UUID
import kotlin.random.Random
typealias DatabaseData = Map<String, List<Map<String, Any?>>>
class BackupTest {
companion object {
val SELF_ACI = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
val ALICE_ACI = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val ALICE_PNI = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
val ALICE_E164 = "+12222222222"
/** Columns that we don't need to check equality of */
private val IGNORED_COLUMNS: Map<String, Set<String>> = mapOf(
RecipientTable.TABLE_NAME to setOf(RecipientTable.STORAGE_SERVICE_ID),
MessageTable.TABLE_NAME to setOf(MessageTable.FROM_DEVICE_ID)
)
/** Tables we don't need to check equality of */
private val IGNORED_TABLES: Set<String> = setOf(
EmojiSearchTable.TABLE_NAME,
"sqlite_sequence",
"message_fts_data",
"message_fts_idx",
"message_fts_docsize"
)
}
@Before
fun setup() {
SignalStore.account().setE164(SELF_E164)
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
}
@Test
fun emptyDatabase() {
backupTest { }
}
@Test
fun noteToSelf() {
backupTest {
individualChat(aci = SELF_ACI, givenName = "Note to Self") {
standardMessage(outgoing = true, body = "A")
standardMessage(outgoing = true, body = "B")
standardMessage(outgoing = true, body = "C")
}
}
}
@Test
fun individualChat() {
backupTest {
individualChat(aci = ALICE_ACI, givenName = "Alice") {
val m1 = standardMessage(outgoing = true, body = "Outgoing 1")
val m2 = standardMessage(outgoing = false, body = "Incoming 1", read = true)
standardMessage(outgoing = true, body = "Outgoing 2", quotes = m2)
standardMessage(outgoing = false, body = "Incoming 2", quotes = m1, quoteTargetMissing = true, read = false)
standardMessage(outgoing = true, body = "Outgoing 3, with mention", randomMention = true)
standardMessage(outgoing = false, body = "Incoming 3, with style", read = false, randomStyling = true)
remoteDeletedMessage(outgoing = true)
remoteDeletedMessage(outgoing = false)
}
}
}
@Test
fun individualRecipients() {
backupTest {
// Comprehensive example
individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
// Trying to get coverage of all the various values
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.NOT_REGISTERED)
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.UNKNOWN)
individualRecipient(pni = PNI.from(UUID.randomUUID()))
individualRecipient(e164 = "+15551234567")
individualRecipient(aci = ACI.from(UUID.randomUUID()), givenName = "Bob")
individualRecipient(aci = ACI.from(UUID.randomUUID()), familyName = "Smith")
individualRecipient(aci = ACI.from(UUID.randomUUID()), profileSharing = false)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hideStory = true)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hidden = true)
}
}
@Test
fun individualCallLogs() {
backupTest {
val aliceId = individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
insertOneToOneCallVariations(1, 1, aliceId)
}
}
private fun insertOneToOneCallVariations(callId: Long, timestamp: Long, id: RecipientId): Long {
val directions = arrayOf(CallTable.Direction.INCOMING, CallTable.Direction.OUTGOING)
val callTypes = arrayOf(CallTable.Type.AUDIO_CALL, CallTable.Type.VIDEO_CALL)
val events = arrayOf(
CallTable.Event.MISSED,
CallTable.Event.OUTGOING_RING,
CallTable.Event.ONGOING,
CallTable.Event.ACCEPTED,
CallTable.Event.NOT_ACCEPTED
)
var callTimestamp: Long = timestamp
var currentCallId = callId
for (direction in directions) {
for (event in events) {
for (type in callTypes) {
insertOneToOneCall(callId = currentCallId, callTimestamp, id, type, direction, event)
callTimestamp++
currentCallId++
}
}
}
return currentCallId
}
private fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: CallTable.Type, direction: CallTable.Direction, event: CallTable.Event) {
val messageType: Long = CallTable.Call.getMessageType(type, direction, event)
SignalDatabase.rawDatabase.withinTransaction {
val recipient = Recipient.resolved(peer)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val outgoing = direction == CallTable.Direction.OUTGOING
val messageValues = contentValuesOf(
MessageTable.FROM_RECIPIENT_ID to if (outgoing) Recipient.self().id.serialize() else peer.serialize(),
MessageTable.FROM_DEVICE_ID to 1,
MessageTable.TO_RECIPIENT_ID to if (outgoing) peer.serialize() else Recipient.self().id.serialize(),
MessageTable.DATE_RECEIVED to timestamp,
MessageTable.DATE_SENT to timestamp,
MessageTable.READ to 1,
MessageTable.TYPE to messageType,
MessageTable.THREAD_ID to threadId
)
val messageId = SignalDatabase.rawDatabase.insert(MessageTable.TABLE_NAME, null, messageValues)
val values = contentValuesOf(
CallTable.CALL_ID to callId,
CallTable.MESSAGE_ID to messageId,
CallTable.PEER to peer.serialize(),
CallTable.TYPE to CallTable.Type.serialize(type),
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
CallTable.EVENT to CallTable.Event.serialize(event),
CallTable.TIMESTAMP to timestamp
)
SignalDatabase.rawDatabase.insert(CallTable.TABLE_NAME, null, values)
SignalDatabase.threads.update(threadId, true)
}
}
@Test
fun accountData() {
val context = ApplicationDependencies.getApplication()
backupTest(validateKeyValue = true) {
val self = Recipient.self()
// TODO note-to-self archived
// TODO note-to-self unread
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().setE164(SELF_E164)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
SignalDatabase.recipients.setProfileKey(self.id, ProfileKey(Random.nextBytes(32)))
SignalDatabase.recipients.setProfileName(self.id, ProfileName.fromParts("Peter", "Parker"))
SignalDatabase.recipients.setProfileAvatar(self.id, "https://example.com/")
SignalStore.donationsValues().markUserManuallyCancelled()
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
SignalStore.settings().isLinkPreviewsEnabled = false
SignalStore.settings().isPreferSystemContactPhotos = true
SignalStore.settings().universalExpireTimer = 42
SignalStore.settings().setKeepMutedChatsArchived(true)
SignalStore.storyValues().viewedReceiptsEnabled = false
SignalStore.storyValues().userHasViewedOnboardingStory = true
SignalStore.storyValues().isFeatureDisabled = false
SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
SignalStore.storyValues().userHasSeenGroupStoryEducationSheet = true
SignalStore.emojiValues().reactions = listOf("a", "b", "c")
TextSecurePreferences.setTypingIndicatorsEnabled(context, false)
TextSecurePreferences.setReadReceiptsEnabled(context, false)
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, true)
}
// Have to check TextSecurePreferences ourselves, since they're not in a database
TextSecurePreferences.isTypingIndicatorsEnabled(context) assertIs false
TextSecurePreferences.isReadReceiptsEnabled(context) assertIs false
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context) assertIs true
}
/**
* Sets up the database, then executes your setup code, then compares snapshots of the database
* before an after an import to ensure that no data was lost/changed.
*
* @param validateKeyValue If true, this will also validate the KeyValueDatabase. You only want to do this if you
* intend on setting most of the values. Otherwise stuff tends to not match since values are lazily written.
*/
private fun backupTest(validateKeyValue: Boolean = false, content: () -> Unit) {
// Under normal circumstances, My Story ends up being the first recipient in the table, and is added automatically.
// This screws with the tests by offsetting all the recipientIds in the initial state.
// Easiest way to get around this is to make the DB a true clean slate by clearing everything.
// (We only really need to clear Recipient/dlists, but doing everything to be consistent.)
SignalDatabase.distributionLists.clearAllDataForBackupRestore()
SignalDatabase.recipients.clearAllDataForBackupRestore()
SignalDatabase.messages.clearAllDataForBackupRestore()
SignalDatabase.threads.clearAllDataForBackupRestore()
// Again, for comparison purposes, because we always import self first, we want to ensure it's the first item
// in the table when we export.
individualRecipient(
aci = SELF_ACI,
pni = SELF_PNI,
e164 = SELF_E164,
profileKey = SELF_PROFILE_KEY,
profileSharing = true
)
content()
val startingMainData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val startingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
val exported: ByteArray = BackupRepository.export()
BackupRepository.import(length = exported.size.toLong(), inputStreamFactory = { ByteArrayInputStream(exported) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
val endingData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val endingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
assertDatabaseMatches(startingMainData, endingData)
assertDatabaseMatches(startingKeyValueData, endingKeyValueData)
}
private fun individualChat(aci: ACI, givenName: String, familyName: String? = null, init: IndividualChatCreator.() -> Unit) {
val recipientId = individualRecipient(aci = aci, givenName = givenName, familyName = familyName, profileSharing = true)
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, false)
IndividualChatCreator(SignalDatabase.rawDatabase, recipientId, threadId).init()
SignalDatabase.threads.update(threadId, false)
}
private fun individualRecipient(
aci: ACI? = null,
pni: PNI? = null,
e164: String? = null,
givenName: String? = null,
familyName: String? = null,
username: String? = null,
hidden: Boolean = false,
registeredState: RecipientTable.RegisteredState = RecipientTable.RegisteredState.UNKNOWN,
profileKey: ProfileKey? = null,
profileSharing: Boolean = false,
hideStory: Boolean = false
): RecipientId {
check(aci != null || pni != null || e164 != null)
val recipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci, pni, e164, pniVerified = true, changeSelf = true)
if (givenName != null || familyName != null) {
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts(givenName, familyName))
}
if (username != null) {
SignalDatabase.recipients.setUsername(recipientId, username)
}
if (registeredState == RecipientTable.RegisteredState.REGISTERED) {
SignalDatabase.recipients.markRegistered(recipientId, aci ?: pni!!)
} else if (registeredState == RecipientTable.RegisteredState.NOT_REGISTERED) {
SignalDatabase.recipients.markUnregistered(recipientId)
}
if (profileKey != null) {
SignalDatabase.recipients.setProfileKey(recipientId, profileKey)
}
SignalDatabase.recipients.setProfileSharing(recipientId, profileSharing)
SignalDatabase.recipients.setHideStory(recipientId, hideStory)
if (hidden) {
SignalDatabase.recipients.markHidden(recipientId)
}
return recipientId
}
private inner class IndividualChatCreator(
private val db: SQLiteDatabase,
private val recipientId: RecipientId,
private val threadId: Long
) {
fun standardMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
body = body,
read = read,
quotes = quotes,
quoteTargetMissing = quoteTargetMissing,
randomMention = randomMention,
randomStyling = randomStyling
)
}
fun remoteDeletedMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
remoteDeleted = true
)
}
}
private fun SQLiteDatabase.insertMessage(
from: RecipientId,
to: RecipientId,
outgoing: Boolean,
threadId: Long,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false,
remoteDeleted: Boolean = false
): Long {
val type = if (outgoing) {
MessageTypes.BASE_SENT_TYPE
} else {
MessageTypes.BASE_INBOX_TYPE
} or MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT
val contentValues = ContentValues()
contentValues.put(MessageTable.DATE_SENT, sentTimestamp)
contentValues.put(MessageTable.DATE_RECEIVED, receivedTimestamp)
contentValues.put(MessageTable.FROM_RECIPIENT_ID, from.serialize())
contentValues.put(MessageTable.TO_RECIPIENT_ID, to.serialize())
contentValues.put(MessageTable.THREAD_ID, threadId)
contentValues.put(MessageTable.BODY, body)
contentValues.put(MessageTable.TYPE, type)
contentValues.put(MessageTable.READ, if (read) 1 else 0)
if (!outgoing) {
contentValues.put(MessageTable.DATE_SERVER, serverTimestamp)
}
if (remoteDeleted) {
contentValues.put(MessageTable.REMOTE_DELETED, 1)
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
if (quotes != null) {
val quoteDetails = this.getQuoteDetailsFor(quotes)
contentValues.put(MessageTable.QUOTE_ID, if (quoteTargetMissing) MessageTable.QUOTE_TARGET_MISSING_ID else quoteDetails.quotedSentTimestamp)
contentValues.put(MessageTable.QUOTE_AUTHOR, quoteDetails.authorId.serialize())
contentValues.put(MessageTable.QUOTE_BODY, quoteDetails.body)
contentValues.put(MessageTable.QUOTE_BODY_RANGES, quoteDetails.bodyRanges)
contentValues.put(MessageTable.QUOTE_TYPE, quoteDetails.type)
contentValues.put(MessageTable.QUOTE_MISSING, quoteTargetMissing.toInt())
}
if (body != null && (randomMention || randomStyling)) {
val ranges: MutableList<BodyRangeList.BodyRange> = mutableListOf()
if (randomMention) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
mentionUuid = if (outgoing) Recipient.resolved(to).requireAci().toString() else Recipient.resolved(from).requireAci().toString()
)
}
if (randomStyling) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
style = BodyRangeList.BodyRange.Style.fromValue(Random.nextInt(BodyRangeList.BodyRange.Style.values().size))
)
}
contentValues.put(MessageTable.MESSAGE_RANGES, BodyRangeList(ranges = ranges).encode())
}
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
private fun assertDatabaseMatches(expected: DatabaseData, actual: DatabaseData) {
assert(expected.keys.size == actual.keys.size) { "Mismatched table count! Expected: ${expected.keys} || Actual: ${actual.keys}" }
assert(expected.keys.containsAll(actual.keys)) { "Table names differ! Expected: ${expected.keys} || Actual: ${actual.keys}" }
val tablesToCheck = expected.keys.filter { !IGNORED_TABLES.contains(it) }
for (table in tablesToCheck) {
val expectedTable: List<Map<String, Any?>> = expected[table]!!
val actualTable: List<Map<String, Any?>> = actual[table]!!
assert(expectedTable.size == actualTable.size) { "Mismatched number of rows for table '$table'! Expected: ${expectedTable.size} || Actual: ${actualTable.size}\n $actualTable" }
val expectedFiltered: List<Map<String, Any?>> = expectedTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
val actualFiltered: List<Map<String, Any?>> = actualTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
assert(contentEquals(expectedFiltered, actualFiltered)) { "Data did not match for table '$table'!\n${prettyDiff(expectedFiltered, actualFiltered)}" }
}
}
private fun contentEquals(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): Boolean {
if (expectedRows == actualRows) {
return true
}
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
return false
}
}
}
return true
}
private fun contentEquals(lhs: Any?, rhs: Any?): Boolean {
return if (lhs is ByteArray && rhs is ByteArray) {
lhs.contentEquals(rhs)
} else {
lhs == rhs
}
}
private fun prettyDiff(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): String {
val builder = StringBuilder()
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
var describedRow = false
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
if (!describedRow) {
builder.append("-- ROW ${i + 1}\n")
describedRow = true
}
builder.append("  [$key] Expected: ${expectedValue.prettyPrint()} || Actual: ${actualValue.prettyPrint()} \n")
}
}
if (describedRow) {
builder.append("\n")
builder.append("Expected: $expectedRow\n")
builder.append("Actual: $actualRow\n")
}
}
return builder.toString()
}
private fun Any?.prettyPrint(): String {
return when (this) {
is ByteArray -> "Bytes(${Hex.toString(this)})"
else -> this.toString()
}
}
private fun List<Map<String, Any?>>.withoutExcludedColumns(ignored: Set<String>?): List<Map<String, Any?>> {
return if (ignored != null) {
this.map { row ->
row.filterKeys { !ignored.contains(it) }
}
} else {
this
}
}
private fun SQLiteDatabase.getQuoteDetailsFor(messageId: Long): QuoteDetails {
return this
.select(
MessageTable.DATE_SENT,
MessageTable.FROM_RECIPIENT_ID,
MessageTable.BODY,
MessageTable.MESSAGE_RANGES
)
.from(MessageTable.TABLE_NAME)
.where("${MessageTable.ID} = ?", messageId)
.run()
.readToSingleObject { cursor ->
QuoteDetails(
quotedSentTimestamp = cursor.requireLong(MessageTable.DATE_SENT),
authorId = RecipientId.from(cursor.requireLong(MessageTable.FROM_RECIPIENT_ID)),
body = cursor.requireString(MessageTable.BODY),
bodyRanges = cursor.requireBlob(MessageTable.MESSAGE_RANGES),
type = QuoteModel.Type.NORMAL.code
)
}!!
}
private fun SQLiteDatabase.readAllContents(): DatabaseData {
return SqlUtil.getAllTables(this).associateWith { table -> this.getAllTableData(table) }
}
private fun SQLiteDatabase.getAllTableData(table: String): List<Map<String, Any?>> {
return this
.select()
.from(table)
.run()
.readToList { cursor ->
val map: MutableMap<String, Any?> = mutableMapOf()
for (i in 0 until cursor.columnCount) {
val column = cursor.getColumnName(i)
when (cursor.getType(i)) {
Cursor.FIELD_TYPE_INTEGER -> map[column] = cursor.getInt(i)
Cursor.FIELD_TYPE_FLOAT -> map[column] = cursor.getFloat(i)
Cursor.FIELD_TYPE_STRING -> map[column] = cursor.getString(i)
Cursor.FIELD_TYPE_BLOB -> map[column] = cursor.getBlob(i)
Cursor.FIELD_TYPE_NULL -> map[column] = null
}
}
map
}
}
private data class QuoteDetails(
val quotedSentTimestamp: Long,
val authorId: RecipientId,
val body: String?,
val bodyRanges: ByteArray?,
val type: Int
)
}
@@ -0,0 +1,48 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.whispersystems.signalservice.api.util.toByteArray
import java.util.UUID
import kotlin.random.Random
object TestRecipientUtils {
private var upperGenAci = 13131313L
private var lowerGenAci = 0L
private var upperGenPni = 12121212L
private var lowerGenPni = 0L
private var groupMasterKeyRandom = Random(12345)
fun generateProfileKey(): ByteArray {
return ProfileKeyUtil.createNew().serialize()
}
fun nextPni(): ByteArray {
synchronized(this) {
lowerGenPni++
var uuid = UUID(upperGenPni, lowerGenPni)
return uuid.toByteArray()
}
}
fun nextAci(): ByteArray {
synchronized(this) {
lowerGenAci++
var uuid = UUID(upperGenAci, lowerGenAci)
return uuid.toByteArray()
}
}
fun generateGroupMasterKey(): ByteArray {
val masterKey = ByteArray(32)
groupMasterKeyRandom.nextBytes(masterKey)
return masterKey
}
}
@@ -10,13 +10,11 @@ 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
@@ -37,6 +35,7 @@ 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.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@@ -48,20 +47,16 @@ class ChangeNumberViewModelTest {
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
verifyAccountRepository = VerifyAccountRepository(harness.application)
)
viewModel.setNewCountry(1)
@@ -78,7 +73,7 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -185,7 +180,7 @@ class ChangeNumberViewModelTest {
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -230,13 +225,11 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
@@ -274,7 +267,7 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -318,13 +311,11 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
@@ -8,8 +8,10 @@ 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.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
@@ -63,7 +65,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -72,7 +75,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
ThreadUtil.sleep(1)
}
@@ -82,7 +85,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -91,7 +95,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
val insert = SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) {
@@ -142,6 +146,8 @@ class ConversationItemPreviewer {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,
@@ -7,6 +7,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
@@ -0,0 +1,332 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.Observer
import com.bumptech.glide.RequestManager
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.FakeMessageRecords
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.testing.SignalActivityRule
import kotlin.time.Duration.Companion.minutes
class V2ConversationItemShapeTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
@Test
fun givenNextAndPreviousMessageDoNotExist_whenISetMessageShape_thenIExpectSingle() {
val testSubject = V2ConversationItemShape(FakeConversationContext())
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousWithinTimeoutAndNoNext_whenISetMessageShape_thenIExpectEnd() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextWithinTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectStart() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextWithinTimeout_whenISetMessageShape_thenIExpectMiddle() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val next = now + 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousOutsideTimeoutAndNoNext_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextOutsideTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextOutsideTimeout_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val next = now + 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
private fun getMessageRecord(
timestamp: Long = System.currentTimeMillis()
): MessageRecord {
return FakeMessageRecords.buildMediaMmsMessageRecord(
dateReceived = timestamp,
dateSent = timestamp,
dateServer = timestamp
)
}
private class FakeConversationContext(
private val hasWallpaper: Boolean = false,
private val previousMessage: MessageRecord? = null,
private val nextMessage: MessageRecord? = null
) : V2ConversationContext {
private val colorizer = Colorizer()
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true
override val searchQuery: String? = null
override val requestManager: RequestManager = mockk()
override val isParentInScroll: Boolean = false
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
override fun hasWallpaper(): Boolean = hasWallpaper
override fun getColorizer(): Colorizer = colorizer
override fun getNextMessage(adapterPosition: Int): MessageRecord? = nextMessage
override fun getPreviousMessage(adapterPosition: Int): MessageRecord? = previousMessage
}
private object FakeConversationItemClickListener : ConversationAdapter.ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) = Unit
override fun onLinkPreviewClicked(linkPreview: LinkPreview) = Unit
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) = Unit
override fun onStickerClicked(stickerLocator: StickerLocator) = Unit
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) = Unit
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) = Unit
override fun onAddToContactsClicked(contact: Contact) = Unit
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) = Unit
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) = Unit
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) = Unit
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) = Unit
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) = Unit
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onVoiceNotePause(uri: Uri) = Unit
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) = Unit
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) = Unit
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) = Unit
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) = Unit
override fun onChatSessionRefreshLearnMoreClicked() = Unit
override fun onBadDecryptLearnMoreClicked(author: RecipientId) = Unit
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) = Unit
override fun onJoinGroupCallClicked() = Unit
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) = Unit
override fun onEnableCallNotificationsClicked() = Unit
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) = Unit
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) = Unit
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) = Unit
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit
override fun onBlockJoinRequest(recipient: Recipient) = Unit
override fun onRecipientNameClicked(target: RecipientId) = Unit
override fun onInviteToSignalClicked() = Unit
override fun onActivatePaymentsClicked() = Unit
override fun onSendPaymentClicked(recipientId: RecipientId) = Unit
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit
override fun onUrlClicked(url: String): Boolean = false
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) = Unit
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) = Unit
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) = Unit
override fun onItemClick(item: MultiselectPart?) = Unit
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
override fun onShowSafetyTips(forGroup: Boolean) = Unit
override fun onReportSpamLearnMoreClicked() = Unit
override fun onMessageRequestAcceptOptionsClicked() = Unit
}
}
@@ -6,12 +6,16 @@ import androidx.test.filters.FlakyTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.AttachmentId
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.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
@@ -47,23 +51,22 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
false
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3)),
false
createMediaStream(byteArrayOf(1, 2, 3))
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
@FlakyTest
@Ignore("test is flaky")
@Test
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
@@ -74,22 +77,92 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
true
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3, 4)),
true
createMediaStream(byteArrayOf(1, 2, 3, 4))
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
/**
* Given: A previous attachment and two pre-upload attachments with the same data but different transform properties (standard and high).
*
* When changing content of standard pre-upload attachment to match pre-existing attachment
*
* Then update standard pre-upload attachment to match previous attachment, do not update high pre-upload attachment, and do
* not delete shared pre-upload uri from disk as it is still being used by the high pre-upload attachment.
*/
@Test
fun doNotDeleteDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val compressedData = byteArrayOf(1, 2, 3)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val previousAttachment = createAttachment(1, BlobProvider.getInstance().forData(compressedData).createForSingleSessionInMemory(), AttachmentTable.TransformProperties.empty())
val previousDatabaseAttachmentId: AttachmentId = SignalDatabase.attachments.insertAttachmentsForMessage(1, listOf(previousAttachment), emptyList()).values.first()
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
// WHEN
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData))
// THEN
val previousInfo = SignalDatabase.attachments.getDataFileInfo(previousDatabaseAttachmentId)!!
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
assertNotEquals(standardInfo, highInfo)
highInfo.file assertIsNot standardInfo.file
highInfo.file.exists() assertIs true
}
/**
* Given: Three pre-upload attachments with the same data but different transform properties (1x standard and 2x high).
*
* When inserting content of high pre-upload attachment.
*
* Then do not deduplicate with standard pre-upload attachment, but do deduplicate second high insert.
*/
@Test
fun doNotDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
// WHEN
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
val secondHighQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
// THEN
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
val secondHighInfo = SignalDatabase.attachments.getDataFileInfo(secondHighDatabaseAttachment.attachmentId)!!
highInfo.file assertIsNot standardInfo.file
secondHighInfo.file assertIs highInfo.file
standardInfo.file.exists() assertIs true
highInfo.file.exists() assertIs true
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,
@@ -0,0 +1,804 @@
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
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.core.util.Base64
import org.signal.core.util.update
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MediaUtil
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.File
import java.util.UUID
import kotlin.random.Random
import kotlin.time.Duration.Companion.days
/**
* Collection of [AttachmentTable] tests focused around deduping logic.
*/
@RunWith(AndroidJUnit4::class)
class AttachmentTableTest_deduping {
companion object {
val DATA_A = byteArrayOf(1, 2, 3)
val DATA_A_COMPRESSED = byteArrayOf(4, 5, 6)
val DATA_A_HASH = byteArrayOf(1, 1, 1)
val DATA_B = byteArrayOf(7, 8, 9)
}
@Before
fun setUp() {
SignalStore.account().setAci(ServiceId.ACI.from(UUID.randomUUID()))
SignalStore.account().setPni(ServiceId.PNI.from(UUID.randomUUID()))
SignalStore.account().setE164("+15558675309")
SignalDatabase.attachments.deleteAllAttachments()
}
/**
* Creates two different files with different data. Should not dedupe.
*/
@Test
fun differentFiles() {
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_B)
assertDataFilesAreDifferent(id1, id2)
}
}
/**
* Inserts files with identical data but with transform properties that make them incompatible. Should not dedupe.
*/
@Test
fun identicalFiles_incompatibleTransforms() {
// Non-matching qualities
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim flag
test {
val id1 = insertWithData(DATA_A, TransformProperties())
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim start time
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim end time
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 1))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching mp4 fast start
test {
val id1 = insertWithData(DATA_A, TransformProperties(mp4FastStart = true))
val id2 = insertWithData(DATA_A, TransformProperties(mp4FastStart = false))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
}
/**
* Inserts files with identical data and compatible transform properties. Should dedupe.
*/
@Test
fun identicalFiles_compatibleTransforms() {
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
}
/**
* Walks through various scenarios where files are compressed and uploaded.
*/
@Test
fun compressionAndUploads() {
// Matches after the first is compressed, skip transform properly set
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Matches after the first is uploaded, skip transform and ending hash properly set
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Mimics sending two files at once. Ensures all fields are kept in sync as we compress and upload.
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
compress(id1, DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
upload(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
// Re-use the upload when uploaded recently
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Do not re-use old uploads
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis() - 100.days.inWholeMilliseconds)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
assertDoesNotHaveRemoteFields(id2)
}
// This isn't so much "desirable behavior" as it is documenting how things work.
// If an attachment is compressed but not uploaded yet, it will have a DATA_HASH_START that doesn't match the actual file content.
// This means that if we insert a new attachment with data that matches the compressed data, we won't find a match.
// This is ok because we don't allow forwarding unsent messages, so the chances of the user somehow sending a file that matches data we compressed are very low.
// What *is* more common is that the user may send DATA_A again, and in this case we will still catch the dedupe (which is already tested above).
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreDifferent(id1, id2)
}
// This represents what would happen if you forward an already-send compressed attachment. We should match, skip transform, and skip upload.
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it. We should match, skip transform, and skip upload.
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it, but *edited the forwarded video*. We should not dedupe.
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, false)
assertDoesNotHaveRemoteFields(id2)
}
// This represents what would happen if you sent an image using standard quality, then forwarded it using high quality.
// Since you're forwarding, it doesn't matter if the new thing has a higher quality, we should still match and skip transform.
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you sent an image using high quality, then forwarded it using standard quality.
// Since you're forwarding, it doesn't matter if the new thing has a lower quality, we should still match and skip transform.
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// Make sure that files marked as unhashable are all updated together
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
upload(id1)
upload(id2)
clearHashes(id1)
clearHashes(id2)
val file = dataFile(id1)
SignalDatabase.attachments.markDataFileAsUnhashable(file)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id1)!!
assertTrue(dataFileInfo.hashEnd!!.startsWith("UNHASHABLE-"))
}
}
/**
* Various deletion scenarios to ensure that duped files don't deleted while there's still references.
*/
@Test
fun deletions() {
// Delete original then dupe
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
val dataFile = dataFile(id1)
assertDataFilesAreTheSame(id1, id2)
delete(id1)
assertDeleted(id1)
assertRowAndFileExists(id2)
assertTrue(dataFile.exists())
delete(id2)
assertDeleted(id2)
assertFalse(dataFile.exists())
}
// Delete dupe then original
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
val dataFile = dataFile(id1)
assertDataFilesAreTheSame(id1, id2)
delete(id2)
assertDeleted(id2)
assertRowAndFileExists(id1)
assertTrue(dataFile.exists())
delete(id1)
assertDeleted(id1)
assertFalse(dataFile.exists())
}
// Delete original after it was compressed
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A)
delete(id1)
assertDeleted(id1)
assertRowAndFileExists(id2)
assertSkipTransform(id2, true)
}
// Quotes are weak references and should not prevent us from deleting the file
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
val dataFile = dataFile(id1)
delete(id1)
assertDeleted(id1)
assertRowExists(id2)
assertFalse(dataFile.exists())
}
}
@Test
fun quotes() {
// Basic quote deduping
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Making sure remote fields carry
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
upload(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
// Making sure things work for quotes of videos, which have trickier transform properties
test {
val id1 = insertWithData(DATA_A, transformProperties = TransformProperties.forVideoTrim(1, 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1)
val id2 = insertQuote(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
}
/**
* Suite of tests around the migration where we hash all of the attachments and potentially dedupe them.
*/
@Test
fun migration() {
// Verifying that getUnhashedDataFile only returns if there's actually missing hashes
test {
val id = insertWithData(DATA_A)
upload(id)
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// Verifying that getUnhashedDataFile finds the missing hash
test {
val id = insertWithData(DATA_A)
upload(id)
clearHashes(id)
assertNotNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// Verifying that getUnhashedDataFile doesn't return if the file isn't done downloading
test {
val id = insertWithData(DATA_A)
upload(id)
setTransferState(id, AttachmentTable.TRANSFER_PROGRESS_PENDING)
clearHashes(id)
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// If two attachments share the same file, when we backfill the hash, make sure both get their hashes set
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
upload(id1)
upload(id2)
clearHashes(id1)
clearHashes(id2)
val file = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
assertDataHashEndMatches(id1, id2)
}
// Creates a situation where two different attachments have the same data but wrote to different files, and verifies the migration dedupes it
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
upload(id2)
clearHashes(id2)
assertDataFilesAreDifferent(id1, id2)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
assertDataFilesAreDifferent(id1, id2)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertFalse(file2.exists())
}
// We've got three files now with the same data, with two of them sharing a file. We want to make sure *both* entries that share the same file get deduped.
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
val id3 = insertWithData(DATA_A)
upload(id2)
upload(id3)
clearHashes(id2)
clearHashes(id3)
assertDataFilesAreDifferent(id1, id2)
assertDataFilesAreTheSame(id2, id3)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertDataHashEndMatches(id2, id3)
assertFalse(file2.exists())
}
// We don't want to mess with files that are still downloading, so this makes sure that even if data matches, we don't dedupe and don't delete the file
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
// *not* uploaded
clearHashes(id2)
assertDataFilesAreDifferent(id1, id2)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreDifferent(id1, id2)
assertTrue(file2.exists())
}
}
private class TestContext {
fun insertWithData(data: ByteArray, transformProperties: TransformProperties = TransformProperties.empty()): AttachmentId {
val uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
val attachment = UriAttachmentBuilder.build(
id = Random.nextLong(),
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = transformProperties
)
return SignalDatabase.attachments.insertAttachmentForPreUpload(attachment).attachmentId
}
fun insertQuote(attachmentId: AttachmentId): AttachmentId {
val originalAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.self())
val messageId = SignalDatabase.messages.insertMessageOutbox(
message = OutgoingMessage(
threadRecipient = Recipient.self(),
sentTimeMillis = System.currentTimeMillis(),
body = "some text",
outgoingQuote = QuoteModel(
id = 123,
author = Recipient.self().id,
text = "Some quote text",
isOriginalMissing = false,
attachments = listOf(originalAttachment),
mentions = emptyList(),
type = QuoteModel.Type.NORMAL,
bodyRanges = null
)
),
threadId = threadId,
forceSms = false,
insertListener = null
)
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId)
return attachments[0].attachmentId
}
fun compress(attachmentId: AttachmentId, newData: ByteArray, mp4FastStart: Boolean = false) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
SignalDatabase.attachments.updateAttachmentData(databaseAttachment, newData.asMediaStream())
SignalDatabase.attachments.markAttachmentAsTransformed(attachmentId, withFastStart = mp4FastStart)
}
fun upload(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()) {
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createPointerAttachment(attachmentId, uploadTimestamp), uploadTimestamp)
}
fun delete(attachmentId: AttachmentId) {
SignalDatabase.attachments.deleteAttachment(attachmentId)
}
fun dataFile(attachmentId: AttachmentId): File {
return SignalDatabase.attachments.getDataFileInfo(attachmentId)!!.file
}
fun setTransferState(attachmentId: AttachmentId, transferState: Int) {
// messageId doesn't actually matter -- that's for notifying listeners
SignalDatabase.attachments.setTransferState(messageId = -1, attachmentId = attachmentId, transferState = transferState)
}
fun clearHashes(id: AttachmentId) {
SignalDatabase.attachments.writableDatabase
.update(AttachmentTable.TABLE_NAME)
.values(
AttachmentTable.DATA_HASH_START to null,
AttachmentTable.DATA_HASH_END to null
)
.where("${AttachmentTable.ID} = ?", id)
.run()
}
fun assertDeleted(attachmentId: AttachmentId) {
assertNull("$attachmentId exists, but it shouldn't!", SignalDatabase.attachments.getAttachment(attachmentId))
}
fun assertRowAndFileExists(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
assertNotNull("$attachmentId does not exist!", databaseAttachment)
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(attachmentId)
assertTrue("The file for $attachmentId does not exist!", dataFileInfo!!.file.exists())
}
fun assertRowExists(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
assertNotNull("$attachmentId does not exist!", databaseAttachment)
}
fun assertDataFilesAreTheSame(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assert(lhsInfo.file.exists())
assert(rhsInfo.file.exists())
assertEquals(lhsInfo.file, rhsInfo.file)
assertEquals(lhsInfo.length, rhsInfo.length)
assertArrayEquals(lhsInfo.random, rhsInfo.random)
}
fun assertDataFilesAreDifferent(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assert(lhsInfo.file.exists())
assert(rhsInfo.file.exists())
assertNotEquals(lhsInfo.file, rhsInfo.file)
}
fun assertDataHashStartMatches(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assertNotNull(lhsInfo.hashStart)
assertEquals("DATA_HASH_START's did not match!", lhsInfo.hashStart, rhsInfo.hashStart)
}
fun assertDataHashEndMatches(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assertNotNull(lhsInfo.hashEnd)
assertEquals("DATA_HASH_END's did not match!", lhsInfo.hashEnd, rhsInfo.hashEnd)
}
fun assertDataHashEnd(id: AttachmentId, byteArray: ByteArray) {
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id)!!
assertArrayEquals(byteArray, Base64.decode(dataFileInfo.hashEnd!!))
}
fun assertRemoteFieldsMatch(lhs: AttachmentId, rhs: AttachmentId) {
val lhsAttachment = SignalDatabase.attachments.getAttachment(lhs)!!
val rhsAttachment = SignalDatabase.attachments.getAttachment(rhs)!!
assertEquals(lhsAttachment.remoteLocation, rhsAttachment.remoteLocation)
assertEquals(lhsAttachment.remoteKey, rhsAttachment.remoteKey)
assertArrayEquals(lhsAttachment.remoteDigest, rhsAttachment.remoteDigest)
assertArrayEquals(lhsAttachment.incrementalDigest, rhsAttachment.incrementalDigest)
assertEquals(lhsAttachment.incrementalMacChunkSize, rhsAttachment.incrementalMacChunkSize)
assertEquals(lhsAttachment.cdnNumber, rhsAttachment.cdnNumber)
}
fun assertDoesNotHaveRemoteFields(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
assertEquals(0, databaseAttachment.uploadTimestamp)
assertNull(databaseAttachment.remoteLocation)
assertNull(databaseAttachment.remoteDigest)
assertNull(databaseAttachment.remoteKey)
assertEquals(0, databaseAttachment.cdnNumber)
}
fun assertSkipTransform(attachmentId: AttachmentId, state: Boolean) {
val transformProperties = SignalDatabase.attachments.getTransformProperties(attachmentId)!!
assertEquals("Incorrect skipTransform!", transformProperties.skipTransform, state)
}
private fun ByteArray.asMediaStream(): MediaStream {
return MediaStream(this.inputStream(), MediaUtil.IMAGE_JPEG, 2, 2)
}
private fun createPointerAttachment(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()): PointerAttachment {
val location = "somewhere-${Random.nextLong()}"
val key = "somekey-${Random.nextLong()}"
val digest = Random.nextBytes(32)
val incrementalDigest = Random.nextBytes(16)
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
return PointerAttachment(
"image/jpeg",
AttachmentTable.TRANSFER_PROGRESS_DONE,
databaseAttachment.size, // size
null,
3, // cdnNumber
location,
key,
digest,
incrementalDigest,
5, // incrementalMacChunkSize
null,
databaseAttachment.voiceNote,
databaseAttachment.borderless,
databaseAttachment.videoGif,
databaseAttachment.width,
databaseAttachment.height,
uploadTimestamp,
databaseAttachment.caption,
databaseAttachment.stickerLocator,
databaseAttachment.blurHash
)
}
}
private fun test(content: TestContext.() -> Unit) {
SignalDatabase.attachments.deleteAllAttachments()
val context = TestContext()
context.content()
}
}
@@ -0,0 +1,102 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallLinkTableTest {
companion object {
private val ROOM_ID_A = byteArrayOf(1, 2, 3, 4)
private val ROOM_ID_B = byteArrayOf(2, 2, 3, 4)
private const val TIMESTAMP_A = 1000L
private const val TIMESTAMP_B = 2000L
}
@get:Rule
val harness = SignalActivityRule(createGroup = true)
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteBeforeFirst_thenIExpectNeitherDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A - 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(2, callEvents.size)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteOnFirst_thenIExpectFirstDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(1, callEvents.size)
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteAfterFirstAndBeforeSecond_thenIExpectFirstDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B - 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(1, callEvents.size)
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteOnSecond_thenIExpectBothDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(0, callEvents.size)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteAfterSecond_thenIExpectBothDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B + 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(0, callEvents.size)
}
private fun insertTwoNonAdminCallLinksWithEvents() {
insertCallLinkWithEvent(ROOM_ID_A, 1000)
insertCallLinkWithEvent(ROOM_ID_B, 2000)
}
private fun insertCallLinkWithEvent(roomId: ByteArray, timestamp: Long) {
SignalDatabase.callLinks.insertCallLink(
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
roomId = CallLinkRoomId.fromBytes(roomId),
credentials = CallLinkCredentials(
linkKeyBytes = roomId,
adminPassBytes = null
),
state = SignalCallLinkState()
)
)
val callLinkRecipient = SignalDatabase.recipients.getByCallLinkRoomId(CallLinkRoomId.fromBytes(roomId)).get()
SignalDatabase.calls.insertAcceptedGroupCall(
1,
callLinkRecipient,
CallTable.Direction.INCOMING,
timestamp
)
}
}
@@ -10,13 +10,18 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.ringrtc.CallId
import org.signal.ringrtc.CallManager
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallTableTest {
@get:Rule
val harness = SignalActivityRule()
val harness = SignalActivityRule(createGroup = true)
private val groupRecipientId: RecipientId
get() = harness.group!!.recipientId
@Test
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
@@ -24,13 +29,13 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.setTimestamp(callId, harness.others[0], -1L)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
SignalDatabase.calls.setTimestamp(callId, groupRecipientId, -1L)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(-1L, call?.timestamp)
@@ -45,15 +50,15 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.deleteGroupCall(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
@@ -66,12 +71,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -86,12 +91,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
assertEquals(harness.self.id, call?.ringerRecipient)
@@ -103,12 +108,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.JOINED, call?.event)
assertNull(call?.ringerRecipient)
@@ -120,13 +125,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
@@ -134,7 +139,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -143,13 +148,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
@@ -157,7 +162,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -166,13 +171,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
@@ -180,7 +185,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -189,7 +194,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -197,7 +202,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
@@ -205,16 +210,134 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
}
@Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
fun givenAnOutgoingRingCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId = callId,
recipientId = groupRecipientId,
direction = CallTable.Direction.OUTGOING,
timestamp = 1
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenARingingCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAMissedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenADeclinedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAnAcceptedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(
SignalDatabase.calls.getCallById(callId, groupRecipientId)!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAGenericGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -222,7 +345,58 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenAJoinedGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(SignalDatabase.calls.getCallById(callId, groupRecipientId)!!)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
}
@@ -233,7 +407,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -241,13 +415,13 @@ class CallTableTest {
isCallFull = false
)
SignalDatabase.calls.getCallById(callId, harness.others[0]).let {
SignalDatabase.calls.getCallById(callId, groupRecipientId).let {
assertNotNull(it)
assertEquals(now, it?.timestamp)
}
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = 1L,
peekGroupCallEraId = "aaa",
@@ -255,7 +429,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
assertEquals(1L, call?.timestamp)
@@ -266,20 +440,20 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId = callId,
recipientId = harness.others[0],
recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DELETE, call?.event)
}
@@ -290,7 +464,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -300,13 +474,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -319,20 +493,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -344,7 +518,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -354,13 +528,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -372,7 +546,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -382,7 +556,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -390,13 +564,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -409,20 +583,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -434,20 +608,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -458,7 +632,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -468,7 +642,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -476,13 +650,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -493,7 +667,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -503,7 +677,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -511,13 +685,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -528,7 +702,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -538,13 +712,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -556,7 +730,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -564,13 +738,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -582,7 +756,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
@@ -590,13 +764,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -608,20 +782,20 @@ class CallTableTest {
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
}
@@ -631,13 +805,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -649,13 +823,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -667,13 +841,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.CANCELLED_BY_RINGER
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -685,13 +859,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -702,13 +876,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -719,13 +893,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertNotNull(call?.messageId)
@@ -736,15 +910,85 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
assertNotNull(call?.messageId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallB_thenOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallA_thenIDoNotDeleteAnyCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(2, allCallEvents.size)
assertEquals(2, allCallEvents[0].record.callId)
assertEquals(1, allCallEvents[1].record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallA_thenIOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
@Test
fun givenTwoCalls_whenIDeleteAfterCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
private fun insertTwoCallEvents() {
SignalDatabase.calls.insertAcceptedGroupCall(
1,
groupRecipientId,
CallTable.Direction.INCOMING,
1000
)
SignalDatabase.calls.insertAcceptedGroupCall(
2,
groupRecipientId,
CallTable.Direction.OUTGOING,
2000
)
}
}
@@ -7,7 +7,7 @@ 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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
class DistributionListTablesTest {
@@ -7,7 +7,7 @@ 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.deleteAll
import org.signal.core.util.readToList
import org.signal.core.util.requireLong
import org.signal.core.util.withinTransaction
@@ -33,8 +33,8 @@ class GroupTableTest {
fun setUp() {
groupTable = SignalDatabase.groups
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
}
@Test
@@ -293,22 +293,22 @@ class GroupTableTest {
private fun insertPushGroup(
members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setUuid(Recipient.resolved(harness.others[0]).requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
)
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(members)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
@@ -317,23 +317,23 @@ class GroupTableTest {
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val selfMember: DecryptedMember = DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
val selfMember: DecryptedMember = DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
val otherMembers: List<DecryptedMember> = others.map { id ->
DecryptedMember.newBuilder()
.setUuid(Recipient.resolved(id).requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(id).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
}
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(listOf(selfMember) + otherMembers)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(listOf(selfMember) + otherMembers)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
@@ -0,0 +1,176 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import org.junit.Test
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLongOrNull
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.kem.KEMKeyPair
import org.signal.libsignal.protocol.kem.KEMKeyType
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
class KyberPreKeyTableTest {
private val aci: ACI = ACI.from(UUID.randomUUID())
private val pni: PNI = PNI.from(UUID.randomUUID())
@Test
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
insertTestRecord(aci, id = 1)
insertTestRecord(aci, id = 2)
insertTestRecord(aci, id = 3, staleTime = 42)
insertTestRecord(pni, id = 4)
val now = System.currentTimeMillis()
SignalDatabase.kyberPreKeys.markAllStaleIfNecessary(aci, now)
assertEquals(now, getStaleTime(aci, 1))
assertEquals(now, getStaleTime(aci, 2))
assertEquals(42L, getStaleTime(aci, 3))
assertEquals(0L, getStaleTime(pni, 4))
}
@Test
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 15)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
insertTestRecord(aci, id = 1, staleTime = 0)
insertTestRecord(aci, id = 2, staleTime = 0)
insertTestRecord(aci, id = 3, staleTime = 0)
insertTestRecord(aci, id = 4, staleTime = 0)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 1)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectAccount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(pni, id = 4, staleTime = 10)
insertTestRecord(pni, id = 5, staleTime = 10)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(pni, 4))
assertNotNull(getStaleTime(pni, 5))
}
@Test
fun deleteAllStaleBefore_ignoreLastResortForMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10, lastResort = true)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteLastResort() {
insertTestRecord(aci, id = 1, staleTime = 10, lastResort = true)
insertTestRecord(aci, id = 2, staleTime = 10, lastResort = true)
insertTestRecord(aci, id = 3, staleTime = 10, lastResort = true)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
}
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0, lastResort: Boolean = false) {
val kemKeyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024)
SignalDatabase.kyberPreKeys.insert(
serviceId = account,
keyId = id,
record = KyberPreKeyRecord(
id,
System.currentTimeMillis(),
kemKeyPair,
Curve.generateKeyPair().privateKey.calculateSignature(kemKeyPair.publicKey.serialize())
),
lastResort = lastResort
)
val count = SignalDatabase.rawDatabase
.update(KyberPreKeyTable.TABLE_NAME)
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.run()
assertEquals(1, count)
}
private fun getStaleTime(account: ServiceId, id: Int): Long? {
return SignalDatabase.rawDatabase
.select(KyberPreKeyTable.STALE_TIMESTAMP)
.from(KyberPreKeyTable.TABLE_NAME)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.run()
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
}
}
@@ -0,0 +1,288 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import org.junit.Test
import org.signal.core.util.forEach
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
import org.signal.core.util.updateAll
import org.thoughtcrime.securesms.crash.CrashConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.testing.assertIs
class LogDatabaseTest {
private val db: LogDatabase = LogDatabase.getInstance(ApplicationDependencies.getApplication())
@Test
fun crashTable_matchesNamePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(messagePattern = "Message")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndMessageAndStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message", stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_doesNotMatchNamePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNameButNotMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNameButNotStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNamePatternButPromptedTooRecently() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
db.writableDatabase
.updateAll(LogDatabase.CrashTable.TABLE_NAME)
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
.run()
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime - 100
)
foundMatch assertIs false
}
@Test
fun crashTable_noMatches() {
val currentTime = System.currentTimeMillis()
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime - 100
)
foundMatch assertIs false
}
@Test
fun crashTable_updatesLastPromptTime() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
db.crashes.saveCrash(
createdAt = currentTime,
name = "XXX",
message = "XXX",
stackTrace = "XXX"
)
db.crashes.markAsPrompted(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptedAt = currentTime
)
db.writableDatabase
.select(LogDatabase.CrashTable.NAME, LogDatabase.CrashTable.LAST_PROMPTED_AT)
.from(LogDatabase.CrashTable.TABLE_NAME)
.run()
.forEach {
if (it.requireNonNullString(LogDatabase.CrashTable.NAME) == "TestName") {
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs currentTime
} else {
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs 0
}
}
}
}
@@ -10,9 +10,8 @@ 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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@Suppress("ClassName")
@@ -34,7 +33,7 @@ class MessageTableTest_gifts {
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
}
@Test
@@ -49,7 +48,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -63,7 +62,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -77,13 +76,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -97,13 +96,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
@@ -116,18 +115,18 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -141,18 +140,18 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -166,18 +165,18 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -3,7 +3,7 @@ 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.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional
@@ -17,7 +17,6 @@ object MmsHelper {
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,
@@ -32,7 +31,6 @@ object MmsHelper {
recipient = recipient,
body = body,
timestamp = sentTimeMillis,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
viewOnce = viewOnce,
distributionType = distributionType,
@@ -57,9 +55,9 @@ object MmsHelper {
}
fun insert(
message: IncomingMediaMessage,
message: IncomingMessage,
threadId: Long
): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
return SignalDatabase.messages.insertMessageInbox(message, threadId)
}
}
@@ -13,12 +13,11 @@ 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.mms.IncomingMessage
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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
import java.util.concurrent.TimeUnit
@@ -45,7 +44,7 @@ class MmsTableTest_stories {
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())) }
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
@@ -74,7 +73,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -96,7 +96,8 @@ class MmsTableTest_stories {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -123,7 +124,8 @@ class MmsTableTest_stories {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -155,7 +157,8 @@ class MmsTableTest_stories {
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -169,7 +172,8 @@ class MmsTableTest_stories {
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -214,7 +218,8 @@ class MmsTableTest_stories {
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[0],
sentTimeMillis = 200,
serverTimeMillis = 2,
@@ -253,8 +258,7 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
// WHEN
@@ -319,19 +323,19 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = myStory.id,
sentTimeMillis = 201,
serverTimeMillis = 201,
receivedTimeMillis = 202,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
),
-1
SignalDatabase.threads.getOrCreateThreadIdFor(myStory, ThreadTable.DistributionTypes.DEFAULT)
)
// WHEN
@@ -0,0 +1,137 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import org.junit.Test
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLongOrNull
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
class OneTimePreKeyTableTest {
private val aci: ACI = ACI.from(UUID.randomUUID())
private val pni: PNI = PNI.from(UUID.randomUUID())
@Test
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
insertTestRecord(aci, id = 1)
insertTestRecord(aci, id = 2)
insertTestRecord(aci, id = 3, staleTime = 42)
insertTestRecord(pni, id = 4)
val now = System.currentTimeMillis()
SignalDatabase.oneTimePreKeys.markAllStaleIfNecessary(aci, now)
assertEquals(now, getStaleTime(aci, 1))
assertEquals(now, getStaleTime(aci, 2))
assertEquals(42L, getStaleTime(aci, 3))
assertEquals(0L, getStaleTime(pni, 4))
}
@Test
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 15)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
insertTestRecord(aci, id = 1, staleTime = 0)
insertTestRecord(aci, id = 2, staleTime = 0)
insertTestRecord(aci, id = 3, staleTime = 0)
insertTestRecord(aci, id = 4, staleTime = 0)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 0)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectAccount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(pni, id = 4, staleTime = 10)
insertTestRecord(pni, id = 5, staleTime = 10)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(pni, 4))
assertNotNull(getStaleTime(pni, 5))
}
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0) {
SignalDatabase.oneTimePreKeys.insert(
serviceId = account,
keyId = id,
record = PreKeyRecord(id, Curve.generateKeyPair())
)
val count = SignalDatabase.rawDatabase
.update(OneTimePreKeyTable.TABLE_NAME)
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.run()
assertEquals(1, count)
}
private fun getStaleTime(account: ServiceId, id: Int): Long? {
return SignalDatabase.rawDatabase
.select(OneTimePreKeyTable.STALE_TIMESTAMP)
.from(OneTimePreKeyTable.TABLE_NAME)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.run()
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
}
}
@@ -11,10 +11,8 @@ 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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@RunWith(AndroidJUnit4::class)
@@ -24,14 +22,14 @@ class RecipientTableTest {
val harness = SignalActivityRule()
@Test
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIExpectHiddenToBeReturned() {
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)
assertEquals(1, results.count)
}
@Test
@@ -59,7 +57,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Hidden", false))!!
assertEquals(0, results.count)
}
@@ -130,7 +128,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results = SignalDatabase.recipients.querySignalContacts("Blocked", false)!!
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Blocked", false))!!
assertEquals(0, results.count)
}
@@ -167,16 +165,14 @@ class RecipientTableTest {
@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 byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)
@@ -185,17 +181,15 @@ class RecipientTableTest {
@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!!)
SignalDatabase.recipients.splitForStorageSyncIfNecessary(mainRecord.aci!!)
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)
@@ -0,0 +1,60 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_applyStorageSyncContactUpdate {
@get:Rule
val harness = SignalActivityRule()
@Test
fun insertMessageOnVerifiedToDefault() {
// GIVEN
val identities = ApplicationDependencies.getProtocolStore().aci().identities()
val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other)
identities.setVerified(other.id, harness.othersKeys[0].publicKey, IdentityTable.VerifiedStatus.VERIFIED)
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
val newProto = oldRecord
.toProto()
.newBuilder()
.identityState(ContactRecord.IdentityState.DEFAULT)
.build()
val newRecord = SignalContactRecord(oldRecord.id, newProto)
val update = StorageRecordUpdate<SignalContactRecord>(oldRecord, newRecord)
// WHEN
val oldVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
SignalDatabase.recipients.applyStorageSyncContactUpdate(update)
val newVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
// THEN
oldVerifiedStatus assertIs IdentityTable.VerifiedStatus.VERIFIED
newVerifiedStatus assertIs IdentityTable.VerifiedStatus.DEFAULT
val messages = MessageTableTestUtils.getMessages(SignalDatabase.threads.getThreadIdFor(other.id)!!)
messages.first().isIdentityDefault assertIs true
}
}
@@ -7,13 +7,18 @@ import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
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.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.exists
import org.signal.core.util.orNull
import org.signal.core.util.readToSingleBoolean
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
@@ -32,21 +37,17 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
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.push.ServiceId
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_getAndPossiblyMerge {
@@ -55,7 +56,6 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
}
@Test
@@ -83,6 +83,42 @@ class RecipientTableTest_getAndPossiblyMerge {
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni insert") {
val id = process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+aci insert") {
val id = process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni+aci insert") {
val id = process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni+aci insert, pni verified") {
val id = process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
process(E164_A, PNI_A, ACI_A, pniVerified = false)
expectPniVerified()
}
}
@Test
@@ -92,9 +128,9 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, null, null)
}
test("no match, e164 and pni") {
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
test("no match, pni-only") {
process(null, PNI_A, null)
expect(null, PNI_A, null)
}
test("no match, aci-only") {
@@ -102,6 +138,11 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(null, null, ACI_A)
}
test("no match, e164 and pni") {
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
}
test("no match, e164 and aci") {
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
@@ -111,6 +152,31 @@ class RecipientTableTest_getAndPossiblyMerge {
process(null, null, null)
}
test("pni matches, pni+aci provided, no pni session") {
given(E164_A, PNI_A, null)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("pni matches, pni+aci provided, pni session") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectSessionSwitchoverEvent(E164_A)
}
test("pni matches, pni+aci provided, pni session, pni-verified") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
expectPniVerified()
}
test("no match, all fields") {
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
@@ -166,6 +232,14 @@ class RecipientTableTest_getAndPossiblyMerge {
expectSessionSwitchoverEvent(E164_A)
}
test("e164 and pni matches, all provided, new aci, existing pni session, pni-verified") {
given(E164_A, PNI_A, null, pniSession = true)
process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
}
test("e164 and aci matches, all provided, new pni") {
given(E164_A, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
@@ -308,6 +382,26 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, pni is changed") {
given(E164_A, PNI_B, ACI_A)
given(E164_B, PNI_A, null)
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, ACI_A)
expect(E164_B, null, null)
}
test("steal, pni is changed, aci left behind") {
given(E164_B, PNI_A, ACI_A)
given(E164_A, PNI_B, null)
process(E164_A, PNI_A, null)
expect(E164_B, null, ACI_A)
expect(E164_A, PNI_A, null)
}
test("steal, e164+pni & e164+pni, no aci provided, no pni session") {
given(E164_A, PNI_B, null)
given(E164_B, PNI_A, null)
@@ -352,7 +446,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, e164 & pni+e164, no aci provided") {
test("steal, e164 & pni+e164, no aci provided, pni session exists") {
val id1 = given(E164_A, null, null)
val id2 = given(E164_B, PNI_A, null, pniSession = true)
@@ -365,6 +459,16 @@ class RecipientTableTest_getAndPossiblyMerge {
expectSessionSwitchoverEvent(id2, E164_B)
}
test("steal, e164 & pni+e164, no aci provided, no pni session") {
given(E164_A, null, null)
given(E164_B, PNI_A, null)
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
expect(E164_B, null, null)
}
test("steal, e164+pni+aci & e164+aci, no pni provided, change number") {
given(E164_A, PNI_A, ACI_A)
given(E164_B, null, ACI_B)
@@ -377,6 +481,76 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, e164+aci & aci, no pni provided, existing aci session") {
given(E164_A, null, ACI_A, aciSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, null, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci & aci, no pni provided, existing aci session") {
given(E164_A, PNI_A, ACI_A, aciSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, PNI_A, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci & aci, no pni provided, existing pni session") {
given(E164_A, PNI_A, ACI_A, pniSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, PNI_A, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni & aci, no pni provided, no pni session") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expect(null, PNI_A, null)
expect(E164_A, null, ACI_A)
}
test("steal, e164+pni & aci, no pni provided, pni session") {
given(E164_A, PNI_A, null, pniSession = true)
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expect(null, PNI_A, null)
expect(E164_A, null, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci * pni+aci, all provided, aci sessions but not pni sessions, no SSE expected") {
given(E164_A, PNI_A, ACI_A, createThread = true, aciSession = true, pniSession = false)
given(null, PNI_B, ACI_B, createThread = false, aciSession = true, pniSession = false)
process(E164_A, PNI_B, ACI_A)
expect(E164_A, PNI_B, ACI_A)
expect(null, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("merge, e164 & pni & aci, all provided") {
given(E164_A, null, null)
given(null, PNI_A, null)
@@ -501,7 +675,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expectThreadMergeEvent(E164_A)
}
test("merge, e164+pni & aci, pni session, thread merge shadows") {
test("merge, e164+pni & aci, pni session, thread merge shadows SSE") {
given(E164_A, PNI_A, null, pniSession = true)
given(null, null, ACI_A)
@@ -533,6 +707,8 @@ class RecipientTableTest_getAndPossiblyMerge {
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
}
test("merge, e164+pni & aci, pni session, pni verified") {
@@ -545,6 +721,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectPniVerified()
}
test("merge, e164+pni & e164+pni+aci, change number") {
@@ -599,6 +776,41 @@ class RecipientTableTest_getAndPossiblyMerge {
expectThreadMergeEvent(E164_A)
}
test("merge, e164+pni & e164+aci, pni+aci provided, change number") {
given(E164_A, PNI_A, null)
given(E164_B, null, ACI_A)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectChangeNumberEvent()
}
test("merge, e164 + pni reassigned, aci abandoned") {
given(E164_A, PNI_A, ACI_A)
given(E164_B, PNI_B, ACI_B)
process(E164_A, PNI_A, ACI_B)
expect(null, null, ACI_A)
expect(E164_A, PNI_A, ACI_B)
expectChangeNumberEvent()
}
test("merge, e164 follows pni+aci") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectPniVerified()
}
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF)
given(null, null, ACI_A)
@@ -620,6 +832,22 @@ class RecipientTableTest_getAndPossiblyMerge {
process(E164_A, null, ACI_SELF, changeSelf = true)
expect(E164_A, null, ACI_SELF)
}
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
given(E164_SELF, PNI_SELF, ACI_SELF)
process(E164_SELF, PNI_A, ACI_A)
expect(E164_SELF, PNI_SELF, ACI_SELF)
}
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
given(E164_SELF, PNI_A, ACI_SELF)
process(E164_SELF, PNI_SELF, ACI_A)
expect(E164_SELF, PNI_A, ACI_SELF)
}
}
/**
@@ -636,9 +864,9 @@ class RecipientTableTest_getAndPossiblyMerge {
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
val smsId3: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
@@ -685,8 +913,8 @@ class RecipientTableTest_getAndPossiblyMerge {
// Thread validation
assertEquals(threadIdAci, retrievedThreadId)
Assert.assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
// SMS validation
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
@@ -730,10 +958,10 @@ class RecipientTableTest_getAndPossiblyMerge {
// Identity validation
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
Assert.assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
// Session validation
Assert.assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
// Reaction validation
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
@@ -758,18 +986,42 @@ class RecipientTableTest_getAndPossiblyMerge {
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = time,
serverTimeMillis = time,
receivedTimeMillis = time,
body = body,
groupId = groupId.orNull(),
isUnidentified = true
)
}
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false)
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
groupId = groupId.orNull(),
body = body,
sentTimeMillis = time,
receivedTimeMillis = time,
serverTimeMillis = time,
isUnidentified = true
)
}
private fun identityKey(value: Byte): IdentityKey {
val byteArray = ByteArray(32)
byteArray[0] = value
return identityKey(byteArray)
}
private fun identityKey(value: ByteArray): IdentityKey {
val bytes = ByteArray(33)
bytes[0] = 0x05
bytes[1] = value
value.copyInto(bytes, 1)
return IdentityKey(bytes)
}
@@ -824,6 +1076,10 @@ class RecipientTableTest_getAndPossiblyMerge {
if (!test.sessionSwitchoverExpected) {
test.expectNoSessionSwitchoverEvent()
}
if (!test.pniVerifiedExpected) {
test.expectPniNotVerified()
}
} catch (e: Throwable) {
if (e.javaClass != exception) {
val error = java.lang.AssertionError("[$name] ${e.message}")
@@ -843,6 +1099,7 @@ class RecipientTableTest_getAndPossiblyMerge {
var changeNumberExpected = false
var threadMergeExpected = false
var sessionSwitchoverExpected = false
var pniVerifiedExpected = false
init {
// Need to delete these first to prevent foreign key crash
@@ -857,6 +1114,7 @@ class RecipientTableTest_getAndPossiblyMerge {
}
ApplicationDependencies.getRecipientCache().clear()
ApplicationDependencies.getRecipientCache().clearSelf()
RecipientId.clearCache()
}
@@ -865,14 +1123,15 @@ class RecipientTableTest_getAndPossiblyMerge {
pni: PNI?,
aci: ACI?,
createThread: Boolean = true,
pniSession: Boolean = false
pniSession: Boolean = false,
aciSession: Boolean = false
): RecipientId {
val id = insert(e164, pni, aci)
generatedIds += id
if (createThread) {
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
val result = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = id, time = (Math.random() * 10000000).toLong(), body = "1"))
SignalDatabase.threads.markAsActiveEarly(result.get().threadId)
}
if (pniSession) {
@@ -883,11 +1142,42 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalDatabase.sessions.store(pni, SignalProtocolAddress(pni.toString(), 1), SessionRecord())
}
if (aciSession) {
if (aci == null) {
throw IllegalArgumentException("aciSession = true but aci is null!")
}
SignalDatabase.sessions.store(aci, SignalProtocolAddress(aci.toString(), 1), SessionRecord())
}
if (aci != null) {
SignalDatabase.identities.saveIdentity(
addressName = aci.toString(),
recipientId = id,
identityKey = identityKey(Util.getSecretBytes(32)),
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
firstUse = true,
timestamp = 0,
nonBlockingApproval = false
)
}
if (pni != null) {
SignalDatabase.identities.saveIdentity(
addressName = pni.toString(),
recipientId = id,
identityKey = identityKey(Util.getSecretBytes(32)),
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
firstUse = true,
timestamp = 0,
nonBlockingApproval = false
)
}
return id
}
fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false, pniVerified: Boolean = false): RecipientId {
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
generatedIds += outputRecipientId
return outputRecipientId
}
@@ -901,15 +1191,15 @@ class RecipientTableTest_getAndPossiblyMerge {
val expected = RecipientTuple(
e164 = e164,
pni = pni,
serviceId = aci ?: pni
aci = aci
)
val actual = RecipientTuple(
e164 = recipient.e164.orElse(null),
pni = recipient.pni.orElse(null),
serviceId = recipient.serviceId.orElse(null)
aci = recipient.aci.orElse(null)
)
assertEquals(expected, actual)
assertEquals("Recipient $id did not match expected result!", expected, actual)
}
fun expectDeleted() {
@@ -917,21 +1207,21 @@ class RecipientTableTest_getAndPossiblyMerge {
}
fun expectDeleted(id: RecipientId) {
SignalDatabase.rawDatabase
.select("1")
.from(RecipientTable.TABLE_NAME)
val found = SignalDatabase.rawDatabase
.exists(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", id)
.run()
.use { !it.moveToFirst() }
assertFalse("Expected $id to be deleted, but it's still present!", found)
}
fun expectChangeNumberEvent() {
assertEquals(1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
assertEquals("Missing change number event!", 1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = true
}
fun expectNoChangeNumberEvent() {
assertEquals(0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
assertEquals("Unexpected change number event!", 0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = false
}
@@ -941,42 +1231,57 @@ class RecipientTableTest_getAndPossiblyMerge {
fun expectSessionSwitchoverEvent(recipientId: RecipientId, e164: String) {
val event: SessionSwitchoverEvent? = getLatestSessionSwitchoverEvent(recipientId)
assertNotNull(event)
assertNotNull("Missing session switchover event! Expected one with e164 = $e164", event)
assertEquals(e164, event!!.e164)
sessionSwitchoverExpected = true
}
fun expectNoSessionSwitchoverEvent() {
assertNull(getLatestSessionSwitchoverEvent(outputRecipientId))
assertNull("Unexpected session switchover event!", getLatestSessionSwitchoverEvent(outputRecipientId))
}
fun expectThreadMergeEvent(previousE164: String) {
val event: ThreadMergeEvent? = getLatestThreadMergeEvent(outputRecipientId)
assertNotNull(event)
assertEquals(previousE164, event!!.previousE164)
assertNotNull("Missing thread merge event! Expected one with e164 = $previousE164", event)
assertEquals("E164 on thread merge event doesn't match!", previousE164, event!!.previousE164)
threadMergeExpected = true
}
fun expectNoThreadMergeEvent() {
assertNull(getLatestThreadMergeEvent(outputRecipientId))
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
}
fun expectPniVerified() {
assertTrue("Expected PNI to be verified!", isPniVerified(outputRecipientId))
pniVerifiedExpected = true
}
fun expectPniNotVerified() {
assertFalse("Expected PNI to be not be verified!", isPniVerified(outputRecipientId))
}
private fun isPniVerified(recipientId: RecipientId): Boolean {
return SignalDatabase.rawDatabase
.select(RecipientTable.PNI_SIGNATURE_VERIFIED)
.from(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", recipientId)
.run()
.readToSingleBoolean(false)
}
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
val serviceIdString: String? = (aci ?: pni)?.toString()
val pniString: String? = pni?.toString()
val id: Long = SignalDatabase.rawDatabase.insert(
RecipientTable.TABLE_NAME,
null,
contentValuesOf(
RecipientTable.PHONE to e164,
RecipientTable.SERVICE_ID to serviceIdString,
RecipientTable.PNI_COLUMN to pniString,
RecipientTable.E164 to e164,
RecipientTable.ACI_COLUMN to aci?.toString(),
RecipientTable.PNI_COLUMN to pni?.toString(),
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
)
)
assertTrue("Failed to insert! E164: $e164, ServiceId: $serviceIdString, PNI: $pniString", id > 0)
assertTrue("Failed to insert! E164: $e164, ACI: $aci, PNI: $pni", id > 0)
return RecipientId.from(id)
}
@@ -985,14 +1290,14 @@ class RecipientTableTest_getAndPossiblyMerge {
data class RecipientTuple(
val e164: String?,
val pni: PNI?,
val serviceId: ServiceId?
val aci: ACI?
) {
/**
* The intent here is to give nice diffs with the name of the constants rather than the values.
*/
override fun toString(): String {
return "(${e164.e164String()}, ${pni.pniString()}, ${serviceId.serviceIdString()})"
return "(${e164.e164String()}, ${pni.pniString()}, ${aci.aciString()})"
}
private fun String?.e164String(): String {
@@ -1016,12 +1321,9 @@ class RecipientTableTest_getAndPossiblyMerge {
} ?: "null"
}
private fun ServiceId?.serviceIdString(): String {
private fun ACI?.aciString(): String {
return this?.let {
when (it) {
PNI_A -> "PNI_A"
PNI_B -> "PNI_B"
PNI_SELF -> "PNI_SELF"
ACI_A -> "ACI_A"
ACI_B -> "ACI_B"
ACI_SELF -> "ACI_SELF"
@@ -1042,7 +1344,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
ThreadMergeEvent.parseFrom(bytes)
ThreadMergeEvent.ADAPTER.decode(bytes)
} else {
null
}
@@ -1060,7 +1362,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
SessionSwitchoverEvent.parseFrom(bytes)
SessionSwitchoverEvent.ADAPTER.decode(bytes)
} else {
null
}
@@ -18,13 +18,10 @@ 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.mms.IncomingMessage
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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@@ -273,18 +270,34 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = wallClock,
serverTimeMillis = wallClock,
receivedTimeMillis = wallClock,
body = body,
groupId = groupId,
isUnidentified = true
)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
groupContext = groupContext,
serverGuid = null
)
}
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 aliceServiceId: ACI = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
private val bobServiceId: ACI = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
private val groupId = GroupId.v2(masterKey)
@@ -12,19 +12,24 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
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.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendTableTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 0, createGroup = false)
private val distributionId1 = DistributionId.from(UUID.randomUUID())
private val distributionId2 = DistributionId.from(UUID.randomUUID())
private val distributionId3 = DistributionId.from(UUID.randomUUID())
@@ -460,7 +465,7 @@ class StorySendTableTest {
private fun makeRecipients(count: Int): List<RecipientId> {
return (1..count).map {
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))
}
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
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.ACI
import java.util.UUID
@Suppress("ClassName")
class ThreadTableTest_active {
@Rule
@JvmField
val databaseRule = SignalDatabaseRule()
private lateinit var recipient: Recipient
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test
fun givenActiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(1, threads.count)
val record = ThreadTable.StaticReader(threads, InstrumentationRegistry.getInstrumentation().context).getNext()
assertNotNull(record)
assertEquals(record!!.recipient.id, recipient.id)
}
}
@Test
fun givenInactiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
}
@Test
fun givenActiveArchivedThread_whenIGetArchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(1, threads.count)
}
}
@Test
fun givenInactiveArchivedThread_whenIGetArchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIDeactivateThread_thenIExpectNoMessages() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(1, it.count)
}
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(0, it.count)
}
}
}
@@ -9,7 +9,7 @@ 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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@Suppress("ClassName")
@@ -23,7 +23,7 @@ class ThreadTableTest_pinned {
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test
@@ -10,7 +10,7 @@ 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 org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@Suppress("ClassName")
@@ -25,7 +25,7 @@ class ThreadTableTest_recents {
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test
@@ -13,9 +13,9 @@ import okio.ByteString
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
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
@@ -23,31 +23,25 @@ import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success
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 org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
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].
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
*/
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
class InstrumentationApplicationDependencyProvider(val application: Application, private val 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 {
@@ -74,20 +68,20 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
signalServiceUrls = arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdnUrlMap = 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)),
emptyArray(),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
networkInterceptors = emptyList(),
dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {
@@ -96,8 +90,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
on { uncensoredConfiguration } doReturn uncensoredConfiguration
}
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() }
}
@@ -105,10 +97,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
return serviceNetworkAccessMock
}
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
override fun provideRecipientCache(): LiveRecipientCache {
return recipientCache
}
@@ -0,0 +1,84 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobs
import android.net.Uri
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.StreamUtil
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.UriAttachmentBuilder
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
class AttachmentCompressionJobTest {
@get:Rule
val harness = SignalActivityRule()
@Test
fun testCompressionJobsWithDifferentTransformPropertiesCompleteSuccessfully() {
val imageBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("images/sample_image.png").use {
StreamUtil.readFully(it)
}
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication())
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
val firstCompressionJob: AttachmentCompressionJob = AttachmentCompressionJob.fromAttachment(firstDatabaseAttachment, false, -1)
var secondCompressionJob: AttachmentCompressionJob? = null
var firstJobResult: Job.Result? = null
var secondJobResult: Job.Result? = null
val secondJobLatch = CountDownLatch(1)
val jobThread = Thread {
firstCompressionJob.setContext(ApplicationDependencies.getApplication())
firstJobResult = firstCompressionJob.run()
secondJobLatch.await()
secondCompressionJob!!.setContext(ApplicationDependencies.getApplication())
secondJobResult = secondCompressionJob!!.run()
}
jobThread.start()
val secondPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val secondDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondPreUpload)
secondCompressionJob = AttachmentCompressionJob.fromAttachment(secondDatabaseAttachment, false, -1)
secondJobLatch.countDown()
jobThread.join()
firstJobResult!!.isSuccess assertIs true
secondJobResult!!.isSuccess assertIs true
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = transformProperties
)
}
}
@@ -1,212 +0,0 @@
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
}
}
}
@@ -1,175 +0,0 @@
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)
}
}
@@ -22,10 +22,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password() throws IOException {
String pin = "password";
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] salt = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -40,10 +40,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_another_password() throws IOException {
String pin = "anotherpassword";
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
byte[] salt = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -58,10 +58,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
String pin = " Pass६örd ";
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
byte[] salt = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -78,10 +78,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
String pin = " ६१८ ";
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
byte[] salt = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -22,8 +22,9 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.EditMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import kotlin.time.Duration.Companion.seconds
@RunWith(AndroidJUnit4::class)
@@ -38,7 +39,6 @@ class EditMessageSyncProcessorTest {
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@@ -46,13 +46,13 @@ class EditMessageSyncProcessorTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var processorV2: MessageContentProcessor
private lateinit var testResult: TestResults
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV2 = MessageContentProcessorV2(harness.context)
processorV2 = MessageContentProcessor(harness.context)
envelopeTimestamp = System.currentTimeMillis()
testResult = TestResults()
}
@@ -67,16 +67,17 @@ class EditMessageSyncProcessorTest {
val content = MessageContentFuzzer.fuzzTextMessage()
val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id)
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setTimestamp(originalTimestamp)
.setExpirationStartTimestamp(originalTimestamp)
.setMessage(content.dataMessage)
)
val syncContent = Content.Builder().syncMessage(
SyncMessage.Builder().sent(
SyncMessage.Sent.Builder()
.destinationServiceId(metadata.destinationServiceId.toString())
.timestamp(originalTimestamp)
.expirationStartTimestamp(originalTimestamp)
.message(content.dataMessage)
.build()
).build()
).build()
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
val syncTextMessage = TestMessage(
envelope = MessageContentFuzzer.envelope(originalTimestamp),
content = syncContent,
@@ -86,18 +87,20 @@ class EditMessageSyncProcessorTest {
val editTimestamp = originalTimestamp + 200
val editedContent = MessageContentFuzzer.fuzzTextMessage()
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setTimestamp(editTimestamp)
.setExpirationStartTimestamp(editTimestamp)
.setEditMessage(
EditMessage.newBuilder()
.setDataMessage(editedContent.dataMessage)
.setTargetSentTimestamp(originalTimestamp)
val editSyncContent = Content.Builder().syncMessage(
SyncMessage.Builder().sent(
SyncMessage.Sent.Builder()
.destinationServiceId(metadata.destinationServiceId.toString())
.timestamp(editTimestamp)
.expirationStartTimestamp(editTimestamp)
.editMessage(
EditMessage.Builder()
.dataMessage(editedContent.dataMessage)
.targetSentTimestamp(originalTimestamp)
.build()
)
)
.build()
).build()
).build()
val syncEditMessage = TestMessage(
@@ -109,38 +112,38 @@ class EditMessageSyncProcessorTest {
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer / 1000)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000)
val originalTextMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = originalTimestamp,
body = content.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = content.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = content.dataMessage.bodyRangesList.toBodyRangeList()
bodyRanges = content.dataMessage?.bodyRanges.toBodyRangeList()
)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient)
val originalMessageId = SignalDatabase.messages.insertMessageOutbox(originalTextMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(originalMessageId, true)
if (content.dataMessage.expireTimer > 0) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp)
}
val editMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = editTimestamp,
body = editedContent.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = editedContent.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = editedContent.dataMessage.bodyRangesList.toBodyRangeList(),
bodyRanges = editedContent.dataMessage?.bodyRanges.toBodyRangeList(),
messageToEdit = originalMessageId
)
val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(editMessageId, true)
if (content.dataMessage.expireTimer > 0) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp)
}
testResult.collectLocal()
@@ -167,7 +170,7 @@ class EditMessageSyncProcessorTest {
fun runSync(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasSyncMessage()) {
if (content.syncMessage != null) {
processorV2.process(
envelope,
content,
@@ -1,62 +0,0 @@
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()
}
}
}
@@ -1,313 +0,0 @@
package org.thoughtcrime.securesms.messages
import android.database.Cursor
import android.util.Base64
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.signal.core.util.readToList
import org.signal.core.util.select
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.InMemoryLogger
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import java.util.Optional
@RunWith(AndroidJUnit4::class)
class MessageContentProcessorTestV2 {
companion object {
private val TAGS = listOf(MessageContentProcessor.TAG, MessageContentProcessorV2.TAG, AttachmentTable.TAG)
private val GENERALIZE_TAG = mapOf(
MessageContentProcessor.TAG to "MCP",
MessageContentProcessorV2.TAG to "MCP",
AttachmentTable.TAG to AttachmentTable.TAG
)
private val IGNORE_MESSAGE_COLUMNS = listOf(
MessageTable.DATE_RECEIVED,
MessageTable.NOTIFIED_TIMESTAMP,
MessageTable.REACTIONS_LAST_SEEN,
MessageTable.NOTIFIED
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV1: MessageContentProcessor
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var testResult: TestResults
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV1 = MessageContentProcessor(harness.context)
processorV2 = MessageContentProcessorV2(harness.context)
envelopeTimestamp = System.currentTimeMillis()
testResult = TestResults()
}
@Test
fun textMessage() {
var start = envelopeTimestamp
val messages: List<TestMessage> = (0 until 100).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(messages)
testResult.runV1(messages)
testResult.assert()
}
@Test
fun mediaMessage() {
var start = envelopeTimestamp
val textMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val firstBatchMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageWithBody(textMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val secondBatchNoContentMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoContent(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val thirdBatchNoTextMediaMessagesMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoText(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.runV1(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.assert()
}
private inner class TestResults {
private lateinit var v1Logs: List<Entry>
private lateinit var v1Messages: List<List<Pair<String, String?>>>
private lateinit var v1Attachments: List<List<Pair<String, String?>>>
private lateinit var v2Logs: List<Entry>
private lateinit var v2Messages: List<List<Pair<String, String?>>>
private lateinit var v2Attachments: List<List<Pair<String, String?>>>
fun runV1(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV1.process(
MessageContentProcessor.MessageState.DECRYPTED_OK,
toSignalServiceContent(envelope, content, metadata, serverDeliveredTimestamp),
null,
envelope.timestamp,
-1
)
ThreadUtil.sleep(1)
}
}
v1Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v1Messages = dumpMessages()
v1Attachments = dumpAttachments()
}
fun runV2(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV2.process(
envelope,
content,
metadata,
serverDeliveredTimestamp,
false
)
ThreadUtil.sleep(1)
}
}
v2Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v2Messages = dumpMessages()
v2Attachments = dumpAttachments()
cleanup()
}
fun cleanup() {
SignalDatabase.rawDatabase.withinTransaction { db ->
SignalDatabase.threads.deleteAllConversations()
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${MessageTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${ThreadTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${AttachmentTable.TABLE_NAME}'")
}
}
fun assert() {
v2Logs.zip(v1Logs)
.forEach { (v2, v1) ->
GENERALIZE_TAG[v2.tag]!!.assertIs(GENERALIZE_TAG[v1.tag]!!)
if (v2.tag != AttachmentTable.TAG) {
if (v2.message?.startsWith("[") == true && v1.message?.startsWith("[") == false) {
v2.message!!.substring(v2.message!!.indexOf(']') + 2).assertIs(v1.message)
} else {
v2.message.assertIs(v1.message)
}
} else {
if (v2.message?.startsWith("Inserted attachment at ID: AttachmentId::") == true) {
v2.message!!
.substring(0, v2.message!!.indexOf(','))
.assertIs(
v1.message!!
.substring(0, v1.message!!.indexOf(','))
)
} else {
v2.message.assertIs(v1.message)
}
}
v2.throwable.assertIs(v1.throwable)
}
v2Messages.zip(v1Messages)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
v2Attachments.zip(v1Attachments)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
}
private fun InMemoryLogger.logs(): List<Entry> {
return entries()
.filter { TAGS.contains(it.tag) }
}
private fun dumpMessages(): List<List<Pair<String, String?>>> {
return dumpTable(MessageTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_MESSAGE_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpAttachments(): List<List<Pair<String, String?>>> {
return dumpTable(AttachmentTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_ATTACHMENT_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpTable(table: String): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
}
map
}
}
}
private fun toSignalServiceContent(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long): SignalServiceContent {
val localAddress = SignalServiceAddress(metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164))
val signalServiceMetadata = SignalServiceMetadata(
SignalServiceAddress(metadata.sourceServiceId, Optional.ofNullable(metadata.sourceE164)),
metadata.sourceDeviceId,
envelope.timestamp,
envelope.serverTimestamp,
serverDeliveredTimestamp,
metadata.sealedSender,
envelope.serverGuid,
Optional.ofNullable(metadata.groupId),
metadata.destinationServiceId.toString()
)
val contentProto = SignalServiceContentProto.newBuilder()
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(signalServiceMetadata))
.setContent(content)
.build()
return SignalServiceContent.createFromProto(contentProto)!!
}
}
@@ -1,181 +0,0 @@
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!!)
}
}
@@ -1,33 +0,0 @@
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)
}
}
@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import okio.ByteString.Companion.toByteString
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.GroupReceiptTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
@@ -15,22 +15,22 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.GroupContextV2
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MessageContentProcessorV2__recipientStatusTest {
class MessageContentProcessor__recipientStatusTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var processor: MessageContentProcessor
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV2 = MessageContentProcessorV2(harness.context)
processor = MessageContentProcessor(harness.context)
envelopeTimestamp = System.currentTimeMillis()
}
@@ -41,18 +41,18 @@ class MessageContentProcessorV2__recipientStatusTest {
@Test
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember())
val groupContextV2 = GroupContextV2.newBuilder().setRevision(0).setMasterKey(masterKey.serialize().toProtoByteString()).build()
val groupContextV2 = GroupContextV2.Builder().revision(0).masterKey(masterKey.serialize().toByteString()).build()
val initialTextMessage = DataMessage.newBuilder().buildWith {
val initialTextMessage = DataMessage.Builder().buildWith {
body = MessageContentFuzzer.string()
groupV2 = groupContextV2
timestamp = envelopeTimestamp
}
processorV2.process(
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
@@ -61,10 +61,10 @@ class MessageContentProcessorV2__recipientStatusTest {
val firstMessageId = firstSyncMessages[0].id
val firstReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
processorV2.process(
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
@@ -6,9 +6,9 @@ import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import okio.ByteString
import okio.ByteString.Companion.toByteString
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
@@ -25,9 +25,9 @@ import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.FakeClientHelpers
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
@@ -37,7 +37,7 @@ import android.util.Log as AndroidLog
/**
* Sends N messages from Bob to Alice to track performance of Alice's processing of messages.
*/
// @Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@RunWith(AndroidJUnit4::class)
class MessageProcessingPerformanceTest {
@@ -58,14 +58,14 @@ class MessageProcessingPerformanceTest {
mockkStatic(UnidentifiedAccessUtil::class)
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkObject(MessageContentProcessorV2)
every { MessageContentProcessorV2.create(harness.application) } returns TimingMessageContentProcessorV2(harness.application)
mockkObject(MessageContentProcessor)
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
}
@After
fun after() {
unmockkStatic(UnidentifiedAccessUtil::class)
unmockkStatic(MessageContentProcessorV2::class)
unmockkStatic(MessageContentProcessor::class)
}
@Test
@@ -92,7 +92,7 @@ class MessageProcessingPerformanceTest {
val messageCount = 100
val envelopes = generateInboundEnvelopes(bobClient, messageCount)
val firstTimestamp = envelopes.first().timestamp
val lastTimestamp = envelopes.last().timestamp
val lastTimestamp = envelopes.last().timestamp ?: 0
// Inject the envelopes into the websocket
Thread {
@@ -106,7 +106,7 @@ class MessageProcessingPerformanceTest {
// Wait until they've all been fully decrypted + processed
harness
.inMemoryLogger
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(lastTimestamp))
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(lastTimestamp))
.awaitFor(1.minutes)
harness.inMemoryLogger.flush()
@@ -125,7 +125,7 @@ class MessageProcessingPerformanceTest {
// Calculate MessageContentProcessor
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessorV2.TAG }.drop(2)
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessor.TAG }.drop(2)
val iterator = takeLast.iterator()
var processCount = 0L
var processDuration = 0L
@@ -141,7 +141,7 @@ class MessageProcessingPerformanceTest {
// Calculate messages per second from "retrieving" first message post session initialization to processing last message
val start = entries.first { it.message == "Retrieved envelope! $firstTimestamp" }
val end = entries.first { it.message == TimingMessageContentProcessorV2.endTag(lastTimestamp) }
val end = entries.first { it.message == TimingMessageContentProcessor.endTag(lastTimestamp) }
val duration = (end.timestamp - start.timestamp).toFloat() / 1000f
val messagePerSecond = messageCount.toFloat() / duration
@@ -156,7 +156,7 @@ class MessageProcessingPerformanceTest {
val aliceProcessFirstMessageLatch = harness
.inMemoryLogger
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(firstPreKeyMessageTimestamp))
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(firstPreKeyMessageTimestamp))
Thread { aliceClient.process(encryptedEnvelope, System.currentTimeMillis()) }.start()
aliceProcessFirstMessageLatch.awaitFor(15.seconds)
@@ -178,32 +178,19 @@ class MessageProcessingPerformanceTest {
}
private fun webSocketTombstone(): ByteString {
return WebSocketMessage
.newBuilder()
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/queue/empty")
)
.build()
.toByteArray()
.toByteString()
return WebSocketMessage(request = WebSocketRequestMessage(verb = "PUT", path = "/api/v1/queue/empty")).encodeByteString()
}
private fun Envelope.toWebSocketPayload(): ByteString {
return WebSocketMessage
.newBuilder()
.setType(WebSocketMessage.Type.REQUEST)
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/message")
.setId(Random(System.currentTimeMillis()).nextLong())
.addHeaders("X-Signal-Timestamp: ${this.timestamp}")
.setBody(this.toByteString())
return WebSocketMessage(
type = WebSocketMessage.Type.REQUEST,
request = WebSocketRequestMessage(
verb = "PUT",
path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.encodeByteString()
)
.build()
.toByteArray()
.toByteString()
).encodeByteString()
}
}
@@ -0,0 +1,225 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_readSyncs {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var alice: RecipientId
private lateinit var bob: RecipientId
private lateinit var group: GroupTestingUtils.TestGroupInfo
private lateinit var processor: MessageContentProcessor
@Before
fun setUp() {
alice = harness.others[0]
bob = harness.others[1]
group = harness.group!!
processor = MessageContentProcessor(harness.context)
val threadIdSlot = slot<Long>()
mockkStatic(ThreadUpdateJob::class)
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
SignalDatabase.threads.update(threadIdSlot.captured, false)
}
}
@After
fun tearDown() {
unmockkStatic(ThreadUpdateJob::class)
}
@Test
fun handleSynchronizeReadMessage() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message1Timestamp, alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadMessageMissingTimestamp() {
val messageHelper = MessageHelper()
messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEdits() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.syncReadMessage(alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(message1Timestamp).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(editMessage1Timestamp1).timestamp
val message2Timestamp = messageHelper.incomingMedia().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEditsInGroup() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText(sender = alice, destination = group.recipientId).timestamp
messageHelper.syncReadMessage(alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = alice, destination = group.recipientId).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = alice, destination = group.recipientId).timestamp
val message2Timestamp = messageHelper.incomingMedia(sender = bob, destination = group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(group.recipientId)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(bob to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
private inner class MessageHelper(var startTime: Long = System.currentTimeMillis()) {
fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzStickerMediaMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.editTextMessage(
targetTimestamp = targetTimestamp,
editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
).dataMessage!!
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncReadMessage(vararg reads: Pair<RecipientId, Long>): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
}
private data class MessageData(val serverGuid: UUID = UUID.randomUUID(), val timestamp: Long)
}
@@ -1,11 +1,12 @@
package org.thoughtcrime.securesms.messages
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
data class TestMessage(
val envelope: SignalServiceProtos.Envelope,
val content: SignalServiceProtos.Content,
val envelope: Envelope,
val content: Content,
val metadata: EnvelopeMetadata,
val serverDeliveredTimestamp: Long
)
@@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
companion object {
val TAG = Log.tag(TimingMessageContentProcessor::class.java)
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
entry.tag == TAG && entry.message == endTag(timestamp)
}
private fun startTag(timestamp: Long) = "$timestamp start"
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp!!))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
Log.d(TAG, endTag(envelope.timestamp!!))
}
}
@@ -1,26 +0,0 @@
package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
class TimingMessageContentProcessorV2(context: Context) : MessageContentProcessorV2(context) {
companion object {
val TAG = Log.tag(TimingMessageContentProcessorV2::class.java)
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
entry.tag == TAG && entry.message == endTag(timestamp)
}
private fun startTag(timestamp: Long) = "$timestamp start"
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean) {
Log.d(TAG, startTag(envelope.timestamp))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent)
Log.d(TAG, endTag(envelope.timestamp))
}
}
@@ -33,6 +33,7 @@ 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.api.util.Usernames
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import java.util.concurrent.TimeUnit
@@ -56,27 +57,10 @@ class UsernameEditFragmentTest {
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()
val scenario = createScenario(UsernameEditMode.NORMAL)
scenario.moveToState(Lifecycle.State.RESUMED)
@@ -96,7 +80,7 @@ class UsernameEditFragmentTest {
fun testNicknameUpdateHappyPath() {
val nickname = "Spiderman"
val discriminator = "4578"
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
val username = "$nickname${Usernames.DELIMITER}$discriminator"
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/username/reserved") {
@@ -107,7 +91,7 @@ class UsernameEditFragmentTest {
}
)
val scenario = createScenario(isInRegistration = true)
val scenario = createScenario(UsernameEditMode.NORMAL)
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.username_text)).perform(typeText(nickname))
@@ -131,8 +115,8 @@ class UsernameEditFragmentTest {
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()
private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
return launchFragmentInContainer(
fragmentArgs = fragmentArgs,
themeResId = R.style.Signal_DayNight_NoActionBar
@@ -6,16 +6,14 @@ import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
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.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@@ -29,35 +27,68 @@ class ContactRecordProcessorTest {
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() {
fun process_splitContact_normalSplit_twoRecords() {
// 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 remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
pni = PNI_A.toString(),
e164 = 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 byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(originalId, byAci)
assertEquals(byE164, byPni)
assertNotEquals(byAci, byE164)
}
@Test
fun process_splitContact_normalSplit_oneRecord() {
// GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByAci(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)
@@ -70,23 +101,29 @@ class ContactRecordProcessorTest {
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 remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 0
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
aci = PNI_A.toString(),
pni = PNI_A.toString(),
e164 = 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 byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
@@ -95,14 +132,14 @@ class ContactRecordProcessorTest {
assertEquals(byAci, byE164)
}
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
private fun buildRecord(id: StorageId, record: ContactRecord): SignalContactRecord {
return SignalContactRecord(id, record)
}
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId.raw))
.where("${RecipientTable.ID} = ?", recipientId)
.run()
}
@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
/**
* Welcome to Alice's Client.
@@ -27,7 +27,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
private val aliceSenderCertificate = FakeClientHelpers.createCertificateFor(
trustRoot = trustRoot,
uuid = serviceId.uuid(),
uuid = serviceId.rawUuid,
e164 = e164,
deviceId = 1,
identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey,
@@ -40,7 +40,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
ApplicationDependencies.getIncomingMessageObserver()
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() }
?.forEach { ApplicationDependencies.getJobManager().add(it) }
?.forEach { it.enqueue() }
bufferedStore.flushToDisk()
val end = System.currentTimeMillis()
@@ -31,7 +31,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -49,7 +49,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
private val serviceAddress = SignalServiceAddress(serviceId, e164)
private val registrationId = KeyHelper.generateRegistrationId(false)
private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair)
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.uuid(), e164, 1, identityKeyPair.publicKey.publicKey, 31337)
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.rawUuid, e164, 1, identityKeyPair.publicKey.publicKey, 31337)
private val sessionLock = object : SignalSessionLock {
private val lock = ReentrantLock()
@@ -60,7 +60,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(now: Long): SignalServiceProtos.Envelope {
fun encrypt(now: Long): Envelope {
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
@@ -71,10 +71,10 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
.toEnvelope(envelopeContent.content.get().dataMessage.timestamp, getAliceServiceId())
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
}
fun decrypt(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) {
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}
@@ -143,7 +143,6 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
@@ -161,13 +160,20 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord?) = throw UnsupportedOperationException()
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean = throw UnsupportedOperationException()
override fun markKyberPreKeyUsed(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeEcPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeEcPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException()
override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableMap<SignalProtocolAddress, SessionRecord> = throw UnsupportedOperationException()
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) = throw UnsupportedOperationException()
override fun removeKyberPreKey(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeKyberPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeKyberPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
}
}
@@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.testing
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64
import org.signal.libsignal.internal.Native
import org.signal.libsignal.internal.NativeHandleGuard
import org.signal.libsignal.metadata.certificate.CertificateValidator
@@ -9,16 +11,16 @@ import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.util.Base64
import java.util.Optional
import java.util.UUID
@@ -52,9 +54,9 @@ object FakeClientHelpers {
}
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
val content = SignalServiceProtos.Content.newBuilder().apply {
setDataMessage(
SignalServiceProtos.DataMessage.newBuilder().apply {
val content = Content.Builder().apply {
dataMessage(
DataMessage.Builder().buildWith {
body = message
timestamp = now
}
@@ -64,16 +66,16 @@ object FakeClientHelpers {
}
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
return Envelope.newBuilder()
.setType(Envelope.Type.valueOf(this.type))
.setSourceDevice(1)
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 1)
.setDestinationUuid(destination.toString())
.setServerGuid(UUID.randomUUID().toString())
.setContent(Base64.decode(this.content).toProtoByteString())
.setUrgent(true)
.setStory(false)
return Envelope.Builder()
.type(Envelope.Type.fromValue(this.type))
.sourceDevice(1)
.timestamp(timestamp)
.serverTimestamp(timestamp + 1)
.destinationServiceId(destination.toString())
.serverGuid(UUID.randomUUID().toString())
.content(Base64.decode(this.content).toByteString())
.urgent(true)
.story(false)
.build()
}
}
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.testing
import okio.ByteString.Companion.toByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
@@ -8,27 +9,28 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.internal.push.GroupContextV2
import kotlin.random.Random
/**
* Helper methods for creating groups for message processing tests et al.
*/
object GroupTestingUtils {
fun member(serviceId: ServiceId, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.newBuilder()
.setUuid(serviceId.toByteString())
.setJoinedAtRevision(revision)
.setRole(role)
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.Builder()
.aciBytes(aci.toByteString())
.joinedAtRevision(revision)
.role(role)
.build()
}
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members.toList())
.setRevision(revision)
.setTitle(MessageContentFuzzer.string())
val decryptedGroupState = DecryptedGroup.Builder()
.members(members.toList())
.revision(revision)
.title(MessageContentFuzzer.string())
.build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
@@ -43,8 +45,11 @@ object GroupTestingUtils {
}
fun Recipient.asMember(): DecryptedMember {
return member(serviceId = requireServiceId())
return member(aci = requireAci())
}
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId)
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId) {
val groupV2Context: GroupContextV2
get() = GroupContextV2(masterKey = masterKey.serialize().toByteString(), revision = 0)
}
}
@@ -1,21 +1,21 @@
package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.database.model.toProtoByteString
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.messages.TestMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.UUID
import kotlin.random.Random
import kotlin.random.nextInt
@@ -34,22 +34,22 @@ object MessageContentFuzzer {
/**
* Create an [Envelope].
*/
fun envelope(timestamp: Long): Envelope {
return Envelope.newBuilder()
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 5)
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID()))
fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
return Envelope.Builder()
.timestamp(timestamp)
.serverTimestamp(timestamp + 5)
.serverGuid(serverGuid.toString())
.build()
}
/**
* Create metadata to match an [Envelope].
*/
fun envelopeMetadata(source: RecipientId, destination: RecipientId, groupId: GroupId.V2? = null): EnvelopeMetadata {
fun envelopeMetadata(source: RecipientId, destination: RecipientId, sourceDeviceId: Int = 1, groupId: GroupId.V2? = null): EnvelopeMetadata {
return EnvelopeMetadata(
sourceServiceId = Recipient.resolved(source).requireServiceId(),
sourceE164 = null,
sourceDeviceId = 1,
sourceDeviceId = sourceDeviceId,
sealedSender = true,
groupId = groupId?.decodedId,
destinationServiceId = Recipient.resolved(destination).requireServiceId()
@@ -61,21 +61,24 @@ object MessageContentFuzzer {
* - An expire timer value
* - Bold style body ranges
*/
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
body = string()
if (random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
addBodyRanges(
SignalServiceProtos.BodyRange.newBuilder().buildWith {
start = 0
length = 1
style = SignalServiceProtos.BodyRange.Style.BOLD
}
bodyRanges(
listOf(
BodyRange.Builder().buildWith {
start = 0
length = 1
style = BodyRange.Style.BOLD
}
)
)
}
if (groupContextV2 != null) {
@@ -86,6 +89,20 @@ object MessageContentFuzzer {
.build()
}
/**
* Create an edit message.
*/
fun editTextMessage(targetTimestamp: Long, editedDataMessage: DataMessage): Content {
return Content.Builder()
.editMessage(
EditMessage.Builder().buildWith {
targetSentTimestamp = targetTimestamp
dataMessage = editedDataMessage
}
)
.build()
}
/**
* Create a sync sent text message for the given [DataMessage].
*/
@@ -95,17 +112,17 @@ object MessageContentFuzzer {
recipientUpdate: Boolean = false
): Content {
return Content
.newBuilder()
.setSyncMessage(
SyncMessage.newBuilder().buildWith {
sent = SyncMessage.Sent.newBuilder().buildWith {
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
sent = SyncMessage.Sent.Builder().buildWith {
timestamp = textMessage.timestamp
message = textMessage
isRecipientUpdate = recipientUpdate
addAllUnidentifiedStatus(
unidentifiedStatus(
deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
destinationUuid = Recipient.resolved(it).requireServiceId().toString()
SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true
}
}
@@ -115,6 +132,24 @@ object MessageContentFuzzer {
).build()
}
/**
* Create a sync reads message for the given [RecipientId] and message timestamp pairings.
*/
fun syncReadsMessage(timestamps: List<Pair<RecipientId, Long>>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
read = timestamps.map { (senderId, timestamp) ->
SyncMessage.Read.Builder().buildWith {
this.senderAci = Recipient.resolved(senderId).requireAci().toString()
this.timestamp = timestamp
}
}
}
).build()
}
/**
* Create a random media message that may be:
* - A text body
@@ -123,9 +158,9 @@ object MessageContentFuzzer {
* - A message with 0-2 attachment pointers and may contain a text body
*/
fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextBoolean()) {
body = string()
}
@@ -133,28 +168,28 @@ object MessageContentFuzzer {
if (random.nextBoolean() && quoteAble.isNotEmpty()) {
body = string()
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
quote = DataMessage.Quote.Builder().buildWith {
id = quoted.envelope.timestamp
authorUuid = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
addAllAttachments(quoted.content.dataMessage.attachmentsList)
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage?.body
attachments(quoted.content.dataMessage?.attachments ?: emptyList())
bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
type = DataMessage.Quote.Type.NORMAL
}
}
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
authorUuid = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
quote = DataMessage.Quote.Builder().buildWith {
id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage?.body
}
}
if (random.nextFloat() < 0.25) {
val total = random.nextInt(1, 2)
(0..total).forEach { _ -> addAttachments(attachmentPointer()) }
attachments((0..total).map { attachmentPointer() })
}
}
)
@@ -166,15 +201,15 @@ object MessageContentFuzzer {
* - A reaction to a prior message
*/
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.25) {
val reactTo = previousMessages.random(random)
reaction = DataMessage.Reaction.newBuilder().buildWith {
reaction = DataMessage.Reaction.Builder().buildWith {
emoji = emojis.random(random)
remove = false
targetAuthorUuid = reactTo.metadata.sourceServiceId.toString()
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
targetSentTimestamp = reactTo.envelope.timestamp
}
}
@@ -183,22 +218,21 @@ object MessageContentFuzzer {
}
/**
* Create a random media message that can never contain a text body. It may be:
* - A sticker
* Create a random media message that contains a sticker.
*/
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
if (random.nextFloat() < 0.9) {
sticker = DataMessage.Sticker.newBuilder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data = attachmentPointer()
emoji = emojis.random(random)
}
fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data_ = attachmentPointer()
emoji = emojis.random(random)
}
groupV2 = groupContextV2
}
).build()
}
@@ -223,14 +257,14 @@ object MessageContentFuzzer {
* Generate a random [ByteString].
*/
fun byteString(length: Int = 512): ByteString {
return random.nextBytes(length).toProtoByteString()
return random.nextBytes(length).toByteString()
}
/**
* Generate a random [AttachmentPointer].
*/
fun attachmentPointer(): AttachmentPointer {
return AttachmentPointer.newBuilder().run {
return AttachmentPointer.Builder().run {
cdnKey = string()
contentType = mediaTypes.random(random)
key = byteString()
@@ -1,31 +1,15 @@
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.signal.libsignal.svr2.PinHash
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.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
@@ -46,7 +30,8 @@ object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
backupCredentials = AuthCredentials.create("username", "password")
svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -83,29 +68,6 @@ object MockProvider {
}
}
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: PinHash?): 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())
@@ -22,6 +22,8 @@ class Put(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestP
class Post(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("POST", path), responseFactory)
class Delete(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("DELETE", path), responseFactory)
fun MockResponse.success(response: Any? = null): MockResponse {
return setResponseCode(200).apply {
if (response != null) {
@@ -29,14 +29,14 @@ 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.testing.GroupTestingUtils.asMember
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.ServiceId.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
/**
@@ -45,7 +45,7 @@ import java.util.UUID
*
* To use: `@get:Rule val harness = SignalActivityRule()`
*/
class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() {
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication()
@@ -57,6 +57,9 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
private set
lateinit var othersKeys: List<IdentityKeyPair>
var group: GroupTestingUtils.TestGroupInfo? = null
private set
val inMemoryLogger: InMemoryLogger
get() = (application as SignalInstrumentationApplicationContext).inMemoryLogger
@@ -68,6 +71,15 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
others = setupOthers.first
othersKeys = setupOthers.second
if (createGroup && others.size >= 2) {
group = GroupTestingUtils.insertGroup(
revision = 0,
self.asMember(),
others[0].asMember(),
others[1].asMember()
)
}
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@@ -78,6 +90,9 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
@@ -92,13 +107,19 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
),
VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null),
VerifyResponse(
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
),
false
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
SignalStore.svr().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -120,7 +141,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
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.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
@@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.testing
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
/**
@@ -34,7 +36,8 @@ class SignalDatabaseRule(
private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) {
SignalDatabase.messages.deleteAllThreads()
SignalDatabase.threads.deleteAllConversations()
SignalDatabase.rawDatabase.deleteAll(ThreadTable.TABLE_NAME)
}
}
}
@@ -1,70 +0,0 @@
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()
}
}
}
@@ -1,11 +1,19 @@
package org.thoughtcrime.securesms.testing
import android.database.Cursor
import android.util.Base64
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 org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.select
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@@ -53,3 +61,29 @@ fun CountDownLatch.awaitFor(duration: Duration) {
throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms")
}
}
fun dumpTableToLogs(tag: String = "TestUtils", table: String) {
dumpTable(table).forEach { Log.d(tag, it.toString()) }
}
fun dumpTable(table: String): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
}
map
}
}
@@ -6,6 +6,6 @@ package org.thoughtcrime.securesms.util;
public final class FeatureFlagsAccessor {
public static void forceValue(String key, Object value) {
FeatureFlags.FORCED_VALUES.put(FeatureFlags.PHONE_NUMBER_PRIVACY, true);
FeatureFlags.FORCED_VALUES.put(key, value);
}
}
@@ -38,10 +38,8 @@ object MessageTableTestUtils {
isKeyExchangeType:${type and MessageTypes.KEY_EXCHANGE_BIT != 0L}
isIdentityVerified:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT != 0L}
isIdentityDefault:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT != 0L}
isCorruptedKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CORRUPTED_BIT != 0L}
isInvalidVersionKeyExchange:${type and MessageTypes.KEY_EXCHANGE_INVALID_VERSION_BIT != 0L}
isBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_BUNDLE_BIT != 0L}
isContentBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CONTENT_FORMAT != 0L}
isIdentityUpdate:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_UPDATE_BIT != 0L}
isRateLimited:${type and MessageTypes.MESSAGE_RATE_LIMITED_BIT != 0L}
isExpirationTimerUpdate:${type and MessageTypes.EXPIRATION_TIMER_UPDATE_BIT != 0L}
@@ -6,8 +6,6 @@ import org.signal.benchmark.setup.TestMessages
import org.signal.benchmark.setup.TestUsers
import org.thoughtcrime.securesms.BaseActivity
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient
class BenchmarkSetupActivity : BaseActivity() {
@@ -53,13 +51,6 @@ class BenchmarkSetupActivity : BaseActivity() {
TestMessages.insertOutgoingTextMessage(other = recipient, body = "Test message $i", timestamp = generator.nextTimestamp())
}
val voiceMessageId = TestMessages.insertIncomingVoiceMessage(other = recipient, timestamp = generator.nextTimestamp())
val mmsRecord = SignalDatabase.messages.getMessageRecord(voiceMessageId) as MediaMmsMessageRecord
TestMessages.insertOutgoingImageMessage(other = recipient, body = "test", 2, generator.nextTimestamp())
TestMessages.insertIncomingTextMessage(other = recipient, "reply to the test message", generator.nextTimestamp())
TestMessages.insertIncomingQuoteTextMessage(other = recipient, quote = QuoteModel(mmsRecord.timestamp, recipient.id, "Fake voice message text", false, mmsRecord.slideDeck.asAttachments(), null, QuoteModel.Type.NORMAL, null), body = "Here is a cool quote", timestamp = generator.nextTimestamp())
TestMessages.insertOutgoingTextMessage(other = recipient, body = "longaweorijoaijwerijoiajwer", timestamp = generator.nextTimestamp())
SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true)
}
}
@@ -7,8 +7,8 @@ import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import java.io.IOException
import java.util.Optional
@@ -2,9 +2,10 @@ package org.signal.benchmark.setup
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TestDbUtils
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient
@@ -65,7 +66,8 @@ object TestMessages {
return insert
}
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -73,10 +75,11 @@ object TestMessages {
receivedTimeMillis = timestamp ?: System.currentTimeMillis()
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
}
fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -90,28 +93,30 @@ object TestMessages {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
imageAttachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = failed)
return insertIncomingMessage(recipient = other, message = message, failed = failed)
}
fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List<SignalServiceAttachment>))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = false)
return insertIncomingMessage(recipient = other, message = message, failed = false)
}
private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long {
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage, failed: Boolean = false): Long {
val id = insertIncomingMessage(recipient = recipient, message = message)
if (failed) {
setMessageMediaFailed(id)
@@ -122,8 +127,8 @@ object TestMessages {
return id
}
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
}
private fun setMessageMediaFailed(messageId: Long) {
@@ -148,6 +153,8 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,
@@ -169,6 +176,8 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.aac"),
true,
false,
@@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.signal.benchmark.DummyAccountManagerFactory
import org.signal.core.util.concurrent.safeBlockingGet
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
@@ -22,7 +23,7 @@ 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.ServiceId.ACI
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
@@ -43,6 +44,9 @@ object TestUsers {
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
val registrationData = RegistrationData(
code = "123123",
@@ -54,16 +58,26 @@ object TestUsers {
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
)
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
val verifyResponse = VerifyResponse(
VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
)
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
registrationData,
verifyResponse,
false
).blockingGet()
).safeBlockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
SignalStore.svr().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -86,7 +100,7 @@ object TestUsers {
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.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
@@ -0,0 +1,85 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import org.signal.core.ui.Rows
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
/**
* Configuration fragment for the internal conversation test fragment.
*/
class InternalConversationSpringboardFragment : ComposeFragment() {
private val viewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
@Composable
override fun FragmentContent() {
Content(this::navigateBack, this::launchTestFragment, viewModel.hasWallpaper)
}
private fun navigateBack() {
findNavController().popBackStack()
}
private fun launchTestFragment() {
findNavController().navigate(
InternalConversationSpringboardFragmentDirections
.actionInternalConversationSpringboardFragmentToInternalConversationTestFragment()
)
}
}
@Preview
@Composable
private fun ContentPreview() {
val hasWallpaper = remember { mutableStateOf(false) }
SignalTheme(isDarkMode = true) {
Content(onBackPressed = {}, onLaunchTestFragment = {}, hasWallpaper = hasWallpaper)
}
}
@Composable
private fun Content(
onBackPressed: () -> Unit,
onLaunchTestFragment: () -> Unit,
hasWallpaper: MutableState<Boolean>
) {
Scaffolds.Settings(
title = "Conversation Test Springboard",
onNavigationClick = onBackPressed,
navigationIconPainter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24))
) {
Column(modifier = Modifier.padding(it)) {
Rows.TextRow(
text = "Launch Conversation Test Fragment",
onClick = onLaunchTestFragment
)
Rows.ToggleRow(
checked = hasWallpaper.value,
text = "Enable Wallpaper",
onCheckChanged = { hasWallpaper.value = it }
)
}
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class InternalConversationSpringboardViewModel : ViewModel() {
val hasWallpaper = mutableStateOf(false)
}
@@ -0,0 +1,142 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import java.security.SecureRandom
import kotlin.time.Duration.Companion.milliseconds
/**
* Generates random conversation messages via the given set of parameters.
*/
class ConversationElementGenerator {
private val mappingModelCache = mutableMapOf<ConversationElementKey, MappingModel<*>>()
private val random = SecureRandom()
private val wordBank = listOf(
"A",
"Test",
"Message",
"To",
"Display",
"Content",
"In",
"Bubbles",
"User",
"Signal",
"The"
)
fun getMappingModel(key: ConversationElementKey): MappingModel<*> {
val cached = mappingModelCache[key]
if (cached != null) {
return cached
}
val messageModel = generateMessage(key)
mappingModelCache[key] = messageModel
return messageModel
}
private fun getIncomingType(): Long {
return MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentOutgoingType(): Long {
return MessageTypes.BASE_SENT_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentFailedOutgoingType(): Long {
return MessageTypes.BASE_SENT_FAILED_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getPendingOutgoingType(): Long {
return MessageTypes.BASE_OUTBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun generateMessage(key: ConversationElementKey): MappingModel<*> {
val messageId = key.requireMessageId()
val now = getNow()
val testMessageWordLength = random.nextInt(3) + 1
val testMessage = (0 until testMessageWordLength).map {
wordBank.random()
}.joinToString(" ")
val isIncoming = random.nextBoolean()
val record = MmsMessageRecord(
messageId,
if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
0,
if (isIncoming) Recipient.self() else Recipient.UNKNOWN,
now,
now,
now,
true,
1,
testMessage,
SlideDeck(),
if (isIncoming) getIncomingType() else getPendingOutgoingType(),
emptySet(),
emptySet(),
0,
0,
0,
false,
true,
null,
emptyList(),
emptyList(),
false,
emptyList(),
false,
false,
now,
true,
now,
null,
StoryType.NONE,
null,
null,
null,
null,
-1,
null,
null,
0,
false,
null
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
record,
Recipient.UNKNOWN
)
return if (isIncoming) {
IncomingTextOnly(conversationMessage)
} else {
OutgoingTextOnly(conversationMessage)
}
}
private fun getNow(): Long {
val now = System.currentTimeMillis()
return now - random.nextInt(20.milliseconds.inWholeMilliseconds.toInt()).toLong()
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElement
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import kotlin.math.min
class InternalConversationTestDataSource(
private val size: Int,
private val generator: ConversationElementGenerator
) : PagedDataSource<ConversationElementKey, MappingModel<*>> {
override fun size(): Int = size
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): MutableList<MappingModel<*>> {
val end = min(start + length, totalSize)
return (start until end).map {
load(ConversationElementKey.forMessage(it.toLong()))!!
}.toMutableList()
}
override fun getKey(data: MappingModel<*>): ConversationElementKey {
check(data is ConversationMessageElement)
return ConversationElementKey.forMessage(data.conversationMessage.messageRecord.id)
}
override fun load(key: ConversationElementKey?): MappingModel<*>? {
return key?.let { generator.getMappingModel(it) }
}
}
@@ -0,0 +1,315 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.navGraphViewModels
import com.bumptech.glide.Glide
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard.InternalConversationSpringboardViewModel
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.databinding.ConversationTestFragmentBinding
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.doAfterNextLayout
class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fragment) {
companion object {
private val TAG = Log.tag(InternalConversationTestFragment::class.java)
}
private val binding by ViewBinderDelegate(ConversationTestFragmentBinding::bind)
private val viewModel: InternalConversationTestViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
private val springboardViewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = ConversationAdapterV2(
lifecycleOwner = viewLifecycleOwner,
requestManager = Glide.with(this),
clickListener = ClickListener(),
hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(),
startExpirationTimeout = {},
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) },
displayDialogFragment = {}
)
if (springboardViewModel.hasWallpaper.value) {
binding.root.setBackgroundColor(0xFF32C7E2.toInt())
}
var startTime = 0L
var firstRender = true
lifecycleDisposable.bindTo(viewLifecycleOwner)
adapter.setPagingController(viewModel.controller)
lifecycleDisposable += viewModel.data.observeOn(AndroidSchedulers.mainThread()).subscribeBy {
if (firstRender) {
startTime = System.currentTimeMillis()
}
adapter.submitList(it) {
if (firstRender) {
firstRender = false
binding.root.doAfterNextLayout {
val endTime = System.currentTimeMillis()
Log.d(TAG, "First render in ${endTime - startTime} millis")
}
}
}
}
binding.recycler.layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
binding.recycler.adapter = adapter
RecyclerViewColorizer(binding.recycler).apply {
setChatColors(ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto))
}
}
private inner class ClickListener : ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onStickerClicked(stickerLocator: StickerLocator) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onAddToContactsClicked(contact: Contact) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePause(uri: Uri) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChatSessionRefreshLearnMoreClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinGroupCallClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEnableCallNotificationsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChangeNumberUpdateContact(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onDonateClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBlockJoinRequest(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRecipientNameClicked(target: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteToSignalClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onActivatePaymentsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSendPaymentClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUrlClicked(url: String): Boolean {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
return true
}
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemClick(item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowSafetyTips(forGroup: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onReportSpamLearnMoreClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageRequestAcceptOptionsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import androidx.lifecycle.ViewModel
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
class InternalConversationTestViewModel : ViewModel() {
private val generator = ConversationElementGenerator()
private val dataSource = InternalConversationTestDataSource(
500,
generator
)
private val config = PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.build()
private val pagedData = PagedData.createForObservable(dataSource, config)
val controller = pagedData.controller
val data = pagedData.data
}
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:usesCleartextTraffic="true"
tools:replace="android:usesCleartextTraffic"
+351 -257
View File
@@ -17,21 +17,15 @@
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
@@ -43,20 +37,16 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<!-- For sending/receiving events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -73,17 +63,14 @@
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- For fixing MMS -->
<!-- For device transfer -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
@@ -93,6 +80,10 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -133,28 +124,26 @@
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"/>
android:launchMode="singleTask"
android:exported="false" />
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:noHistory="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
<activity android:name=".InviteActivity"
android:theme="@style/Signal.Light.NoActionBar.Invite"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".MainActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true">
@@ -174,12 +163,10 @@
</intent-filter>
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize" />
android:windowSoftInputMode="adjustResize"
android:exported="false" />
<activity android:name=".sharing.v2.ShareActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
@@ -239,6 +226,7 @@
<activity-alias android:name=".RoutingActivity"
android:targetActivity=".MainActivity"
android:resizeableActivity="true"
android:exported="true">
<intent-filter>
@@ -580,6 +568,13 @@
android:host="signal.group"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="signaldonations.org" android:pathPrefix="/stripe/return/ideal"/>
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -602,10 +597,13 @@
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="signal.link" />
<data android:scheme="sgnl" />
<data android:scheme="https" />
<data android:host="signal.link" />
</intent-filter>
</activity>
@@ -613,26 +611,19 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".conversation.ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity">
android:parentActivityName=".MainActivity"
android:resizeableActivity="true"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".conversation.BubbleConversationActivity"
android:theme="@style/Signal.DayNight"
android:allowEmbedded="true"
android:resizeableActivity="true" />
android:theme="@style/Signal.DayNight"
android:allowEmbedded="true"
android:resizeableActivity="true"
android:exported="false"/>
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
@@ -640,78 +631,99 @@
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@style/TextSecure.LightTheme.Popup"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize"
android:exported="false"/>
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphraseCreateActivity"
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".NewConversationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".recipients.ui.findby.FindByActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".calls.links.details.CallLinkDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".calls.new.NewCallActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".giph.ui.GiphyActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
<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=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<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=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".verify.VerifyIdentityActivity"
android:exported="false"
@@ -733,13 +745,22 @@
android:name=".stories.my.MyStoriesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden" />
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity
android:name=".backup.v2.ui.MessageBackupsFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".stories.settings.StorySettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
android:exported="false"/>
<activity
android:name=".stories.viewer.StoryViewerActivity"
@@ -748,82 +769,99 @@
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:parentActivityName=".MainActivity">
android:parentActivityName=".MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<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=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".revealable.ViewOnceMessageActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DeviceActivity"
android:screenOrientation="portrait"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".logsubmit.SubmitDebugLogActivity"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediapreview.MediaPreviewV2Activity"
android:label="@string/AndroidManifest__media_preview"
@@ -836,19 +874,21 @@
<activity android:name=".AvatarPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity
android:name=".avatar.photo.PhotoEditorActivity"
<activity android:name=".avatar.photo.PhotoEditorActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:label="@string/AndroidManifest__media_preview"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateHidden" />
android:windowSoftInputMode="stateHidden"
android:exported="false"/>
<activity android:name=".mediaoverview.MediaOverviewActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
@@ -859,7 +899,8 @@
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
android:finishOnTaskLaunch="true"
android:exported="false"/>
<activity android:name=".PlayServicesProblemActivity"
android:exported="false"
@@ -885,12 +926,12 @@
</activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:exported="true"
android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:exported="true"
android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -902,136 +943,185 @@
<activity android:name=".mediasend.AvatarSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".blocked.BlockedUsersActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".profiles.edit.EditProfileActivity"
<activity android:name=".profiles.edit.CreateProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".profiles.username.AddAUsernameActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".backup.v2.ui.MessageBackupsTestRestoreActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:exported="false"/>
<activity android:name=".profiles.manage.ManageProfileActivity"
<activity android:name=".profiles.manage.EditProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".nicknames.NicknameActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".lock.v2.CreateKbsPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".lock.v2.KbsMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".lock.v2.CreateSvrPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity
android:name=".lock.v2.SvrMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
android:theme="@style/TextSecure.DialogActivity"
android:exported="false"/>
<activity android:name=".contactshare.ContactShareEditActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contactshare.ContactNameEditActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contactshare.SharedContactDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".ShortcutLauncherActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".MainActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:resizeableActivity="true"
android:exported="false"/>
<activity android:name=".pin.PinRestoreActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".megaphone.ClientDeprecatedActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
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" />
android:launchMode="singleTask"
android:exported="false"/>
<activity android:name=".ratelimit.RecaptchaProofActivity"
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"
android:exported="false"/>
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar" />
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:screenOrientation="portrait"
android:theme="@style/Theme.Signal.WallpaperCropper" />
android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrScannerActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".reactions.edit.EditReactionsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".exporter.flow.SmsExportActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<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:name=".messages.IncomingMessageObserver$ForegroundService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$BackgroundService"/>
<service android:name=".service.webrtc.AndroidCallConnectionService"
<service
android:enabled="true"
android:name=".service.webrtc.WebRtcCallService"
android:foregroundServiceType="camera|microphone"
android:exported="false"/>
<service
android:enabled="true"
android:exported="false"
android:name=".service.KeyCachingService" />
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$ForegroundService"
android:exported="false"/>
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$BackgroundService"
android:exported="false"/>
<service
android:name=".service.webrtc.AndroidCallConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
@@ -1039,9 +1129,13 @@
</intent-filter>
</service>
<service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
<service
android:name=".components.voice.VoiceNotePlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
@@ -1051,19 +1145,6 @@
</intent-filter>
</receiver>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
@@ -1079,11 +1160,21 @@
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service android:name=".service.GenericForegroundService"/>
<service
android:name=".service.GenericForegroundService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchBackgroundService" />
<service
android:name=".service.AttachmentProgressService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchForegroundService" />
<service
android:name=".gcm.FcmFetchBackgroundService"
android:exported="false"/>
<service
android:name=".gcm.FcmFetchForegroundService"
android:exported="false"/>
<service android:name=".gcm.FcmReceiveService" android:exported="true">
<intent-filter>
@@ -1091,39 +1182,6 @@
</intent-filter>
</service>
<receiver android:name=".service.SmsListener"
android:permission="android.permission.BROADCAST_SMS"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
</intent-filter>
</receiver>
<receiver android:name=".service.SmsDeliveryListener"
android:exported="true">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
</intent-filter>
</receiver>
<receiver android:name=".service.MmsListener"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">
@@ -1140,19 +1198,38 @@
</intent-filter>
</receiver>
<receiver android:name=".service.ExpirationListener" />
<receiver
android:name=".service.ExpirationListener"
android:exported="false"/>
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
<receiver
android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm"
android:exported="false"/>
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<receiver
android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm"
android:exported="false"/>
<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />
<receiver
android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm"
android:exported="false"/>
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
<receiver
android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm"
android:exported="false"/>
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
<receiver
android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm"
android:exported="false"/>
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
<receiver
android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver"
android:exported="false"/>
<provider android:name=".providers.AvatarProvider"
android:authorities="${applicationId}.avatar"
android:exported="false"
android:grantUriPermissions="true" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
@@ -1164,11 +1241,6 @@
android:exported="false"
android:grantUriPermissions="true" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}.mms" />
<provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
@@ -1203,7 +1275,7 @@
</intent-filter>
</receiver>
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver" android:exported="false">
<receiver android:name=".messageprocessingalarm.RoutineMessageFetchReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
@@ -1216,6 +1288,12 @@
</intent-filter>
</receiver>
<receiver android:name=".service.AnalyzeDatabaseAlarmListener" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
@@ -1250,12 +1328,14 @@
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service
@@ -1278,6 +1358,20 @@
</intent-filter>
</receiver>
<service
android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallForegroundService"
android:exported="false"
android:foregroundServiceType="camera|microphone" />
<receiver android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallServiceReceiver" android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.DENY"/>
</intent-filter>
<intent-filter>
<action android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.HANGUP"/>
</intent-filter>
</receiver>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 210 KiB

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