Compare commits

..

1645 Commits

Author SHA1 Message Date
Cody Henthorne 6c302b708a Bump version to 7.11.0 2024-07-09 13:55:58 -04:00
Cody Henthorne 6a22919c50 Update baseline profile. 2024-07-09 13:49:15 -04:00
Cody Henthorne 9073ce5c7b Update translations and other static files. 2024-07-09 13:46:48 -04:00
Michelle Tang 9024c19169 Update device-specific notification support configs. 2024-07-09 13:40:41 -04:00
Arthur-GYT 60a0565ba8 Show max edits warning before editing.
Fixes #13428
Closes #13615

Signed-off-by: Arthur-GYT <a.gayot@ik.me>
2024-07-09 13:40:41 -04:00
Cody Henthorne 383f7556e3 Fix delete for everyone dialog option in note to self. 2024-07-09 13:40:41 -04:00
Greyson Parrelli 94795599e2 Inline the delete sync feature flag. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley 84fbb7c466 Update "lower hand" label to "lower" 2024-07-09 13:40:41 -04:00
Cody Henthorne c339f39b70 Fix camera manager memory leak. 2024-07-09 13:40:41 -04:00
Cody Henthorne 41a3609f06 Fix edit quote draft loading incorrectly bug. 2024-07-09 13:40:41 -04:00
Cody Henthorne f5abd7acdf Add Group Send Endorsements support. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley 414368e251 Prevent crash when trying to display Contact Support bottom sheet multiple times. 2024-07-09 13:40:41 -04:00
Jim Gustafson a3d1197aef Update to RingRTC v2.44.3 2024-07-09 13:40:41 -04:00
Nicholas Tinsley d91760eefc Upgrade AndroidX Media3 to 1.3.1. 2024-07-09 13:40:41 -04:00
Greyson Parrelli ee20ced238 Switch MediaName to hex encoding. 2024-07-09 13:40:41 -04:00
Alex Hart 066892c11a Make group subtitles auto-update as names change. 2024-07-09 13:40:41 -04:00
Alex Hart 69fd4f79db Stop reading redemptionTime field. 2024-07-09 13:40:41 -04:00
Alex Hart f49e2768c1 Fix crash in review card repository. 2024-07-09 13:40:41 -04:00
Greyson Parrelli 70378b85d7 Remove unused capabilities. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley 585401a98e Do not download attachment if we do not have a digest. 2024-07-09 13:40:41 -04:00
Alex Hart cf7ebfa03d Do not mark update unread if user was ever in the call. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley aec0a9951a Prevent backups from being scheduled twice within the jitter window.
Fixes #13559.
2024-07-09 13:40:41 -04:00
Nicholas Tinsley b113eec940 Show "Update" action button on profile name change. 2024-07-09 13:40:41 -04:00
Michelle Tang a966812bfc Add full send attachments. 2024-07-09 13:40:41 -04:00
Alex Hart 3879a8ffdb Pluralize backup strings. 2024-07-09 13:40:41 -04:00
Alex Hart 5b949b0116 Fix call id serialization. 2024-07-09 13:40:41 -04:00
Rashad Sookram 3c13619ce8 Update to RingRTC v2.44.2 2024-07-09 13:40:41 -04:00
Cody Henthorne 24bba98122 Fix sync thread delete sending another sync back. 2024-07-09 13:40:41 -04:00
Cody Henthorne a96e5e6ae6 Fix delete sync capability updating on linked devices. 2024-07-09 13:40:41 -04:00
Alex Hart 4cfdfab31e Rename more in-app-payment classes. 2024-07-09 13:40:41 -04:00
Alex Hart 77d3116431 Rename DonationValues to InAppPaymentValues. 2024-07-09 13:40:41 -04:00
Alex Hart b943df1ce4 Add translatable copy for backup alert fragment. 2024-07-09 13:40:41 -04:00
Alex Hart 8bbb7d56e0 Implements a bunch of missing things in the backup checkout flow stuff. 2024-07-09 13:40:41 -04:00
Clark 079a3d4fee Add import/export tests for contact messages and link previews. 2024-07-09 13:40:41 -04:00
Jim Gustafson 176e0e7765 Update to RingRTC v2.44.1 2024-07-09 13:40:41 -04:00
Greyson Parrelli c73e80f8d9 Include username link entropy in backups. 2024-07-09 13:40:41 -04:00
Greyson Parrelli 47cd1b568f Add lock screen help dialog. 2024-07-09 13:40:41 -04:00
Clark 058c523329 Add support for import/export of shared contact messages. 2024-07-09 13:40:40 -04:00
Clark 84515482a6 Message backup support for link previews. 2024-07-09 13:40:40 -04:00
Chris Eager 02629020df Remove Option.RECAPTCHA from ProofRequiredException. 2024-07-09 13:40:40 -04:00
Clark 58d769b21f Allow exporting backup tests to binproto. 2024-07-09 13:40:40 -04:00
Greyson Parrelli 9dc67e0466 Do not use quote contents in edit sync messages.
Resolves #13571
2024-07-09 13:40:40 -04:00
Greyson Parrelli 72d02104dc Move StringExtensions to core-util-jvm. 2024-07-09 13:40:40 -04:00
Cody Henthorne 371a39049d Ignore flakey delete sync test. 2024-07-03 14:02:59 -04:00
Clark 47e4a6cf5a Regularly delete any archived media we don't know about. 2024-07-03 14:02:59 -04:00
Clark Chen 4a41e9f9a1 Bump version to 7.10.3 2024-07-02 11:21:45 -04:00
Clark Chen 9fa1b58019 Update translations and other static files. 2024-07-02 11:21:45 -04:00
Michelle Tang c24473e176 Fix default name for linked devices. 2024-07-01 16:44:53 -04:00
mtang-signal 1311ec498f Default to back camera when linking device. 2024-07-01 08:59:35 -04:00
Nicholas Tinsley 251cec5dee Bump version to 7.10.2 2024-06-28 15:23:01 -04:00
Nicholas Tinsley 1e15a8c1d3 Update translations and other static files. 2024-06-28 15:22:12 -04:00
Michelle Tang 9c5c58794b Fix invalid qr code crash. 2024-06-27 16:12:38 -04:00
Nicholas Tinsley 50063854d7 Bump version to 7.10.1 2024-06-26 13:57:39 -04:00
Nicholas Tinsley 79d2041e46 Update translations and other static files. 2024-06-26 13:45:34 -04:00
Nicholas Tinsley 479b27ce94 Fix benchmark tests. 2024-06-26 13:39:28 -04:00
Cody Henthorne a66857a7cc Fix incorrect local group state bug. 2024-06-26 13:39:28 -04:00
Alex Hart 37815a3f39 Fix bug with cut off avatars. 2024-06-26 13:39:28 -04:00
Alex Hart b55ba67b66 Split out backup and subscription error sheet handling. 2024-06-26 13:39:28 -04:00
Michelle Tang 37a2d5fbca Release updated linked devices. 2024-06-26 13:39:28 -04:00
Nicholas Tinsley d7b5c6bff3 Delete registration V1. 2024-06-26 13:39:28 -04:00
Alex Hart f11028529e Fix hard coded placeholder in iDEAL dialog. 2024-06-25 10:19:27 -03:00
Greyson Parrelli 93ec322bb9 Bump version to 7.10.0 2024-06-24 15:37:57 -04:00
Greyson Parrelli 71e0468d2c Update translations and other static files. 2024-06-24 15:37:08 -04:00
moiseev-signal 816e3442a0 Adopt libsignal 0.51.1 2024-06-24 15:04:03 -04:00
Cody Henthorne c37ed722dc Attempt to fix potential draft setting loop. 2024-06-24 15:04:03 -04:00
Michelle Tang e08c2966c3 Move biometrics check when linking a device. 2024-06-24 15:04:03 -04:00
Cody Henthorne 976f80ff7e Fix conversation not closing after delete bug. 2024-06-24 15:04:03 -04:00
Greyson Parrelli 34a4bda331 Do not send PNI-hello-worlds for new installs. 2024-06-24 15:04:03 -04:00
Greyson Parrelli a4077ccb4a Add app bundle support.
Co-authored-by: Joshua Lund <josh@signal.org>
2024-06-24 15:04:03 -04:00
Cody Henthorne 21ada2a503 Fix request to rejoin after group updates bug. 2024-06-24 15:04:03 -04:00
Alex Hart 57a70c3085 Quiet down auth check job. 2024-06-24 15:04:03 -04:00
Cody Henthorne 16c8b88f0f Fix multiple text attachments when forwarding to multiple recipients.
Fixes #13593
2024-06-24 15:04:03 -04:00
Michelle Tang b806952430 Add device-specific support configs. 2024-06-24 15:04:03 -04:00
Alex Hart c0da0bd272 Add proper payment method type to BackupTypeSettings screen. 2024-06-24 15:04:03 -04:00
Alex Hart 45239c2264 Add payment history screens for backups. 2024-06-24 15:04:03 -04:00
Alex Hart 690236c4e5 Handle manual cancellation UI hint in DonationValues. 2024-06-24 15:04:03 -04:00
Clark ebee3f72e6 Add test infrastructure for backup binprotos. 2024-06-24 15:04:03 -04:00
Alex Hart 37cec7d44f Implement 1:1 call event delete syncs. 2024-06-24 15:04:03 -04:00
Alex Hart 187fd63a75 Add content for disk full alert for backups. 2024-06-24 15:04:03 -04:00
Greyson Parrelli 362cdfc463 Use a snapshot of the SignalStore during backups. 2024-06-24 15:04:03 -04:00
Greyson Parrelli 863b443317 Convert SignalStore to kotlin. 2024-06-24 15:04:03 -04:00
Greyson Parrelli 341c474610 Remove some indirect database reads from backup export. 2024-06-24 15:04:03 -04:00
Greyson Parrelli cbb3c0911c Create backups from copies of the database file.
Still more work here to do with regards to certain tables, like
SignalStore and Recipient.
2024-06-24 15:04:03 -04:00
Greyson Parrelli 890facc6f6 Clean up ChatItemExportIterator. 2024-06-24 15:04:03 -04:00
Greyson Parrelli 6fa8337058 Update to the latest backup v2 spec.
Removes some dead protos, removes some sticker details, adds in gift
badges.
2024-06-24 15:04:03 -04:00
Alex Hart 3f1cb65e02 Migrate translatable strings from fragment to xml. 2024-06-24 15:04:03 -04:00
Cody Henthorne 3551e7ec00 Remove rx send remote config and group send using rx always. 2024-06-24 15:04:03 -04:00
Alex Hart 5ecf60a306 Add ability to turn off and delete backups. 2024-06-24 15:04:03 -04:00
Cody Henthorne 6659700a1c Improve delete sync coverage for partial expiring threads. 2024-06-24 15:04:02 -04:00
Cody Henthorne 070174fee6 Add delete sync capability to log section. 2024-06-24 15:04:02 -04:00
Cody Henthorne 09003d85b1 Add single attachment delete sync. 2024-06-24 15:04:02 -04:00
Alex Hart ea87108def Heal InAppPaymentSubscriber currency if we have a payment with a matching subscriber id. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley 7a696f9a62 Increase pluralization for raised hand snackbar. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley 8ba57a2733 Upgrade OkHttp to 4.12.
Addresses #13491
2024-06-24 15:04:02 -04:00
Cody Henthorne 9824cc2cbe Update delete sync strings. 2024-06-24 15:04:02 -04:00
Michelle Tang ad60cc72cb Add empty linked device state. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley 1950b80402 Update AndroidX Camera libraries to 1.3.4. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley 2acb47952b Differently pluralize raise hand strings. 2024-06-24 15:04:02 -04:00
Cody Henthorne 14cacaef86 Add verbose tracking to DelteForMeSync test to help finding flakey test. 2024-06-24 15:04:02 -04:00
Michelle Tang 958e815933 Remove ability to scan qr code from gallery. 2024-06-24 15:04:02 -04:00
Alex Hart 6b50be78c0 Implement start of backups payment integration work. 2024-06-24 15:04:02 -04:00
Michelle Tang 680223c4b6 Update permission buttons for contacts.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2024-06-24 15:04:02 -04:00
Greyson Parrelli 1af914d5ef Use SubsamplingImageView for everything except GIFs.
Fixes #10324
2024-06-24 15:04:02 -04:00
Cody Henthorne a2fc710261 Add support for addressing attachments within a message. 2024-06-24 15:04:02 -04:00
Dan Brunwasser 10922594b3 Improve system emoji rendering across the app with EmojiCompat2.
Resolves #13327
2024-06-24 15:04:02 -04:00
Michelle Tang abd80c5204 Update linked devices UI. 2024-06-24 15:04:02 -04:00
Alex Hart ff589e3b91 Fix call link export crash. 2024-06-24 15:04:02 -04:00
Cody Henthorne c80ccd70ec Add additional delete sync support. 2024-06-24 15:04:02 -04:00
Jim Gustafson d22d18da47 Update to RingRTC v2.44.0 2024-06-24 15:04:02 -04:00
Clark 75b41c34ea Add import/export for stickers and sticker packs. 2024-06-24 15:04:02 -04:00
Alex Hart 11557e4815 Rewrite fallbackphoto system. 2024-06-24 15:04:02 -04:00
Greyson Parrelli d698f74d0b Rename FeatureFlags -> RemoteConfig. 2024-06-24 15:04:02 -04:00
Greyson Parrelli ecbea9fd95 Improve FeatureFlag change detection, use for SVR3. 2024-06-24 15:04:02 -04:00
Greyson Parrelli 13f7a64139 Refactor FeatureFlags. 2024-06-24 15:04:02 -04:00
Michelle Tang 39cb1c638e Remove sms tag from contacts. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley 489b58ad67 Abort transcoding if frame processing gets stuck. 2024-06-24 15:04:02 -04:00
Cody Henthorne f20fe33af9 Ignore flakey delete sync test. 2024-06-24 15:04:02 -04:00
Clark 6adddf4a0c Add display of last backup time to restore flow. 2024-06-24 15:04:01 -04:00
Clark Chen 16773c9b17 Fix import/export tests with my story. 2024-06-24 15:04:01 -04:00
Michelle Tang 4b50365fa9 Release updated linked devices behind feature flag. 2024-06-24 15:04:01 -04:00
Clark Chen 98766b9ebb Fix backup type dark mode. 2024-06-24 15:04:01 -04:00
Cody Henthorne 45a739ce92 Show notification for group adds. 2024-06-24 15:04:01 -04:00
Clark c0d7145ada Add handling for "My Story" import/export. 2024-06-24 15:04:01 -04:00
Clark f94c007af8 Make message backup settings screen update properly. 2024-06-24 15:04:01 -04:00
Michelle Tang df19cb5795 Increase minimum height of keyboard. 2024-06-24 15:04:01 -04:00
Nicholas Tinsley e6ceb55092 Match account deletion number by short NSN.
Fixes #13583.
2024-06-24 15:04:01 -04:00
Michelle Tang bfe2b5cba9 Add loading screen to linked devices. 2024-06-21 09:19:47 -03:00
Alex Hart 571004df50 Tokenize group title search. 2024-06-21 09:19:47 -03:00
Greyson Parrelli f32b59f0aa Fix crash with AnalyzeDatabaseJob. 2024-06-21 09:19:47 -03:00
Greyson Parrelli e4019d8595 Ignore deprecated backup tests. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 0b66a8701e Convert FeatureFlags to kotlin. 2024-06-21 09:19:47 -03:00
Clark e62b8de1bc Fix most of import/export integration tests. 2024-06-21 09:19:47 -03:00
Michelle Tang d5cd790871 Remove redundant gallery permission ask. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 664c22d8f1 Add mostly-working SVR3 implementation behind flag. 2024-06-21 09:19:47 -03:00
Cody Henthorne 143a61e312 Fix calling error state display bugs. 2024-06-21 09:19:47 -03:00
Michelle Tang baaad0e475 Fix camera-first qr scans. 2024-06-21 09:19:47 -03:00
Michelle Tang 7086709082 Update devices screen after linking a new device. 2024-06-21 09:19:47 -03:00
Michelle Tang 7bd5ad8c0b Use common compose qr screen for usernames. 2024-06-21 09:19:47 -03:00
Michelle Tang df19c91ae2 Add padding to quoted messages. 2024-06-21 09:19:47 -03:00
Clark e5872037e0 Add import/export of chat colors. 2024-06-21 09:19:47 -03:00
Clark b782fabbb6 Update backup proto with subscriber and recipient changes. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 485b466bd2 Crash on RuntimeExceptions thrown during all Jobs. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 3beac6dfa9 Fix linked device inactive filtering. 2024-06-21 09:19:47 -03:00
Michelle Tang 98290a9fa3 Update max limit string. 2024-06-21 09:19:47 -03:00
Michelle Tang 13dd59f226 Skip biometrics check if unavailable when linking a device. 2024-06-21 09:19:47 -03:00
Michelle Tang d9c42a4135 Add ability to scan linked device qr code from gallery. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 644b93e5a3 Provide default text background color. 2024-06-21 09:19:47 -03:00
Greyson Parrelli 3ff218f9c6 Make build deprecation more resilient to clock skew. 2024-06-21 09:19:47 -03:00
Alex Hart f572eb5322 Add CallLink Observed event and handling. 2024-06-21 09:19:47 -03:00
Michelle Tang d3eb480d31 Update add linked devices screen. 2024-06-21 09:19:47 -03:00
Michelle Tang ac52b5b992 Update linked devices screen. 2024-06-21 09:19:47 -03:00
Michelle Tang 5c181e774f Prevent editing on stickers. 2024-06-21 09:19:46 -03:00
Michelle Tang 05d25718da Add animation when editing a message. 2024-06-21 09:19:46 -03:00
Clark 66c50bef44 Hook up message backup restore flow to reg v2.
Co-authored-by: Nicholas Tinsley <nicholas@signal.org>
2024-06-21 09:19:46 -03:00
Alex Hart 26bd59c378 Bump version to 7.9.6 2024-06-20 18:13:53 -03:00
Alex Hart e90eae6080 Update baseline profile. 2024-06-20 18:08:30 -03:00
Alex Hart 86a7db7653 Update translations and other static files. 2024-06-20 18:05:24 -03:00
Cody Henthorne 230de7e9dc Use URL for S3.
Thanks to Oscar Mira <valldrac@molly.im> for bringing this to our attention.
2024-06-20 12:15:49 -04:00
Greyson Parrelli 4b8546a151 Bump version to 7.9.5 2024-06-14 15:16:17 -04:00
Greyson Parrelli ecd214b91b Update translations and other static files. 2024-06-14 15:15:35 -04:00
Greyson Parrelli 6b5de6e3e5 Only do local donation cancel if it's currently active. 2024-06-14 15:06:13 -04:00
Greyson Parrelli 58b6e49aae Fix NPE when canceling a donation. 2024-06-14 15:06:13 -04:00
Greyson Parrelli c480512600 Bump version to 7.9.4 2024-06-13 18:34:38 -04:00
Greyson Parrelli 3a5b6476aa Updated language translations. 2024-06-13 18:32:18 -04:00
Alex Hart cb171092cf Fix crash loop when writing invalid currency . 2024-06-13 18:03:20 -03:00
Nicholas Tinsley 71979b34db Alert user to file system errors during backup restore. 2024-06-13 16:13:43 -04:00
Nicholas Tinsley 73142cea39 Don't hold lazy reference to view binding in delayed runnable. 2024-06-13 15:43:51 -04:00
Nicholas Tinsley 2ab2c6f039 Ensure that substrings match in the registration contact support bottom sheet. 2024-06-13 15:36:41 -04:00
Greyson Parrelli 0bea15c0af Bump version to 7.9.3 2024-06-12 11:23:20 -04:00
Greyson Parrelli cbd78d78ba Update translations and other static files. 2024-06-12 11:22:44 -04:00
Cody Henthorne ac0604a753 Fix rare remote megaphone crash. 2024-06-12 11:10:59 -04:00
Nicholas Tinsley 0e57335be1 Split Raise Hand plurals into separate strings. 2024-06-11 11:43:58 -04:00
Greyson Parrelli c4e64f6fa3 Bump version to 7.9.2 2024-06-10 16:11:51 -04:00
Greyson Parrelli bf9716f206 Update translations and other static files. 2024-06-10 16:10:42 -04:00
Cody Henthorne 057ffdbaaf Fix conversation memory leak. 2024-06-10 14:54:02 -04:00
Nicholas Tinsley 65dc0d3f34 Disable verbose logging in media converter. 2024-06-10 14:38:19 -04:00
Clark 173ee95e62 Fix backup jitter and add unit tests. 2024-06-10 14:20:56 -04:00
Nicholas Tinsley 789339afa7 Update Raise Hand string. 2024-06-10 11:01:56 -04:00
Nicholas Tinsley 21b518da7a Don't show volume indicator nor switch camera button until incoming call connects. 2024-06-10 11:01:56 -04:00
Nicholas Tinsley 57b6b8dcf1 Improve Raise Hand behavior when in a call with a linked device. 2024-06-07 13:53:58 -04:00
Cody Henthorne 543a85316e Improve FCM check clock skew handling. 2024-06-07 13:02:44 -04:00
Cody Henthorne 2fedb3a0ee Bump version to 7.9.1 2024-06-07 11:59:02 -04:00
Cody Henthorne ae450aed67 Update baseline profile. 2024-06-07 11:48:08 -04:00
Cody Henthorne 0abb4727fc Update translations and other static files. 2024-06-07 11:30:34 -04:00
Alex Hart 4bc6eb96ff Fix 3DS waiting-for-auth state when launching external application. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley e6a126d416 Only submit captcha once in Registration V2. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley fdf858f379 Prevent crash if linked device also raises their hand. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley 4151d123cd Fix crash in registration v2 country code drop down. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley c8a9759eba Unify Raise Hand copy. 2024-06-07 11:05:09 -04:00
Cody Henthorne c59b74627f Improve strings for localization. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley f2191d2996 Adjust text colors in dark mode in Registration V2. 2024-06-07 11:05:09 -04:00
Cody Henthorne 7dfffbd50b Add missing windows aapt2. 2024-06-06 10:21:22 -04:00
Cody Henthorne 329fc52077 Bump version to 7.9.0 2024-06-05 16:20:29 -04:00
Cody Henthorne 8976111f61 Update translations and other static files. 2024-06-05 15:54:32 -04:00
Cody Henthorne 7402959ac6 Fix error handling for resumable uploads to cdn3. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 220d3877a2 Fix phone number autofill in Registration V2. 2024-06-05 15:46:01 -04:00
Clark Chen 380c33642c Clean quote when canceling edit message. 2024-06-05 15:46:01 -04:00
Alex Hart 7acb2bef3d Fix private story listing My Story as a recipient. 2024-06-05 15:46:01 -04:00
Greyson Parrelli 1a103106a5 Catch more stuff in SqlCipherDeletingErrorHandler.
Fixes #13577
2024-06-05 15:46:01 -04:00
Greyson Parrelli 6025e423e8 Fix payment request message text. 2024-06-05 15:46:01 -04:00
Greyson Parrelli 54656ea14e Potentially fix 'design assumption violated' ISE. 2024-06-05 15:46:01 -04:00
Alex Hart fd00ed71b5 Fix clickable link in donation thanks sheet. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley d4fba5f3c7 Ship Raise Hand. 2024-06-05 15:46:01 -04:00
Alex Hart ce244f2e8f Fix hit detection for story link previews. 2024-06-05 15:46:01 -04:00
Cody Henthorne 4ad466390f Fix second person translations for group story reactions. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley c5c9b09f7b Turn on Registration V2 and Change Number V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 0638b31c1f More registration lock V2 improvements. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley c3c713a75a Prevent getting stuck on registration lock V2 fragment. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 9af1c72233 Registration V2 restore tweaks. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 500a1e46ad Improve ABS logging. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 4b446877af Change number captcha submission improvement. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 015548613a Fix change view model test.
Co-authored-by: Cody Henthorne <cody@signal.org>
2024-06-05 15:46:01 -04:00
Alex Hart 30b339a482 Remove unnecessary subscriber call for manual cancel. 2024-06-05 15:46:01 -04:00
Cody Henthorne 6dcb2e8d24 Fix androidTest message content fuzer for attachment pointers. 2024-06-05 15:46:01 -04:00
Cody Henthorne 3e8e17526b Fix missing group context on message records. 2024-06-05 15:46:01 -04:00
Clark Chen 30ecaf7aea Remove double remote deleted column from ChatItemImportInserter. 2024-06-05 15:46:01 -04:00
Greyson Parrelli f761008509 Clean up some stuff around ImportExportTest. 2024-06-05 15:46:01 -04:00
Greyson Parrelli c3ab8dddd0 Fix runPostSuccessfulTransaction behavior. 2024-06-05 15:46:01 -04:00
Alex Hart 164f089d37 Fix NPE in deleteAll call. 2024-06-05 15:46:01 -04:00
Alex Hart a021b400bd Fix SafetyNumberBottomSheetRepositoryTest. 2024-06-05 15:46:01 -04:00
Greyson Parrelli fac8f403be Remove outdated dlist test. 2024-06-05 15:46:01 -04:00
Clark d85ab37828 Add import and tombstones for mobile coin payments. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 1e35403c87 Change Number V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley b99c2165fa Handle SVR exceptions in Registration V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 303100bb6b Further registration lock improvements in Registration V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley b71ba79b8a Flesh out registration lock support for Registration V2. 2024-06-05 15:46:01 -04:00
Clark 54cd84b842 Add handling for import/export of edited messages. 2024-06-05 15:46:01 -04:00
Clark 1565ecdcea Fix multiple scheduled backups due to jitter. 2024-06-05 15:46:01 -04:00
Clark 0a99b68d25 Fix shared contacts avatar double upload. 2024-06-05 15:46:01 -04:00
Alex Hart f4fac5bd90 Prevent reactions from being overlaid by raise hand. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley f6760b90da Flesh out verification challenge support for registration v2. 2024-06-05 15:46:01 -04:00
Clark ad9b1f05b4 Disable restore on open if auto-download is off. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 17581a7a5e Update SignalProgressDialog.
Fixes #12949.
2024-06-05 15:46:01 -04:00
Clark b41bf66133 Disable "Edited" click listener for outgoing messages. 2024-06-05 15:46:01 -04:00
Clark 8bb3d71472 Fix thumbnail not being clickable on initial media receive. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley 295d4b9466 Pin left GIF button to bottom of compose box. 2024-06-05 15:46:01 -04:00
Ahmed El herz 5e490376f4 Fix initial event not triggering onTouchEvent.
Fixes #13351
2024-06-05 15:46:01 -04:00
Greyson Parrelli fa27531c00 Inline SVR2 feature flag. 2024-06-05 15:46:01 -04:00
Cody Henthorne 2737e5613c Use raw values for learned profile name event. 2024-06-05 15:46:01 -04:00
Greyson Parrelli d84612ebf4 Revert "Validate full edit message payload."
This reverts commit 268b621667e3144fb3f07099d04aa5609387a5e6.
2024-06-05 15:46:00 -04:00
Greyson Parrelli 96165ad5a8 Fix username getting prematurely removed from recipient. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley 19caef057e Update to AGP 8.4.1. 2024-06-05 15:46:00 -04:00
Clark 29cafb11eb Update proto and add payments export without tombstone. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 7e458bfde0 Validate full edit message payload. 2024-06-05 15:46:00 -04:00
Clark 2a3cb80217 Add ui wiring for archive thumbnail support. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 3d382ee15e Use extension functions instead of LibSignalNetwork class. 2024-06-05 15:46:00 -04:00
Clark 6069dfc6f8 Add a separate column for tracking thumbnail restore state. 2024-06-05 15:46:00 -04:00
Clark Chen dee19ed94a Fix attachment table v231 migration consistency. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 905b0681f5 Update otpk/kpk tests. 2024-06-05 15:46:00 -04:00
Greyson Parrelli b6a4e1f145 Rewrite the AppDependencies system. 2024-06-05 15:46:00 -04:00
Alex Hart a0131bf39b Fix db inconsistency. 2024-06-05 15:46:00 -04:00
Alex Hart 7ed77a00df Remove unused method from RecipientTable. 2024-06-05 15:46:00 -04:00
Alex Hart 887c173d8f Move camera flip and improve movement of some ui elements. 2024-06-05 15:46:00 -04:00
Cody Henthorne 6362da7a50 Refactor group state processing. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 1296365bed Upgrade to libsignal 0.47.0 2024-06-05 15:46:00 -04:00
Alex Hart 99ae7c5961 Add search view extension for incognito keyboards. 2024-06-05 15:46:00 -04:00
Clark 5c3ea712fe Add streaming video support for attachment files. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley bc5cb454bf Ship "instant" video playback. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 8a7c2c1e20 Rotate libsignal CDS feature flag. 2024-06-05 15:46:00 -04:00
Cody Henthorne a81a675d59 Add Delete for Me sync support. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 1c66da7873 Update slow notification debugging info. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley afe3cd1098 Additional error handling for registration v2. 2024-06-05 15:46:00 -04:00
Alex Hart 4f3ee9ca1d Skip the contact links migration if contact permissions are disabled. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley 7771aaa501 Sort the language list during build for determinism.
Addresses #13565.
2024-06-05 15:46:00 -04:00
Greyson Parrelli 5ad38c7960 Ensure archive data is copied when deduping. 2024-06-05 15:46:00 -04:00
Alex Hart 0fb1514da2 Consolidate subscription information and manage button to a single row. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley f37efd7e15 Add additional error handling for registration v2. 2024-06-05 15:46:00 -04:00
mtang-signal 1ae2464df1 Update remaining gallery permission UI. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 0425b70d31 Do not show unregistered contacts in search results. 2024-06-05 15:46:00 -04:00
Clark 7b0d3f36dc Ignore digest for downloading archived thumbnails. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 14b917dc7e Fix typo in query resulting in contacts not being unlinked. 2024-06-05 15:46:00 -04:00
Ehren Kret 6184cc0307 Migrate existing raw contacts to add video call links. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley 870aa8e7b0 Fix country code sorting in reg v2. 2024-06-05 15:46:00 -04:00
Greyson Parrelli d88016669b Shorten groupId string patterns. 2024-06-05 15:46:00 -04:00
Greyson Parrelli a464b413d9 Use correct label in log. 2024-06-05 15:46:00 -04:00
Alex Hart d719edf104 Rewrite in-app-payment flows to prepare for backups support. 2024-06-05 15:46:00 -04:00
mtang-signal b36b00a11c Update camera permission UI for voice calls. 2024-06-05 15:46:00 -04:00
mtang-signal a99db2b16e Update camera permission UI for usernames. 2024-06-05 15:46:00 -04:00
Greyson Parrelli 2744dec43a Switch to using dateSent for jump-to-calendar.
We use dateSent for date dividers, but were using dateReceived for
calendar date availability, which would occasionally result in a
mismatch. Switched to use the same thing we use for date dividers.
2024-06-05 15:45:59 -04:00
Greyson Parrelli 6f2cce1494 Add acknowledgements from libsigna/ringrtc. 2024-06-05 15:45:59 -04:00
Greyson Parrelli 689ee243aa Fix potential sqlite conflict in dlist on recipient remap. 2024-06-05 15:45:59 -04:00
Cody Henthorne 537fc0ef5c Update to Kotlin 1.9.20, AGP 8.4.0, and Gradle 8.6 2024-06-05 15:45:59 -04:00
Clark Chen e647b31f29 Explicitly persist message backup tier. 2024-05-17 10:39:09 -04:00
Alex Hart b59932cd88 Fix compilation error in contacts test app. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley cfb4377de3 Apply automated ktlint 1.2.1 formatting. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley e861c022da Disable new ktlint rules with preexisting violations. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley 59006d3182 Upgrade ktlint to 1.2.1. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley 503faea3a9 Support voice verification in registration v2. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley eb114de5c8 Bump version to 7.8.1 2024-05-16 15:50:48 -04:00
Nicholas Tinsley 1bf9695cff Update translations and other static files. 2024-05-16 15:45:51 -04:00
Clark 241bf065e8 Fix missing thumbnail_file column in media query. 2024-05-16 13:09:58 -04:00
Clark Chen e0f3b35805 Fix missing archive_thumbnail_cdn column. 2024-05-16 12:30:33 -04:00
Nicholas Tinsley 5741dfc00b Bump version to 7.8.0 2024-05-16 10:24:48 -04:00
Nicholas Tinsley ec430da772 Update translations and other static files. 2024-05-16 10:20:08 -04:00
Rashad Sookram 5e6d9434de Update to RingRTC v2.42.0 2024-05-16 10:16:10 -04:00
Clark b72d586748 Add initial thumbnail restore for message backup. 2024-05-16 10:16:10 -04:00
Ehren Kret 757c0fd2ea create video call mimetype for raw contacts links 2024-05-16 10:16:10 -04:00
Nicholas Tinsley c4e4eaf110 Remove lower hand confirmation dialog. 2024-05-16 10:16:10 -04:00
Nicholas Tinsley f83275e246 Add customize button to in-call reaction picker. 2024-05-16 10:16:10 -04:00
Greyson Parrelli d0340d39db Reset backupV2 credentials on 403. 2024-05-16 10:16:10 -04:00
Greyson Parrelli 227a279131 Make sure note to self is included in backupsV2. 2024-05-16 10:16:10 -04:00
mtang-signal 0465fdea62 Update contacts permission UI. 2024-05-16 10:16:10 -04:00
Nicholas Tinsley 13bd4a9c74 Update regv2 result field name. 2024-05-16 10:16:10 -04:00
Greyson Parrelli f570f1f2c4 Initial test implementation of SVR3. 2024-05-15 15:55:22 -04:00
Nicholas Tinsley 68ced18ea1 Fleshed out session management in registration v2. 2024-05-15 15:55:22 -04:00
Greyson Parrelli b4a8f01980 Include message timestamp in local send timings. 2024-05-15 15:55:22 -04:00
mtang-signal c3c743fbb8 Update camera permission UI in media. 2024-05-15 15:55:22 -04:00
Adam Mork b14eddefc9 Add payment enclave measurements for v6.0.0 2024-05-15 15:55:21 -04:00
Nicholas Tinsley 46638a1948 Bump version to 7.7.2 2024-05-15 15:54:11 -04:00
Nicholas Tinsley 5cee85fcdc Update translations and other static files. 2024-05-15 15:44:52 -04:00
mtang-signal f97d7e3dfd Fix permissions ask in gallery. 2024-05-15 12:18:50 -07:00
Alex Hart 6da0ecf827 Bump version to 7.7.1 2024-05-10 22:54:45 -03:00
Alex Hart 9803550bba Update baseline profile. 2024-05-10 22:51:55 -03:00
Alex Hart 15284da4c5 Update translations and other static files. 2024-05-10 22:48:55 -03:00
Alex Hart 351c3219e4 Replace RxStore with MutableStateFlow for better lifecycle control. 2024-05-10 22:44:05 -03:00
Alex Hart ab95dbbc77 Bump version to 7.7.0 2024-05-08 16:43:30 -03:00
Alex Hart cc6cba45c6 Update baseline profile. 2024-05-08 16:36:44 -03:00
Alex Hart ce37660df2 Update translations and other static files. 2024-05-08 16:34:17 -03:00
Nicholas Tinsley ca14ed9b2c Allow for captcha solving for reg v2. 2024-05-08 16:30:53 -03:00
Clark ba4cdea75d Add cellular backup toggle for message backup. 2024-05-08 16:30:53 -03:00
Clark 83c34dd4cc Integrate swapping backup tiers from backup settings. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley b6db3802d3 Set raised hand list to be distinct by RecipientID. 2024-05-08 16:30:53 -03:00
Greyson Parrelli a9a19d3ae0 Add job to upload thumbnails to archive. 2024-05-08 16:30:53 -03:00
Alex Hart 52fb873b1b Specify vibrate attributes to resolve vibrate from background. 2024-05-08 16:30:53 -03:00
moiseev-signal 9a0bb243cd Implement a libsignal-net shadowing web socket. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley 78bbab37fb Show missing FCM dialog in registration V2. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley 9af73b1409 Allow initialization of registration V2 without FCM. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley 9c5bb4aa17 Initial error handling for registration v2. 2024-05-08 16:30:53 -03:00
Clark 49ba83dda8 Integrate message backup frequency. 2024-05-08 16:30:53 -03:00
Clark de3b0d4ca2 Integrate the backup size into backup settings. 2024-05-08 16:30:53 -03:00
fm-sys b2efc42357 Add back ability to long press title bar to go to system contact.
Resolves #13372
2024-05-08 16:30:53 -03:00
Cody Henthorne a71faf674d Cleanup group management code. 2024-05-08 16:30:53 -03:00
moiseev-signal 34faa9003f Upgrade to libsignal 0.46.0. 2024-05-08 16:30:53 -03:00
Clark bc527a2bc1 Basic settings functionality for message backup. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley 0a3f96935a Support device transfers in restore flow v2. 2024-05-08 16:30:53 -03:00
Alex Konradi 35232a3928 Unwrap ExecutionException from Future observable 2024-05-08 16:30:53 -03:00
Alex Hart 70d74e0bb1 Allow users who have disabled Contacts permission to hide system contacts. 2024-05-08 16:30:53 -03:00
Alex Hart 36c91a95e2 Check spannable intersect when moving between stories. 2024-05-08 16:30:53 -03:00
Greyson Parrelli 4600e38a2a Add partial index to improve unread count perf. 2024-05-08 16:30:52 -03:00
Alex Hart 55abd88a03 Implement better handling for call peeking when opening the calls tab. 2024-05-08 16:30:52 -03:00
mtang-signal cd880b0879 Expand double tap area. 2024-05-08 16:30:52 -03:00
mtang-signal bbae6d876f Avoid translating support email strings. 2024-05-08 16:30:52 -03:00
dalamsya50 48a0c5a5a9 Fix git error when running on GitHub Actions.
Fixes #13495
Resolves #13547
2024-05-08 16:30:52 -03:00
BenjaminMuslic c261df41b0 Added automatic capitalization to profile name fields.
Resolves #13544
2024-05-08 16:30:52 -03:00
Greyson Parrelli cc98eced27 Short-circuit query if list is empty. 2024-05-08 16:30:52 -03:00
moiseev-signal 452d5960e4 Add test and extra cleanup around usage of incremental mac. 2024-05-08 16:30:52 -03:00
mtang-signal c95b180728 Update gallery permission UI 2024-05-08 16:30:52 -03:00
Greyson Parrelli 3c380d35fd Attempt to reduce impact of thread updates. 2024-05-08 16:30:52 -03:00
Nicholas Tinsley 41935120e5 DeviceTransferFragment Kotlin rewrite. 2024-05-01 16:45:36 -04:00
Alex Hart 03d8f72c41 Fix group collisions error. 2024-05-01 16:45:36 -04:00
Greyson Parrelli ab9ecff4d4 Improve timing of query methods. 2024-05-01 16:45:36 -04:00
Alex Hart e351a0b235 Correct flags for story replies. 2024-05-01 16:45:36 -04:00
Nicholas Tinsley 4a08de370a Fix issue with Mp4Writer with massive time scales. 2024-05-01 16:45:36 -04:00
Cody Henthorne 6d657b449c Convert and update Manage Storage Settings. 2024-05-01 16:45:36 -04:00
Clark adef572abb Store group snapshot attributes in GroupAttributeBlobs. 2024-05-01 16:45:36 -04:00
Greyson Parrelli d6f2039bd1 Update attachment cipher tests to use longer inputs. 2024-05-01 16:45:35 -04:00
Clark 1223c3c768 Add support for new backup calls proto and call links. 2024-05-01 16:45:35 -04:00
Greyson Parrelli 333fa22c96 Bump version to 7.6.2 2024-05-01 16:42:29 -04:00
Greyson Parrelli 76c04d8d6d Update translations and other static files. 2024-05-01 16:42:03 -04:00
Greyson Parrelli c3070f2913 Revert "Expand double tap touch area."
This reverts commit 8c81e47737.
2024-05-01 16:33:33 -04:00
Nicholas Tinsley 234b3967ed Fix button crash in v1 PIN restore fragment. 2024-05-01 10:42:23 -04:00
Greyson Parrelli 89d420cda8 Bump version to 7.6.1 2024-04-30 16:42:44 -04:00
Greyson Parrelli ced4ece5b8 Update translations and other static files. 2024-04-30 16:41:02 -04:00
mtang-signal 8c81e47737 Expand double tap touch area. 2024-04-30 16:29:33 -04:00
Cody Henthorne 5d15eef61d Improve translations with pluralized string resources. 2024-04-30 16:04:14 -04:00
Greyson Parrelli 8f3e62245f Fix some issues where views were accessed after being destroyed. 2024-04-30 15:22:57 -04:00
Greyson Parrelli e4ab795c62 Fix stream reading error. 2024-04-30 15:22:57 -04:00
mtang-signal e4d6f9240f Fix double tap layout warning. 2024-04-30 14:44:00 -04:00
Greyson Parrelli cfaf40e605 Fix KeyValueDataSet tests. 2024-04-30 11:05:39 -07:00
Greyson Parrelli bdcf2431e7 Bump version to 7.6.0 2024-04-29 22:04:56 -04:00
Greyson Parrelli 7241283be2 Update baseline profile. 2024-04-29 22:04:56 -04:00
Greyson Parrelli dde2a8b63a Update translations and other static files. 2024-04-29 22:04:56 -04:00
Greyson Parrelli f7763a5b82 Be more lenient around long-int conversion in SignalStore. 2024-04-29 22:04:47 -04:00
Greyson Parrelli c6f4a01001 Hopeful fix for crash in SimpleProgressDialog. 2024-04-29 22:04:31 -04:00
Greyson Parrelli 95a6835988 Improve handling of backup initialization. 2024-04-29 19:26:06 -04:00
moiseev-signal f9a8f447d2 Support proxy in connections managed by libsignal. 2024-04-29 19:26:06 -04:00
Nicholas Tinsley d20f588802 Inline the group call reactions feature flag. 2024-04-29 19:25:59 -04:00
Nicholas Tinsley f23476a4e9 Initial support for restoring backups and skipping SMS in registration v2. 2024-04-29 19:25:59 -04:00
mtang-signal fd4864b3b1 Update microphone permission UI for calls. 2024-04-29 19:25:59 -04:00
mtang-signal c5c0c432c4 Update microphone permission UI for voice messages. 2024-04-29 19:25:59 -04:00
Jim Gustafson 69c40a6835 Update to RingRTC v2.41.0 2024-04-29 19:25:59 -04:00
moiseev-signal 7ef7aa65e6 Upgrade to libsignal 0.45.1. 2024-04-29 19:25:59 -04:00
Greyson Parrelli 97c08f0d52 Add additional validations to incremental attachment streams. 2024-04-29 19:25:59 -04:00
mtang-signal 18e6c57e75 Update location permission UI. 2024-04-29 19:25:59 -04:00
mtang-signal ffc1463cda Add double tap editing feature. 2024-04-29 19:25:59 -04:00
Clark 84e654efb2 Set archive transfer state when archive data is set. 2024-04-29 19:25:59 -04:00
Clark d983265e08 Persist group state in backup. 2024-04-29 19:25:59 -04:00
Alex Hart e60b32202e Improved missed call state handling. 2024-04-29 19:25:59 -04:00
moiseev-signal 95fbd7a31c Implement unauthenticated chat web socket connection via libsignal-net. 2024-04-29 19:25:59 -04:00
Nicholas Tinsley 00a91e32fc Multiple skin tones for reaction bursts. 2024-04-29 19:25:59 -04:00
Alex Hart fa32b7a883 Fix coloring on outgoing calls. 2024-04-24 15:10:12 -03:00
Alex Hart 63e6f955ed Prevent getCallLinks from returning links without root keys. 2024-04-24 14:17:58 -03:00
Alex Hart 7dcb8a425a Handle joined sync message for call links. 2024-04-24 13:31:35 -03:00
Cody Henthorne f35ce068f9 Change profile fetch REST fallback based on authentication error. 2024-04-24 11:41:31 -04:00
Nicholas Tinsley 881d231a93 Improve group call reactions UI when presented without raise hand.
This also dismisses the custom reaction picker when switching to PiP mode.
2024-04-24 10:13:56 -04:00
Alex Hart 293634c758 Send call link update sync message upon call link creation. 2024-04-24 10:48:00 -03:00
Greyson Parrelli 4134df3f35 Use archive-specific endpoint for attachment backfill. 2024-04-23 16:29:03 -04:00
Clark f78a019c70 Use seconds instead of millis for redemption time. 2024-04-23 15:56:38 -04:00
Cody Henthorne d561a1385c Fix extremely long emoji search crash. 2024-04-23 12:29:03 -04:00
moiseev-signal 9b5387e221 Upgrade to libsignal 0.45.0 2024-04-23 12:29:03 -04:00
Cody Henthorne 25b1a814fe Remove legacy keyword search flag from emoji search infra. 2024-04-23 12:29:03 -04:00
Clark b043b6e458 Schedule message backups when enabled. 2024-04-23 12:29:03 -04:00
Clark 8a972d93e9 Actually use backup jitter in local backups. 2024-04-23 12:29:03 -04:00
Cody Henthorne 8fe66a14c5 Fix multi-window camera crash. 2024-04-23 12:29:03 -04:00
Clark f82bd64c10 Copy inbound attachments to archive service. 2024-04-23 12:29:03 -04:00
Nicholas Tinsley 4bcab49539 Correct UnopinionatedResponseCodeHandler constant name. 2024-04-23 12:29:03 -04:00
mtang-signal 0f4618ab11 Remove link preview images from shared media. 2024-04-23 12:29:03 -04:00
Cody Henthorne 475ca50fab Fix missing local participant state changes in group calls bug. 2024-04-23 12:29:03 -04:00
Greyson Parrelli a64a02fa0c Fix issue where structured contact name syncing was delayed. 2024-04-23 12:29:02 -04:00
Clark f3669a5865 Fix message extra column not being restored properly. 2024-04-23 12:29:02 -04:00
Greyson Parrelli 34dbd11db0 Update file format for backupV2. 2024-04-23 12:29:02 -04:00
Nicholas Tinsley 2e7279c72f Only display "Processing" text on outgoing media. 2024-04-23 12:29:02 -04:00
Nicholas Tinsley 6ad72f00af Fix phone number formatter in Registration V2. 2024-04-23 12:29:02 -04:00
Alex Hart b771a21518 Add screen for managing backup type. 2024-04-23 12:29:02 -04:00
Greyson Parrelli 04fb459acd Remove unused backup outputstream class. 2024-04-23 12:29:02 -04:00
Greyson Parrelli 690a68f0d0 Remove libweb submodule entirely. 2024-04-23 12:29:02 -04:00
Greyson Parrelli f34ae8d118 Add padding to the gzipped backup output. 2024-04-23 12:29:02 -04:00
Cody Henthorne da43ff1e95 Bump version to 7.5.2 2024-04-23 11:42:24 -04:00
Cody Henthorne f053ebbd51 Update baseline profile. 2024-04-23 11:36:15 -04:00
Cody Henthorne 87606af29c Update translations and other static files. 2024-04-23 11:30:56 -04:00
Cody Henthorne c811bdcffa Fix benchmark test messages. 2024-04-23 11:26:36 -04:00
Cody Henthorne 0536628da3 Stagger app wake ups due to analyze database alarm. 2024-04-23 10:44:09 -04:00
Nicholas Tinsley 1fa53cfcb8 Prevent crash on attachment delete while voice note system tone is playing. 2024-04-23 10:22:01 -04:00
Cody Henthorne a9ea3854d2 Bump version to 7.5.1 2024-04-22 17:06:18 -04:00
Cody Henthorne dc35261e00 Update translations and other static files. 2024-04-22 16:56:39 -04:00
Cody Henthorne 716bc1f5e7 Cleanup dangling domain reference. 2024-04-22 16:52:02 -04:00
Cody Henthorne db27204084 Validate pni signature message. 2024-04-22 16:33:03 -04:00
Cody Henthorne 42aeceffe2 Revert full usage of ActiveCallManager. 2024-04-22 16:32:27 -04:00
Greyson Parrelli 03845eabaf Bump version to 7.5.0 2024-04-18 16:44:32 -04:00
Greyson Parrelli 62af9dad50 Update translations and other static files. 2024-04-18 16:43:51 -04:00
Cody Henthorne ee58d47926 Cycle rx message sending flag. 2024-04-18 16:24:13 -04:00
Greyson Parrelli d74260b536 Improve network reliability. 2024-04-18 16:24:13 -04:00
Alex Hart 15d8a698c5 Add new name collision state management. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 62cf3feeaa Restore a Local Backup v2 2024-04-18 16:24:13 -04:00
Alex Hart 947ab7d48b Implement skeleton for backup sheets. 2024-04-18 16:24:13 -04:00
Greyson Parrelli a82b9ee25f Add a job to backfill attachment uploads to the archive service. 2024-04-18 16:24:13 -04:00
mtang-signal 1e4d96b7c4 Add camera permission check to group stories. 2024-04-18 16:24:13 -04:00
Alex Hart 735a8e680c Add backupSubscription field to configuration object. 2024-04-18 16:24:13 -04:00
Alex Hart d9e9fe1d6a Move backups selection code to its own package. 2024-04-18 16:24:13 -04:00
Greyson Parrelli 4bcd1df4f8 Expand account consistency checks. 2024-04-18 16:24:13 -04:00
Greyson Parrelli 9762899272 Remove old thread remappings. 2024-04-18 16:24:13 -04:00
Alex Hart ce1b73970c Implement BackupStatus widget. 2024-04-18 16:24:13 -04:00
Alex Hart 58282e589b Implement backups settings fragment. 2024-04-18 16:24:13 -04:00
mtang-signal 75bd113545 Fix missing send button for voice notes. 2024-04-18 16:24:13 -04:00
Cody Henthorne 7a6bd0e1f2 Revert "Remove vestigial call camera toggle button."
This reverts commit 7a9c01e6e5.
2024-04-18 16:24:13 -04:00
Greyson Parrelli f673c4eb83 Remove sql language annotation (for now).
It's broken in newer versions of Android Studio. It doesn't seem to
allow partial-sql anymore, only fully-formed statements. Same with
roomsql.
2024-04-18 16:24:13 -04:00
Jim Gustafson cbb04e8f0c Update to RingRTC v2.40.0 2024-04-18 16:24:13 -04:00
mtang-signal cd03da54d5 Fix note to self message detail text. 2024-04-18 16:24:13 -04:00
Clark 5f31f5966c Update backup locator proto. 2024-04-18 16:24:13 -04:00
Clark d8bbfe2678 Add archived media sync job. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 7a2d408ca2 Stop voice memo playback if the current item is deleted.
Fixes #13502.
2024-04-18 16:24:13 -04:00
Nicholas Tinsley 5e4dfcc65f Add translator notes for some strings. 2024-04-18 16:24:13 -04:00
Clark 7811e51b41 Add CDN number as parameter for read credential call. 2024-04-18 16:24:13 -04:00
Alex Konradi 9703a868e5 Request new ZKC-based auth credential. 2024-04-18 16:24:13 -04:00
Alex Hart 1b7784b01f Update call strings to align with new designs. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley a83abaca1d Order story viewer names alphabetically. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 29b3f09d8a Catch possible ISE at end of re-registration. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley d36b2a23f5 Hide irrelevant rows in self about sheet. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 8f1722c718 Update placeholder label for view once media. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 5416c3b8aa Improve play button display logic on video editor fragment. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley 89eeae36c4 Fix signed int overflow in disappearing timer UI message. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley eec2685e67 Registration refactor initial scaffolding. 2024-04-18 16:24:13 -04:00
Clark 318b59a6b2 Do not fallback to REST for resumable upload spec on ratelimit. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley a2e0468cd9 Remove "lower hand" confirmation dialog. 2024-04-18 16:24:13 -04:00
Clark 689eacd618 Add initial support for backup and restore of message and media to staging.
Co-authored-by: Cody Henthorne <cody@signal.org>
2024-04-18 16:24:13 -04:00
tedgravlin 8617a074ad Update CLA link in PR template. 2024-04-18 16:24:12 -04:00
Greyson Parrelli 046b8da880 Add missing static IPs.
Fixes #13513
2024-04-18 16:24:12 -04:00
Clark Chen 34a36ddfea Bump version to 7.4.2 2024-04-15 16:32:09 -04:00
Clark Chen 9330448198 Update translations and other static files. 2024-04-15 16:24:09 -04:00
Clark Chen b3336b4d84 Revert "Use existing libsignal proguard rules."
This reverts commit 2ce6ea9a2a.
2024-04-15 10:17:36 -04:00
Alex Hart 9553c94097 Bump version to 7.4.1 2024-04-12 16:38:43 -03:00
Alex Hart c1845ae1c4 Update baseline profile. 2024-04-12 16:33:11 -03:00
Alex Hart b6cc3852b0 Update translations and other static files. 2024-04-12 16:28:07 -03:00
Cody Henthorne eefc86f27e Fix dangling call notification and remove active call manager flag. 2024-04-12 09:38:06 -04:00
Nicholas Tinsley 09404157aa Add processor information to debug log. 2024-04-11 16:09:33 -04:00
Alex Hart abfd9f8f41 Add proper capitalization settings in nickname activity. 2024-04-11 10:35:47 -03:00
Bishal e04381fd75 Add fix for missing play button when the audio is not sent in offline mode. 2024-04-11 10:31:12 -03:00
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
3961 changed files with 235251 additions and 109857 deletions
+12 -1
View File
@@ -6,4 +6,15 @@ ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false ij_kotlin_allow_trailing_comma = false
ktlint_code_style = intellij_idea ktlint_code_style = intellij_idea
twitter_compose_allowed_composition_locals=LocalExtendedColors twitter_compose_allowed_composition_locals=LocalExtendedColors
ktlint_standard_class-naming = disabled ktlint_standard_class-naming = disabled
# below rules disabled during ktlint version migration because they were preexisting but should be corrected and re-enabled ASAP
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_property-naming = disabled
ktlint_standard_enum-wrapping = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_backing-property-naming = disabled
ktlint_standard_statement-wrapping = disabled
internal:ktlint-suppression = disabled
ktlint_standard_unnecessary-parentheses-before-trailing-lambda = disabled
ktlint_standard_value-parameter-comment = disabled
+1 -1
View File
@@ -2,7 +2,7 @@
### First time contributor checklist ### First time contributor checklist
<!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly --> <!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly -->
- [ ] I have read [how to contribute](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md) to this project - [ ] I have read [how to contribute](https://github.com/signalapp/Signal-Android/blob/master/CONTRIBUTING.md) to this project
- [ ] I have signed the [Contributor License Agreement](https://whispersystems.org/cla/) - [ ] I have signed the [Contributor License Agreement](https://signal.org/cla/)
### Contributor checklist ### Contributor checklist
<!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly --> <!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly -->
+3 -1
View File
@@ -18,6 +18,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 17 - name: set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v3
@@ -30,7 +32,7 @@ jobs:
uses: gradle/wrapper-validation-action@v1 uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew qa --parallel run: ./gradlew qa
- name: Archive reports for failed build - name: Archive reports for failed build
if: ${{ failure() }} if: ${{ failure() }}
+4 -2
View File
@@ -15,6 +15,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }} ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17 - name: set up JDK 17
@@ -37,7 +38,7 @@ jobs:
- name: Build with Gradle - name: Build with Gradle
if: steps.cache-base.outputs.cache-hit != 'true' if: steps.cache-base.outputs.cache-hit != 'true'
run: ./gradlew assemblePlayProdRelease --parallel run: ./gradlew assemblePlayProdRelease
- name: Copy base apk - name: Copy base apk
if: steps.cache-base.outputs.cache-hit != 'true' if: steps.cache-base.outputs.cache-hit != 'true'
@@ -45,10 +46,11 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true
clean: 'false' clean: 'false'
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew assemblePlayProdRelease --parallel run: ./gradlew assemblePlayProdRelease
- name: Copy PR apk - name: Copy PR apk
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
+2 -1
View File
@@ -3,6 +3,7 @@ captures/
project.properties project.properties
keystore.debug.properties keystore.debug.properties
keystore.staging.properties keystore.staging.properties
nightly-url.txt
.project .project
.settings .settings
bin/ bin/
@@ -28,4 +29,4 @@ jni/libspeex/.deps/
pkcs11.password pkcs11.password
dev.keystore dev.keystore
maps.key maps.key
local/ local/
+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. 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 ## Issues
### Useful bug reports ### Useful bug reports
+2 -2
View File
@@ -1,4 +1,4 @@
# Signal Android # Signal Android
Signal is a simple, powerful, and secure messenger. 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 ## 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 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
-719
View File
@@ -1,719 +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 'translations'
id 'licenses'
}
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'
}
protoPath {
srcDir "${project.rootDir}/libsignal/service/src/main/protowire"
}
}
ktlint {
version = "0.49.1"
}
def canonicalVersionCode = 1330
def canonicalVersionName = "6.33.2"
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 = signalKotlinJvmTarget
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.4.4'
}
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_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", 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_CDN3_IPS", cdn3_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_CDSI_IPS", cdsi_ips
buildConfigField "String[]", "SIGNAL_SVR2_IPS", svr2_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'
}
resourceConfigurations += []
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_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", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"ee1d0d972b7ea903615670de43ab1b6e7a825e811c70a29bb5fe0f819e0975fa\", " +
"\"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)
}
}
android.buildTypes.each {
if (it.name != 'release') {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/debug/java"
} else {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/release/java"
}
}
}
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.compose.runtime.livedata
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.media3
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) { version { strictly "1.70" } } // Used by roboelectric
testImplementation (testLibs.bouncycastle.bcpkix.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
testImplementation testLibs.conscrypt.openjdk.uber // Used by robolectric
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"
}
+715
View File
@@ -0,0 +1,715 @@
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 = 1433
val canonicalVersionName = "7.11.0"
val currentHotfixVersion = 0
val maxHotfixVersions = 100
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("1.2.1")
}
android {
namespace = "org.thoughtcrime.securesms"
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions += listOf("distribution", "environment")
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
android.bundle.language.enableSplit = false
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.5.4"
}
defaultConfig {
versionCode = (canonicalVersionCode * maxHotfixVersions) + currentHotfixVersion
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_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("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.INFO")
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_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("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.DEBUG")
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")) {
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")
if (currentHotfixVersion >= maxHotfixVersions) {
throw AssertionError("Hotfix version is too large!")
}
}
}
}
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.androidx.emoji2)
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.lottie.compose)
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.kotlinx.coroutines.play.services)
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()
.sorted() + "en"
}
fun String.capitalize(): String {
return this.replaceFirstChar { it.uppercase() }
}
+7
View File
@@ -2,8 +2,11 @@
-dontobfuscate -dontobfuscate
-keepattributes SourceFile,LineNumberTable -keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; } -keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.net.** { *; }
-keep class org.signal.libsignal.protocol.** { *; } -keep class org.signal.libsignal.protocol.** { *; }
-keep class org.signal.libsignal.usernames.** { *; }
-keep class org.thoughtcrime.securesms.** { *; } -keep class org.thoughtcrime.securesms.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** { -keepclassmembers class ** {
public void onEvent*(**); public void onEvent*(**);
} }
@@ -13,6 +16,10 @@
-keep class androidx.window.** { *; } -keep class androidx.window.** { *; }
-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
public <init>();
}
# AGP generated dont warns # AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl -dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl -dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
@@ -5,7 +5,7 @@ import org.signal.core.util.logging.AndroidLogger
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
import org.thoughtcrime.securesms.database.LogDatabase import org.thoughtcrime.securesms.database.LogDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
@@ -21,14 +21,12 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() { override fun initializeAppDependencies() {
val default = ApplicationDependencyProvider(this) val default = ApplicationDependencyProvider(this)
ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default)) AppDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
ApplicationDependencies.getDeadlockDetector().start() AppDependencies.deadlockDetector.start()
} }
override fun initializeLogging() { override fun initializeLogging() {
persistentLogger = PersistentLogger(this) Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger()) SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
@@ -37,4 +35,18 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
LogDatabase.getInstance(this).logs.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,684 @@
/*
* 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.Ignore
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.components.settings.app.subscription.InAppPaymentsRepository
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.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.dependencies.AppDependencies
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.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.Currency
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()
}
@Ignore("Will likely be removed soon")
@Test
fun emptyDatabase() {
backupTest { }
}
@Ignore("Will likely be removed soon")
@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")
}
}
}
@Ignore("Will likely be removed soon")
@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)
}
}
}
@Ignore("Will likely be removed soon")
@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)
}
}
@Ignore("Will likely be removed soon")
@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)
}
}
@Ignore("Will likely be removed soon")
@Test
fun accountData() {
val context = AppDependencies.application
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/")
InAppPaymentsRepository.setSubscriber(InAppPaymentSubscriberRecord(SubscriberId.generate(), Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION, false, InAppPaymentData.PaymentMethodType.UNKNOWN))
SignalStore.inAppPayments.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.story.viewedReceiptsEnabled = false
SignalStore.story.userHasViewedOnboardingStory = true
SignalStore.story.isFeatureDisabled = false
SignalStore.story.userHasBeenNotifiedAboutStories = true
SignalStore.story.userHasSeenGroupStoryEducationSheet = true
SignalStore.emoji.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,74 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.signal.core.util.Base64
import org.signal.core.util.StreamUtil
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.ByteArrayInputStream
import java.util.UUID
import kotlin.random.Random
@RunWith(Parameterized::class)
class ImportExportTestSuite(private val path: String) {
companion object {
val SELF_ACI = ServiceId.ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = ServiceId.PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
val MASTER_KEY = Base64.decode("sHuBMP4ToZk4tcNU+S8eBUeCt8Am5EZnvuqTBJIR4Do")
const val TESTS_FOLDER = "backupTests"
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun data(): Collection<Array<String>> {
val testFiles = InstrumentationRegistry.getInstrumentation().context.resources.assets.list(TESTS_FOLDER)
return testFiles?.map { arrayOf(it) }!!.toList()
}
}
@Before
fun setup() {
SignalStore.svr.setMasterKey(MasterKey(MASTER_KEY), "1234")
SignalStore.account.setE164(SELF_E164)
SignalStore.account.setAci(SELF_ACI)
SignalStore.account.setPni(SELF_PNI)
SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account.generatePniIdentityKeyIfNecessary()
}
@Test
fun testBinProto() {
val binProtoBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("${TESTS_FOLDER}/$path").use {
StreamUtil.readFully(it)
}
import(binProtoBytes)
val generatedBackupData = BackupRepository.export()
compare(binProtoBytes, generatedBackupData)
}
private fun import(importData: ByteArray) {
BackupRepository.import(
length = importData.size.toLong(),
inputStreamFactory = { ByteArrayInputStream(importData) },
selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY),
plaintext = true
)
}
// TODO compare with libsignal's library
private fun compare(import: ByteArray, export: ByteArray) {
}
}
@@ -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
}
}
@@ -1,397 +0,0 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber
import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.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.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.MockProvider
import org.thoughtcrime.securesms.testing.Post
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.testing.connectionFailure
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.testing.timeout
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ChangeNumberViewModelTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
@Before
fun setUp() {
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application)
)
viewModel.setNewCountry(1)
viewModel.setNewNationalNumber("5555550102")
}
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
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) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
/**
* If we encounter a server error, this means the server ack our request and rejected it. In this
* case we know the change *did not* take on the server and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenServerFailedApiCall() {
// GIVEN
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { MockResponse().failure(500) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs true
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our old details, so we can know the change *did not* take on the server
* and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToServer() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { MockResponse().connectionFailure() },
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs false
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our new details, so we can know the change *did* take on the server
* and need to keep the app in a locked state. The test then uses the ChangeNumberLockActivity to unlock
* and apply the pending state after confirming the change on the server.
*/
@Test
@FlakyTest
@Ignore("Test sometimes requires manual intervention to continue.")
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToClient() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
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) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().timeout()
},
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, newPni, "+15555550102")) },
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs true
SignalStore.misc().pendingChangeNumberMetadata.assertIsNotNull()
// WHEN AGAIN Processing lock
val scenario = harness.launchActivity<ChangeNumberLockActivity>()
scenario.onActivity {}
ThreadUtil.sleep(500)
// THEN AGAIN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
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) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
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) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
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) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else if (changeNumberRequest.deviceMessages.size == 1) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2, 3)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Get("/v2/keys/$aci/3") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore = SignalStore.account().pniPreKeys
Recipient.self().requireE164() assertIs "+15555550102"
Recipient.self().requirePni() assertIs newPni
SignalStore.account().pniRegistrationId assertIs changeNumberRequest.pniRegistrationIds["1"]!!
SignalStore.account().pniIdentityKey.publicKey assertIs changeNumberRequest.pniIdentityKey
pniMetadataStore.activeSignedPreKeyId assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.keyId
val activeSignedPreKey: SignedPreKeyRecord = pniProtocolStore.loadSignedPreKey(pniMetadataStore.activeSignedPreKeyId)
activeSignedPreKey.keyPair.publicKey assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.publicKey
activeSignedPreKey.signature assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.signature
setPreKeysRequest.signedPreKey.publicKey assertIs activeSignedPreKey.keyPair.publicKey
setPreKeysRequest.preKeys assertIsSize 100
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
}
@@ -7,14 +7,15 @@ import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil import org.signal.core.util.ThreadUtil
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase 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.mms.OutgoingMessage
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
@@ -64,7 +65,8 @@ class ConversationItemPreviewer {
attachment() attachment()
} }
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
body = body, body = body,
sentTimeMillis = System.currentTimeMillis(), sentTimeMillis = System.currentTimeMillis(),
@@ -73,7 +75,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments)) 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) ThreadUtil.sleep(1)
} }
@@ -83,7 +85,8 @@ class ConversationItemPreviewer {
attachment() attachment()
} }
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
body = body, body = body,
sentTimeMillis = System.currentTimeMillis(), sentTimeMillis = System.currentTimeMillis(),
@@ -92,7 +95,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments)) 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 -> SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) { // if (index != 1) {
@@ -134,7 +137,7 @@ class ConversationItemPreviewer {
private fun attachment(): SignalServiceAttachmentPointer { private fun attachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer( return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER, Cdn.CDN_3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""), SignalServiceAttachmentRemoteId.from(""),
"image/webp", "image/webp",
null, null,
@@ -144,13 +147,15 @@ class ConversationItemPreviewer {
1024, 1024,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
0,
Optional.of("/not-there.jpg"), Optional.of("/not-there.jpg"),
false, false,
false, false,
false, false,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
System.currentTimeMillis() System.currentTimeMillis(),
null
) )
} }
} }
@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
@@ -64,7 +64,7 @@ class SafetyNumberChangeDialogPreviewer {
scenario.onActivity { conversationActivity -> scenario.onActivity { conversationActivity ->
SafetyNumberBottomSheet SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations( .forIdentityRecordsAndDestinations(
identityRecords = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(othersRecipients).identityRecords, identityRecords = AppDependencies.protocolStore.aci().identities().getIdentityRecords(othersRecipients).identityRecords,
destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true)) destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true))
) )
.show(conversationActivity.supportFragmentManager) .show(conversationActivity.supportFragmentManager)
@@ -7,7 +7,9 @@ package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri import android.net.Uri
import android.view.View import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.bumptech.glide.RequestManager
import io.mockk.mockk import io.mockk.mockk
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule import org.junit.Rule
@@ -29,7 +31,6 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerLocator
@@ -47,7 +48,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(), currentMessage = getMessageRecord(),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -69,7 +69,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.END val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now), currentMessage = getMessageRecord(now),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -91,7 +90,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.START val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev), currentMessage = getMessageRecord(prev),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -115,7 +113,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.MIDDLE val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now), currentMessage = getMessageRecord(now),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -137,7 +134,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now), currentMessage = getMessageRecord(now),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -159,7 +155,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev), currentMessage = getMessageRecord(prev),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -183,7 +178,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape( val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now), currentMessage = getMessageRecord(now),
isGroupThread = false, isGroupThread = false,
adapterPosition = 5 adapterPosition = 5
@@ -210,14 +204,15 @@ class V2ConversationItemShapeTest {
private val colorizer = Colorizer() private val colorizer = Colorizer()
override val lifecycleOwner: LifecycleOwner = mockk(relaxed = true)
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet() override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true override val isMessageRequestAccepted: Boolean = true
override val searchQuery: String? = null override val searchQuery: String? = null
override val glideRequests: GlideRequests = mockk() override val requestManager: RequestManager = mockk()
override val isParentInScroll: Boolean = false override val isParentInScroll: Boolean = false
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
@@ -295,6 +290,8 @@ class V2ConversationItemShapeTest {
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
override fun onChangeProfileNameUpdateContact(recipient: Recipient) = Unit
override fun onCallToAction(action: String) = Unit override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit override fun onDonateClicked() = Unit
@@ -319,7 +316,7 @@ class V2ConversationItemShapeTest {
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
@@ -328,5 +325,14 @@ class V2ConversationItemShapeTest {
override fun onItemClick(item: MultiselectPart?) = Unit override fun onItemClick(item: MultiselectPart?) = Unit
override fun onItemLongClick(itemView: View?, 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
override fun onItemDoubleClick(item: MultiselectPart) = Unit
override fun onPaymentTombstoneClicked() = Unit
} }
} }
@@ -51,18 +51,16 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData( SignalDatabase.attachments.updateAttachmentData(
attachment, attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)), createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
false
) )
SignalDatabase.attachments.updateAttachmentData( SignalDatabase.attachments.updateAttachmentData(
attachment2, attachment2,
createMediaStream(byteArrayOf(1, 2, 3)), createMediaStream(byteArrayOf(1, 2, 3))
false
) )
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA) val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA) val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info) assertNotEquals(attachment1Info, attachment2Info)
} }
@@ -79,18 +77,16 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData( SignalDatabase.attachments.updateAttachmentData(
attachment, attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)), createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
true
) )
SignalDatabase.attachments.updateAttachmentData( SignalDatabase.attachments.updateAttachmentData(
attachment2, attachment2,
createMediaStream(byteArrayOf(1, 2, 3, 4)), createMediaStream(byteArrayOf(1, 2, 3, 4))
true
) )
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA) val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA) val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info) assertNotEquals(attachment1Info, attachment2Info)
} }
@@ -121,15 +117,14 @@ class AttachmentTableTest {
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload) val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
// WHEN // WHEN
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false) SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData))
// THEN // THEN
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA)!! val previousInfo = SignalDatabase.attachments.getDataFileInfo(previousDatabaseAttachmentId)!!
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!! val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!! val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
assertNotEquals(standardInfo, highInfo) assertNotEquals(standardInfo, highInfo)
standardInfo.file assertIs previousInfo.file
highInfo.file assertIsNot standardInfo.file highInfo.file assertIsNot standardInfo.file
highInfo.file.exists() assertIs true highInfo.file.exists() assertIs true
} }
@@ -158,9 +153,9 @@ class AttachmentTableTest {
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload) val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
// THEN // THEN
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!! val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!! val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA)!! val secondHighInfo = SignalDatabase.attachments.getDataFileInfo(secondHighDatabaseAttachment.attachmentId)!!
highInfo.file assertIsNot standardInfo.file highInfo.file assertIsNot standardInfo.file
secondHighInfo.file assertIs highInfo.file secondHighInfo.file assertIs highInfo.file
@@ -0,0 +1,838 @@
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.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.backup.v2.BackupRepository.getMediaName
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.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.backup.MediaId
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)
assertRemoteFieldsMatch(id1, id2)
assertArchiveFieldsMatch(id1, id2)
}
// 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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(id1, 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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
assertArchiveFieldsMatch(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)
val attachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
SignalDatabase.attachments.setArchiveData(
attachmentId = attachmentId,
archiveCdn = Cdn.CDN_3.cdnNumber,
archiveMediaName = attachment.getMediaName().name,
archiveThumbnailMediaId = MediaId(Util.getSecretBytes(15)).encode(),
archiveMediaId = MediaId(Util.getSecretBytes(15)).encode()
)
}
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.cdn.cdnNumber, rhsAttachment.cdn.cdnNumber)
}
fun assertArchiveFieldsMatch(lhs: AttachmentId, rhs: AttachmentId) {
val lhsAttachment = SignalDatabase.attachments.getAttachment(lhs)!!
val rhsAttachment = SignalDatabase.attachments.getAttachment(rhs)!!
assertEquals(lhsAttachment.archiveCdn, rhsAttachment.archiveCdn)
assertEquals(lhsAttachment.archiveMediaName, rhsAttachment.archiveMediaName)
assertEquals(lhsAttachment.archiveMediaId, rhsAttachment.archiveMediaId)
}
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.cdn.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,
Cdn.CDN_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,
databaseAttachment.uuid
)
}
}
private fun test(content: TestContext.() -> Unit) {
SignalDatabase.attachments.deleteAllAttachments()
val context = TestContext()
context.content()
}
}
@@ -56,7 +56,7 @@ class CallTableTest {
) )
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId) val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.deleteGroupCall(call!!) SignalDatabase.calls.markCallDeletedFromSyncEvent(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId) val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp() val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -69,9 +69,10 @@ class CallTableTest {
@Test @Test
fun givenNoPreExistingEvent_whenIDeleteGroupCall_thenIInsertAndMarkCallDeleted() { fun givenNoPreExistingEvent_whenIDeleteGroupCall_thenIInsertAndMarkCallDeleted() {
val callId = 1L val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent( SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId, callId,
groupRecipientId, groupRecipientId,
CallTable.Type.GROUP_CALL,
CallTable.Direction.OUTGOING, CallTable.Direction.OUTGOING,
System.currentTimeMillis() System.currentTimeMillis()
) )
@@ -214,6 +215,175 @@ class CallTableTest {
assertEquals(CallTable.Event.JOINED, acceptedCall?.event) assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
} }
@Test
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 = 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.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 @Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() { fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
val era = "aaa" val era = "aaa"
@@ -269,11 +439,12 @@ class CallTableTest {
@Test @Test
fun givenADeletedCallEvent_whenIReceiveARingUpdate_thenIIgnoreTheRingUpdate() { fun givenADeletedCallEvent_whenIReceiveARingUpdate_thenIIgnoreTheRingUpdate() {
val callId = 1L val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent( SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId = callId, callId = callId,
recipientId = groupRecipientId, recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING, direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis() timestamp = System.currentTimeMillis(),
type = CallTable.Type.GROUP_CALL
) )
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState( SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
@@ -15,7 +15,7 @@ import org.signal.core.util.getIndexes
import org.signal.core.util.readToList import org.signal.core.util.readToList
import org.signal.core.util.requireNonNullString import org.signal.core.util.requireNonNullString
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
/** /**
@@ -30,7 +30,7 @@ class DatabaseConsistencyTest {
@Test @Test
fun testUpgradeConsistency() { fun testUpgradeConsistency() {
val currentVersionStatements = SignalDatabase.rawDatabase.getAllCreateStatements() val currentVersionStatements = SignalDatabase.rawDatabase.getAllCreateStatements()
val testHelper = InMemoryTestHelper(ApplicationDependencies.getApplication()).also { val testHelper = InMemoryTestHelper(AppDependencies.application).also {
it.onUpgrade(it.writableDatabase, 181, SignalDatabaseMigrations.DATABASE_VERSION) it.onUpgrade(it.writableDatabase, 181, SignalDatabaseMigrations.DATABASE_VERSION)
} }
@@ -8,7 +8,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.concurrent.SignalExecutors import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@@ -26,7 +26,7 @@ class DatabaseObserverTest {
@Before @Before
fun setup() { fun setup() {
db = SignalDatabase.instance!!.signalWritableDatabase db = SignalDatabase.instance!!.signalWritableDatabase
observer = ApplicationDependencies.getDatabaseObserver() observer = AppDependencies.databaseObserver
} }
@Test @Test
@@ -25,15 +25,6 @@ class DistributionListTablesTest {
Assert.assertNotNull(id) Assert.assertNotNull(id)
} }
@Test
fun createList_whenNameConflict_failToInsert() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
val id2: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNull(id2)
}
@Test @Test
fun getList_returnCorrectList() { fun getList_returnCorrectList() {
createRecipients(3) createRecipients(3)
@@ -2,12 +2,11 @@ package org.thoughtcrime.securesms.database
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test 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.readToList
import org.signal.core.util.requireLong import org.signal.core.util.requireLong
import org.signal.core.util.withinTransaction import org.signal.core.util.withinTransaction
@@ -33,8 +32,8 @@ class GroupTableTest {
fun setUp() { fun setUp() {
groupTable = SignalDatabase.groups groupTable = SignalDatabase.groups
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run() groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run() groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
} }
@Test @Test
@@ -75,21 +74,6 @@ class GroupTableTest {
assertEquals(2, groups.size) assertEquals(2, groups.size)
} }
@Test
fun givenGroups_whenIQueryGroupsByMembership_thenIExpectBothGroups() {
insertPushGroup()
insertMmsGroup(members = listOf(harness.others[1]))
val groups = groupTable.queryGroupsByMembership(
setOf(harness.self.id, harness.others[1]),
includeInactive = false,
excludeV1 = false,
excludeMms = false
)
assertEquals(2, groups.cursor?.count)
}
@Test @Test
fun givenGroups_whenIGetGroups_thenIExpectBothGroups() { fun givenGroups_whenIGetGroups_thenIExpectBothGroups() {
insertPushGroup() insertPushGroup()
@@ -181,72 +165,10 @@ class GroupTableTest {
assertFalse(actual) assertFalse(actual)
} }
@Test
fun givenAGroup_whenIUpdateMembers_thenIExpectUpdatedMembers() {
val v2Group = insertPushGroup()
groupTable.updateMembers(v2Group, listOf(harness.self.id, harness.others[1]))
val groupRecord = groupTable.getGroup(v2Group)
assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet())
}
@Test
fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val members: List<RecipientId> = listOf(harness.self.id, harness.others[0])
val other = insertMmsGroup(members + listOf(harness.others[1]))
val mmsGroup = insertMmsGroup(members)
val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet())
assertNotEquals(other, actual)
assertEquals(mmsGroup, actual)
}
@Test
fun givenMultipleMmsGroups_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1])
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2])
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMultipleMmsGroupsWithDifferentMemberOrders_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1], harness.others[2]).shuffled()
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2], harness.others[3]).shuffled()
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.shuffled().toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.shuffled().toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMmsGroupWithOneMember_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val groupMembers: List<RecipientId> = listOf(harness.self.id)
val group: GroupId = insertMmsGroup(groupMembers)
val groupResult: GroupId = groupTable.getOrCreateMmsGroupForMembers(groupMembers.toSet())
assertEquals(group, groupResult)
}
@Test @Test
fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() { fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() {
val g1 = insertPushGroup(listOf()) val g1 = insertPushGroup(members = emptyList())
val g2 = insertPushGroup(listOf()) val g2 = insertPushGroup(members = emptyList())
val gr1 = groupTable.getGroup(g1) val gr1 = groupTable.getGroup(g1)
val gr2 = groupTable.getGroup(g2) val gr2 = groupTable.getGroup(g2)
@@ -273,6 +195,85 @@ class GroupTableTest {
assertEquals(groups[0].id, groupInCommon) assertEquals(groups[0].id, groupInCommon)
} }
@Test
fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForTheSharedToken_thenIExpectBothGroups() {
insertPushGroup("Group Alice")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(2, it.cursor?.count)
val firstGroup = it.getNext()
val secondGroup = it.getNext()
assertEquals("Group Alice", firstGroup?.title)
assertEquals("Group Bob", secondGroup?.title)
}
}
@Test
fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForAnUnsharedToken_thenIExpectOneGroup() {
insertPushGroup("Group Alice")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Alice",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(1, it.cursor?.count)
val firstGroup = it.getNext()
assertEquals("Group Alice", firstGroup?.title)
}
}
@Test
fun givenAGroupWithThreeTokens_whenISearchForTheFirstAndLastToken_thenIExpectThatGroup() {
insertPushGroup("Group & Alice")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group Alice",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(1, it.cursor?.count)
val firstGroup = it.getNext()
assertEquals("Group & Alice", firstGroup?.title)
}
}
@Test
fun givenTwoGroupsWithSharedTokens_whenISearchForAnExactMatch_thenIExpectThatGroupFirst() {
insertPushGroup("Group Alice Bob")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group Bob",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(2, it.cursor?.count)
val firstGroup = it.getNext()
val second = it.getNext()
assertEquals("Group Bob", firstGroup?.title)
assertEquals("Group Alice Bob", second?.title)
}
}
private fun insertThread(groupId: GroupId): Long { private fun insertThread(groupId: GroupId): Long {
val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get() val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get()
return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient)) return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient))
@@ -292,50 +293,52 @@ class GroupTableTest {
} }
private fun insertPushGroup( private fun insertPushGroup(
title: String = "Test Group",
members: List<DecryptedMember> = listOf( members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder() DecryptedMember.Builder()
.setAciBytes(harness.self.requireAci().toByteString()) .aciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0) .joinedAtRevision(0)
.setRole(Member.Role.DEFAULT) .role(Member.Role.DEFAULT)
.build(), .build(),
DecryptedMember.newBuilder() DecryptedMember.Builder()
.setAciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString()) .aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.setJoinedAtRevision(0) .joinedAtRevision(0)
.setRole(Member.Role.DEFAULT) .role(Member.Role.DEFAULT)
.build() .build()
) )
): GroupId { ): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE)) val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder() val decryptedGroupState = DecryptedGroup.Builder()
.addAllMembers(members) .title(title)
.setRevision(0) .members(members)
.revision(0)
.build() .build()
return groupTable.create(groupMasterKey, decryptedGroupState)!! return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
} }
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId { private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE)) val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val selfMember: DecryptedMember = DecryptedMember.newBuilder() val selfMember: DecryptedMember = DecryptedMember.Builder()
.setAciBytes(harness.self.requireAci().toByteString()) .aciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0) .joinedAtRevision(0)
.setRole(Member.Role.DEFAULT) .role(Member.Role.DEFAULT)
.build() .build()
val otherMembers: List<DecryptedMember> = others.map { id -> val otherMembers: List<DecryptedMember> = others.map { id ->
DecryptedMember.newBuilder() DecryptedMember.Builder()
.setAciBytes(Recipient.resolved(id).requireAci().toByteString()) .aciBytes(Recipient.resolved(id).requireAci().toByteString())
.setJoinedAtRevision(0) .joinedAtRevision(0)
.setRole(Member.Role.DEFAULT) .role(Member.Role.DEFAULT)
.build() .build()
} }
val decryptedGroupState = DecryptedGroup.newBuilder() val decryptedGroupState = DecryptedGroup.Builder()
.addAllMembers(listOf(selfMember) + otherMembers) .members(listOf(selfMember) + otherMembers)
.setRevision(0) .revision(0)
.build() .build()
return groupTable.create(groupMasterKey, decryptedGroupState)!! return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
} }
} }
@@ -159,7 +159,7 @@ class KyberPreKeyTableTest {
val count = SignalDatabase.rawDatabase val count = SignalDatabase.rawDatabase
.update(KyberPreKeyTable.TABLE_NAME) .update(KyberPreKeyTable.TABLE_NAME)
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime) .values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account) .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run() .run()
assertEquals(1, count) assertEquals(1, count)
@@ -169,8 +169,15 @@ class KyberPreKeyTableTest {
return SignalDatabase.rawDatabase return SignalDatabase.rawDatabase
.select(KyberPreKeyTable.STALE_TIMESTAMP) .select(KyberPreKeyTable.STALE_TIMESTAMP)
.from(KyberPreKeyTable.TABLE_NAME) .from(KyberPreKeyTable.TABLE_NAME)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account) .where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run() .run()
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) } .readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
} }
private fun ServiceId.toAccountId(): String {
return when (this) {
is ACI -> this.toString()
is PNI -> KyberPreKeyTable.PNI_ACCOUNT_ID
}
}
} }
@@ -10,14 +10,14 @@ import org.signal.core.util.forEach
import org.signal.core.util.requireLong import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString import org.signal.core.util.requireNonNullString
import org.signal.core.util.select import org.signal.core.util.select
import org.signal.core.util.update import org.signal.core.util.updateAll
import org.thoughtcrime.securesms.crash.CrashConfig import org.thoughtcrime.securesms.crash.CrashConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.testing.assertIs import org.thoughtcrime.securesms.testing.assertIs
class LogDatabaseTest { class LogDatabaseTest {
private val db: LogDatabase = LogDatabase.getInstance(ApplicationDependencies.getApplication()) private val db: LogDatabase = LogDatabase.getInstance(AppDependencies.application)
@Test @Test
fun crashTable_matchesNamePattern() { fun crashTable_matchesNamePattern() {
@@ -220,7 +220,7 @@ class LogDatabaseTest {
) )
db.writableDatabase db.writableDatabase
.update(LogDatabase.CrashTable.TABLE_NAME) .updateAll(LogDatabase.CrashTable.TABLE_NAME)
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime) .values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
.run() .run()
@@ -30,8 +30,8 @@ class MessageTableTest_gifts {
mms.deleteAllThreads() mms.deleteAllThreads()
SignalStore.account().setAci(localAci) SignalStore.account.setAci(localAci)
SignalStore.account().setPni(localPni) SignalStore.account.setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) } recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
} }
@@ -48,7 +48,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val result = mms.setOutgoingGiftsRevealed(listOf(messageId)) val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -62,7 +62,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
mms.setOutgoingGiftsRevealed(listOf(messageId)) mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -76,13 +76,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
MmsHelper.insert( MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2, sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val result = mms.setOutgoingGiftsRevealed(listOf(messageId)) val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -96,13 +96,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val messageId2 = MmsHelper.insert( val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2, sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2)) val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
@@ -115,13 +115,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val messageId2 = MmsHelper.insert( val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2, sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
MmsHelper.insert( MmsHelper.insert(
@@ -140,13 +140,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val messageId2 = MmsHelper.insert( val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2, sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val messageId3 = MmsHelper.insert( val messageId3 = MmsHelper.insert(
@@ -165,13 +165,13 @@ class MessageTableTest_gifts {
MmsHelper.insert( MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1, sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
MmsHelper.insert( MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]), recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2, sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance() giftBadge = GiftBadge()
) )
val messageId3 = MmsHelper.insert( val messageId3 = MmsHelper.insert(
@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
import org.thoughtcrime.securesms.database.model.ParentStoryId import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge 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.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional import java.util.Optional
@@ -55,9 +55,9 @@ object MmsHelper {
} }
fun insert( fun insert(
message: IncomingMediaMessage, message: IncomingMessage,
threadId: Long threadId: Long
): Optional<MessageTable.InsertResult> { ): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId) return SignalDatabase.messages.insertMessageInbox(message, threadId)
} }
} }
@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.ParentStoryId import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.keyvalue.SignalStore 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.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -40,14 +40,14 @@ class MmsTableTest_stories {
mms.deleteAllThreads() mms.deleteAllThreads()
SignalStore.account().setAci(localAci) SignalStore.account.setAci(localAci)
SignalStore.account().setPni(localPni) SignalStore.account.setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY)) myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) } recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient()) releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id) SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelRecipient.id)
} }
@Test @Test
@@ -73,7 +73,8 @@ class MmsTableTest_stories {
) )
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = sender, from = sender,
sentTimeMillis = 2, sentTimeMillis = 2,
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -95,7 +96,8 @@ class MmsTableTest_stories {
// GIVEN // GIVEN
val sender = recipients[0] val sender = recipients[0]
val messageId = MmsHelper.insert( val messageId = MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = sender, from = sender,
sentTimeMillis = 2, sentTimeMillis = 2,
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -122,7 +124,8 @@ class MmsTableTest_stories {
// GIVEN // GIVEN
val messageIds = recipients.take(5).map { val messageIds = recipients.take(5).map {
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = it, from = it,
sentTimeMillis = 2, sentTimeMillis = 2,
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -154,7 +157,8 @@ class MmsTableTest_stories {
val unviewedIds: List<Long> = (0 until 5).map { val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5) Thread.sleep(5)
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it], from = recipients[it],
sentTimeMillis = System.currentTimeMillis(), sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -168,7 +172,8 @@ class MmsTableTest_stories {
val viewedIds: List<Long> = (0 until 5).map { val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5) Thread.sleep(5)
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it], from = recipients[it],
sentTimeMillis = System.currentTimeMillis(), sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -213,7 +218,8 @@ class MmsTableTest_stories {
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() { fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN // GIVEN
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = recipients[0], from = recipients[0],
sentTimeMillis = 200, sentTimeMillis = 200,
serverTimeMillis = 2, serverTimeMillis = 2,
@@ -321,7 +327,8 @@ class MmsTableTest_stories {
) )
MmsHelper.insert( MmsHelper.insert(
IncomingMediaMessage( IncomingMessage(
type = MessageType.NORMAL,
from = myStory.id, from = myStory.id,
sentTimeMillis = 201, sentTimeMillis = 201,
serverTimeMillis = 201, serverTimeMillis = 201,
@@ -0,0 +1,239 @@
/*
* Copyright 2024 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.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIsSize
@RunWith(AndroidJUnit4::class)
class NameCollisionTablesTest {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var alice: RecipientId
private lateinit var bob: RecipientId
private lateinit var charlie: RecipientId
@Before
fun setUp() {
alice = setUpRecipient(harness.others[0])
bob = setUpRecipient(harness.others[1])
charlie = setUpRecipient(harness.others[2])
}
@Test
fun givenAUserWithAThreadIdButNoConflicts_whenIGetCollisionsForThreadRecipient_thenIExpectNoCollisions() {
val threadRecipientId = alice
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(threadRecipientId))
val actual = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(threadRecipientId)
actual assertIsSize 0
}
@Test
fun givenTwoUsers_whenOneChangesTheirProfileNameToMatchTheOther_thenIExpectANameCollision() {
setProfileName(alice, ProfileName.fromParts("Alice", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
val actualAlice = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
val actualBob = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(bob)
actualAlice assertIsSize 2
actualBob assertIsSize 2
}
@Test
fun givenTwoUsersWithANameCollisions_whenOneChangesToADifferentName_thenIExpectNoNameCollisions() {
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
setProfileName(alice, ProfileName.fromParts("Alice", "Android"))
val actualAlice = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
val actualBob = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(bob)
actualAlice assertIsSize 0
actualBob assertIsSize 0
}
@Test
fun givenThreeUsersWithANameCollisions_whenOneChangesToADifferentName_thenIExpectTwoNameCollisions() {
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
setProfileName(charlie, ProfileName.fromParts("Bob", "Android"))
setProfileName(alice, ProfileName.fromParts("Alice", "Android"))
val actualAlice = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
val actualBob = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(bob)
val actualCharlie = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(charlie)
actualAlice assertIsSize 0
actualBob assertIsSize 2
actualCharlie assertIsSize 2
}
@Test
fun givenTwoUsersWithADismissedNameCollision_whenOneChangesToADifferentNameAndBack_thenIExpectANameCollision() {
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.nameCollisions.markCollisionsForThreadRecipientDismissed(alice)
setProfileName(alice, ProfileName.fromParts("Alice", "Android"))
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
val actualAlice = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
actualAlice assertIsSize 2
}
@Test
fun givenADismissedNameCollisionForAlice_whenIGetNameCollisionsForAlice_thenIExpectNoNameCollisions() {
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.nameCollisions.markCollisionsForThreadRecipientDismissed(alice)
val actualCollisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
actualCollisions assertIsSize 0
}
@Test
fun givenADismissedNameCollisionForAliceThatIUpdate_whenIGetNameCollisionsForAlice_thenIExpectNoNameCollisions() {
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(alice))
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.nameCollisions.markCollisionsForThreadRecipientDismissed(alice)
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
val actualCollisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(alice)
actualCollisions assertIsSize 0
}
@Test
fun givenADismissedNameCollisionForAlice_whenIGetNameCollisionsForBob_thenIExpectANameCollisionWithTwoEntries() {
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(alice))
setProfileName(alice, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.nameCollisions.markCollisionsForThreadRecipientDismissed(alice)
val actualCollisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(bob)
actualCollisions assertIsSize 2
}
@Test
fun givenAGroupWithAliceAndBob_whenIInsertNameChangeMessageForAlice_thenIExpectAGroupNameCollision() {
val alice = Recipient.resolved(alice)
val bob = Recipient.resolved(bob)
val info = createGroup()
setProfileName(alice.id, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob.id, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(info.recipientId))
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "Bob Android", "Alice Android")
val collisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(info.recipientId)
collisions assertIsSize 2
}
@Test
fun givenAGroupWithAliceAndBobWithDismissedCollision_whenIInsertNameChangeMessageForAlice_thenIExpectAGroupNameCollision() {
val alice = Recipient.resolved(alice)
val bob = Recipient.resolved(bob)
val info = createGroup()
setProfileName(alice.id, ProfileName.fromParts("Bob", "Android"))
setProfileName(bob.id, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(info.recipientId))
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "Bob Android", "Alice Android")
SignalDatabase.nameCollisions.markCollisionsForThreadRecipientDismissed(info.recipientId)
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "Bob Android", "Alice Android")
val collisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(info.recipientId)
collisions assertIsSize 0
}
@Test
fun givenAGroupWithAliceAndBob_whenIInsertNameChangeMessageForAliceWithMismatch_thenIExpectNoGroupNameCollision() {
val alice = Recipient.resolved(alice)
val bob = Recipient.resolved(bob)
val info = createGroup()
setProfileName(alice.id, ProfileName.fromParts("Alice", "Android"))
setProfileName(bob.id, ProfileName.fromParts("Bob", "Android"))
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(info.recipientId))
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "Alice Android", "Bob Android")
val collisions = SignalDatabase.nameCollisions.getCollisionsForThreadRecipientId(info.recipientId)
collisions assertIsSize 0
}
private fun setUpRecipient(recipientId: RecipientId): RecipientId {
SignalDatabase.recipients.setProfileSharing(recipientId, false)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, false)
MmsHelper.insert(
threadId = threadId,
message = IncomingMessage(
type = MessageType.NORMAL,
from = recipientId,
groupId = null,
body = "hi",
sentTimeMillis = 100L,
receivedTimeMillis = 200L,
serverTimeMillis = 100L,
isUnidentified = true
)
)
return recipientId
}
private fun setProfileName(recipientId: RecipientId, name: ProfileName) {
SignalDatabase.recipients.setProfileName(recipientId, name)
SignalDatabase.nameCollisions.handleIndividualNameCollision(recipientId)
}
private fun createGroup(): GroupTestingUtils.TestGroupInfo {
return GroupTestingUtils.insertGroup(
revision = 0,
DecryptedMember(
aciBytes = harness.self.requireAci().toByteString(),
role = Member.Role.ADMINISTRATOR
),
DecryptedMember(
aciBytes = Recipient.resolved(alice).requireAci().toByteString(),
role = Member.Role.ADMINISTRATOR
),
DecryptedMember(
aciBytes = Recipient.resolved(bob).requireAci().toByteString(),
role = Member.Role.ADMINISTRATOR
)
)
}
}
@@ -120,7 +120,7 @@ class OneTimePreKeyTableTest {
val count = SignalDatabase.rawDatabase val count = SignalDatabase.rawDatabase
.update(OneTimePreKeyTable.TABLE_NAME) .update(OneTimePreKeyTable.TABLE_NAME)
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime) .values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account) .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run() .run()
assertEquals(1, count) assertEquals(1, count)
@@ -130,8 +130,15 @@ class OneTimePreKeyTableTest {
return SignalDatabase.rawDatabase return SignalDatabase.rawDatabase
.select(OneTimePreKeyTable.STALE_TIMESTAMP) .select(OneTimePreKeyTable.STALE_TIMESTAMP)
.from(OneTimePreKeyTable.TABLE_NAME) .from(OneTimePreKeyTable.TABLE_NAME)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account) .where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run() .run()
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) } .readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
} }
private fun ServiceId.toAccountId(): String {
return when (this) {
is ACI -> this.toString()
is PNI -> OneTimePreKeyTable.PNI_ACCOUNT_ID
}
}
} }
@@ -11,8 +11,6 @@ import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID import java.util.UUID
@@ -59,7 +57,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person")) SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient) SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!! val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Hidden", false))!!
assertEquals(0, results.count) assertEquals(0, results.count)
} }
@@ -130,7 +128,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person")) SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true) 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) assertEquals(0, results.count)
} }
@@ -167,8 +165,6 @@ class RecipientTableTest {
@Test @Test
fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() { fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
SignalDatabase.recipients.markUnregistered(mainId) SignalDatabase.recipients.markUnregistered(mainId)
@@ -185,12 +181,10 @@ class RecipientTableTest {
@Test @Test
fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() { fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
val mainRecord = SignalDatabase.recipients.getRecord(mainId) val mainRecord = SignalDatabase.recipients.getRecord(mainId)
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!) SignalDatabase.recipients.splitForStorageSyncIfNecessary(mainRecord.aci!!)
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get() val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
@@ -9,7 +9,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels import org.thoughtcrime.securesms.storage.StorageSyncModels
@@ -28,7 +28,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
@Test @Test
fun insertMessageOnVerifiedToDefault() { fun insertMessageOnVerifiedToDefault() {
// GIVEN // GIVEN
val identities = ApplicationDependencies.getProtocolStore().aci().identities() val identities = AppDependencies.protocolStore.aci().identities()
val other = Recipient.resolved(harness.others[0]) val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other) MmsHelper.insert(recipient = other)
@@ -14,8 +14,11 @@ import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil import org.signal.core.util.SqlUtil
import org.signal.core.util.exists 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.requireLong
import org.signal.core.util.requireNonNullString import org.signal.core.util.requireNonNullString
import org.signal.core.util.select import org.signal.core.util.select
@@ -31,17 +34,13 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore 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.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.thoughtcrime.securesms.util.Util import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
@@ -54,10 +53,9 @@ class RecipientTableTest_getAndPossiblyMerge {
@Before @Before
fun setup() { fun setup() {
SignalStore.account().setE164(E164_SELF) SignalStore.account.setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF) SignalStore.account.setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF) SignalStore.account.setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
} }
@Test @Test
@@ -109,6 +107,18 @@ class RecipientTableTest_getAndPossiblyMerge {
val record = SignalDatabase.recipients.getRecord(id) val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered) 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 @Test
@@ -142,6 +152,31 @@ class RecipientTableTest_getAndPossiblyMerge {
process(null, null, null) 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") { test("no match, all fields") {
process(E164_A, PNI_A, ACI_A) process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
@@ -201,6 +236,8 @@ class RecipientTableTest_getAndPossiblyMerge {
given(E164_A, PNI_A, null, pniSession = true) given(E164_A, PNI_A, null, pniSession = true)
process(E164_A, PNI_A, ACI_A, pniVerified = true) process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
} }
test("e164 and aci matches, all provided, new pni") { test("e164 and aci matches, all provided, new pni") {
@@ -502,6 +539,18 @@ class RecipientTableTest_getAndPossiblyMerge {
expectNoSessionSwitchoverEvent() 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") { test("merge, e164 & pni & aci, all provided") {
given(E164_A, null, null) given(E164_A, null, null)
given(null, PNI_A, null) given(null, PNI_A, null)
@@ -658,6 +707,8 @@ class RecipientTableTest_getAndPossiblyMerge {
expectDeleted() expectDeleted()
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
} }
test("merge, e164+pni & aci, pni session, pni verified") { test("merge, e164+pni & aci, pni session, pni verified") {
@@ -670,6 +721,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, PNI_A, ACI_A) expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A) expectThreadMergeEvent(E164_A)
expectPniVerified()
} }
test("merge, e164+pni & e164+pni+aci, change number") { test("merge, e164+pni & e164+pni+aci, change number") {
@@ -724,6 +776,18 @@ class RecipientTableTest_getAndPossiblyMerge {
expectThreadMergeEvent(E164_A) 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") { test("merge, e164 + pni reassigned, aci abandoned") {
given(E164_A, PNI_A, ACI_A) given(E164_A, PNI_A, ACI_A)
given(E164_B, PNI_B, ACI_B) given(E164_B, PNI_B, ACI_B)
@@ -736,6 +800,17 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent() 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") { test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF) given(E164_SELF, null, ACI_SELF)
given(null, null, ACI_A) given(null, null, ACI_A)
@@ -789,9 +864,9 @@ class RecipientTableTest_getAndPossiblyMerge {
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId 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 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 mmsId1: Long = SignalDatabase.messages.insertMessageInbox(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 mmsId2: Long = SignalDatabase.messages.insertMessageInbox(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 mmsId3: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!! val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!! val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
@@ -838,8 +913,8 @@ class RecipientTableTest_getAndPossiblyMerge {
// Thread validation // Thread validation
assertEquals(threadIdAci, retrievedThreadId) assertEquals(threadIdAci, retrievedThreadId)
Assert.assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164)) assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164)) assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
// SMS validation // SMS validation
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!! val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
@@ -883,10 +958,10 @@ class RecipientTableTest_getAndPossiblyMerge {
// Identity validation // Identity validation
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey) assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
Assert.assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A)) assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
// Session validation // 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 // Reaction validation
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1)) val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
@@ -911,12 +986,30 @@ class RecipientTableTest_getAndPossiblyMerge {
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB)) 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 { private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null) 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 { private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false) 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 { private fun identityKey(value: Byte): IdentityKey {
@@ -983,6 +1076,10 @@ class RecipientTableTest_getAndPossiblyMerge {
if (!test.sessionSwitchoverExpected) { if (!test.sessionSwitchoverExpected) {
test.expectNoSessionSwitchoverEvent() test.expectNoSessionSwitchoverEvent()
} }
if (!test.pniVerifiedExpected) {
test.expectPniNotVerified()
}
} catch (e: Throwable) { } catch (e: Throwable) {
if (e.javaClass != exception) { if (e.javaClass != exception) {
val error = java.lang.AssertionError("[$name] ${e.message}") val error = java.lang.AssertionError("[$name] ${e.message}")
@@ -1002,6 +1099,7 @@ class RecipientTableTest_getAndPossiblyMerge {
var changeNumberExpected = false var changeNumberExpected = false
var threadMergeExpected = false var threadMergeExpected = false
var sessionSwitchoverExpected = false var sessionSwitchoverExpected = false
var pniVerifiedExpected = false
init { init {
// Need to delete these first to prevent foreign key crash // Need to delete these first to prevent foreign key crash
@@ -1015,8 +1113,8 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalDatabase.rawDatabase.execSQL("DELETE FROM $table") SignalDatabase.rawDatabase.execSQL("DELETE FROM $table")
} }
ApplicationDependencies.getRecipientCache().clear() AppDependencies.recipientCache.clear()
ApplicationDependencies.getRecipientCache().clearSelf() AppDependencies.recipientCache.clearSelf()
RecipientId.clearCache() RecipientId.clearCache()
} }
@@ -1153,6 +1251,24 @@ class RecipientTableTest_getAndPossiblyMerge {
assertNull("Unexpected thread merge event!", 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 { private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
val id: Long = SignalDatabase.rawDatabase.insert( val id: Long = SignalDatabase.rawDatabase.insert(
RecipientTable.TABLE_NAME, RecipientTable.TABLE_NAME,
@@ -1228,7 +1344,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor -> .use { cursor: Cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY)) val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
ThreadMergeEvent.parseFrom(bytes) ThreadMergeEvent.ADAPTER.decode(bytes)
} else { } else {
null null
} }
@@ -1246,7 +1362,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor -> .use { cursor: Cursor ->
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY)) val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
SessionSwitchoverEvent.parseFrom(bytes) SessionSwitchoverEvent.ADAPTER.decode(bytes)
} else { } else {
null null
} }
@@ -10,7 +10,9 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.Hex import org.signal.core.util.Hex
import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
@@ -18,12 +20,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
import java.util.UUID import java.util.UUID
@Suppress("ClassName", "TestFunctionName") @Suppress("ClassName", "TestFunctionName")
@@ -46,8 +46,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
recipients = SignalDatabase.recipients recipients = SignalDatabase.recipients
sms = SignalDatabase.messages sms = SignalDatabase.messages
SignalStore.account().setAci(localAci) SignalStore.account.setAci(localAci)
SignalStore.account().setPni(localPni) SignalStore.account.setPni(localPni)
alice = recipients.getOrInsertFromServiceId(aliceServiceId) alice = recipients.getOrInsertFromServiceId(aliceServiceId)
bob = recipients.getOrInsertFromServiceId(bobServiceId) bob = recipients.getOrInsertFromServiceId(bobServiceId)
@@ -272,13 +272,36 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue()) 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++ 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 { private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext) wallClock++
val updateDescription = GV2UpdateDescription(
gv2ChangeDescription = groupContext,
groupChangeUpdate = GroupsV2UpdateMessageConverter.translateDecryptedChangeUpdate(SignalStore.account.getServiceIds(), groupContext)
)
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
update = updateDescription,
isGroupAdd = false,
serverGuid = null
)
} }
companion object { companion object {
@@ -5,6 +5,7 @@ import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.audio.AudioHash import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.blurhash.BlurHash import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerLocator
import java.util.UUID
object UriAttachmentBuilder { object UriAttachmentBuilder {
fun build( fun build(
@@ -22,23 +23,28 @@ object UriAttachmentBuilder {
stickerLocator: StickerLocator? = null, stickerLocator: StickerLocator? = null,
blurHash: BlurHash? = null, blurHash: BlurHash? = null,
audioHash: AudioHash? = null, audioHash: AudioHash? = null,
transformProperties: AttachmentTable.TransformProperties? = null transformProperties: AttachmentTable.TransformProperties? = null,
uuid: UUID? = UUID.randomUUID()
): UriAttachment { ): UriAttachment {
return UriAttachment( return UriAttachment(
uri, dataUri = uri,
contentType, contentType = contentType,
transferState, transferState = transferState,
size, size = size,
fileName, width = 0,
voiceNote, height = 0,
borderless, fileName = fileName,
videoGif, fastPreflightId = null,
quote, voiceNote = voiceNote,
caption, borderless = borderless,
stickerLocator, videoGif = videoGif,
blurHash, quote = quote,
audioHash, caption = caption,
transformProperties stickerLocator = stickerLocator,
blurHash = blurHash,
audioHash = audioHash,
transformProperties = transformProperties,
uuid = uuid
) )
} }
} }
@@ -0,0 +1,148 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
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.readToSingleObject
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toDecimalValue
import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.FiatValue
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.thoughtcrime.securesms.testing.assertIs
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.math.BigDecimal
import java.util.Currency
@RunWith(AndroidJUnit4::class)
class FixInAppCurrencyIfAbleTest {
@get:Rule
val harness = SignalDatabaseRule(deleteAllThreadsOnEachRun = false)
@Test
fun givenNoSubscribers_whenIMigrate_thenIDoNothing() {
migrate()
}
@Test
fun givenASubscriberButNoPayment_whenIMigrate_thenIDoNothing() {
val subscriber = insertSubscriber("USD")
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndMismatchedPayment_whenIMigrate_thenIDoNothing() {
val subscriber = insertSubscriber("USD")
val otherSubscriber = insertSubscriber("EUR")
insertPayment(otherSubscriber)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndPaymentWithNoSubscriber_whenIMigrate_thenDoNothing() {
val subscriber = insertSubscriber("USD")
insertPayment(null)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndMatchingPayment_whenIMigrate_thenUpdateCurrencyCode() {
val subscriber = insertSubscriber("USD")
insertPayment(subscriber)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs "USD"
}
@Test
fun givenASupercededSubscriber_whenIMigrate_thenIDoNothing() {
val oldSubscriber = insertSubscriber("USD")
insertPayment(oldSubscriber)
clearCurrencyCode(oldSubscriber)
insertSubscriber("USD")
migrate()
}
private fun migrate() {
V236_FixInAppSubscriberCurrencyIfAble.migrate(
context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
db = SignalDatabase.rawDatabase,
oldVersion = 0,
newVersion = 0
)
}
private fun insertSubscriber(currencyCode: String): InAppPaymentSubscriberRecord {
val record = InAppPaymentSubscriberRecord(
subscriberId = SubscriberId.generate(),
currency = Currency.getInstance(currencyCode),
type = InAppPaymentSubscriberRecord.Type.DONATION,
requiresCancel = false,
paymentMethodType = InAppPaymentData.PaymentMethodType.PAYPAL
)
SignalDatabase.inAppPaymentSubscribers.insertOrReplace(record)
return record
}
private fun clearCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord) {
SignalDatabase.rawDatabase.update(InAppPaymentSubscriberTable.TABLE_NAME)
.values(InAppPaymentSubscriberTable.CURRENCY_CODE to "")
.where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
.run()
}
private fun getCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord): String {
return SignalDatabase.rawDatabase.select(InAppPaymentSubscriberTable.CURRENCY_CODE)
.from(InAppPaymentSubscriberTable.TABLE_NAME)
.where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
.run()
.readToSingleObject { it.requireNonNullString(InAppPaymentSubscriberTable.CURRENCY_CODE) }!!
}
private fun insertPayment(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord?): InAppPaymentTable.InAppPayment {
val id = SignalDatabase.inAppPayments.insert(
type = InAppPaymentType.RECURRING_DONATION,
state = InAppPaymentTable.State.END,
subscriberId = inAppPaymentSubscriberRecord?.subscriberId,
endOfPeriod = null,
inAppPaymentData = InAppPaymentData(
amount = FiatValue(
currencyCode = inAppPaymentSubscriberRecord?.currency?.currencyCode ?: "USD",
amount = BigDecimal.ONE.toDecimalValue()
),
level = 200,
paymentMethodType = inAppPaymentSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
)
)
return SignalDatabase.inAppPayments.getById(id)!!
}
}
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.dependencies package org.thoughtcrime.securesms.dependencies
import android.app.Application import android.app.Application
import io.mockk.spyk
import okhttp3.ConnectionSpec import okhttp3.ConnectionSpec
import okhttp3.Response import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
@@ -13,9 +14,9 @@ import okio.ByteString
import org.mockito.kotlin.any import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock import org.mockito.kotlin.mock
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.KbsEnclave
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
import org.thoughtcrime.securesms.push.SignalServiceTrustStore import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.thoughtcrime.securesms.recipients.LiveRecipientCache import org.thoughtcrime.securesms.recipients.LiveRecipientCache
@@ -23,33 +24,30 @@ import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Verb import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.util.Base64 import org.whispersystems.signalservice.api.SignalServiceDataStore
import org.whispersystems.signalservice.api.KeyBackupService import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.SignalWebSocket
import org.whispersystems.signalservice.api.push.TrustStore import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl 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.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
import java.security.KeyStore
import java.util.Optional import java.util.Optional
/** /**
* Dependency provider used for instrumentation tests (aka androidTests). * 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 * Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
* [KeyBackupService].
*/ */
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default { class InstrumentationApplicationDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : AppDependencies.Provider by default {
private val serviceTrustStore: TrustStore private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val keyBackupService: KeyBackupService
private val recipientCache: LiveRecipientCache private val recipientCache: LiveRecipientCache
private var signalServiceMessageSender: SignalServiceMessageSender? = null
init { init {
runSync { runSync {
@@ -60,18 +58,21 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
Get("/v1/websocket/?login=") { Get("/v1/websocket/?login=") {
MockResponse().success().withWebSocketUpgrade(mockIdentifiedWebSocket) MockResponse().success().withWebSocketUpgrade(mockIdentifiedWebSocket)
}, },
Get("/v1/websocket", { !it.path.contains("login") }) { Get("/v1/websocket", {
val path = it.path
return@Get path == null || !path.contains("login")
}) {
MockResponse().success().withWebSocketUpgrade(object : WebSocketListener() {}) MockResponse().success().withWebSocketUpgrade(object : WebSocketListener() {})
} }
) )
} }
webServer.setDispatcher(object : Dispatcher() { webServer.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse { override fun dispatch(request: RecordedRequest): MockResponse {
val handler = handlers.firstOrNull { it.requestPredicate(request) } val handler = handlers.firstOrNull { it.requestPredicate(request) }
return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500) return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500)
} }
}) }
serviceTrustStore = SignalServiceTrustStore(application) serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration( uncensoredConfiguration = SignalServiceConfiguration(
@@ -80,7 +81,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), 0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)) 2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
), ),
signalKeyBackupServiceUrls = arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)), signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)), signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
@@ -88,7 +88,8 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
dns = Optional.of(SignalServiceNetworkAccess.DNS), dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(), signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS), zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS) genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
) )
serviceNetworkAccessMock = mock { serviceNetworkAccessMock = mock {
@@ -97,8 +98,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
on { uncensoredConfiguration } doReturn uncensoredConfiguration on { uncensoredConfiguration } doReturn uncensoredConfiguration
} }
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() } recipientCache = LiveRecipientCache(application) { r -> r.run() }
} }
@@ -106,14 +105,21 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
return serviceNetworkAccessMock return serviceNetworkAccessMock
} }
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
override fun provideRecipientCache(): LiveRecipientCache { override fun provideRecipientCache(): LiveRecipientCache {
return recipientCache return recipientCache
} }
override fun provideSignalServiceMessageSender(
signalWebSocket: SignalWebSocket,
protocolStore: SignalServiceDataStore,
signalServiceConfiguration: SignalServiceConfiguration
): SignalServiceMessageSender {
if (signalServiceMessageSender == null) {
signalServiceMessageSender = spyk(objToCopy = default.provideSignalServiceMessageSender(signalWebSocket, protocolStore, signalServiceConfiguration))
}
return signalServiceMessageSender!!
}
class MockWebSocket : WebSocketListener() { class MockWebSocket : WebSocketListener() {
private val TAG = "MockWebSocket" private val TAG = "MockWebSocket"
@@ -16,7 +16,7 @@ import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.UriAttachmentBuilder import org.thoughtcrime.securesms.database.UriAttachmentBuilder
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.mms.SentMediaQuality import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider import org.thoughtcrime.securesms.providers.BlobProvider
@@ -38,7 +38,7 @@ class AttachmentCompressionJobTest {
StreamUtil.readFully(it) StreamUtil.readFully(it)
} }
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication()) val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(AppDependencies.application)
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty()) val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload) val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
@@ -51,12 +51,12 @@ class AttachmentCompressionJobTest {
val secondJobLatch = CountDownLatch(1) val secondJobLatch = CountDownLatch(1)
val jobThread = Thread { val jobThread = Thread {
firstCompressionJob.setContext(ApplicationDependencies.getApplication()) firstCompressionJob.setContext(AppDependencies.application)
firstJobResult = firstCompressionJob.run() firstJobResult = firstCompressionJob.run()
secondJobLatch.await() secondJobLatch.await()
secondCompressionJob!!.setContext(ApplicationDependencies.getApplication()) secondCompressionJob!!.setContext(AppDependencies.application)
secondJobResult = secondCompressionJob!!.run() secondJobResult = secondCompressionJob!!.run()
} }
@@ -1,181 +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.Delete
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.account().usernameOutOfSync = false
}
@Test
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
// GIVEN
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Delete("/v1/accounts/username_hash") { MockResponse().success() }
)
// 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.account().usernameOutOfSync)
}
@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.account().usernameOutOfSync)
}
@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.account().usernameOutOfSync)
}
@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.account().usernameOutOfSync)
}
}
@@ -22,8 +22,9 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.EditMessage import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -38,7 +39,6 @@ class EditMessageSyncProcessorTest {
) )
private val IGNORE_ATTACHMENT_COLUMNS = listOf( private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE AttachmentTable.TRANSFER_FILE
) )
} }
@@ -67,16 +67,17 @@ class EditMessageSyncProcessorTest {
val content = MessageContentFuzzer.fuzzTextMessage() val content = MessageContentFuzzer.fuzzTextMessage()
val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id) val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id)
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage( val syncContent = Content.Builder().syncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent( SyncMessage.Builder().sent(
SignalServiceProtos.SyncMessage.Sent.newBuilder() SyncMessage.Sent.Builder()
.setDestinationServiceId(metadata.destinationServiceId.toString()) .destinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(originalTimestamp) .timestamp(originalTimestamp)
.setExpirationStartTimestamp(originalTimestamp) .expirationStartTimestamp(originalTimestamp)
.setMessage(content.dataMessage) .message(content.dataMessage)
) .build()
).build()
).build() ).build()
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer) SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
val syncTextMessage = TestMessage( val syncTextMessage = TestMessage(
envelope = MessageContentFuzzer.envelope(originalTimestamp), envelope = MessageContentFuzzer.envelope(originalTimestamp),
content = syncContent, content = syncContent,
@@ -86,18 +87,20 @@ class EditMessageSyncProcessorTest {
val editTimestamp = originalTimestamp + 200 val editTimestamp = originalTimestamp + 200
val editedContent = MessageContentFuzzer.fuzzTextMessage() val editedContent = MessageContentFuzzer.fuzzTextMessage()
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage( val editSyncContent = Content.Builder().syncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent( SyncMessage.Builder().sent(
SignalServiceProtos.SyncMessage.Sent.newBuilder() SyncMessage.Sent.Builder()
.setDestinationServiceId(metadata.destinationServiceId.toString()) .destinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(editTimestamp) .timestamp(editTimestamp)
.setExpirationStartTimestamp(editTimestamp) .expirationStartTimestamp(editTimestamp)
.setEditMessage( .editMessage(
EditMessage.newBuilder() EditMessage.Builder()
.setDataMessage(editedContent.dataMessage) .dataMessage(editedContent.dataMessage)
.setTargetSentTimestamp(originalTimestamp) .targetSentTimestamp(originalTimestamp)
.build()
) )
) .build()
).build()
).build() ).build()
val syncEditMessage = TestMessage( val syncEditMessage = TestMessage(
@@ -109,38 +112,38 @@ class EditMessageSyncProcessorTest {
testResult.runSync(listOf(syncTextMessage, syncEditMessage)) 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( val originalTextMessage = OutgoingMessage(
threadRecipient = toRecipient, threadRecipient = toRecipient,
sentTimeMillis = originalTimestamp, sentTimeMillis = originalTimestamp,
body = content.dataMessage.body, body = content.dataMessage?.body ?: "",
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds, expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true, isUrgent = true,
isSecure = true, isSecure = true,
bodyRanges = content.dataMessage.bodyRangesList.toBodyRangeList() bodyRanges = content.dataMessage?.bodyRanges.toBodyRangeList()
) )
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient) val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient)
val originalMessageId = SignalDatabase.messages.insertMessageOutbox(originalTextMessage, threadId, false, null) val originalMessageId = SignalDatabase.messages.insertMessageOutbox(originalTextMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(originalMessageId, true) SignalDatabase.messages.markAsSent(originalMessageId, true)
if (content.dataMessage.expireTimer > 0) { if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp) SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp)
} }
val editMessage = OutgoingMessage( val editMessage = OutgoingMessage(
threadRecipient = toRecipient, threadRecipient = toRecipient,
sentTimeMillis = editTimestamp, sentTimeMillis = editTimestamp,
body = editedContent.dataMessage.body, body = editedContent.dataMessage?.body ?: "",
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds, expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true, isUrgent = true,
isSecure = true, isSecure = true,
bodyRanges = editedContent.dataMessage.bodyRangesList.toBodyRangeList(), bodyRanges = editedContent.dataMessage?.bodyRanges.toBodyRangeList(),
messageToEdit = originalMessageId messageToEdit = originalMessageId
) )
val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null) val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(editMessageId, true) SignalDatabase.messages.markAsSent(editMessageId, true)
if (content.dataMessage.expireTimer > 0) { if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp) SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp)
} }
testResult.collectLocal() testResult.collectLocal()
@@ -167,7 +170,7 @@ class EditMessageSyncProcessorTest {
fun runSync(messages: List<TestMessage>) { fun runSync(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) -> messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasSyncMessage()) { if (content.syncMessage != null) {
processorV2.process( processorV2.process(
envelope, envelope,
content, content,
@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.messages package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import okio.ByteString.Companion.toByteString
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.GroupReceiptTable import org.thoughtcrime.securesms.database.GroupReceiptTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.testing.GroupTestingUtils import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
@@ -15,8 +15,8 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2 import org.whispersystems.signalservice.internal.push.GroupContextV2
@Suppress("ClassName") @Suppress("ClassName")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@@ -41,9 +41,9 @@ class MessageContentProcessor__recipientStatusTest {
@Test @Test
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() { fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember()) 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() body = MessageContentFuzzer.string()
groupV2 = groupContextV2 groupV2 = groupContextV2
timestamp = envelopeTimestamp timestamp = envelopeTimestamp
@@ -52,7 +52,7 @@ class MessageContentProcessor__recipientStatusTest {
processor.process( processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp), envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])), 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) serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
) )
@@ -64,7 +64,7 @@ class MessageContentProcessor__recipientStatusTest {
processor.process( processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp), envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true), 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) serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
) )
@@ -0,0 +1,290 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import android.net.Uri
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import org.thoughtcrime.securesms.attachments.Attachment
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.database.model.GroupsV2UpdateMessageConverter
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
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.util.MediaUtil
import java.util.UUID
import kotlin.random.Random
/**
* Makes inserting messages through the "normal" code paths simpler. Mostly focused on incoming messages.
*/
class MessageHelper(private val harness: SignalActivityRule, var startTime: Long = System.currentTimeMillis()) {
val alice: RecipientId = harness.others[0]
val bob: RecipientId = harness.others[1]
val group: GroupTestingUtils.TestGroupInfo = harness.group!!
val processor: MessageContentProcessor = MessageContentProcessor(harness.context)
init {
val threadIdSlot = slot<Long>()
mockkStatic(ThreadUpdateJob::class)
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
SignalDatabase.threads.update(threadIdSlot.captured, false)
}
}
fun tearDown() {
unmockkStatic(ThreadUpdateJob::class)
}
fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = sender, 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,
allowExpireTimeChanges = false
),
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 outgoingText(conversationId: RecipientId = alice, successfulSend: Boolean = true, updateMessage: ((OutgoingMessage) -> OutgoingMessage)? = null): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val threadRecipient = Recipient.resolved(conversationId)
val message = OutgoingMessage(
threadRecipient = threadRecipient,
body = MessageContentFuzzer.string(),
sentTimeMillis = messageData.timestamp,
isUrgent = true,
isSecure = true
).let { updateMessage?.invoke(it) ?: it }
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
if (successfulSend) {
SignalDatabase.messages.markAsSent(messageId, true)
}
return messageData.copy(messageId = messageId)
}
fun outgoingMessage(conversationId: RecipientId = alice, updateMessage: OutgoingMessage.() -> OutgoingMessage): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val threadRecipient = Recipient.resolved(conversationId)
val message = OutgoingMessage(
threadRecipient = threadRecipient,
sentTimeMillis = messageData.timestamp,
isUrgent = true,
isSecure = true
).apply { updateMessage() }
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
return messageData.copy(messageId = messageId)
}
fun outgoingAttachment(data: ByteArray, uuid: UUID? = UUID.randomUUID()): Attachment {
val uri: Uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
val attachment: UriAttachment = UriAttachmentBuilder.build(
id = Random.nextLong(),
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = AttachmentTable.TransformProperties(),
uuid = uuid
)
return attachment
}
fun outgoingGroupChange(): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val groupRecipient = Recipient.resolved(group.recipientId)
val decryptedGroupV2Context = DecryptedGroupV2Context(
context = group.groupV2Context,
groupState = SignalDatabase.groups.getGroup(group.groupId).get().requireV2GroupProperties().decryptedGroup
)
val updateDescription = GV2UpdateDescription.Builder()
.gv2ChangeDescription(decryptedGroupV2Context)
.groupChangeUpdate(GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account.getServiceIds(), decryptedGroupV2Context))
.build()
val outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, startTime)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(messageId, true)
return messageData.copy(messageId = messageId)
}
fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = sender, 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 = nextStartTime()
val messageData = MessageData(author = sender, 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 = nextStartTime()
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
}
fun syncDeleteForMeMessage(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeMessage(deletes.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeConversation(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeConversation(deletes.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeLocalOnlyConversation(vararg conversations: RecipientId): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeLocalOnlyConversation(conversations.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair<RecipientId, Long>, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeAttachment(conversationId, message, uuid, digest, plainTextHash),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
/**
* Get the next "sentTimestamp" for current + [nextMessageOffset]th message. Useful for early message processing and future message timestamps.
*/
fun nextStartTime(nextMessageOffset: Int = 1): Long {
return startTime + 1000 * nextMessageOffset
}
data class MessageData(
val author: RecipientId = RecipientId.UNKNOWN,
val serverGuid: UUID = UUID.randomUUID(),
val timestamp: Long,
val messageId: Long = -1L
)
}
@@ -6,7 +6,6 @@ import io.mockk.mockkObject
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
import okio.ByteString import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
@@ -17,7 +16,7 @@ import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.Curve import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.AliceClient import org.thoughtcrime.securesms.testing.AliceClient
@@ -26,7 +25,7 @@ import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.FakeClientHelpers import org.thoughtcrime.securesms.testing.FakeClientHelpers
import org.thoughtcrime.securesms.testing.SignalActivityRule import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.awaitFor import org.thoughtcrime.securesms.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -56,8 +55,8 @@ class MessageProcessingPerformanceTest {
@Before @Before
fun setup() { fun setup() {
mockkStatic(UnidentifiedAccessUtil::class) mockkStatic(SealedSenderAccessUtil::class)
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator every { SealedSenderAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkObject(MessageContentProcessor) mockkObject(MessageContentProcessor)
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application) every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
@@ -65,7 +64,7 @@ class MessageProcessingPerformanceTest {
@After @After
fun after() { fun after() {
unmockkStatic(UnidentifiedAccessUtil::class) unmockkStatic(SealedSenderAccessUtil::class)
unmockkStatic(MessageContentProcessor::class) unmockkStatic(MessageContentProcessor::class)
} }
@@ -93,7 +92,7 @@ class MessageProcessingPerformanceTest {
val messageCount = 100 val messageCount = 100
val envelopes = generateInboundEnvelopes(bobClient, messageCount) val envelopes = generateInboundEnvelopes(bobClient, messageCount)
val firstTimestamp = envelopes.first().timestamp val firstTimestamp = envelopes.first().timestamp
val lastTimestamp = envelopes.last().timestamp val lastTimestamp = envelopes.last().timestamp ?: 0
// Inject the envelopes into the websocket // Inject the envelopes into the websocket
Thread { Thread {
@@ -190,7 +189,7 @@ class MessageProcessingPerformanceTest {
path = "/api/v1/message", path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(), id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"), headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.toByteArray().toByteString() body = this.encodeByteString()
) )
).encodeByteString() ).encodeByteString()
} }
@@ -0,0 +1,107 @@
/*
* 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 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.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_readSyncs {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var messageHelper: MessageHelper
@Before
fun setUp() {
messageHelper = MessageHelper(harness)
}
@After
fun tearDown() {
messageHelper.tearDown()
}
@Test
fun handleSynchronizeReadMessage() {
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadMessageMissingTimestamp() {
messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEdits() {
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.syncReadMessage(messageHelper.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(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEditsInGroup() {
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
val message2Timestamp = messageHelper.incomingMedia(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.bob to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
}
@@ -0,0 +1,703 @@
/*
* 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 org.hamcrest.Matchers.greaterThan
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.MessageContentFuzzer.DeleteForMeSync
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assert
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.util.IdentityUtil
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_synchronizeDeleteForMe {
companion object {
private val TAG = "SyncDeleteForMeTest"
}
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var messageHelper: MessageHelper
@Before
fun setUp() {
messageHelper = MessageHelper(harness)
}
@After
fun tearDown() {
messageHelper.tearDown()
}
@Test
fun singleMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.incomingText()
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun singleOutgoingMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.outgoingText().timestamp
messageHelper.incomingText()
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, harness.self.id to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun singleGroupMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 3
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
}
@Test
fun multipleGroupMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
val message3Timestamp = messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 3
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp, messageHelper.bob to message3Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun allMessagesDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 0
val threadRecord = SignalDatabase.threads.getThreadRecord(threadId)
threadRecord assertIs null
}
@Test
fun earlyMessagesDelete() {
// GIVEN
messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
// WHEN
val nextTextMessageTimestamp = messageHelper.nextStartTime(2)
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to nextTextMessageTimestamp)
)
messageHelper.incomingText()
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun multipleConversationMessagesDelete() {
// GIVEN
messageHelper.incomingText(sender = messageHelper.alice)
val aliceMessage2 = messageHelper.incomingText(sender = messageHelper.alice).timestamp
messageHelper.incomingText(sender = messageHelper.bob)
val bobMessage2 = messageHelper.incomingText(sender = messageHelper.bob).timestamp
val aliceThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
aliceMessageCount assertIs 2
val bobThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.bob)!!
var bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
bobMessageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to aliceMessage2),
DeleteForMeSync(conversationId = messageHelper.bob, messageHelper.bob to bobMessage2)
)
// THEN
aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
aliceMessageCount assertIs 1
bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
bobMessageCount assertIs 1
}
@Test
fun singleConversationDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun singleConversationNoRecentsFoundDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
val randomFutureMessages = (1..5).map {
messageHelper.alice to messageHelper.nextStartTime(it)
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, isFullDelete = true)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Unable to find most recent received at timestamp") == true }.size assertIs 1
}
@Test
fun singleConversationNoRecentsFoundNonExpiringRecentsFoundDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
val nonExpiringMessages = messages.takeLast(5).map { it.recipientId to it.timetamp }
val randomFutureMessages = (1..5).map {
messageHelper.alice to messageHelper.nextStartTime(it)
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, nonExpiringMessages, true)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Using backup non-expiring messages") == true }.size assertIs 1
}
@Test
fun localOnlyRemainingAfterConversationDeleteWithFullDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
Log.v(TAG, "Adding normal messages")
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val alice = Recipient.resolved(messageHelper.alice)
Log.v(TAG, "Adding identity message")
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
Log.v(TAG, "Adding profile message")
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
Log.v(TAG, "Adding call message")
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
// WHEN
Log.v(TAG, "Processing sync message")
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun localOnlyRemainingAfterConversationDeleteWithoutFullDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val alice = Recipient.resolved(messageHelper.alice)
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = false
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 3
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
}
@Test
fun groupConversationDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 50) {
messages += when (i % 3) {
1 -> MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp)
2 -> MessageTable.SyncMessageId(messageHelper.bob, messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp)
else -> MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(messageHelper.group.recipientId).timestamp)
}
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.group.recipientId,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun multipleConversationDelete() {
// GIVEN
val allMessages = mapOf<RecipientId, MutableList<MessageTable.SyncMessageId>>(
messageHelper.alice to mutableListOf(),
messageHelper.bob to mutableListOf()
)
allMessages.forEach { (conversation, messages) ->
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(conversation, messageHelper.incomingText(sender = conversation).timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(conversationId = conversation).timestamp)
}
}
val threadIds = allMessages.keys.map { SignalDatabase.threads.getThreadIdFor(it)!! }
threadIds.forEach { SignalDatabase.messages.getMessageCountForThread(it) assertIs 20 }
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, allMessages[messageHelper.alice]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true),
DeleteForMeSync(conversationId = messageHelper.bob, allMessages[messageHelper.bob]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true)
)
// THEN
threadIds.forEach {
SignalDatabase.messages.getMessageCountForThread(it) assertIs 0
SignalDatabase.threads.getThreadRecord(it) assertIs null
}
}
@Test
fun singleLocalOnlyConversation() {
// GIVEN
val alice = Recipient.resolved(messageHelper.alice)
// Insert placeholder message to prevent early thread update deletes
val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
// Cleanup and confirm setup
SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assert greaterThan(0)
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
// THEN
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
}
@Ignore("counts are consistent for some reason")
@Test
fun multipleLocalOnlyConversation() {
// GIVEN
val alice = Recipient.resolved(messageHelper.alice)
// Insert placeholder messages in group and alice thread to prevent early thread update deletes
val groupPlaceholderMessage = messageHelper.outgoingText(conversationId = messageHelper.group.recipientId).messageId
val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
val groupThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.group.recipientId, isGroup = true)
// Identity changes
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
IdentityUtil.markIdentityVerified(harness.context, alice, false, true)
IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
IdentityUtil.markIdentityVerified(harness.context, alice, false, false)
IdentityUtil.markIdentityUpdate(harness.context, alice.id)
// Calls
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
SignalDatabase.calls.insertOneToOneCall(2, System.currentTimeMillis(), alice.id, CallTable.Type.VIDEO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED)
SignalDatabase.calls.insertOneToOneCall(3, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED_NOTIFICATION_PROFILE)
SignalDatabase.calls.insertAcceptedGroupCall(4, messageHelper.group.recipientId, CallTable.Direction.INCOMING, System.currentTimeMillis())
SignalDatabase.calls.insertDeclinedGroupCall(5, messageHelper.group.recipientId, System.currentTimeMillis())
// Detected changes
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.messages.insertLearnedProfileNameChangeMessage(alice, null, "username.42")
SignalDatabase.messages.insertNumberChangeMessages(alice.id)
SignalDatabase.messages.insertSmsExportMessage(alice.id, SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!)
SignalDatabase.messages.insertSessionSwitchoverEvent(alice.id, aliceThreadId, SessionSwitchoverEvent())
// Sent failed
SignalDatabase.messages.markAsSending(messageHelper.outgoingText().messageId)
SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
messageHelper.outgoingText().let {
SignalDatabase.messages.markAsSending(it.messageId)
SignalDatabase.messages.markAsRateLimited(it.messageId)
}
// Group change
messageHelper.outgoingGroupChange()
// Cleanup and confirm setup
SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.messages.deleteMessage(messageId = groupPlaceholderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.rawDatabase.withinTransaction {
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 16
SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 10
}
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice, messageHelper.group.recipientId)
// THEN
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(groupThreadId) assertIs null
}
@Test
fun singleLocalOnlyConversationHasAddressable() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Thread is not local only") == true }.size assertIs 1
}
@Test
fun singleAttachmentDeletes() {
// GIVEN
val message1 = messageHelper.outgoingText { message ->
message.copy(
attachments = listOf(
messageHelper.outgoingAttachment(byteArrayOf(1, 2, 3)),
messageHelper.outgoingAttachment(byteArrayOf(2, 3, 4), null),
messageHelper.outgoingAttachment(byteArrayOf(5, 6, 7), null),
messageHelper.outgoingAttachment(byteArrayOf(10, 11, 12))
)
)
}
var attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
attachments assertIsSize 4
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
// Has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[0].attachmentId,
attachment = attachments[0].copy(digest = byteArrayOf(attachments[0].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
// Missing uuid and digest
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[1].attachmentId,
attachment = attachments[1],
uploadTimestamp = message1.timestamp + 1
)
// Missing uuid and plain text
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[2].attachmentId,
attachment = attachments[2].copy(digest = byteArrayOf(attachments[2].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
SignalDatabase.rawDatabase.update(AttachmentTable.TABLE_NAME).values(AttachmentTable.DATA_HASH_END to null).where("${AttachmentTable.ID} = ?", attachments[2].attachmentId).run()
// Different has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[3].attachmentId,
attachment = attachments[3].copy(digest = byteArrayOf(attachments[3].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
// WHEN
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[0].uuid,
attachments[0].remoteDigest,
attachments[0].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
var updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 3
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[0].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[1].uuid,
attachments[1].remoteDigest,
attachments[1].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 2
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[1].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[2].uuid,
attachments[2].remoteDigest,
attachments[2].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 1
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[2].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[3].uuid,
attachments[3].remoteDigest,
attachments[3].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
private fun DatabaseAttachment.copy(
uuid: UUID? = this.uuid,
digest: ByteArray? = this.remoteDigest
): Attachment {
return DatabaseAttachment(
attachmentId = this.attachmentId,
mmsId = this.mmsId,
hasData = this.hasData,
hasThumbnail = false,
hasArchiveThumbnail = false,
contentType = this.contentType,
transferProgress = this.transferState,
size = this.size,
fileName = this.fileName,
cdn = this.cdn,
location = this.remoteLocation,
key = this.remoteKey,
digest = digest,
incrementalDigest = this.incrementalDigest,
incrementalMacChunkSize = this.incrementalMacChunkSize,
fastPreflightId = this.fastPreflightId,
voiceNote = this.voiceNote,
borderless = this.borderless,
videoGif = this.videoGif,
width = this.width,
height = this.height,
quote = this.quote,
caption = this.caption,
stickerLocator = this.stickerLocator,
blurHash = this.blurHash,
audioHash = this.audioHash,
transformProperties = this.transformProperties,
displayOrder = this.displayOrder,
uploadTimestamp = this.uploadTimestamp,
dataHash = this.dataHash,
archiveCdn = this.archiveCdn,
archiveThumbnailCdn = this.archiveThumbnailCdn,
archiveMediaName = this.archiveMediaName,
archiveMediaId = this.archiveMediaId,
thumbnailRestoreState = this.thumbnailRestoreState,
uuid = uuid
)
}
}
@@ -1,11 +1,12 @@
package org.thoughtcrime.securesms.messages package org.thoughtcrime.securesms.messages
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata 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( data class TestMessage(
val envelope: SignalServiceProtos.Envelope, val envelope: Envelope,
val content: SignalServiceProtos.Content, val content: Content,
val metadata: EnvelopeMetadata, val metadata: EnvelopeMetadata,
val serverDeliveredTimestamp: Long val serverDeliveredTimestamp: Long
) )
@@ -5,7 +5,8 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata 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
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) { class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
companion object { companion object {
@@ -19,9 +20,9 @@ class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(
fun endTag(timestamp: Long) = "$timestamp end" fun endTag(timestamp: Long) = "$timestamp end"
} }
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) { override fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp)) Log.d(TAG, startTag(envelope.timestamp!!))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric) super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
Log.d(TAG, endTag(envelope.timestamp)) Log.d(TAG, endTag(envelope.timestamp!!))
} }
} }
@@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.migrations
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.count
import org.signal.core.util.readToSingleInt
import org.signal.donations.PaymentSourceType
import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.util.Currency
@RunWith(AndroidJUnit4::class)
class SubscriberIdMigrationJobTest {
private val testSubject = SubscriberIdMigrationJob()
@Test
fun givenNoSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectNoDatabaseEntries() {
testSubject.run()
val actual = SignalDatabase.inAppPaymentSubscribers.readableDatabase.count()
.from(InAppPaymentSubscriberTable.TABLE_NAME)
.run()
.readToSingleInt()
actual assertIs 0
}
@Test
fun givenUSDSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectASingleEntry() {
val subscriberId = SubscriberId.generate()
SignalStore.inAppPayments.setSubscriberCurrency(Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION)
SignalStore.inAppPayments.setSubscriber("USD", subscriberId)
SignalStore.inAppPayments.setSubscriptionPaymentSourceType(PaymentSourceType.PayPal)
SignalStore.inAppPayments.shouldCancelSubscriptionBeforeNextSubscribeAttempt = true
testSubject.run()
val actual = SignalDatabase.inAppPaymentSubscribers.getByCurrencyCode("USD", InAppPaymentSubscriberRecord.Type.DONATION)
actual.assertIsNotNull()
actual!!.subscriberId.bytes assertIs subscriberId.bytes
actual.paymentMethodType assertIs InAppPaymentData.PaymentMethodType.PAYPAL
actual.requiresCancel assertIs true
actual.currency assertIs Currency.getInstance("USD")
actual.type assertIs InAppPaymentSubscriberRecord.Type.DONATION
}
}
@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIsNotNull import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.success import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.util.Usernames
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -56,27 +57,10 @@ class UsernameEditFragmentTest {
InstrumentationApplicationDependencyProvider.clearHandlers() 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.") @Ignore("Flakey espresso test.")
@Test @Test
fun testUsernameCreationOutsideOfRegistration() { fun testUsernameCreationOutsideOfRegistration() {
val scenario = createScenario() val scenario = createScenario(UsernameEditMode.NORMAL)
scenario.moveToState(Lifecycle.State.RESUMED) scenario.moveToState(Lifecycle.State.RESUMED)
@@ -96,7 +80,7 @@ class UsernameEditFragmentTest {
fun testNicknameUpdateHappyPath() { fun testNicknameUpdateHappyPath() {
val nickname = "Spiderman" val nickname = "Spiderman"
val discriminator = "4578" val discriminator = "4578"
val username = "$nickname${UsernameState.DELIMITER}$discriminator" val username = "$nickname${Usernames.DELIMITER}$discriminator"
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers( InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/username/reserved") { 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) scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.username_text)).perform(typeText(nickname)) 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())) onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
} }
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> { private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle() val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
return launchFragmentInContainer( return launchFragmentInContainer(
fragmentArgs = fragmentArgs, fragmentArgs = fragmentArgs,
themeResId = R.style.Signal_DayNight_NoActionBar themeResId = R.style.Signal_DayNight_NoActionBar
@@ -90,10 +90,10 @@ class SafetyNumberBottomSheetRepositoryTest {
subjectUnderTest.removeFromStories(toRemove, listOf(destinationKey)).subscribe() subjectUnderTest.removeFromStories(toRemove, listOf(destinationKey)).subscribe()
testSubscriber.request(1) testSubscriber.request(1)
testScheduler.triggerActions() testScheduler.triggerActions()
testSubscriber.awaitCount(3) testSubscriber.awaitCount(2)
// THEN // THEN
testSubscriber.assertValueAt(2) { map -> testSubscriber.assertValueAt(1) { map ->
assertMatch( assertMatch(
map, map,
mapOf( mapOf(
@@ -116,10 +116,10 @@ class SafetyNumberBottomSheetRepositoryTest {
subjectUnderTest.removeAllFromStory(distributionListMembers, distributionList).subscribe() subjectUnderTest.removeAllFromStory(distributionListMembers, distributionList).subscribe()
testSubscriber.request(1) testSubscriber.request(1)
testScheduler.triggerActions() testScheduler.triggerActions()
testSubscriber.awaitCount(3) testSubscriber.awaitCount(2)
// THEN // THEN
testSubscriber.assertValueAt(2) { map -> testSubscriber.assertValueAt(1) { map ->
assertMatch(map, mapOf()) assertMatch(map, mapOf())
} }
} }
@@ -6,14 +6,12 @@ import org.junit.Assert.assertNotEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.update import org.signal.core.util.update
import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId 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.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord import org.whispersystems.signalservice.api.storage.SignalContactRecord
@@ -26,14 +24,13 @@ class ContactRecordProcessorTest {
@Before @Before
fun setup() { fun setup() {
SignalStore.account().setE164(E164_SELF) SignalStore.account.setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF) SignalStore.account.setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF) SignalStore.account.setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
} }
@Test @Test
fun process_splitContact_normalSplit() { fun process_splitContact_normalSplit_twoRecords() {
// GIVEN // GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A) val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A) setStorageId(originalId, STORAGE_ID_A)
@@ -69,6 +66,35 @@ class ContactRecordProcessorTest {
assertNotEquals(byAci, byE164) 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)
assertNotEquals(byAci, byE164)
}
@Test @Test
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() { fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
// GIVEN // GIVEN
@@ -113,7 +139,7 @@ class ContactRecordProcessorTest {
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) { private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME) .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) .where("${RecipientTable.ID} = ?", recipientId)
.run() .run()
} }
@@ -3,15 +3,14 @@ package org.thoughtcrime.securesms.testing
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.ECKeyPair import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress 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. * Welcome to Alice's Client.
@@ -30,17 +29,17 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
uuid = serviceId.rawUuid, uuid = serviceId.rawUuid,
e164 = e164, e164 = e164,
deviceId = 1, deviceId = 1,
identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey, identityKey = SignalStore.account.aciIdentityKey.publicKey.publicKey,
expires = 31337 expires = 31337
) )
fun process(envelope: Envelope, serverDeliveredTimestamp: Long) { fun process(envelope: Envelope, serverDeliveredTimestamp: Long) {
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val bufferedStore = BufferedProtocolStore.create() val bufferedStore = BufferedProtocolStore.create()
ApplicationDependencies.getIncomingMessageObserver() AppDependencies.incomingMessageObserver
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp) .processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() } ?.mapNotNull { it.run() }
?.forEach { ApplicationDependencies.getJobManager().add(it) } ?.forEach { it.enqueue() }
bufferedStore.flushToDisk() bufferedStore.flushToDisk()
val end = System.currentTimeMillis() val end = System.currentTimeMillis()
@@ -48,9 +47,9 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
} }
fun encrypt(now: Long, destination: Recipient): Envelope { fun encrypt(now: Long, destination: Recipient): Envelope {
return ApplicationDependencies.getSignalServiceMessageSender().getEncryptedMessage( return AppDependencies.signalServiceMessageSender.getEncryptedMessage(
SignalServiceAddress(destination.requireServiceId(), destination.requireE164()), SignalServiceAddress(destination.requireServiceId(), destination.requireE164()),
FakeClientHelpers.getTargetUnidentifiedAccess(ProfileKeyUtil.getSelfProfileKey(), ProfileKey(destination.profileKey), aliceSenderCertificate), FakeClientHelpers.getSealedSenderAccess(ProfileKey(destination.profileKey), aliceSenderCertificate),
1, 1,
FakeClientHelpers.encryptedTextMessage(now), FakeClientHelpers.encryptedTextMessage(now),
false false
@@ -17,7 +17,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.zkgroup.profiles.ProfileKey import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.database.OneTimePreKeyTable import org.thoughtcrime.securesms.database.OneTimePreKeyTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignedPreKeyTable import org.thoughtcrime.securesms.database.SignedPreKeyTable
@@ -25,17 +25,15 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.SignalSessionLock import org.whispersystems.signalservice.api.SignalSessionLock
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress 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.UUID
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import kotlin.UnsupportedOperationException
/** /**
* Welcome to Bob's Client. * Welcome to Bob's Client.
@@ -61,7 +59,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
} }
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */ /** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(now: Long): SignalServiceProtos.Envelope { fun encrypt(now: Long): Envelope {
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now) val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null) val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
@@ -72,16 +70,16 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
} }
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent) 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()) val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, SealedSenderAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp) cipher.decrypt(envelope, serverDeliveredTimestamp)
} }
private fun getAliceServiceId(): ServiceId { private fun getAliceServiceId(): ServiceId {
return SignalStore.account().requireAci() return SignalStore.account.requireAci()
} }
private fun getAlicePreKeyBundle(): PreKeyBundle { private fun getAlicePreKeyBundle(): PreKeyBundle {
@@ -104,7 +102,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
val selfSignedPreKeyRecord = SignalDatabase.signedPreKeys.get(getAliceServiceId(), selfSignedPreKeyId)!! val selfSignedPreKeyRecord = SignalDatabase.signedPreKeys.get(getAliceServiceId(), selfSignedPreKeyId)!!
return PreKeyBundle( return PreKeyBundle(
SignalStore.account().registrationId, SignalStore.account.registrationId,
1, 1,
selfPreKeyId, selfPreKeyId,
selfPreKeyRecord.keyPair.publicKey, selfPreKeyRecord.keyPair.publicKey,
@@ -116,19 +114,19 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
} }
private fun getAliceProtocolAddress(): SignalProtocolAddress { private fun getAliceProtocolAddress(): SignalProtocolAddress {
return SignalProtocolAddress(SignalStore.account().requireAci().toString(), 1) return SignalProtocolAddress(SignalStore.account.requireAci().toString(), 1)
} }
private fun getAlicePublicKey(): IdentityKey { private fun getAlicePublicKey(): IdentityKey {
return SignalStore.account().aciIdentityKey.publicKey return SignalStore.account.aciIdentityKey.publicKey
} }
private fun getAliceProfileKey(): ProfileKey { private fun getAliceProfileKey(): ProfileKey {
return ProfileKeyUtil.getSelfProfileKey() return ProfileKeyUtil.getSelfProfileKey()
} }
private fun getAliceUnidentifiedAccess(): Optional<UnidentifiedAccess> { private fun getAliceUnidentifiedAccess(): SealedSenderAccess? {
return FakeClientHelpers.getTargetUnidentifiedAccess(profileKey, getAliceProfileKey(), senderCertificate) return FakeClientHelpers.getSealedSenderAccess(getAliceProfileKey(), senderCertificate)
} }
private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore { private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore {
@@ -140,10 +138,12 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true
override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord() override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord()
override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): Boolean = false override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): Boolean = false
override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) { aliceSessionRecord = record } override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) {
aliceSessionRecord = record
}
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList() override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account.aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException() override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException() override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException() override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
@@ -166,7 +166,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = 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 loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
override fun archiveSession(address: SignalProtocolAddress?) = 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 getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException() override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException() override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
@@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.testing 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.Native
import org.signal.libsignal.internal.NativeHandleGuard import org.signal.libsignal.internal.NativeHandleGuard
import org.signal.libsignal.metadata.certificate.CertificateValidator 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.ECKeyPair
import org.signal.libsignal.protocol.ecc.ECPublicKey import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey 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.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeContent import org.whispersystems.signalservice.api.crypto.EnvelopeContent
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess 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.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.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.Optional
import java.util.UUID import java.util.UUID
@@ -44,17 +46,16 @@ object FakeClientHelpers {
} }
} }
fun getTargetUnidentifiedAccess(myProfileKey: ProfileKey, theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): Optional<UnidentifiedAccess> { fun getSealedSenderAccess(theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): SealedSenderAccess? {
val selfUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(myProfileKey) val themUnidentifiedAccessKey = UnidentifiedAccess(UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey), senderCertificate.serialized, false)
val themUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey)
return UnidentifiedAccessPair(UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate.serialized, false), UnidentifiedAccess(themUnidentifiedAccessKey, senderCertificate.serialized, false)).targetUnidentifiedAccess return SealedSenderAccess.forIndividual(themUnidentifiedAccessKey)
} }
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent { fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
val content = SignalServiceProtos.Content.newBuilder().apply { val content = Content.Builder().apply {
setDataMessage( dataMessage(
SignalServiceProtos.DataMessage.newBuilder().apply { DataMessage.Builder().buildWith {
body = message body = message
timestamp = now timestamp = now
} }
@@ -64,16 +65,16 @@ object FakeClientHelpers {
} }
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope { fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
return Envelope.newBuilder() return Envelope.Builder()
.setType(Envelope.Type.valueOf(this.type)) .type(Envelope.Type.fromValue(this.type))
.setSourceDevice(1) .sourceDevice(1)
.setTimestamp(timestamp) .timestamp(timestamp)
.setServerTimestamp(timestamp + 1) .serverTimestamp(timestamp + 1)
.setDestinationServiceId(destination.toString()) .destinationServiceId(destination.toString())
.setServerGuid(UUID.randomUUID().toString()) .serverGuid(UUID.randomUUID().toString())
.setContent(Base64.decode(this.content).toProtoByteString()) .content(Base64.decode(this.content).toByteString())
.setUrgent(true) .urgent(true)
.setStory(false) .story(false)
.build() .build()
} }
} }
@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.testing package org.thoughtcrime.securesms.testing
import okio.ByteString.Companion.toByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup import org.signal.storageservice.protos.groups.local.DecryptedGroup
@@ -9,6 +10,7 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.internal.push.GroupContextV2
import kotlin.random.Random import kotlin.random.Random
/** /**
@@ -16,22 +18,22 @@ import kotlin.random.Random
*/ */
object GroupTestingUtils { object GroupTestingUtils {
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember { fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.newBuilder() return DecryptedMember.Builder()
.setAciBytes(aci.toByteString()) .aciBytes(aci.toByteString())
.setJoinedAtRevision(revision) .joinedAtRevision(revision)
.setRole(role) .role(role)
.build() .build()
} }
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo { fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE)) val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder() val decryptedGroupState = DecryptedGroup.Builder()
.addAllMembers(members.toList()) .members(members.toList())
.setRevision(revision) .revision(revision)
.setTitle(MessageContentFuzzer.string()) .title(MessageContentFuzzer.string())
.build() .build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!! val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState, null)!!
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId) val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
SignalDatabase.recipients.setProfileSharing(groupRecipientId, true) SignalDatabase.recipients.setProfileSharing(groupRecipientId, true)
@@ -46,5 +48,8 @@ object GroupTestingUtils {
return member(aci = requireAci()) 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,7 +1,9 @@
package org.thoughtcrime.securesms.testing package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString import okio.ByteString
import org.thoughtcrime.securesms.database.model.toProtoByteString import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.messages.TestMessage import org.thoughtcrime.securesms.messages.TestMessage
@@ -9,13 +11,14 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.util.UuidUtil import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2 import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.UUID import java.util.UUID
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextInt import kotlin.random.nextInt
@@ -34,22 +37,22 @@ object MessageContentFuzzer {
/** /**
* Create an [Envelope]. * Create an [Envelope].
*/ */
fun envelope(timestamp: Long): Envelope { fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
return Envelope.newBuilder() return Envelope.Builder()
.setTimestamp(timestamp) .timestamp(timestamp)
.setServerTimestamp(timestamp + 5) .serverTimestamp(timestamp + 5)
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID())) .serverGuid(serverGuid.toString())
.build() .build()
} }
/** /**
* Create metadata to match an [Envelope]. * 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( return EnvelopeMetadata(
sourceServiceId = Recipient.resolved(source).requireServiceId(), sourceServiceId = Recipient.resolved(source).requireServiceId(),
sourceE164 = null, sourceE164 = null,
sourceDeviceId = 1, sourceDeviceId = sourceDeviceId,
sealedSender = true, sealedSender = true,
groupId = groupId?.decodedId, groupId = groupId?.decodedId,
destinationServiceId = Recipient.resolved(destination).requireServiceId() destinationServiceId = Recipient.resolved(destination).requireServiceId()
@@ -61,21 +64,24 @@ object MessageContentFuzzer {
* - An expire timer value * - An expire timer value
* - Bold style body ranges * - Bold style body ranges
*/ */
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content { fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null, allowExpireTimeChanges: Boolean = true): Content {
return Content.newBuilder() return Content.Builder()
.setDataMessage( .dataMessage(
DataMessage.newBuilder().buildWith { DataMessage.Builder().buildWith {
timestamp = sentTimestamp
body = string() body = string()
if (random.nextBoolean()) { if (allowExpireTimeChanges && random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt()) expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
} }
if (random.nextBoolean()) { if (random.nextBoolean()) {
addBodyRanges( bodyRanges(
SignalServiceProtos.BodyRange.newBuilder().buildWith { listOf(
start = 0 BodyRange.Builder().buildWith {
length = 1 start = 0
style = SignalServiceProtos.BodyRange.Style.BOLD length = 1
} style = BodyRange.Style.BOLD
}
)
) )
} }
if (groupContextV2 != null) { if (groupContextV2 != null) {
@@ -86,6 +92,20 @@ object MessageContentFuzzer {
.build() .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]. * Create a sync sent text message for the given [DataMessage].
*/ */
@@ -95,16 +115,16 @@ object MessageContentFuzzer {
recipientUpdate: Boolean = false recipientUpdate: Boolean = false
): Content { ): Content {
return Content return Content
.newBuilder() .Builder()
.setSyncMessage( .syncMessage(
SyncMessage.newBuilder().buildWith { SyncMessage.Builder().buildWith {
sent = SyncMessage.Sent.newBuilder().buildWith { sent = SyncMessage.Sent.Builder().buildWith {
timestamp = textMessage.timestamp timestamp = textMessage.timestamp
message = textMessage message = textMessage
isRecipientUpdate = recipientUpdate isRecipientUpdate = recipientUpdate
addAllUnidentifiedStatus( unidentifiedStatus(
deliveredTo.map { deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith { SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
destinationServiceId = Recipient.resolved(it).requireServiceId().toString() destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true unidentified = true
} }
@@ -115,6 +135,139 @@ object MessageContentFuzzer {
).build() ).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()
}
fun syncDeleteForMeMessage(allDeletes: List<DeleteForMeSync>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
messageDeletes = allDeletes.map { (conversationId, conversationDeletes) ->
val conversation = Recipient.resolved(conversationId)
SyncMessage.DeleteForMe.MessageDeletes(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
messages = conversationDeletes.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
}
)
}
)
)
).build()
}
fun syncDeleteForMeConversation(allDeletes: List<DeleteForMeSync>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
conversationDeletes = allDeletes.map { delete ->
val conversation = Recipient.resolved(delete.conversationId)
SyncMessage.DeleteForMe.ConversationDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
mostRecentMessages = delete.messages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
isFullDelete = delete.isFullDelete
)
}
)
)
).build()
}
fun syncDeleteForMeLocalOnlyConversation(conversations: List<RecipientId>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
localOnlyConversationDeletes = conversations.map { conversationId ->
val conversation = Recipient.resolved(conversationId)
SyncMessage.DeleteForMe.LocalOnlyConversationDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
}
)
}
)
)
).build()
}
fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair<RecipientId, Long>, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): Content {
val conversation = Recipient.resolved(conversationId)
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
attachmentDeletes = listOf(
SyncMessage.DeleteForMe.AttachmentDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
targetMessage = SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(message.first).requireAci().toString(),
sentTimestamp = message.second
),
uuid = uuid?.let { UuidUtil.toByteString(it) },
fallbackDigest = digest?.toByteString(),
fallbackPlaintextHash = plainTextHash?.let { Base64.decodeOrNull(it)?.toByteString() }
)
)
)
)
).build()
}
/** /**
* Create a random media message that may be: * Create a random media message that may be:
* - A text body * - A text body
@@ -123,9 +276,9 @@ object MessageContentFuzzer {
* - A message with 0-2 attachment pointers and may contain a text body * - A message with 0-2 attachment pointers and may contain a text body
*/ */
fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content { fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
return Content.newBuilder() return Content.Builder()
.setDataMessage( .dataMessage(
DataMessage.newBuilder().buildWith { DataMessage.Builder().buildWith {
if (random.nextBoolean()) { if (random.nextBoolean()) {
body = string() body = string()
} }
@@ -133,28 +286,28 @@ object MessageContentFuzzer {
if (random.nextBoolean() && quoteAble.isNotEmpty()) { if (random.nextBoolean() && quoteAble.isNotEmpty()) {
body = string() body = string()
val quoted = quoteAble.random(random) val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith { quote = DataMessage.Quote.Builder().buildWith {
id = quoted.envelope.timestamp id = quoted.envelope.timestamp
authorAci = quoted.metadata.sourceServiceId.toString() authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body text = quoted.content.dataMessage?.body
addAllAttachments(quoted.content.dataMessage.attachmentsList) attachments(quoted.content.dataMessage?.attachments ?: emptyList())
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList) bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
type = DataMessage.Quote.Type.NORMAL type = DataMessage.Quote.Type.NORMAL
} }
} }
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) { if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
val quoted = quoteAble.random(random) val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith { quote = DataMessage.Quote.Builder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp) id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
authorAci = quoted.metadata.sourceServiceId.toString() authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body text = quoted.content.dataMessage?.body
} }
} }
if (random.nextFloat() < 0.25) { if (random.nextFloat() < 0.25) {
val total = random.nextInt(1, 2) val total = random.nextInt(1, 2)
(0..total).forEach { _ -> addAttachments(attachmentPointer()) } attachments((0..total).map { attachmentPointer() })
} }
} }
) )
@@ -166,12 +319,12 @@ object MessageContentFuzzer {
* - A reaction to a prior message * - A reaction to a prior message
*/ */
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content { fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder() return Content.Builder()
.setDataMessage( .dataMessage(
DataMessage.newBuilder().buildWith { DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.25) { if (random.nextFloat() < 0.25) {
val reactTo = previousMessages.random(random) val reactTo = previousMessages.random(random)
reaction = DataMessage.Reaction.newBuilder().buildWith { reaction = DataMessage.Reaction.Builder().buildWith {
emoji = emojis.random(random) emoji = emojis.random(random)
remove = false remove = false
targetAuthorAci = reactTo.metadata.sourceServiceId.toString() targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
@@ -183,22 +336,21 @@ object MessageContentFuzzer {
} }
/** /**
* Create a random media message that can never contain a text body. It may be: * Create a random media message that contains a sticker.
* - A sticker
*/ */
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content { fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.newBuilder() return Content.Builder()
.setDataMessage( .dataMessage(
DataMessage.newBuilder().buildWith { DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.9) { timestamp = sentTimestamp
sticker = DataMessage.Sticker.newBuilder().buildWith { sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24) packId = byteString(length = 24)
packKey = byteString(length = 128) packKey = byteString(length = 128)
stickerId = random.nextInt() stickerId = random.nextInt()
data = attachmentPointer() data_ = attachmentPointer()
emoji = emojis.random(random) emoji = emojis.random(random)
}
} }
groupV2 = groupContextV2
} }
).build() ).build()
} }
@@ -223,14 +375,14 @@ object MessageContentFuzzer {
* Generate a random [ByteString]. * Generate a random [ByteString].
*/ */
fun byteString(length: Int = 512): ByteString { fun byteString(length: Int = 512): ByteString {
return random.nextBytes(length).toProtoByteString() return random.nextBytes(length).toByteString()
} }
/** /**
* Generate a random [AttachmentPointer]. * Generate a random [AttachmentPointer].
*/ */
fun attachmentPointer(): AttachmentPointer { fun attachmentPointer(): AttachmentPointer {
return AttachmentPointer.newBuilder().run { return AttachmentPointer.Builder().run {
cdnKey = string() cdnKey = string()
contentType = mediaTypes.random(random) contentType = mediaTypes.random(random)
key = byteString() key = byteString()
@@ -244,7 +396,7 @@ object MessageContentFuzzer {
caption = string(allowNullString = true) caption = string(allowNullString = true)
blurHash = string() blurHash = string()
uploadTimestamp = random.nextLong() uploadTimestamp = random.nextLong()
cdnNumber = 1 cdnNumber = 2
build() build()
} }
@@ -256,4 +408,14 @@ object MessageContentFuzzer {
fun fuzzServerDeliveredTimestamp(envelopeTimestamp: Long): Long { fun fuzzServerDeliveredTimestamp(envelopeTimestamp: Long): Long {
return envelopeTimestamp + 10 return envelopeTimestamp + 10
} }
data class DeleteForMeSync(
val conversationId: RecipientId,
val messages: List<Pair<RecipientId, Long>>,
val nonExpiringMessages: List<Pair<RecipientId, Long>> = emptyList(),
val isFullDelete: Boolean = true,
val attachments: List<Pair<Long, AttachmentTable.SyncAttachmentId>> = emptyList()
) {
constructor(conversationId: RecipientId, vararg messages: Pair<RecipientId, Long>) : this(conversationId, messages.toList())
}
} }
@@ -1,22 +1,12 @@
package org.thoughtcrime.securesms.testing package org.thoughtcrime.securesms.testing
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.protocol.util.Medium import org.signal.libsignal.protocol.util.Medium
import org.signal.libsignal.svr2.PinHash
import org.thoughtcrime.securesms.crypto.PreKeyUtil import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SvrPinData
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
@@ -41,7 +31,7 @@ object MockProvider {
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply { val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
svr1Credentials = AuthCredentials.create("username", "password") svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null svr2Credentials = AuthCredentials.create("username", "password")
} }
val primaryOnlyDeviceList = DeviceInfoList().apply { val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -78,19 +68,7 @@ object MockProvider {
} }
} }
fun mockGetRegistrationLockStringFlow() { fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account.aciIdentityKey, deviceId: Int): PreKeyResponse {
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): SvrPinData = SvrPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(anyOrNull(), anyOrNull()) } doReturn session
}
}
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey) val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair()) val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())
@@ -55,5 +55,5 @@ inline fun <reified T> RecordedRequest.parsedRequestBody(): T {
} }
private fun defaultRequestPredicate(verb: String, path: String, predicate: RequestPredicate = { true }): RequestPredicate = { request -> private fun defaultRequestPredicate(verb: String, path: String, predicate: RequestPredicate = { true }): RequestPredicate = { request ->
request.method == verb && request.path.startsWith("/$path") && predicate(request) request.method == verb && request.path?.startsWith("/$path") == true && predicate(request)
} }
@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.IdentityTable import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
@@ -47,7 +47,7 @@ import java.util.UUID
*/ */
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() { class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication() val application: Application = AppDependencies.application
lateinit var context: Context lateinit var context: Context
private set private set
@@ -90,8 +90,8 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0) val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit() preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary() SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary() SignalStore.account.generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application) val registrationRepository = RegistrationRepository(application)
@@ -111,19 +111,19 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null, masterKey = null,
pin = null, pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys), aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys) pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.pniPreKeys)
), ),
false false
).blockingGet() ).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.svr().optOut() SignalStore.svr.optOut()
RegistrationUtil.maybeMarkRegistrationComplete() RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
SignalStore.settings().isMessageNotificationsEnabled = false SignalStore.settings.isMessageNotificationsEnabled = false
return Recipient.self() return Recipient.self()
} }
@@ -141,11 +141,11 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i))) val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i")) SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) 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, false))
SignalDatabase.recipients.setProfileSharing(recipientId, true) SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci) SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair() val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey) AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
others += recipientId others += recipientId
othersKeys += otherIdentity othersKeys += otherIdentity
} }
@@ -158,14 +158,14 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
} }
fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) { fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) {
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey) AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
} }
fun getIdentity(recipient: Recipient): IdentityKey { fun getIdentity(recipient: Recipient): IdentityKey {
return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0)) return AppDependencies.protocolStore.aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
} }
fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) { fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) {
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED) AppDependencies.protocolStore.aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
} }
} }
@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.testing
import org.junit.rules.TestWatcher import org.junit.rules.TestWatcher
import org.junit.runner.Description import org.junit.runner.Description
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI import org.whispersystems.signalservice.api.push.ServiceId.PNI
@@ -24,8 +26,8 @@ class SignalDatabaseRule(
override fun starting(description: Description?) { override fun starting(description: Description?) {
deleteAllThreads() deleteAllThreads()
SignalStore.account().setAci(localAci) SignalStore.account.setAci(localAci)
SignalStore.account().setPni(localPni) SignalStore.account.setPni(localPni)
} }
override fun finished(description: Description?) { override fun finished(description: Description?) {
@@ -34,7 +36,8 @@ class SignalDatabaseRule(
private fun deleteAllThreads() { private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) { if (deleteAllThreadsOnEachRun) {
SignalDatabase.threads.clearForTests() 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.ACI
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(ACI.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()
.setAuthorAci(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,13 +1,14 @@
package org.thoughtcrime.securesms.testing package org.thoughtcrime.securesms.testing
import android.database.Cursor import android.database.Cursor
import android.util.Base64 import org.hamcrest.Matcher
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue import org.hamcrest.Matchers.nullValue
import org.signal.core.util.Hex
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.signal.core.util.readToList import org.signal.core.util.readToList
import org.signal.core.util.select import org.signal.core.util.select
@@ -56,33 +57,41 @@ infix fun <E, T : Collection<E>> T.assertIsSize(expected: Int) {
assertThat(this, hasSize(expected)) assertThat(this, hasSize(expected))
} }
infix fun <T : Any> T.assert(matcher: Matcher<T>) {
assertThat(this, matcher)
}
fun CountDownLatch.awaitFor(duration: Duration) { fun CountDownLatch.awaitFor(duration: Duration) {
if (!await(duration.inWholeMilliseconds, TimeUnit.MILLISECONDS)) { if (!await(duration.inWholeMilliseconds, TimeUnit.MILLISECONDS)) {
throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms") throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms")
} }
} }
fun dumpTableToLogs(tag: String = "TestUtils", table: String) { fun dumpTableToLogs(tag: String = "TestUtils", table: String, columns: Set<String>? = null) {
dumpTable(table).forEach { Log.d(tag, it.toString()) } dumpTable(table, columns).forEach { Log.d(tag, it.toString()) }
} }
fun dumpTable(table: String): List<List<Pair<String, String?>>> { fun dumpTable(table: String, columns: Set<String>?): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase return SignalDatabase.rawDatabase
.select() .select()
.from(table) .from(table)
.run() .run()
.readToList { cursor -> .readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column -> val map: List<Pair<String, String?>> = cursor.columnNames.mapNotNull { column ->
val index = cursor.getColumnIndex(column) if (columns == null || columns.contains(column)) {
var data: String? = when (cursor.getType(index)) { val index = cursor.getColumnIndex(column)
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0) var data: String? = when (cursor.getType(index)) {
else -> cursor.getString(index) Cursor.FIELD_TYPE_BLOB -> Hex.toStringCondensed(cursor.getBlob(index))
} else -> cursor.getString(index)
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) { }
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index)) if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
} data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data column to data
} else {
null
}
} }
map map
} }
@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.util;
/**
* A class that allows us to inject feature flags during tests.
*/
public final class FeatureFlagsAccessor {
public static void forceValue(String key, Object value) {
FeatureFlags.FORCED_VALUES.put(FeatureFlags.PHONE_NUMBER_PRIVACY, true);
}
}
@@ -38,10 +38,8 @@ object MessageTableTestUtils {
isKeyExchangeType:${type and MessageTypes.KEY_EXCHANGE_BIT != 0L} isKeyExchangeType:${type and MessageTypes.KEY_EXCHANGE_BIT != 0L}
isIdentityVerified:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT != 0L} isIdentityVerified:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT != 0L}
isIdentityDefault:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_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} isInvalidVersionKeyExchange:${type and MessageTypes.KEY_EXCHANGE_INVALID_VERSION_BIT != 0L}
isBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_BUNDLE_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} isIdentityUpdate:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_UPDATE_BIT != 0L}
isRateLimited:${type and MessageTypes.MESSAGE_RATE_LIMITED_BIT != 0L} isRateLimited:${type and MessageTypes.MESSAGE_RATE_LIMITED_BIT != 0L}
isExpirationTimerUpdate:${type and MessageTypes.EXPIRATION_TIMER_UPDATE_BIT != 0L} isExpirationTimerUpdate:${type and MessageTypes.EXPIRATION_TIMER_UPDATE_BIT != 0L}
@@ -2,9 +2,9 @@ package org.signal.benchmark
import android.content.Context import android.content.Context
import org.thoughtcrime.securesms.BuildConfig import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.push.AccountManagerFactory import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.util.FeatureFlags import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.SignalServiceAccountManager import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.account.PreKeyUpload import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ServiceId.ACI import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -16,15 +16,15 @@ import java.util.Optional
class DummyAccountManagerFactory : AccountManagerFactory() { class DummyAccountManagerFactory : AccountManagerFactory() {
override fun createAuthenticated(context: Context, aci: ACI, pni: PNI, number: String, deviceId: Int, password: String): SignalServiceAccountManager { override fun createAuthenticated(context: Context, aci: ACI, pni: PNI, number: String, deviceId: Int, password: String): SignalServiceAccountManager {
return DummyAccountManager( return DummyAccountManager(
ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(number), AppDependencies.signalServiceNetworkAccess.getConfiguration(number),
aci, aci,
pni, pni,
number, number,
deviceId, deviceId,
password, password,
BuildConfig.SIGNAL_AGENT, BuildConfig.SIGNAL_AGENT,
FeatureFlags.okHttpAutomaticRetry(), RemoteConfig.okHttpAutomaticRetry,
FeatureFlags.groupLimits().hardLimit RemoteConfig.groupLimits.hardLimit
) )
} }
@@ -1,14 +1,15 @@
package org.signal.benchmark.setup package org.signal.benchmark.setup
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TestDbUtils 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.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
@@ -65,7 +66,8 @@ object TestMessages {
return insert return insert
} }
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) { fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
body = body, body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(), sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -73,10 +75,11 @@ object TestMessages {
receivedTimeMillis = timestamp ?: System.currentTimeMillis() 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?) { fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
body = body, body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(), sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -90,28 +93,30 @@ object TestMessages {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map { val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
imageAttachment() imageAttachment()
} }
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(), sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(), serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(), receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments)) 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 { fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
val message = IncomingMediaMessage( val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id, from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(), sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(), serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(), receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List<SignalServiceAttachment>)) 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) val id = insertIncomingMessage(recipient = recipient, message = message)
if (failed) { if (failed) {
setMessageMediaFailed(id) setMessageMediaFailed(id)
@@ -122,8 +127,8 @@ object TestMessages {
return id return id
} }
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long { private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
} }
private fun setMessageMediaFailed(messageId: Long) { private fun setMessageMediaFailed(messageId: Long) {
@@ -139,7 +144,7 @@ object TestMessages {
} }
private fun imageAttachment(): SignalServiceAttachmentPointer { private fun imageAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer( return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER, Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""), SignalServiceAttachmentRemoteId.from(""),
"image/webp", "image/webp",
null, null,
@@ -149,19 +154,21 @@ object TestMessages {
1024, 1024,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
0,
Optional.of("/not-there.jpg"), Optional.of("/not-there.jpg"),
false, false,
false, false,
false, false,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
System.currentTimeMillis() System.currentTimeMillis(),
null
) )
} }
private fun voiceAttachment(): SignalServiceAttachmentPointer { private fun voiceAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer( return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER, Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""), SignalServiceAttachmentRemoteId.from(""),
"audio/aac", "audio/aac",
null, null,
@@ -171,13 +178,15 @@ object TestMessages {
1024, 1024,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
0,
Optional.of("/not-there.aac"), Optional.of("/not-there.aac"),
true, true,
false, false,
false, false,
Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(),
System.currentTimeMillis() System.currentTimeMillis(),
null
) )
} }
@@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.profiles.ProfileName import org.thoughtcrime.securesms.profiles.ProfileName
@@ -35,7 +35,7 @@ object TestUsers {
private var generatedOthers: Int = 0 private var generatedOthers: Int = 0
fun setupSelf(): Recipient { fun setupSelf(): Recipient {
val application: Application = ApplicationDependencies.getApplication() val application: Application = AppDependencies.application
DeviceTransferBlockingInterceptor.getInstance().blockNetwork() DeviceTransferBlockingInterceptor.getInstance().blockNetwork()
PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit() PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit()
@@ -44,8 +44,8 @@ object TestUsers {
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0) val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit() preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary() SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary() SignalStore.account.generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application) val registrationRepository = RegistrationRepository(application)
val registrationData = RegistrationData( val registrationData = RegistrationData(
@@ -63,8 +63,8 @@ object TestUsers {
VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null, masterKey = null,
pin = null, pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys), aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys) pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.pniPreKeys)
) )
AccountManagerFactory.setInstance(DummyAccountManagerFactory()) AccountManagerFactory.setInstance(DummyAccountManagerFactory())
@@ -77,7 +77,7 @@ object TestUsers {
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.svr().optOut() SignalStore.svr.optOut()
RegistrationUtil.maybeMarkRegistrationComplete() RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson")) SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -100,11 +100,11 @@ object TestUsers {
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i))) val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i")) SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew()) 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, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true) SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci) SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair() val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey) AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
others += recipientId others += recipientId
} }
@@ -10,9 +10,9 @@ import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.database.MessageTypes import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryType import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
@@ -78,7 +78,7 @@ class ConversationElementGenerator {
val isIncoming = random.nextBoolean() val isIncoming = random.nextBoolean()
val record = MediaMmsMessageRecord( val record = MmsMessageRecord(
messageId, messageId,
if (isIncoming) Recipient.UNKNOWN else Recipient.self(), if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
0, 0,
@@ -86,7 +86,7 @@ class ConversationElementGenerator {
now, now,
now, now,
now, now,
1, true,
1, 1,
testMessage, testMessage,
SlideDeck(), SlideDeck(),
@@ -97,7 +97,7 @@ class ConversationElementGenerator {
0, 0,
0, 0,
false, false,
1, true,
null, null,
emptyList(), emptyList(),
emptyList(), emptyList(),
@@ -106,7 +106,7 @@ class ConversationElementGenerator {
false, false,
false, false,
now, now,
1, true,
now, now,
null, null,
StoryType.NONE, StoryType.NONE,
@@ -117,11 +117,13 @@ class ConversationElementGenerator {
-1, -1,
null, null,
null, null,
0 0,
false,
null
) )
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData( val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(), AppDependencies.application,
record, record,
Recipient.UNKNOWN Recipient.UNKNOWN
) )
@@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.navigation.navGraphViewModels import androidx.navigation.navGraphViewModels
import com.bumptech.glide.Glide
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.concurrent.LifecycleDisposable
@@ -33,6 +34,7 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2 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.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -41,7 +43,6 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator import org.thoughtcrime.securesms.stickers.StickerLocator
@@ -61,11 +62,13 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = ConversationAdapterV2( val adapter = ConversationAdapterV2(
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
glideRequests = GlideApp.with(this), requestManager = Glide.with(this),
clickListener = ClickListener(), clickListener = ClickListener(),
hasWallpaper = springboardViewModel.hasWallpaper.value, hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(), colorizer = Colorizer(),
startExpirationTimeout = {} startExpirationTimeout = {},
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) },
displayDialogFragment = {}
) )
if (springboardViewModel.hasWallpaper.value) { if (springboardViewModel.hasWallpaper.value) {
@@ -228,6 +231,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
} }
override fun onChangeProfileNameUpdateContact(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onCallToAction(action: String) { override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
} }
@@ -277,7 +284,7 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
} }
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) { override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
} }
@@ -296,5 +303,25 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) { override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
} }
override fun onItemDoubleClick(item: MultiselectPart) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onPaymentTombstoneClicked() {
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()
}
} }
} }
@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application <application
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:replace="android:usesCleartextTraffic" tools:replace="android:usesCleartextTraffic"
+91 -124
View File
@@ -22,17 +22,10 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/> <uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/> <uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" /> <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_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" /> <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
@@ -50,16 +43,10 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/> <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 --> <!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <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.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -76,17 +63,14 @@
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/> <uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.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.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" /> <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.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" /> <uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
@@ -160,12 +144,6 @@
android:value=".MainActivity" /> android:value=".MainActivity" />
</activity> </activity>
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DeviceProvisioningActivity" <activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true"> android:exported="true">
@@ -185,10 +163,6 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
<activity android:name=".sharing.interstitial.ShareInterstitialActivity" <activity android:name=".sharing.interstitial.ShareInterstitialActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
@@ -252,6 +226,7 @@
<activity-alias android:name=".RoutingActivity" <activity-alias android:name=".RoutingActivity"
android:targetActivity=".MainActivity" android:targetActivity=".MainActivity"
android:resizeableActivity="true"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@@ -593,6 +568,13 @@
android:host="signal.group"/> android:host="signal.group"/>
</intent-filter> </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"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -630,6 +612,7 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity" android:parentActivityName=".MainActivity"
android:resizeableActivity="true"
android:exported="false"> android:exported="false">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
@@ -678,13 +661,18 @@
<activity android:name=".PassphrasePromptActivity" <activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme" 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"/> android:exported="false"/>
<activity android:name=".NewConversationActivity" <activity android:name=".NewConversationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible" 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:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/> android:exported="false"/>
@@ -716,6 +704,7 @@
android:theme="@style/TextSecure.DarkNoActionBar" android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing" android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/> android:exported="false"/>
@@ -801,13 +790,6 @@
android:windowSoftInputMode="stateAlwaysHidden" android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/> 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"
android:exported="false"/>
<activity <activity
android:name=".wallpaper.ChatWallpaperActivity" android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -834,7 +816,14 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/> android:exported="false"/>
<activity android:name=".registration.RegistrationNavigationActivity" <activity android:name=".registration.ui.RegistrationActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".restore.RestoreActivity"
android:launchMode="singleTask" android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden" android:windowSoftInputMode="stateHidden"
@@ -930,19 +919,25 @@
</activity> </activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare" <activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:exported="true" android:exported="true"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE" android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen" android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask" android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter> <intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" /> <data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
</intent-filter> </intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.videocall" />
</intent-filter>
</activity> </activity>
<activity android:name=".mediasend.AvatarSelectionActivity" <activity android:name=".mediasend.AvatarSelectionActivity"
@@ -961,17 +956,21 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/> android:exported="false"/>
<activity android:name=".profiles.edit.EditProfileActivity" <activity android:name=".profiles.edit.CreateProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme" android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/> android:exported="false"/>
<activity android:name=".profiles.username.AddAUsernameActivity" <activity android:name=".registration.ui.restore.RemoteRestoreActivity"
android:theme="@style/Signal.DayNight.NoActionBar" android:theme="@style/Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".profiles.manage.EditProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize" android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/> android:exported="false"/>
<activity android:name=".profiles.manage.ManageProfileActivity" <activity android:name=".nicknames.NicknameActivity"
android:theme="@style/TextSecure.LightTheme" android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize" android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/> android:exported="false"/>
@@ -997,7 +996,7 @@
android:exported="false"/> android:exported="false"/>
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity" <activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert" android:theme="@style/TextSecure.DialogActivity"
android:exported="false"/> android:exported="false"/>
<activity android:name=".contactshare.ContactShareEditActivity" <activity android:name=".contactshare.ContactShareEditActivity"
@@ -1029,6 +1028,7 @@
<activity android:name=".MainActivity" <activity android:name=".MainActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:resizeableActivity="true"
android:exported="false"/> android:exported="false"/>
<activity android:name=".pin.PinRestoreActivity" <activity android:name=".pin.PinRestoreActivity"
@@ -1062,13 +1062,6 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:exported="false"/> android:exported="false"/>
<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:exported="false"/>
<activity android:name=".ratelimit.RecaptchaProofActivity" <activity android:name=".ratelimit.RecaptchaProofActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
@@ -1085,28 +1078,25 @@
android:theme="@style/Theme.Signal.WallpaperCropper" android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/> android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.QrImageSelectionActivity"
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" <activity android:name=".reactions.edit.EditReactionsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/> android:exported="false"/>
<activity android:name=".exporter.flow.SmsExportActivity" <activity android:name=".components.settings.app.subscription.donate.CheckoutFlowActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:screenOrientation="portrait" android:exported="false"/>
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:exported="false"/>
<service
android:enabled="true"
android:name=".exporter.SignalSmsExportService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service <service
android:enabled="true" android:enabled="true"
@@ -1154,19 +1144,6 @@
</intent-filter> </intent-filter>
</receiver> </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"> <service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.accounts.AccountAuthenticator" /> <action android:name="android.accounts.AccountAuthenticator" />
@@ -1186,6 +1163,14 @@
android:name=".service.GenericForegroundService" android:name=".service.GenericForegroundService"
android:exported="false"/> android:exported="false"/>
<service
android:name=".service.AttachmentProgressService"
android:exported="false"/>
<service
android:name=".service.BackupProgressService"
android:exported="false"/>
<service <service
android:name=".gcm.FcmFetchBackgroundService" android:name=".gcm.FcmFetchBackgroundService"
android:exported="false"/> android:exported="false"/>
@@ -1200,39 +1185,6 @@
</intent-filter> </intent-filter>
</service> </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" <receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false">
@@ -1292,11 +1244,6 @@
android:exported="false" android:exported="false"
android:grantUriPermissions="true" /> 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" <provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
@@ -1344,6 +1291,12 @@
</intent-filter> </intent-filter>
</receiver> </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="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false"> <receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
@@ -1408,6 +1361,20 @@
</intent-filter> </intent-filter>
</receiver> </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"/> <uses-library android:name="org.apache.http.legacy" android:required="false"/>
</application> </application>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 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: 126 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

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: 148 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 111 KiB

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