Compare commits

...

2071 Commits

Author SHA1 Message Date
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
Clark Chen
29c4d9f4d6 Bump version to 6.33.2 2023-09-18 14:09:36 -04:00
Clark Chen
c7de3d299a Update translations and other static files. 2023-09-18 13:57:13 -04:00
Greyson Parrelli
8bad476315 Fix backoff interval scheduling for jobs. 2023-09-18 12:06:42 -04:00
Clark Chen
bc8eb44a53 Bump version to 6.33.1 2023-09-15 15:15:43 -04:00
Clark Chen
f98e22cb76 Update translations and other static files. 2023-09-15 15:03:38 -04:00
Cody Henthorne
5b6326e462 Fix thread body ellipsizing in Conversation List. 2023-09-15 14:20:22 -04:00
Greyson Parrelli
342f249fab Fix possible crash during JobDatabase upgrade.
This seems to be a SQLite/SQLCipher caching issue.

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

Closes #13021
2023-06-28 17:13:15 -04:00
Clark
7786956b11 Fix conversation date header weirdness with edited messages. 2023-06-28 17:13:15 -04:00
Cody Henthorne
c17d62aeab Update ktlint and gradle plugin. 2023-06-28 17:13:15 -04:00
Cody Henthorne
65255121de Add media keyboard support in CFv2. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
b042945fef Add check for internal users around group lock ordering. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
9d979217fa Include 'dont keep activities' setting in debuglog. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
c177de2ec3 Add CDSI and SVR2 to static IP list. 2023-06-26 15:09:39 -04:00
Greyson Parrelli
b4fd57d900 Fix possible NPEs with megaphone container. 2023-06-26 15:09:36 -04:00
Nicholas Tinsley
92339dfdcf Add one more logging statement to the change number flow. 2023-06-26 15:09:36 -04:00
Nicholas
8ae115028e Update PIN switch keyboard button to be more straightforward.
Addresses #12866.
2023-06-26 15:09:36 -04:00
Greyson Parrelli
2dd0221680 Fix issue where FTS recovery path didn't work during migrations.
There was a recursive getDatabase() call because it was happening during
a migration. Solution was to re-use the DB instance we already had.
2023-06-26 15:09:36 -04:00
Greyson Parrelli
1a1923c6c0 Fix docker build by using our own mirror.
snapshot.debian.org is super flaky and unreliable. At this point it's
easier to host our own mirror snapshot.
2023-06-26 15:09:36 -04:00
Cody Henthorne
5801ad4bdb Update FCM to 23.1.2 2023-06-26 15:09:36 -04:00
Cody Henthorne
388f2971e9 Allow libsignal-service to build with JDK17. 2023-06-26 15:09:36 -04:00
Greyson Parrelli
14c3a36ec0 Improve logging for GV2 validation error. 2023-06-26 15:09:36 -04:00
Alex Hart
1ad338ce31 Turn DelveryStatusView into a custom AppCompatImageView. 2023-06-26 15:09:36 -04:00
Greyson Parrelli
71981e8a27 Fix dockerfile for reproducible builds. 2023-06-26 15:09:36 -04:00
Nicholas
5cb10cd054 Improve voice note Bluetooth state handling. 2023-06-26 15:09:36 -04:00
Rashad Sookram
ecf576e9b9 Don't localize audio output in logs. 2023-06-26 15:09:36 -04:00
Alex Hart
09d17659b9 Add media send support to CFV2. 2023-06-26 15:09:36 -04:00
Cody Henthorne
53673be5cb Update AGP to 8.0
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-06-26 15:09:36 -04:00
Alex Hart
ed4a1d6ddd Use delete instead of delete for me in call log dialog. 2023-06-26 15:09:36 -04:00
Alex Hart
e3044b8b85 Add preview treatment for call links. 2023-06-26 15:09:36 -04:00
Alex Hart
4ce05a064c Add CFV2 Sticker Suggestions. 2023-06-26 15:09:36 -04:00
Alex Hart
2fbcc23451 Add LinkPreview support to CFV2. 2023-06-26 15:09:36 -04:00
Alex Hart
3bdffed8c9 Add call link scrubbing for logs. 2023-06-26 15:09:36 -04:00
Nicholas
6cc8e87d46 Bump version to 6.24.4 2023-06-26 14:33:40 -04:00
Nicholas
7d8f549d97 Updated baseline profile. 2023-06-26 14:31:28 -04:00
Nicholas
fb2ef265bd Updated language translations. 2023-06-26 14:16:45 -04:00
Nicholas
96e8256781 Fix TestUser constructor. 2023-06-26 14:10:13 -04:00
Cody Henthorne
dd40517f12 Fix crash after hanging up call. 2023-06-26 14:10:13 -04:00
Cody Henthorne
6c95b766d6 Fix answer audio call and video starting bug. 2023-06-26 14:10:13 -04:00
Greyson Parrelli
feffdcb71e Bump version to 6.24.3 2023-06-22 18:53:58 -04:00
Greyson Parrelli
f33e2c49ca Updated language translations. 2023-06-22 18:53:29 -04:00
Clark
1037acd4a2 Load original message and use it for timestamps in Conversation. 2023-06-22 18:42:56 -04:00
Clark
68042fc755 No longer call startForeground in onCreate. 2023-06-22 14:09:23 -04:00
Alex Hart
1bdc77affe Resolve issue with incoming video call state. 2023-06-22 14:52:10 -03:00
Nicholas Tinsley
88f50da4fb Discard voice note recording on error. 2023-06-22 13:32:27 -04:00
Cody Henthorne
bc1fbd9b6c Fix spoilers not animating after leaving and returning to conversation.
Fixes #13015
2023-06-22 11:57:59 -04:00
Cody Henthorne
c1b7b7c95e Fix styling lost in draft state bug. 2023-06-22 11:16:27 -04:00
Greyson Parrelli
b0688eed5c Bump version to 6.24.2 2023-06-21 20:34:12 -04:00
Greyson Parrelli
e06ed03d33 Updated language translations. 2023-06-21 20:34:12 -04:00
Clark
e2a79394ab Always background legacy FCM fallback. 2023-06-21 20:34:12 -04:00
Clark
5db770ca44 Use original message instead of edit message when checking if we can edit. 2023-06-21 17:52:33 -04:00
Cody Henthorne
df8aaa2005 Provide better 'why' when an attachment is not auto-downloaded. 2023-06-21 16:18:16 -04:00
Cody Henthorne
882748f080 Allow formatting text from overflow menu. 2023-06-21 16:16:16 -04:00
Cody Henthorne
15035f4eb3 Fix rendering when editing a message with spoilers. 2023-06-21 13:08:00 -04:00
Cody Henthorne
1d0a87f52a Add ability to clear or toggle formatting. 2023-06-21 13:05:46 -04:00
Clark
59b2cc5f79 Fix edit message date header showing wrong date. 2023-06-21 12:44:22 -04:00
Nicholas Tinsley
d6758fc264 Store PIN keyboard type in file backup. 2023-06-21 11:53:01 -04:00
Greyson Parrelli
36418bec59 Ensure SqlCipherDeletingErrorHandler runs delete. 2023-06-21 10:24:12 -04:00
Cody Henthorne
07bd8b2fa3 Fix NPE in spoiler renderer. 2023-06-21 10:10:01 -04:00
Greyson Parrelli
d989d02af9 Bump version to 6.24.1 2023-06-20 14:26:35 -04:00
Greyson Parrelli
503ce13122 Updated language translations. 2023-06-20 14:24:16 -04:00
Alex Hart
8f77321adb Dispose notification disposable when stopping service. 2023-06-20 12:07:11 -03:00
Clark
60cdcea791 Revert "Call startForeground onCreate for generic foreground service. " 2023-06-20 10:53:28 -04:00
Clark
86cd4c5c30 Fix remote delete for edit messages. 2023-06-20 10:29:47 -04:00
Nicholas Tinsley
62d5f61a0b Allow verification with PIN rather than SMS when restoring from backup. 2023-06-16 16:31:46 -04:00
Clark
25860867bb Use correct receive timestamp for edit message handling. 2023-06-16 16:30:49 -04:00
Greyson Parrelli
272860f071 Do not animate spoilers if system animations are disabled. 2023-06-16 15:51:24 -04:00
Nicholas
767cfbc717 Fix atomic registrations when not using session ID. 2023-06-16 15:38:16 -04:00
Cody Henthorne
55af6ca84e Bump version to 6.24.0 2023-06-15 15:44:35 -04:00
Cody Henthorne
88933ae051 Updated baseline profile. 2023-06-15 15:36:09 -04:00
Cody Henthorne
4781beebee Updated language translations. 2023-06-15 15:36:09 -04:00
Clark Chen
0049c74323 Fix baseline profile generation benchmark. 2023-06-15 15:36:09 -04:00
Alex Hart
361727cec6 Add resend handler to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
1b95177e0e Add handleViewPaymentDetails to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
8d34c54de2 Add multiselect action support to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
2fa0eba3db Add search call-through in CFV2 override. 2023-06-15 15:36:09 -04:00
Cody Henthorne
6cc41e95c6 Remove edit message receive feature flag. 2023-06-15 15:36:09 -04:00
Cody Henthorne
b1523f5b91 Update text formatting feature flag. 2023-06-15 15:36:09 -04:00
Nicholas
d16002546d Create account in single network request. 2023-06-15 15:36:09 -04:00
Clark
186a93f5d1 Use separate PNI key distribution endpoint instead of change number. 2023-06-15 15:36:09 -04:00
Alex Hart
3d4875bcfe Add sticker suggestion send support to CFV2. 2023-06-15 15:36:09 -04:00
Nicholas
441e30971a Add more logging around Bubble eligibility.
To help diagnose #12036.
2023-06-15 15:36:08 -04:00
Alex Hart
ff115c2349 Add voice recording to CFV2. 2023-06-15 15:36:08 -04:00
Clark
f23e5bdb44 Prepare edit message for beta run. 2023-06-15 15:36:08 -04:00
Jim Gustafson
d0a232d86a Update to RingRTC v2.28.1 2023-06-15 15:36:08 -04:00
Alex Hart
9fef8386e6 Fix initial call state when starting from action. 2023-06-15 15:36:08 -04:00
Alex Hart
b1680ba5c6 Add new call notification strings. 2023-06-15 15:36:08 -04:00
Alex Hart
6cd59daf0a Fix several issues with call notifications. 2023-06-15 15:36:08 -04:00
Greyson Parrelli
38f2b39ac4 Add common interface over SVR implementations. 2023-06-15 15:36:08 -04:00
Alex Hart
51222738df Remove avatar color from CallLink table. 2023-06-15 15:36:08 -04:00
Clark
b9835584d8 Add sharing for PNP usernames badge. 2023-06-15 13:32:00 -04:00
Alex Hart
cff01021c2 Ensure call links only ever have one call event associated with them. 2023-06-15 13:32:00 -04:00
Alex Hart
d19b8a125c Do not enable ringing for call links. 2023-06-15 13:32:00 -04:00
Alex Hart
4caaa0033b Re-enable call delete sync events. 2023-06-15 13:32:00 -04:00
Alex Hart
f3a0a059ea Add search and arbitrary jump support to CFV2. 2023-06-15 13:32:00 -04:00
Alex Hart
290b0fe46f Ensure owned call links are revoked on delete. 2023-06-15 13:32:00 -04:00
g1a55er
03a212eee4 Reschedule job if background web socket message retrieval fails.
Closes #12971
2023-06-15 13:31:59 -04:00
Alex Hart
f3b629bc06 Add reaction support to CFV2. 2023-06-15 13:31:59 -04:00
Alex Hart
b9e002f7b1 Update call links parsing. 2023-06-15 13:31:59 -04:00
Alex Hart
c90779beea Fix jump position for quotes. 2023-06-15 13:31:59 -04:00
Alex Hart
bc8c8a049f Add onViewsRevealed implementation. 2023-06-15 13:31:59 -04:00
Alex Hart
1d9dc66265 Update several androidx dependencies.
Navigation to 1.6.0
Fragment to 1.6.0
Compose BOM to 2023.05.01
Lifecycle to 2.6.1
Activity to 1.7.2
2023-06-15 13:31:59 -04:00
Alex Hart
886c149c3f Add in-call info sheet for call links. 2023-06-15 13:31:59 -04:00
Clark
369ca189d3 Disable stickers and gifs when editing message. 2023-06-15 13:31:59 -04:00
Cody Henthorne
02c4bbe816 Add date headers to CFv2. 2023-06-15 13:31:59 -04:00
Clark
523c9f6576 Add workflow for monitoring APK size changes. 2023-06-15 13:31:59 -04:00
Cody Henthorne
c259430b09 Bump version to 6.23.5 2023-06-15 13:04:42 -04:00
Cody Henthorne
fc94b90a03 Revert "Adopt new APIs for network connectivity check."
This reverts commit de4c6ab7b7.
2023-06-15 12:57:35 -04:00
Cody Henthorne
6af521130d Revert "Fix connectivity over VPN on older API versions."
This reverts commit 7e24252447.
2023-06-15 12:57:14 -04:00
Cody Henthorne
9c001e4f35 Bump version to 6.23.4 2023-06-15 11:30:05 -04:00
Cody Henthorne
9e8dee36a6 Updated baseline profile. 2023-06-15 11:25:25 -04:00
Cody Henthorne
26b17d8a3c Updated language translations. 2023-06-15 11:23:11 -04:00
Cody Henthorne
1c5e2e3359 Fix linkify for valid URLs with ... in the path. 2023-06-15 11:20:22 -04:00
Clark
0437d37f23 Fix pending retry receipt cache deadlock. 2023-06-15 11:20:22 -04:00
Cody Henthorne
ec6b1a44de Add text formatting support to release notes channel. 2023-06-15 11:20:22 -04:00
Clark
08c661bb14 Fix foreground service start/stop race. 2023-06-14 14:42:08 -04:00
Cody Henthorne
62f62d89c5 Bump version to 6.23.3 2023-06-14 12:40:51 -04:00
Cody Henthorne
37dd8b40b2 Updated baseline profile. 2023-06-14 12:28:43 -04:00
Cody Henthorne
15825f6c3f Updated language translations. 2023-06-14 12:26:09 -04:00
Clark
ad196bf03c Fix stopForegroundTask crash. 2023-06-14 10:54:21 -04:00
Cody Henthorne
332c4ca26e Improve spoiler performance by reducing number of particles and frame rate. 2023-06-14 10:36:43 -04:00
Cody Henthorne
305edf1928 Fix SQL crash in backup restore by preventing job from running until restore complete. 2023-06-14 10:28:34 -04:00
Nicholas Tinsley
9c0c25ef99 Detect URL patterns that will crash OkHttp.
Addresses #12998.
2023-06-13 15:48:24 -04:00
Cody Henthorne
458dae227f Bump version to 6.23.2 2023-06-13 11:10:20 -04:00
Cody Henthorne
b1ba9fd54f Updated baseline profile. 2023-06-13 10:55:53 -04:00
Cody Henthorne
dd42b5b851 Updated language translations. 2023-06-13 10:51:44 -04:00
Alex Hart
d8e3edc729 Fix incoming call intent. 2023-06-13 10:47:40 -04:00
Cody Henthorne
36aa8623da Improve spoiler performance by stopping animation when backgrounded. 2023-06-12 13:11:15 -04:00
Cody Henthorne
7e24252447 Fix connectivity over VPN on older API versions. 2023-06-12 12:39:18 -04:00
Cody Henthorne
164ce06177 Fix style growing when applying other styles. 2023-06-12 09:01:21 -04:00
Cody Henthorne
c9d298c447 Bump version to 6.23.1 2023-06-09 16:28:20 -04:00
Cody Henthorne
5129613ce8 Updated baseline profile. 2023-06-09 16:22:01 -04:00
Cody Henthorne
b985ace7ed Updated language translations. 2023-06-09 16:19:06 -04:00
Cody Henthorne
d28afac973 Fix baseline benchmark. 2023-06-09 16:15:00 -04:00
Clark
fd9b5ff7c4 Drop failed processed incoming messages. 2023-06-09 15:48:01 -04:00
Cody Henthorne
75580bea27 Update SQLCipher to 4.5.4-S2 2023-06-09 15:40:12 -04:00
Cody Henthorne
db7056c53b Fix bug when processing duplicate text messages within the same batch. 2023-06-09 15:39:13 -04:00
Cody Henthorne
b55181ffe6 Fix localization issue with group call start strings. 2023-06-09 12:30:30 -04:00
Nicholas Tinsley
81149e5aa8 Hide call audio devices that are not of known type. 2023-06-09 10:18:47 -04:00
Cody Henthorne
3a341eee19 Fix revealing spoilers in text stories. 2023-06-09 10:06:35 -04:00
Cody Henthorne
e19c7efbfe Bump version to 6.23.0 2023-06-07 16:01:13 -04:00
Cody Henthorne
7e7c68321b Updated baseline profile. 2023-06-07 15:17:03 -04:00
Cody Henthorne
9fa3f54c7c Updated language translations. 2023-06-07 15:14:47 -04:00
Cody Henthorne
3ff273f1f2 Ignore tests that fail when run on JDK17. 2023-06-07 15:11:26 -04:00
Cody Henthorne
e607b1962c Cycle text formatting send remote config. 2023-06-07 14:54:52 -04:00
Nicholas
2c4c6bf87c Allow registration with landlines. 2023-06-07 14:46:01 -04:00
Clark
bf048e2a75 Dont block main thread when we try to stop FCM fetch service. 2023-06-07 14:12:35 -04:00
Alex Hart
b0c4bb04e7 Hide clear history action menu item when total count is zero. 2023-06-07 14:25:54 -03:00
Cody Henthorne
7e0e6c2786 Fix pool limits and y-translation issues with CFv2 recycler view. 2023-06-07 12:51:08 -04:00
Alex Hart
d6a03df087 Fix db contention when inserting group ring updates. 2023-06-07 13:28:37 -03:00
Nicholas Tinsley
cfc89d2a74 Gracefully handle invalid audio device selection during calls. 2023-06-07 11:24:22 -04:00
Clark
c491c9dc8c Fix chosen location not being sent sometimes. 2023-06-06 12:47:16 -04:00
Cody Henthorne
eae066b3a2 Fix styling issues when covering mentions. 2023-06-06 12:47:16 -04:00
Clark
71aa17bad6 Verify number of backup frames written is read back. 2023-06-06 12:47:16 -04:00
Alex Hart
93df01e266 CallLink treatment for ConversationItem. 2023-06-06 12:47:16 -04:00
Alex Hart
8f96abb41e Update TAG usage throughout call link processors. 2023-06-06 12:47:16 -04:00
Nicholas Tinsley
1457a6fe16 Deduplicate audio output choices. 2023-06-06 12:47:16 -04:00
Alex Hart
290c107698 Implement simple avatar color picking algorithm to align with iOS. 2023-06-06 12:47:16 -04:00
Alex Hart
bf7aaddbf9 Hook in Call Links integration via factory. 2023-06-06 12:47:16 -04:00
Clark
59435e49c8 Call startForeground onCreate for generic foreground service. 2023-06-06 12:47:16 -04:00
Clark
c3499e538e Fix wallpaper scaling on orientation change. 2023-06-06 12:47:16 -04:00
Cody Henthorne
1d41b1c5a3 Remove a couple old dependencies. 2023-06-06 12:47:16 -04:00
Cody Henthorne
e303570b2f Update to libsignal 0.26.0 2023-06-06 12:47:16 -04:00
Alex Hart
62940893f0 Add peek and join capabilities to call links implementation. 2023-06-06 12:47:16 -04:00
Alex Hart
f8434bede5 Add clear all menu action to calls tab. 2023-06-06 12:47:16 -04:00
Jim Gustafson
c08e108fc3 Update to RingRTC v2.28.0
Co-authored-by: Jordan Rose <jrose@signal.org>
2023-06-06 12:47:16 -04:00
Alex Hart
cd9a160cae Fix pip offset. 2023-06-06 12:47:16 -04:00
Cody Henthorne
bba8b8be56 Fix external share when it contains an image and text. 2023-06-06 12:47:16 -04:00
Bernie Dolan
f56b5d58c6 Update MobileCoin payments and enclave values. 2023-06-06 12:47:16 -04:00
Cody Henthorne
ac4b0ed606 Improve auto-leave group behavior. 2023-06-06 12:47:16 -04:00
Clark Chen
d3e71185e6 Fix story ring not updating on recipient screen. 2023-06-06 12:47:16 -04:00
Alex Hart
b4f2cd9ff4 Add updated phone svg icon. 2023-06-06 12:47:16 -04:00
Alex Hart
fd8d305899 Add voice note draft playback impl to cfv2. 2023-06-06 12:47:16 -04:00
Alex Hart
bde7ae944a Add draft handling in toggle button update method. 2023-06-06 12:47:16 -04:00
Alex Hart
99f83e5dc9 Add handleDial implementation in CFV2. 2023-06-06 12:47:16 -04:00
Cody Henthorne
693aef5c04 Add partial share and draft support to CFv2. 2023-06-06 12:47:16 -04:00
Alex Hart
b9ae537706 Add onItemClick handling in CFV2. 2023-06-06 12:47:16 -04:00
Alex Hart
e41dd6d39d CFV2 Add edit message support. 2023-06-06 12:47:16 -04:00
Alex Hart
5d546f46e4 CFV2 Add reply to message support. 2023-06-06 12:47:16 -04:00
Clark
2bef5653b4 Lower priority for apk update notification. 2023-06-06 12:47:16 -04:00
Clark
63d8549865 Light refactor to thread update. 2023-06-06 12:47:16 -04:00
Clark Chen
f6bac2f476 Fix trash can not appearing when editing group photo. 2023-06-06 12:47:16 -04:00
Alex Hart
0dd51856d3 CFV2 - Implement add to home screen. 2023-06-06 12:47:16 -04:00
Alex Hart
be01f2b511 CFV2 -- Add to Contacts / Mute Conversation. 2023-06-06 12:47:16 -04:00
Alex Hart
045d2cf42f Copy over several more action handlers for CFV2. 2023-06-06 12:47:16 -04:00
Cody Henthorne
64ddd982fe Add review banner to CFv2. 2023-06-06 12:47:16 -04:00
Alex Hart
b785b3f887 CFV2 Save to Disk / Copy Text Content. 2023-06-06 12:47:16 -04:00
Alex Hart
399421e20e CFV2 Implement delete, forward, view once handling. 2023-06-06 12:47:16 -04:00
Cody Henthorne
a656d65d1d Move render split to match CFv2 for fairer comparisons. 2023-06-06 12:47:16 -04:00
Nicholas Tinsley
291a5d57c4 Replace em dash in javadoc with ASCII-safe hyphen. 2023-06-06 12:47:16 -04:00
Nicholas Tinsley
a9a91e3162 Address a bunch of compiler warnings.
None of these should change any behavior, they're all annotations and stuff.
2023-06-06 12:47:16 -04:00
g1a55er
de4c6ab7b7 Adopt new APIs for network connectivity check.
Addresses #12941
2023-06-06 12:47:16 -04:00
Nicholas Tinsley
7ea9fc0c3b Update AlertDialogs to MaterialAlertDialogs.
Addresses #12949.
2023-06-06 12:47:16 -04:00
Greyson Parrelli
1965d5879f Log message procesing speed at 2 decimal places. 2023-06-06 12:47:16 -04:00
Greyson Parrelli
b2b907a86a Add additional validation for group messages. 2023-06-06 12:47:15 -04:00
Cody Henthorne
e565de0724 Add verified updates and unverified banner. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
7318e676f7 Add an internal feature to search your contacts by ID/ACI/PNI. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
7c28d8ad51 Fix possible NPE when opening a story. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
3e21fb77c7 Skip sends to users with prekey failures. 2023-06-06 12:47:15 -04:00
Cody Henthorne
6b91e525db Add Reminders and Conversation Banner to CFv2. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
0aca03a919 Add kyber support for change number. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
e2ef8e2ef9 Add support for kyber prekeys. 2023-06-06 12:47:15 -04:00
Greyson Parrelli
15c248184f Add two-phase commit support for SVR2. 2023-06-06 12:47:15 -04:00
4135 changed files with 250785 additions and 159586 deletions

View File

@@ -1,5 +1,9 @@
root = true
[*.kt]
[*.{kt,kts}]
indent_size = 2
twitter_compose_allowed_composition_locals=LocalExtendedColors
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false
ktlint_code_style = intellij_idea
twitter_compose_allowed_composition_locals=LocalExtendedColors
ktlint_standard_class-naming = disabled

View File

@@ -2,7 +2,7 @@
### First time contributor checklist
<!-- 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 signed the [Contributor License Agreement](https://whispersystems.org/cla/)
- [ ] I have signed the [Contributor License Agreement](https://signal.org/cla/)
### Contributor checklist
<!-- replace the empty checkboxes [ ] below with checked ones [x] accordingly -->

View File

@@ -18,19 +18,21 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Build with Gradle
run: ./gradlew qa --parallel
run: ./gradlew qa
- name: Archive reports for failed build
if: ${{ failure() }}

84
.github/workflows/diffuse.yml vendored Normal file
View File

@@ -0,0 +1,84 @@
name: APK Diff
on:
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
pull-requests: write # to comment on PR
jobs:
assemble-base:
if: ${{ github.repository != 'signalapp/Signal-Android' }}
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
- name: Cache base apk
id: cache-base
uses: actions/cache@v3
with:
path: diffuse-base.apk
key: diffuse-${{ github.event.pull_request.base.sha }}
- name: Build with Gradle
if: steps.cache-base.outputs.cache-hit != 'true'
run: ./gradlew assemblePlayProdRelease
- name: Copy base apk
if: steps.cache-base.outputs.cache-hit != 'true'
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-base.apk
- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'
- name: Build with Gradle
run: ./gradlew assemblePlayProdRelease
- name: Copy PR apk
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
- id: diffuse
uses: usefulness/diffuse-action@v1
with:
old-file-path: diffuse-base.apk
new-file-path: diffuse-new.apk
- uses: peter-evans/find-comment@v2
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: Diffuse output
- uses: peter-evans/create-or-update-comment@v3
with:
body: |
Diffuse output:
${{ steps.diffuse.outputs.diff-gh-comment }}
edit-mode: replace
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/upload-artifact@v3
with:
name: diffuse-output
path: ${{ steps.diffuse.outputs.diff-file }}

3
.gitignore vendored
View File

@@ -3,6 +3,7 @@ captures/
project.properties
keystore.debug.properties
keystore.staging.properties
nightly-url.txt
.project
.settings
bin/
@@ -28,4 +29,4 @@ jni/libspeex/.deps/
pkcs11.password
dev.keystore
maps.key
local/
local/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "libwebp"]
path = libwebp
url = https://github.com/webmproject/libwebp.git

View File

@@ -212,5 +212,12 @@
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -15,6 +15,12 @@ Truths which we believe to be self-evident:
1. **There is no such thing as time.** Protocol ideas that require synchronized clocks are doomed to failure.
## Building
1. You'll need to get the `libwebp` submodule after checking out the repository with `git submodule init && git submodule update`
1. Most things are pretty straightforward, and opening the project in Android Studio should get you most of the way there.
1. Depending on your configuration, you'll also likely need to install additional SDK Tool components, namely the versions of NDK and CMake we are currently using in our [Docker](https://github.com/signalapp/Signal-Android/blob/main/reproducible-builds/Dockerfile#L30) configuration.
## Issues
### Useful bug reports

View File

@@ -1,4 +1,4 @@
# Signal Android
# Signal Android
Signal is a simple, powerful, and secure messenger.
@@ -54,7 +54,7 @@ The form and manner of this distribution makes it eligible for export under the
## License
Copyright 2013-2023 Signal
Copyright 2013-2024 Signal Messenger, LLC
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html

2
apntool/.gitignore vendored
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

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()

View File

@@ -1,3 +0,0 @@
argparse>=1.2.1
lxml>=3.3.3
progressbar-latest>=2.4

View File

@@ -1,702 +0,0 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.protobuf'
id 'androidx.navigation.safeargs'
id 'org.jlleitschuh.gradle.ktlint'
id 'org.jetbrains.kotlin.android'
id 'app.cash.exhaustive'
id 'kotlin-parcelize'
id 'com.squareup.wire'
id 'android-constants'
id 'translations'
}
apply from: 'static-ips.gradle'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.18.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir 'src/main/protowire'
}
}
ktlint {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.47.1"
}
def canonicalVersionCode = 1272
def canonicalVersionName = "6.22.5"
def postFixSize = 100
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
def keystores = [ 'debug' : loadKeystoreProperties('keystore.debug.properties') ]
def selectableVariants = [
'nightlyProdSpinner',
'nightlyProdPerf',
'nightlyProdRelease',
'nightlyStagingRelease',
'nightlyPnpPerf',
'nightlyPnpRelease',
'playProdDebug',
'playProdSpinner',
'playProdCanary',
'playProdPerf',
'playProdBenchmark',
'playProdInstrumentation',
'playProdRelease',
'playStagingDebug',
'playStagingCanary',
'playStagingSpinner',
'playStagingPerf',
'playStagingInstrumentation',
'playPnpDebug',
'playPnpSpinner',
'playStagingRelease',
'websiteProdSpinner',
'websiteProdRelease',
]
android {
namespace 'org.thoughtcrime.securesms'
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions 'distribution', 'environment'
useLibrary 'org.apache.http.legacy'
testBuildType 'instrumentation'
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = ["-Xallow-result-return-type"]
}
signingConfigs {
if (keystores.debug != null) {
debug {
storeFile file("${project.rootDir}/${keystores.debug.storeFile}")
storePassword keystores.debug.storePassword
keyAlias keystores.debug.keyAlias
keyPassword keystores.debug.keyPassword
}
}
}
testOptions {
execution 'ANDROIDX_TEST_ORCHESTRATOR'
unitTests {
includeAndroidResources = true
}
managedDevices {
devices {
pixel3api30 (ManagedVirtualDevice) {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
test {
java.srcDirs += "$projectDir/src/testShared"
}
androidTest {
java.srcDirs += "$projectDir/src/testShared"
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility signalJavaVersion
targetCompatibility signalJavaVersion
}
packagingOptions {
resources {
excludes += ['LICENSE.txt', 'LICENSE', 'NOTICE', 'asm-license.txt', 'META-INF/LICENSE', 'META-INF/LICENSE.md', 'META-INF/NOTICE', 'META-INF/LICENSE-notice.md', 'META-INF/proguard/androidx-annotations.pro', 'libsignal_jni.dylib', 'signal_jni.dll']
}
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.2'
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
minSdkVersion signalMinSdkVersion
targetSdkVersion signalTargetSdkVersion
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders = [mapsKey:"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"]
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "SIGNAL_URL", "\"https://chat.signal.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}"
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String[]", "SIGNAL_SERVICE_IPS", service_ips
buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips
buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips
buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_ips
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"dc9fd472a5a9c871a3c7f76f1af60aa9c1f314abf2e8d1e0c4ba25c8aaa2848c\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
buildConfigField "String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\""
buildConfigField "String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
buildConfigField "String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\""
buildConfigField "boolean", "TRACING_ENABLED", "false"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resConfigs autoResConfig()
splits {
abi {
enable !project.hasProperty('generateBaselineProfile')
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
testInstrumentationRunner "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
}
buildTypes {
debug {
if (keystores['debug'] != null) {
signingConfig signingConfigs.debug
}
isDefault true
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard/proguard-firebase-messaging.pro',
'proguard/proguard-google-play-services.pro',
'proguard/proguard-jackson.pro',
'proguard/proguard-sqlite.pro',
'proguard/proguard-appcompat-v7.pro',
'proguard/proguard-square-okhttp.pro',
'proguard/proguard-square-okio.pro',
'proguard/proguard-rounded-image-view.pro',
'proguard/proguard-glide.pro',
'proguard/proguard-shortcutbadger.pro',
'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro',
'proguard/proguard-mobilecoin.pro',
'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro',
'proguard/proguard.cfg'
testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg'
manifestPlaceholders = [mapsKey:getMapsKey()]
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Debug\""
}
instrumentation {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
applicationIdSuffix ".instrumentation"
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Instrumentation\""
}
spinner {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Spinner\""
}
release {
minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Release\""
}
perf {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Perf\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
benchmark {
initWith debug
isDefault false
debuggable false
minifyEnabled true
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Benchmark\""
buildConfigField "boolean", "TRACING_ENABLED", "true"
}
canary {
initWith debug
isDefault false
minifyEnabled false
matchingFallbacks = ['debug']
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"Canary\""
}
}
productFlavors {
play {
dimension 'distribution'
isDefault true
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"play\""
}
website {
dimension 'distribution'
ext.websiteUpdateUrl = "https://updates.signal.org/android"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"website\""
}
nightly {
dimension 'distribution'
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\""
}
prod {
dimension 'environment'
isDefault true
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\""
}
staging {
dimension 'environment'
applicationIdSuffix ".staging"
buildConfigField "String", "SIGNAL_URL", "\"https://chat.staging.signal.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\""
buildConfigField "String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\""
buildConfigField "String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\""
buildConfigField "String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\""
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\""
buildConfigField "String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\""
}
pnp {
dimension 'environment'
initWith staging
applicationIdSuffix ".pnp"
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\""
}
}
lint {
abortOnError true
baseline file('lint-baseline.xml')
checkReleaseBuilds false
disable 'LintError'
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
if (output.baseName.contains('nightly')) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
def tag = getCurrentGitTag()
if (tag != null && tag.length() > 0) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
android.variantFilter { variant ->
def distribution = variant.getFlavors().get(0).name
def environment = variant.getFlavors().get(1).name
def buildType = variant.buildType.name
def fullName = distribution + environment.capitalize() + buildType.capitalize()
if (!selectableVariants.contains(fullName)) {
variant.setIgnore(true)
}
}
}
dependencies {
implementation libs.androidx.fragment.ktx
lintChecks project(':lintchecks')
coreLibraryDesugaring libs.android.tools.desugar
implementation (libs.androidx.appcompat) {
version {
strictly '1.6.1'
}
}
implementation libs.androidx.window.window
implementation libs.androidx.window.java
implementation libs.androidx.recyclerview
implementation libs.material.material
implementation libs.androidx.legacy.support
implementation libs.androidx.preference
implementation libs.androidx.legacy.preference
implementation libs.androidx.gridlayout
implementation libs.androidx.exifinterface
implementation libs.androidx.compose.rxjava3
implementation libs.androidx.constraintlayout
implementation libs.androidx.multidex
implementation libs.androidx.navigation.fragment.ktx
implementation libs.androidx.navigation.ui.ktx
implementation libs.androidx.lifecycle.viewmodel.ktx
implementation libs.androidx.lifecycle.livedata.ktx
implementation libs.androidx.lifecycle.process
implementation libs.androidx.lifecycle.viewmodel.savedstate
implementation libs.androidx.lifecycle.common.java8
implementation libs.androidx.lifecycle.reactivestreams.ktx
implementation libs.androidx.camera.core
implementation libs.androidx.camera.camera2
implementation libs.androidx.camera.lifecycle
implementation libs.androidx.camera.view
implementation libs.androidx.concurrent.futures
implementation libs.androidx.autofill
implementation libs.androidx.biometric
implementation libs.androidx.sharetarget
implementation libs.androidx.profileinstaller
implementation libs.androidx.asynclayoutinflater
implementation libs.androidx.asynclayoutinflater.appcompat
implementation (libs.firebase.messaging) {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation libs.google.play.services.maps
implementation libs.google.play.services.auth
implementation libs.bundles.exoplayer
implementation libs.conscrypt.android
implementation libs.signal.aesgcmprovider
implementation project(':libsignal-service')
implementation project(':paging')
implementation project(':core-util')
implementation project(':glide-config')
implementation project(':video')
implementation project(':device-transfer')
implementation project(':image-editor')
implementation project(':donations')
implementation project(':contacts')
implementation project(':qr')
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation libs.libsignal.android
implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) {
exclude group: 'com.google.protobuf'
}
implementation libs.signal.ringrtc
implementation libs.leolin.shortcutbadger
implementation libs.emilsjolander.stickylistheaders
implementation libs.jpardogo.materialtabstrip
implementation libs.apache.httpclient.android
implementation libs.glide.glide
implementation libs.roundedimageview
implementation libs.materialish.progress
implementation libs.greenrobot.eventbus
implementation libs.waitingdots
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.google.flexbox
implementation (libs.subsampling.scale.image.view) {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation (libs.android.tooltips) {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation (libs.android.smsmms) {
exclude group: 'com.squareup.okhttp', module: 'okhttp'
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
}
implementation libs.stream
implementation libs.lottie
implementation libs.signal.android.database.sqlcipher
implementation libs.androidx.sqlite
implementation (libs.google.ez.vcard) {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.freemarker'
}
implementation libs.dnsjava
implementation libs.kotlinx.collections.immutable
implementation libs.accompanist.permissions
spinnerImplementation project(":spinner")
canaryImplementation libs.square.leakcanary
testImplementation testLibs.junit.junit
testImplementation testLibs.assertj.core
testImplementation testLibs.mockito.core
testImplementation testLibs.mockito.kotlin
testImplementation testLibs.androidx.test.core
testImplementation (testLibs.robolectric.robolectric) {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
testImplementation testLibs.robolectric.shadows.multidex
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) {
force = true
}
testImplementation testLibs.hamcrest.hamcrest
testImplementation testLibs.mockk
testImplementation(testFixtures(project(":libsignal-service")))
androidTestImplementation testLibs.androidx.test.ext.junit
androidTestImplementation testLibs.espresso.core
androidTestImplementation testLibs.androidx.test.core
androidTestImplementation testLibs.androidx.test.core.ktx
androidTestImplementation testLibs.androidx.test.ext.junit.ktx
androidTestImplementation testLibs.mockito.android
androidTestImplementation testLibs.mockito.kotlin
androidTestImplementation testLibs.mockk.android
androidTestImplementation testLibs.square.okhttp.mockserver
instrumentationImplementation (libs.androidx.fragment.testing) {
exclude group: 'androidx.test', module: 'core'
}
testImplementation testLibs.espresso.core
implementation libs.kotlin.stdlib.jdk8
implementation libs.kotlin.reflect
implementation libs.jackson.module.kotlin
implementation libs.rxjava3.rxandroid
implementation libs.rxjava3.rxkotlin
implementation libs.rxdogtag
androidTestUtil testLibs.androidx.test.orchestrator
implementation project(':core-ui')
ktlintRuleset libs.ktlint.twitter.compose
}
def getLastCommitTimestamp() {
if (!(new File('.git').exists())) {
return System.currentTimeMillis().toString()
}
new ByteArrayOutputStream().withStream { os ->
exec {
executable = 'git'
args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os
}
return os.toString() + "000"
}
}
def getGitHash() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-parse', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
def getCurrentGitTag() {
if (!(new File('.git').exists())) {
throw new IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'tag', '--points-at', 'HEAD'
standardOutput = stdout
}
def output = stdout.toString().trim()
if (output != null && output.size() > 0) {
def tags = output.split('\n').toList()
return tags.stream().filter(t -> t.contains('nightly')).findFirst().orElse(tags.get(0))
} else {
return null
}
}
tasks.withType(Test) {
testLogging {
events "failed"
exceptionFormat "full"
showCauses true
showExceptions true
showStackTraces true
}
}
def loadKeystoreProperties(filename) {
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
if (keystorePropertiesFile.exists()) {
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
return keystoreProperties
} else {
return null
}
}
static def getDateSuffix() {
def date = new Date()
def formattedDate = date.format('yyyy-MM-dd-HH:mm')
return formattedDate
}
def getMapsKey() {
def mapKey = file("${project.rootDir}/maps.key")
if (mapKey.exists()) {
return mapKey.readLines()[0]
}
return "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}

720
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,720 @@
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 = 1413
val canonicalVersionName = "7.5.2"
val postFixSize = 100
val abiPostFix: Map<String, Int> = mapOf(
"universal" to 0,
"armeabi-v7a" to 1,
"arm64-v8a" to 2,
"x86" to 3,
"x86_64" to 4
)
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
val selectableVariants = listOf(
"nightlyProdSpinner",
"nightlyProdPerf",
"nightlyProdRelease",
"nightlyStagingRelease",
"playProdDebug",
"playProdSpinner",
"playProdCanary",
"playProdPerf",
"playProdBenchmark",
"playProdInstrumentation",
"playProdRelease",
"playStagingDebug",
"playStagingCanary",
"playStagingSpinner",
"playStagingPerf",
"playStagingInstrumentation",
"playStagingRelease",
"websiteProdSpinner",
"websiteProdRelease"
)
val signalBuildToolsVersion: String by rootProject.extra
val signalCompileSdkVersion: String by rootProject.extra
val signalTargetSdkVersion: Int by rootProject.extra
val signalMinSdkVersion: Int by rootProject.extra
val signalJavaVersion: JavaVersion by rootProject.extra
val signalKotlinJvmTarget: String by rootProject.extra
wire {
kotlin {
javaInterop = true
}
sourcePath {
srcDir("src/main/protowire")
}
protoPath {
srcDir("${project.rootDir}/libsignal-service/src/main/protowire")
}
}
ktlint {
version.set("0.49.1")
}
android {
namespace = "org.thoughtcrime.securesms"
buildToolsVersion = signalBuildToolsVersion
compileSdkVersion = signalCompileSdkVersion
flavorDimensions += listOf("distribution", "environment")
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
keystores["debug"]?.let { properties ->
signingConfigs.getByName("debug").apply {
storeFile = file("${project.rootDir}/${properties.getProperty("storeFile")}")
storePassword = properties.getProperty("storePassword")
keyAlias = properties.getProperty("keyAlias")
keyPassword = properties.getProperty("keyPassword")
}
}
testOptions {
execution = "ANDROIDX_TEST_ORCHESTRATOR"
unitTests {
isIncludeAndroidResources = true
}
managedDevices {
devices {
create<ManagedVirtualDevice>("pixel3api30") {
device = "Pixel 3"
apiLevel = 30
systemImageSource = "google-atd"
require64Bit = false
}
}
}
}
sourceSets {
getByName("test") {
java.srcDir("$projectDir/src/testShared")
}
getByName("androidTest") {
java.srcDir("$projectDir/src/testShared")
}
}
compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = signalJavaVersion
targetCompatibility = signalJavaVersion
}
packagingOptions {
resources {
excludes += setOf("LICENSE.txt", "LICENSE", "NOTICE", "asm-license.txt", "META-INF/LICENSE", "META-INF/LICENSE.md", "META-INF/NOTICE", "META-INF/LICENSE-notice.md", "META-INF/proguard/androidx-annotations.pro", "libsignal_jni.dylib", "signal_jni.dll")
}
}
buildFeatures {
viewBinding = true
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
}
defaultConfig {
versionCode = canonicalVersionCode * postFixSize
versionName = canonicalVersionName
minSdk = signalMinSdkVersion
targetSdk = signalTargetSdkVersion
multiDexEnabled = true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal")
manifestPlaceholders["mapsKey"] = "AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
buildConfigField("long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L")
buildConfigField("String", "GIT_HASH", "\"${getGitHash()}\"")
buildConfigField("String", "SIGNAL_URL", "\"https://chat.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\"")
buildConfigField("String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\"")
buildConfigField("String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\"")
buildConfigField("String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\"")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}")
buildConfigField("String[]", "SIGNAL_SFU_INTERNAL_URLS", "new String[]{\"https://sfu.test.voip.signal.org\", \"https://sfu.staging.voip.signal.org\", \"https://sfu.staging.test.voip.signal.org\"}")
buildConfigField("String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\"")
buildConfigField("int", "CONTENT_PROXY_PORT", "443")
buildConfigField("String[]", "SIGNAL_SERVICE_IPS", rootProject.extra["service_ips"] as String)
buildConfigField("String[]", "SIGNAL_STORAGE_IPS", rootProject.extra["storage_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN_IPS", rootProject.extra["cdn_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN2_IPS", rootProject.extra["cdn2_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDN3_IPS", rootProject.extra["cdn3_ips"] as String)
buildConfigField("String[]", "SIGNAL_SFU_IPS", rootProject.extra["sfu_ips"] as String)
buildConfigField("String[]", "SIGNAL_CONTENT_PROXY_IPS", rootProject.extra["content_proxy_ips"] as String)
buildConfigField("String[]", "SIGNAL_CDSI_IPS", rootProject.extra["cdsi_ips"] as String)
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
buildConfigField("String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97\"")
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
buildConfigField("int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode")
buildConfigField("String", "DEFAULT_CURRENCIES", "\"EUR,AUD,GBP,CAD,CNY\"")
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"unset\"")
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
buildConfigField("boolean", "TRACING_ENABLED", "false")
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "false")
ndk {
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
}
resourceConfigurations += listOf()
splits {
abi {
isEnable = !project.hasProperty("generateBaselineProfile")
reset()
include("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
isUniversalApk = true
}
}
testInstrumentationRunner = "org.thoughtcrime.securesms.testing.SignalTestRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
}
buildTypes {
getByName("debug") {
if (keystores["debug"] != null) {
signingConfig = signingConfigs["debug"]
}
isDefault = true
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android.txt"),
"proguard/proguard-firebase-messaging.pro",
"proguard/proguard-google-play-services.pro",
"proguard/proguard-jackson.pro",
"proguard/proguard-sqlite.pro",
"proguard/proguard-appcompat-v7.pro",
"proguard/proguard-square-okhttp.pro",
"proguard/proguard-square-okio.pro",
"proguard/proguard-rounded-image-view.pro",
"proguard/proguard-glide.pro",
"proguard/proguard-shortcutbadger.pro",
"proguard/proguard-retrofit.pro",
"proguard/proguard-webrtc.pro",
"proguard/proguard-klinker.pro",
"proguard/proguard-mobilecoin.pro",
"proguard/proguard-retrolambda.pro",
"proguard/proguard-okhttp.pro",
"proguard/proguard-ez-vcard.pro",
"proguard/proguard.cfg"
)
testProguardFiles(
"proguard/proguard-automation.pro",
"proguard/proguard.cfg"
)
manifestPlaceholders["mapsKey"] = getMapsKey()
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Debug\"")
}
getByName("release") {
isMinifyEnabled = true
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
}
create("instrumentation") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
applicationIdSuffix = ".instrumentation"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Instrumentation\"")
}
create("spinner") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Spinner\"")
}
create("perf") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Perf\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("benchmark") {
initWith(getByName("debug"))
isDefault = false
isDebuggable = false
isMinifyEnabled = true
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Benchmark\"")
buildConfigField("boolean", "TRACING_ENABLED", "true")
}
create("canary") {
initWith(getByName("debug"))
isDefault = false
isMinifyEnabled = false
matchingFallbacks += "debug"
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Canary\"")
}
}
productFlavors {
create("play") {
dimension = "distribution"
isDefault = true
buildConfigField("boolean", "MANAGES_APP_UPDATES", "false")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "null")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"play\"")
}
create("website") {
dimension = "distribution"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"website\"")
}
create("nightly") {
val apkUpdateManifestUrl = if (file("${project.rootDir}/nightly-url.txt").exists()) {
file("${project.rootDir}/nightly-url.txt").readText().trim()
} else {
"<unset>"
}
dimension = "distribution"
versionNameSuffix = "-nightly-untagged-${getDateSuffix()}"
buildConfigField("boolean", "MANAGES_APP_UPDATES", "true")
buildConfigField("String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\"")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\"")
}
create("prod") {
dimension = "environment"
isDefault = true
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"mainnet\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Prod\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
buildConfigField("String", "SIGNAL_URL", "\"https://chat.staging.signal.org\"")
buildConfigField("String", "STORAGE_URL", "\"https://storage-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\"")
buildConfigField("String", "SVR2_MRENCLAVE", "\"acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482\"")
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true")
}
}
lint {
abortOnError = true
baseline = file("lint-baseline.xml")
checkReleaseBuilds = false
disable += "LintError"
}
applicationVariants.all {
outputs
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
.forEach { output ->
if (output.baseName.contains("nightly")) {
output.versionCodeOverride = canonicalVersionCode * postFixSize + 5
var tag = getCurrentGitTag()
if (!tag.isNullOrEmpty()) {
if (tag.startsWith("v")) {
tag = tag.substring(1)
}
output.versionNameOverride = tag
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
val abiName: String = output.getFilter("ABI") ?: "universal"
val postFix: Int = abiPostFix[abiName]!!
if (postFix >= postFixSize) {
throw AssertionError("postFix is too large")
}
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
androidComponents {
beforeVariants { variant ->
variant.enable = variant.name in selectableVariants
}
}
val releaseDir = "$projectDir/src/release/java"
val debugDir = "$projectDir/src/debug/java"
android.buildTypes.configureEach {
val path = if (name == "release") releaseDir else debugDir
sourceSets.named(name) {
java.srcDir(path)
}
}
}
dependencies {
lintChecks(project(":lintchecks"))
ktlintRuleset(libs.ktlint.twitter.compose)
coreLibraryDesugaring(libs.android.tools.desugar)
implementation(project(":libsignal-service"))
implementation(project(":paging"))
implementation(project(":core-util"))
implementation(project(":glide-config"))
implementation(project(":video"))
implementation(project(":device-transfer"))
implementation(project(":image-editor"))
implementation(project(":donations"))
implementation(project(":contacts"))
implementation(project(":qr"))
implementation(project(":sticky-header-grid"))
implementation(project(":photoview"))
implementation(project(":core-ui"))
implementation(libs.androidx.fragment.ktx)
implementation(libs.androidx.appcompat) {
version {
strictly("1.6.1")
}
}
implementation(libs.androidx.window.window)
implementation(libs.androidx.window.java)
implementation(libs.androidx.recyclerview)
implementation(libs.material.material)
implementation(libs.androidx.legacy.support)
implementation(libs.androidx.preference)
implementation(libs.androidx.legacy.preference)
implementation(libs.androidx.gridlayout)
implementation(libs.androidx.exifinterface)
implementation(libs.androidx.compose.rxjava3)
implementation(libs.androidx.compose.runtime.livedata)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.multidex)
implementation(libs.androidx.navigation.fragment.ktx)
implementation(libs.androidx.navigation.ui.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.livedata.ktx)
implementation(libs.androidx.lifecycle.process)
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.extensions)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.concurrent.futures)
implementation(libs.androidx.autofill)
implementation(libs.androidx.biometric)
implementation(libs.androidx.sharetarget)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
}
implementation(libs.google.play.services.maps)
implementation(libs.google.play.services.auth)
implementation(libs.bundles.media3)
implementation(libs.conscrypt.android)
implementation(libs.signal.aesgcmprovider)
implementation(libs.libsignal.android)
implementation(libs.mobilecoin)
implementation(libs.signal.ringrtc)
implementation(libs.leolin.shortcutbadger)
implementation(libs.emilsjolander.stickylistheaders)
implementation(libs.apache.httpclient.android)
implementation(libs.glide.glide)
implementation(libs.roundedimageview)
implementation(libs.materialish.progress)
implementation(libs.greenrobot.eventbus)
implementation(libs.google.zxing.android.integration)
implementation(libs.google.zxing.core)
implementation(libs.google.flexbox)
implementation(libs.subsampling.scale.image.view) {
exclude(group = "com.android.support", module = "support-annotations")
}
implementation(libs.android.tooltips) {
exclude(group = "com.android.support", module = "appcompat-v7")
}
implementation(libs.stream)
implementation(libs.lottie)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
implementation(libs.google.ez.vcard) {
exclude(group = "com.fasterxml.jackson.core")
exclude(group = "org.freemarker")
}
implementation(libs.dnsjava)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.accompanist.permissions)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlin.reflect)
implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
implementation(libs.rxdogtag)
"spinnerImplementation"(project(":spinner"))
"canaryImplementation"(libs.square.leakcanary)
"instrumentationImplementation"(libs.androidx.fragment.testing) {
exclude(group = "androidx.test", module = "core")
}
testImplementation(testLibs.junit.junit)
testImplementation(testLibs.assertj.core)
testImplementation(testLibs.mockito.core)
testImplementation(testLibs.mockito.kotlin)
testImplementation(testLibs.androidx.test.core)
testImplementation(testLibs.robolectric.robolectric) {
exclude(group = "com.google.protobuf", module = "protobuf-java")
}
testImplementation(testLibs.robolectric.shadows.multidex)
testImplementation(testLibs.bouncycastle.bcprov.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.bouncycastle.bcpkix.jdk15on) {
version {
strictly("1.70")
}
}
testImplementation(testLibs.conscrypt.openjdk.uber)
testImplementation(testLibs.hamcrest.hamcrest)
testImplementation(testLibs.mockk)
testImplementation(testFixtures(project(":libsignal-service")))
testImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.ext.junit)
androidTestImplementation(testLibs.espresso.core)
androidTestImplementation(testLibs.androidx.test.core)
androidTestImplementation(testLibs.androidx.test.core.ktx)
androidTestImplementation(testLibs.androidx.test.ext.junit.ktx)
androidTestImplementation(testLibs.mockito.android)
androidTestImplementation(testLibs.mockito.kotlin)
androidTestImplementation(testLibs.mockk.android)
androidTestImplementation(testLibs.square.okhttp.mockserver)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
fun assertIsGitRepo() {
if (!file("${project.rootDir}/.git").exists()) {
throw IllegalStateException("Must be a git repository to guarantee reproducible builds! (git hash is part of APK)")
}
}
fun getLastCommitTimestamp(): String {
assertIsGitRepo()
ByteArrayOutputStream().use { os ->
exec {
executable = "git"
args = listOf("log", "-1", "--pretty=format:%ct")
standardOutput = os
}
return os.toString() + "000"
}
}
fun getGitHash(): String {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "HEAD")
standardOutput = stdout
}
return stdout.toString().trim().substring(0, 12)
}
fun getCurrentGitTag(): String? {
assertIsGitRepo()
val stdout = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "tag", "--points-at", "HEAD")
standardOutput = stdout
}
val output: String = stdout.toString().trim()
return if (output.isNotEmpty()) {
val tags = output.split("\n").toList()
tags.firstOrNull { it.contains("nightly") } ?: tags[0]
} else {
null
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("failed")
exceptionFormat = TestExceptionFormat.FULL
showCauses = true
showExceptions = true
showStackTraces = true
}
}
project.tasks.configureEach {
if (name.lowercase().contains("nightly") && name != "checkNightlyParams") {
dependsOn(tasks.getByName("checkNightlyParams"))
}
}
tasks.register("checkNightlyParams") {
doFirst {
if (project.gradle.startParameter.taskNames.any { it.lowercase().contains("nightly") }) {
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
throw GradleException("Cannot find 'nightly-url.txt' for nightly build! It must exist in the root of this project and contain the location of the nightly manifest.")
}
}
}
}
fun loadKeystoreProperties(filename: String): Properties? {
val keystorePropertiesFile = file("${project.rootDir}/$filename")
return if (keystorePropertiesFile.exists()) {
val keystoreProperties = Properties()
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
keystoreProperties
} else {
null
}
}
fun getDateSuffix(): String {
return SimpleDateFormat("yyyy-MM-dd-HH:mm").format(Date())
}
fun getMapsKey(): String {
val mapKey = file("${project.rootDir}/maps.key")
return if (mapKey.exists()) {
mapKey.readLines()[0]
} else {
"AIzaSyCSx9xea86GwDKGznCAULE9Y5a8b-TfN9U"
}
}
fun Project.languageList(): List<String> {
return fileTree("src/main/res") { include("**/strings.xml") }
.map { stringFile -> stringFile.parentFile.name }
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct() + "en"
}
fun String.capitalize(): String {
return this.replaceFirstChar { it.uppercase() }
}

View File

@@ -2,8 +2,11 @@
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.net.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.signal.libsignal.usernames.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
@@ -11,4 +14,11 @@
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }
-keep class androidx.window.** { *; }
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@@ -26,15 +26,27 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
}
override fun initializeLogging() {
persistentLogger = PersistentLogger(this)
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
SignalExecutors.UNBOUNDED.execute {
Log.blockUntilAllWritesFinished()
LogDatabase.getInstance(this).trimToSize()
LogDatabase.getInstance(this).logs.trimToSize()
}
}
override fun beginJobLoop() = Unit
/**
* Some of the jobs can interfere with some of the instrumentation tests.
*
* For example, we may try to create a release channel recipient while doing
* an import/backup test.
*
* This can be used to start the job loop if needed for tests that rely on it.
*/
fun beginJobLoopForTests() {
super.beginJobLoop()
}
}

View File

@@ -0,0 +1,675 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2
import android.content.ContentValues
import android.database.Cursor
import androidx.core.content.contentValuesOf
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.junit.Before
import org.junit.Test
import org.signal.core.util.Hex
import org.signal.core.util.SqlUtil
import org.signal.core.util.insertInto
import org.signal.core.util.readToList
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireBlob
import org.signal.core.util.requireLong
import org.signal.core.util.requireString
import org.signal.core.util.select
import org.signal.core.util.toInt
import org.signal.core.util.withinTransaction
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.backup.v2.database.clearAllDataForBackupRestore
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.EmojiSearchTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.subscription.Subscriber
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.io.ByteArrayInputStream
import java.util.UUID
import kotlin.random.Random
typealias DatabaseData = Map<String, List<Map<String, Any?>>>
class BackupTest {
companion object {
val SELF_ACI = ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
val ALICE_ACI = ACI.from(UUID.fromString("aaaa0000-5a76-47fa-a98a-7e72c948a82e"))
val ALICE_PNI = PNI.from(UUID.fromString("aaaa1111-c960-4f6c-8385-671ad2ffb999"))
val ALICE_E164 = "+12222222222"
/** Columns that we don't need to check equality of */
private val IGNORED_COLUMNS: Map<String, Set<String>> = mapOf(
RecipientTable.TABLE_NAME to setOf(RecipientTable.STORAGE_SERVICE_ID),
MessageTable.TABLE_NAME to setOf(MessageTable.FROM_DEVICE_ID)
)
/** Tables we don't need to check equality of */
private val IGNORED_TABLES: Set<String> = setOf(
EmojiSearchTable.TABLE_NAME,
"sqlite_sequence",
"message_fts_data",
"message_fts_idx",
"message_fts_docsize"
)
}
@Before
fun setup() {
SignalStore.account().setE164(SELF_E164)
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
}
@Test
fun emptyDatabase() {
backupTest { }
}
@Test
fun noteToSelf() {
backupTest {
individualChat(aci = SELF_ACI, givenName = "Note to Self") {
standardMessage(outgoing = true, body = "A")
standardMessage(outgoing = true, body = "B")
standardMessage(outgoing = true, body = "C")
}
}
}
@Test
fun individualChat() {
backupTest {
individualChat(aci = ALICE_ACI, givenName = "Alice") {
val m1 = standardMessage(outgoing = true, body = "Outgoing 1")
val m2 = standardMessage(outgoing = false, body = "Incoming 1", read = true)
standardMessage(outgoing = true, body = "Outgoing 2", quotes = m2)
standardMessage(outgoing = false, body = "Incoming 2", quotes = m1, quoteTargetMissing = true, read = false)
standardMessage(outgoing = true, body = "Outgoing 3, with mention", randomMention = true)
standardMessage(outgoing = false, body = "Incoming 3, with style", read = false, randomStyling = true)
remoteDeletedMessage(outgoing = true)
remoteDeletedMessage(outgoing = false)
}
}
}
@Test
fun individualRecipients() {
backupTest {
// Comprehensive example
individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
// Trying to get coverage of all the various values
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.NOT_REGISTERED)
individualRecipient(aci = ACI.from(UUID.randomUUID()), registeredState = RecipientTable.RegisteredState.UNKNOWN)
individualRecipient(pni = PNI.from(UUID.randomUUID()))
individualRecipient(e164 = "+15551234567")
individualRecipient(aci = ACI.from(UUID.randomUUID()), givenName = "Bob")
individualRecipient(aci = ACI.from(UUID.randomUUID()), familyName = "Smith")
individualRecipient(aci = ACI.from(UUID.randomUUID()), profileSharing = false)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hideStory = true)
individualRecipient(aci = ACI.from(UUID.randomUUID()), hidden = true)
}
}
@Test
fun individualCallLogs() {
backupTest {
val aliceId = individualRecipient(
aci = ALICE_ACI,
pni = ALICE_PNI,
e164 = ALICE_E164,
givenName = "Alice",
familyName = "Smith",
username = "alice.99",
hidden = false,
registeredState = RecipientTable.RegisteredState.REGISTERED,
profileKey = ProfileKey(Random.nextBytes(32)),
profileSharing = true,
hideStory = false
)
insertOneToOneCallVariations(1, 1, aliceId)
}
}
private fun insertOneToOneCallVariations(callId: Long, timestamp: Long, id: RecipientId): Long {
val directions = arrayOf(CallTable.Direction.INCOMING, CallTable.Direction.OUTGOING)
val callTypes = arrayOf(CallTable.Type.AUDIO_CALL, CallTable.Type.VIDEO_CALL)
val events = arrayOf(
CallTable.Event.MISSED,
CallTable.Event.OUTGOING_RING,
CallTable.Event.ONGOING,
CallTable.Event.ACCEPTED,
CallTable.Event.NOT_ACCEPTED
)
var callTimestamp: Long = timestamp
var currentCallId = callId
for (direction in directions) {
for (event in events) {
for (type in callTypes) {
insertOneToOneCall(callId = currentCallId, callTimestamp, id, type, direction, event)
callTimestamp++
currentCallId++
}
}
}
return currentCallId
}
private fun insertOneToOneCall(callId: Long, timestamp: Long, peer: RecipientId, type: CallTable.Type, direction: CallTable.Direction, event: CallTable.Event) {
val messageType: Long = CallTable.Call.getMessageType(type, direction, event)
SignalDatabase.rawDatabase.withinTransaction {
val recipient = Recipient.resolved(peer)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val outgoing = direction == CallTable.Direction.OUTGOING
val messageValues = contentValuesOf(
MessageTable.FROM_RECIPIENT_ID to if (outgoing) Recipient.self().id.serialize() else peer.serialize(),
MessageTable.FROM_DEVICE_ID to 1,
MessageTable.TO_RECIPIENT_ID to if (outgoing) peer.serialize() else Recipient.self().id.serialize(),
MessageTable.DATE_RECEIVED to timestamp,
MessageTable.DATE_SENT to timestamp,
MessageTable.READ to 1,
MessageTable.TYPE to messageType,
MessageTable.THREAD_ID to threadId
)
val messageId = SignalDatabase.rawDatabase.insert(MessageTable.TABLE_NAME, null, messageValues)
val values = contentValuesOf(
CallTable.CALL_ID to callId,
CallTable.MESSAGE_ID to messageId,
CallTable.PEER to peer.serialize(),
CallTable.TYPE to CallTable.Type.serialize(type),
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
CallTable.EVENT to CallTable.Event.serialize(event),
CallTable.TIMESTAMP to timestamp
)
SignalDatabase.rawDatabase.insert(CallTable.TABLE_NAME, null, values)
SignalDatabase.threads.update(threadId, true)
}
}
@Test
fun accountData() {
val context = ApplicationDependencies.getApplication()
backupTest(validateKeyValue = true) {
val self = Recipient.self()
// TODO note-to-self archived
// TODO note-to-self unread
SignalStore.account().setAci(SELF_ACI)
SignalStore.account().setPni(SELF_PNI)
SignalStore.account().setE164(SELF_E164)
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
SignalDatabase.recipients.setProfileKey(self.id, ProfileKey(Random.nextBytes(32)))
SignalDatabase.recipients.setProfileName(self.id, ProfileName.fromParts("Peter", "Parker"))
SignalDatabase.recipients.setProfileAvatar(self.id, "https://example.com/")
SignalStore.donationsValues().markUserManuallyCancelled()
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
SignalStore.settings().isLinkPreviewsEnabled = false
SignalStore.settings().isPreferSystemContactPhotos = true
SignalStore.settings().universalExpireTimer = 42
SignalStore.settings().setKeepMutedChatsArchived(true)
SignalStore.storyValues().viewedReceiptsEnabled = false
SignalStore.storyValues().userHasViewedOnboardingStory = true
SignalStore.storyValues().isFeatureDisabled = false
SignalStore.storyValues().userHasBeenNotifiedAboutStories = true
SignalStore.storyValues().userHasSeenGroupStoryEducationSheet = true
SignalStore.emojiValues().reactions = listOf("a", "b", "c")
TextSecurePreferences.setTypingIndicatorsEnabled(context, false)
TextSecurePreferences.setReadReceiptsEnabled(context, false)
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, true)
}
// Have to check TextSecurePreferences ourselves, since they're not in a database
TextSecurePreferences.isTypingIndicatorsEnabled(context) assertIs false
TextSecurePreferences.isReadReceiptsEnabled(context) assertIs false
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context) assertIs true
}
/**
* Sets up the database, then executes your setup code, then compares snapshots of the database
* before an after an import to ensure that no data was lost/changed.
*
* @param validateKeyValue If true, this will also validate the KeyValueDatabase. You only want to do this if you
* intend on setting most of the values. Otherwise stuff tends to not match since values are lazily written.
*/
private fun backupTest(validateKeyValue: Boolean = false, content: () -> Unit) {
// Under normal circumstances, My Story ends up being the first recipient in the table, and is added automatically.
// This screws with the tests by offsetting all the recipientIds in the initial state.
// Easiest way to get around this is to make the DB a true clean slate by clearing everything.
// (We only really need to clear Recipient/dlists, but doing everything to be consistent.)
SignalDatabase.distributionLists.clearAllDataForBackupRestore()
SignalDatabase.recipients.clearAllDataForBackupRestore()
SignalDatabase.messages.clearAllDataForBackupRestore()
SignalDatabase.threads.clearAllDataForBackupRestore()
// Again, for comparison purposes, because we always import self first, we want to ensure it's the first item
// in the table when we export.
individualRecipient(
aci = SELF_ACI,
pni = SELF_PNI,
e164 = SELF_E164,
profileKey = SELF_PROFILE_KEY,
profileSharing = true
)
content()
val startingMainData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val startingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
val exported: ByteArray = BackupRepository.export()
BackupRepository.import(length = exported.size.toLong(), inputStreamFactory = { ByteArrayInputStream(exported) }, selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY))
val endingData: DatabaseData = SignalDatabase.rawDatabase.readAllContents()
val endingKeyValueData: DatabaseData = if (validateKeyValue) SignalDatabase.rawDatabase.readAllContents() else emptyMap()
assertDatabaseMatches(startingMainData, endingData)
assertDatabaseMatches(startingKeyValueData, endingKeyValueData)
}
private fun individualChat(aci: ACI, givenName: String, familyName: String? = null, init: IndividualChatCreator.() -> Unit) {
val recipientId = individualRecipient(aci = aci, givenName = givenName, familyName = familyName, profileSharing = true)
val threadId: Long = SignalDatabase.threads.getOrCreateThreadIdFor(recipientId, false)
IndividualChatCreator(SignalDatabase.rawDatabase, recipientId, threadId).init()
SignalDatabase.threads.update(threadId, false)
}
private fun individualRecipient(
aci: ACI? = null,
pni: PNI? = null,
e164: String? = null,
givenName: String? = null,
familyName: String? = null,
username: String? = null,
hidden: Boolean = false,
registeredState: RecipientTable.RegisteredState = RecipientTable.RegisteredState.UNKNOWN,
profileKey: ProfileKey? = null,
profileSharing: Boolean = false,
hideStory: Boolean = false
): RecipientId {
check(aci != null || pni != null || e164 != null)
val recipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci, pni, e164, pniVerified = true, changeSelf = true)
if (givenName != null || familyName != null) {
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts(givenName, familyName))
}
if (username != null) {
SignalDatabase.recipients.setUsername(recipientId, username)
}
if (registeredState == RecipientTable.RegisteredState.REGISTERED) {
SignalDatabase.recipients.markRegistered(recipientId, aci ?: pni!!)
} else if (registeredState == RecipientTable.RegisteredState.NOT_REGISTERED) {
SignalDatabase.recipients.markUnregistered(recipientId)
}
if (profileKey != null) {
SignalDatabase.recipients.setProfileKey(recipientId, profileKey)
}
SignalDatabase.recipients.setProfileSharing(recipientId, profileSharing)
SignalDatabase.recipients.setHideStory(recipientId, hideStory)
if (hidden) {
SignalDatabase.recipients.markHidden(recipientId)
}
return recipientId
}
private inner class IndividualChatCreator(
private val db: SQLiteDatabase,
private val recipientId: RecipientId,
private val threadId: Long
) {
fun standardMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
body = body,
read = read,
quotes = quotes,
quoteTargetMissing = quoteTargetMissing,
randomMention = randomMention,
randomStyling = randomStyling
)
}
fun remoteDeletedMessage(
outgoing: Boolean,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp
): Long {
return db.insertMessage(
from = if (outgoing) Recipient.self().id else recipientId,
to = if (outgoing) recipientId else Recipient.self().id,
outgoing = outgoing,
threadId = threadId,
sentTimestamp = sentTimestamp,
receivedTimestamp = receivedTimestamp,
serverTimestamp = serverTimestamp,
remoteDeleted = true
)
}
}
private fun SQLiteDatabase.insertMessage(
from: RecipientId,
to: RecipientId,
outgoing: Boolean,
threadId: Long,
sentTimestamp: Long = System.currentTimeMillis(),
receivedTimestamp: Long = if (outgoing) sentTimestamp else sentTimestamp + 1,
serverTimestamp: Long = sentTimestamp,
body: String? = null,
read: Boolean = true,
quotes: Long? = null,
quoteTargetMissing: Boolean = false,
randomMention: Boolean = false,
randomStyling: Boolean = false,
remoteDeleted: Boolean = false
): Long {
val type = if (outgoing) {
MessageTypes.BASE_SENT_TYPE
} else {
MessageTypes.BASE_INBOX_TYPE
} or MessageTypes.SECURE_MESSAGE_BIT or MessageTypes.PUSH_MESSAGE_BIT
val contentValues = ContentValues()
contentValues.put(MessageTable.DATE_SENT, sentTimestamp)
contentValues.put(MessageTable.DATE_RECEIVED, receivedTimestamp)
contentValues.put(MessageTable.FROM_RECIPIENT_ID, from.serialize())
contentValues.put(MessageTable.TO_RECIPIENT_ID, to.serialize())
contentValues.put(MessageTable.THREAD_ID, threadId)
contentValues.put(MessageTable.BODY, body)
contentValues.put(MessageTable.TYPE, type)
contentValues.put(MessageTable.READ, if (read) 1 else 0)
if (!outgoing) {
contentValues.put(MessageTable.DATE_SERVER, serverTimestamp)
}
if (remoteDeleted) {
contentValues.put(MessageTable.REMOTE_DELETED, 1)
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
if (quotes != null) {
val quoteDetails = this.getQuoteDetailsFor(quotes)
contentValues.put(MessageTable.QUOTE_ID, if (quoteTargetMissing) MessageTable.QUOTE_TARGET_MISSING_ID else quoteDetails.quotedSentTimestamp)
contentValues.put(MessageTable.QUOTE_AUTHOR, quoteDetails.authorId.serialize())
contentValues.put(MessageTable.QUOTE_BODY, quoteDetails.body)
contentValues.put(MessageTable.QUOTE_BODY_RANGES, quoteDetails.bodyRanges)
contentValues.put(MessageTable.QUOTE_TYPE, quoteDetails.type)
contentValues.put(MessageTable.QUOTE_MISSING, quoteTargetMissing.toInt())
}
if (body != null && (randomMention || randomStyling)) {
val ranges: MutableList<BodyRangeList.BodyRange> = mutableListOf()
if (randomMention) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
mentionUuid = if (outgoing) Recipient.resolved(to).requireAci().toString() else Recipient.resolved(from).requireAci().toString()
)
}
if (randomStyling) {
ranges += BodyRangeList.BodyRange(
start = 0,
length = Random.nextInt(body.length),
style = BodyRangeList.BodyRange.Style.fromValue(Random.nextInt(BodyRangeList.BodyRange.Style.values().size))
)
}
contentValues.put(MessageTable.MESSAGE_RANGES, BodyRangeList(ranges = ranges).encode())
}
return this
.insertInto(MessageTable.TABLE_NAME)
.values(contentValues)
.run()
}
private fun assertDatabaseMatches(expected: DatabaseData, actual: DatabaseData) {
assert(expected.keys.size == actual.keys.size) { "Mismatched table count! Expected: ${expected.keys} || Actual: ${actual.keys}" }
assert(expected.keys.containsAll(actual.keys)) { "Table names differ! Expected: ${expected.keys} || Actual: ${actual.keys}" }
val tablesToCheck = expected.keys.filter { !IGNORED_TABLES.contains(it) }
for (table in tablesToCheck) {
val expectedTable: List<Map<String, Any?>> = expected[table]!!
val actualTable: List<Map<String, Any?>> = actual[table]!!
assert(expectedTable.size == actualTable.size) { "Mismatched number of rows for table '$table'! Expected: ${expectedTable.size} || Actual: ${actualTable.size}\n $actualTable" }
val expectedFiltered: List<Map<String, Any?>> = expectedTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
val actualFiltered: List<Map<String, Any?>> = actualTable.withoutExcludedColumns(IGNORED_COLUMNS[table])
assert(contentEquals(expectedFiltered, actualFiltered)) { "Data did not match for table '$table'!\n${prettyDiff(expectedFiltered, actualFiltered)}" }
}
}
private fun contentEquals(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): Boolean {
if (expectedRows == actualRows) {
return true
}
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
return false
}
}
}
return true
}
private fun contentEquals(lhs: Any?, rhs: Any?): Boolean {
return if (lhs is ByteArray && rhs is ByteArray) {
lhs.contentEquals(rhs)
} else {
lhs == rhs
}
}
private fun prettyDiff(expectedRows: List<Map<String, Any?>>, actualRows: List<Map<String, Any?>>): String {
val builder = StringBuilder()
assert(expectedRows.size == actualRows.size)
for (i in expectedRows.indices) {
val expectedRow = expectedRows[i]
val actualRow = actualRows[i]
var describedRow = false
for (key in expectedRow.keys) {
val expectedValue = expectedRow[key]
val actualValue = actualRow[key]
if (!contentEquals(expectedValue, actualValue)) {
if (!describedRow) {
builder.append("-- ROW ${i + 1}\n")
describedRow = true
}
builder.append("  [$key] Expected: ${expectedValue.prettyPrint()} || Actual: ${actualValue.prettyPrint()} \n")
}
}
if (describedRow) {
builder.append("\n")
builder.append("Expected: $expectedRow\n")
builder.append("Actual: $actualRow\n")
}
}
return builder.toString()
}
private fun Any?.prettyPrint(): String {
return when (this) {
is ByteArray -> "Bytes(${Hex.toString(this)})"
else -> this.toString()
}
}
private fun List<Map<String, Any?>>.withoutExcludedColumns(ignored: Set<String>?): List<Map<String, Any?>> {
return if (ignored != null) {
this.map { row ->
row.filterKeys { !ignored.contains(it) }
}
} else {
this
}
}
private fun SQLiteDatabase.getQuoteDetailsFor(messageId: Long): QuoteDetails {
return this
.select(
MessageTable.DATE_SENT,
MessageTable.FROM_RECIPIENT_ID,
MessageTable.BODY,
MessageTable.MESSAGE_RANGES
)
.from(MessageTable.TABLE_NAME)
.where("${MessageTable.ID} = ?", messageId)
.run()
.readToSingleObject { cursor ->
QuoteDetails(
quotedSentTimestamp = cursor.requireLong(MessageTable.DATE_SENT),
authorId = RecipientId.from(cursor.requireLong(MessageTable.FROM_RECIPIENT_ID)),
body = cursor.requireString(MessageTable.BODY),
bodyRanges = cursor.requireBlob(MessageTable.MESSAGE_RANGES),
type = QuoteModel.Type.NORMAL.code
)
}!!
}
private fun SQLiteDatabase.readAllContents(): DatabaseData {
return SqlUtil.getAllTables(this).associateWith { table -> this.getAllTableData(table) }
}
private fun SQLiteDatabase.getAllTableData(table: String): List<Map<String, Any?>> {
return this
.select()
.from(table)
.run()
.readToList { cursor ->
val map: MutableMap<String, Any?> = mutableMapOf()
for (i in 0 until cursor.columnCount) {
val column = cursor.getColumnName(i)
when (cursor.getType(i)) {
Cursor.FIELD_TYPE_INTEGER -> map[column] = cursor.getInt(i)
Cursor.FIELD_TYPE_FLOAT -> map[column] = cursor.getFloat(i)
Cursor.FIELD_TYPE_STRING -> map[column] = cursor.getString(i)
Cursor.FIELD_TYPE_BLOB -> map[column] = cursor.getBlob(i)
Cursor.FIELD_TYPE_NULL -> map[column] = null
}
}
map
}
}
private data class QuoteDetails(
val quotedSentTimestamp: Long,
val authorId: RecipientId,
val body: String?,
val bodyRanges: ByteArray?,
val type: Int
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
}
}

View File

@@ -10,13 +10,11 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
@@ -37,6 +35,7 @@ import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.testing.timeout
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@@ -48,20 +47,16 @@ class ChangeNumberViewModelTest {
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
private lateinit var kbsRepository: KbsRepository
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
kbsRepository = mock()
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application),
kbsRepository = kbsRepository
verifyAccountRepository = VerifyAccountRepository(harness.application)
)
viewModel.setNewCountry(1)
@@ -78,7 +73,7 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -185,7 +180,7 @@ class ChangeNumberViewModelTest {
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -230,13 +225,11 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
@@ -274,7 +267,7 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
@@ -318,13 +311,11 @@ class ChangeNumberViewModelTest {
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = ServiceId.from(UUID.randomUUID())
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },

View File

@@ -7,13 +7,15 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
@@ -63,7 +65,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -72,7 +75,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
ThreadUtil.sleep(1)
}
@@ -82,7 +85,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -91,7 +95,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
val insert = SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) {
@@ -133,7 +137,7 @@ class ConversationItemPreviewer {
private fun attachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.CDN_3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"image/webp",
null,
@@ -142,6 +146,8 @@ class ConversationItemPreviewer {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,

View File

@@ -7,6 +7,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId

View File

@@ -0,0 +1,332 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.Observer
import com.bumptech.glide.RequestManager
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.database.FakeMessageRecords
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.testing.SignalActivityRule
import kotlin.time.Duration.Companion.minutes
class V2ConversationItemShapeTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
@Test
fun givenNextAndPreviousMessageDoNotExist_whenISetMessageShape_thenIExpectSingle() {
val testSubject = V2ConversationItemShape(FakeConversationContext())
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousWithinTimeoutAndNoNext_whenISetMessageShape_thenIExpectEnd() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextWithinTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectStart() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextWithinTimeout_whenISetMessageShape_thenIExpectMiddle() {
val now = System.currentTimeMillis()
val prev = now - 2.minutes.inWholeMilliseconds
val next = now + 2.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousOutsideTimeoutAndNoNext_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenNextOutsideTimeoutAndNoPrevious_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
nextMessage = getMessageRecord(now)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
@Test
fun givenPreviousAndNextOutsideTimeout_whenISetMessageShape_thenIExpectSingle() {
val now = System.currentTimeMillis()
val prev = now - 4.minutes.inWholeMilliseconds
val next = now + 4.minutes.inWholeMilliseconds
val testSubject = V2ConversationItemShape(
FakeConversationContext(
previousMessage = getMessageRecord(prev),
nextMessage = getMessageRecord(next)
)
)
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
)
assertEquals(expected, actual)
}
private fun getMessageRecord(
timestamp: Long = System.currentTimeMillis()
): MessageRecord {
return FakeMessageRecords.buildMediaMmsMessageRecord(
dateReceived = timestamp,
dateSent = timestamp,
dateServer = timestamp
)
}
private class FakeConversationContext(
private val hasWallpaper: Boolean = false,
private val previousMessage: MessageRecord? = null,
private val nextMessage: MessageRecord? = null
) : V2ConversationContext {
private val colorizer = Colorizer()
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true
override val searchQuery: String? = null
override val requestManager: RequestManager = mockk()
override val isParentInScroll: Boolean = false
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
override fun hasWallpaper(): Boolean = hasWallpaper
override fun getColorizer(): Colorizer = colorizer
override fun getNextMessage(adapterPosition: Int): MessageRecord? = nextMessage
override fun getPreviousMessage(adapterPosition: Int): MessageRecord? = previousMessage
}
private object FakeConversationItemClickListener : ConversationAdapter.ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) = Unit
override fun onLinkPreviewClicked(linkPreview: LinkPreview) = Unit
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) = Unit
override fun onStickerClicked(stickerLocator: StickerLocator) = Unit
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) = Unit
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) = Unit
override fun onAddToContactsClicked(contact: Contact) = Unit
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) = Unit
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) = Unit
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) = Unit
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) = Unit
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) = Unit
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) = Unit
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) = Unit
override fun onVoiceNotePause(uri: Uri) = Unit
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) = Unit
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) = Unit
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) = Unit
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) = Unit
override fun onChatSessionRefreshLearnMoreClicked() = Unit
override fun onBadDecryptLearnMoreClicked(author: RecipientId) = Unit
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) = Unit
override fun onJoinGroupCallClicked() = Unit
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) = Unit
override fun onEnableCallNotificationsClicked() = Unit
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) = Unit
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) = Unit
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) = Unit
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit
override fun onBlockJoinRequest(recipient: Recipient) = Unit
override fun onRecipientNameClicked(target: RecipientId) = Unit
override fun onInviteToSignalClicked() = Unit
override fun onActivatePaymentsClicked() = Unit
override fun onSendPaymentClicked(recipientId: RecipientId) = Unit
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) = Unit
override fun onUrlClicked(url: String): Boolean = false
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) = Unit
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) = Unit
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) = Unit
override fun onItemClick(item: MultiselectPart?) = Unit
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
override fun onShowSafetyTips(forGroup: Boolean) = Unit
override fun onReportSpamLearnMoreClicked() = Unit
override fun onMessageRequestAcceptOptionsClicked() = Unit
}
}

View File

@@ -6,12 +6,16 @@ import androidx.test.filters.FlakyTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
@@ -47,23 +51,22 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
false
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3)),
false
createMediaStream(byteArrayOf(1, 2, 3))
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
@FlakyTest
@Ignore("test is flaky")
@Test
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()
@@ -74,22 +77,92 @@ class AttachmentTableTest {
SignalDatabase.attachments.updateAttachmentData(
attachment,
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
true
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
)
SignalDatabase.attachments.updateAttachmentData(
attachment2,
createMediaStream(byteArrayOf(1, 2, 3, 4)),
true
createMediaStream(byteArrayOf(1, 2, 3, 4))
)
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
/**
* Given: A previous attachment and two pre-upload attachments with the same data but different transform properties (standard and high).
*
* When changing content of standard pre-upload attachment to match pre-existing attachment
*
* Then update standard pre-upload attachment to match previous attachment, do not update high pre-upload attachment, and do
* not delete shared pre-upload uri from disk as it is still being used by the high pre-upload attachment.
*/
@Test
fun doNotDeleteDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val compressedData = byteArrayOf(1, 2, 3)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val previousAttachment = createAttachment(1, BlobProvider.getInstance().forData(compressedData).createForSingleSessionInMemory(), AttachmentTable.TransformProperties.empty())
val previousDatabaseAttachmentId: AttachmentId = SignalDatabase.attachments.insertAttachmentsForMessage(1, listOf(previousAttachment), emptyList()).values.first()
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
// WHEN
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData))
// THEN
val previousInfo = SignalDatabase.attachments.getDataFileInfo(previousDatabaseAttachmentId)!!
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
assertNotEquals(standardInfo, highInfo)
highInfo.file assertIsNot standardInfo.file
highInfo.file.exists() assertIs true
}
/**
* Given: Three pre-upload attachments with the same data but different transform properties (1x standard and 2x high).
*
* When inserting content of high pre-upload attachment.
*
* Then do not deduplicate with standard pre-upload attachment, but do deduplicate second high insert.
*/
@Test
fun doNotDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
// WHEN
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
val secondHighQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
// THEN
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
val secondHighInfo = SignalDatabase.attachments.getDataFileInfo(secondHighDatabaseAttachment.attachmentId)!!
highInfo.file assertIsNot standardInfo.file
secondHighInfo.file assertIs highInfo.file
standardInfo.file.exists() assertIs true
highInfo.file.exists() assertIs true
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,

View File

@@ -0,0 +1,805 @@
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.database.AttachmentTable.TransformProperties
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.MediaUtil
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.File
import java.util.UUID
import kotlin.random.Random
import kotlin.time.Duration.Companion.days
/**
* Collection of [AttachmentTable] tests focused around deduping logic.
*/
@RunWith(AndroidJUnit4::class)
class AttachmentTableTest_deduping {
companion object {
val DATA_A = byteArrayOf(1, 2, 3)
val DATA_A_COMPRESSED = byteArrayOf(4, 5, 6)
val DATA_A_HASH = byteArrayOf(1, 1, 1)
val DATA_B = byteArrayOf(7, 8, 9)
}
@Before
fun setUp() {
SignalStore.account().setAci(ServiceId.ACI.from(UUID.randomUUID()))
SignalStore.account().setPni(ServiceId.PNI.from(UUID.randomUUID()))
SignalStore.account().setE164("+15558675309")
SignalDatabase.attachments.deleteAllAttachments()
}
/**
* Creates two different files with different data. Should not dedupe.
*/
@Test
fun differentFiles() {
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_B)
assertDataFilesAreDifferent(id1, id2)
}
}
/**
* Inserts files with identical data but with transform properties that make them incompatible. Should not dedupe.
*/
@Test
fun identicalFiles_incompatibleTransforms() {
// Non-matching qualities
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim flag
test {
val id1 = insertWithData(DATA_A, TransformProperties())
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim start time
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching video trim end time
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 1))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Non-matching mp4 fast start
test {
val id1 = insertWithData(DATA_A, TransformProperties(mp4FastStart = true))
val id2 = insertWithData(DATA_A, TransformProperties(mp4FastStart = false))
assertDataFilesAreDifferent(id1, id2)
assertDataHashStartMatches(id1, id2)
}
}
/**
* Inserts files with identical data and compatible transform properties. Should dedupe.
*/
@Test
fun identicalFiles_compatibleTransforms() {
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
}
}
/**
* Walks through various scenarios where files are compressed and uploaded.
*/
@Test
fun compressionAndUploads() {
// Matches after the first is compressed, skip transform properly set
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Matches after the first is uploaded, skip transform and ending hash properly set
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Mimics sending two files at once. Ensures all fields are kept in sync as we compress and upload.
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, false)
assertSkipTransform(id2, false)
compress(id1, DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
upload(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
// Re-use the upload when uploaded recently
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
}
// Do not re-use old uploads
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis() - 100.days.inWholeMilliseconds)
val id2 = insertWithData(DATA_A)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, true)
assertDoesNotHaveRemoteFields(id2)
}
// This isn't so much "desirable behavior" as it is documenting how things work.
// If an attachment is compressed but not uploaded yet, it will have a DATA_HASH_START that doesn't match the actual file content.
// This means that if we insert a new attachment with data that matches the compressed data, we won't find a match.
// This is ok because we don't allow forwarding unsent messages, so the chances of the user somehow sending a file that matches data we compressed are very low.
// What *is* more common is that the user may send DATA_A again, and in this case we will still catch the dedupe (which is already tested above).
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreDifferent(id1, id2)
}
// This represents what would happen if you forward an already-send compressed attachment. We should match, skip transform, and skip upload.
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it. We should match, skip transform, and skip upload.
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you edited a video, sent it, then forwarded it, but *edited the forwarded video*. We should not dedupe.
test {
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
assertDataFilesAreDifferent(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id2, false)
assertDoesNotHaveRemoteFields(id2)
}
// This represents what would happen if you sent an image using standard quality, then forwarded it using high quality.
// Since you're forwarding, it doesn't matter if the new thing has a higher quality, we should still match and skip transform.
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// This represents what would happen if you sent an image using high quality, then forwarded it using standard quality.
// Since you're forwarding, it doesn't matter if the new thing has a lower quality, we should still match and skip transform.
test {
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
compress(id1, DATA_A_COMPRESSED)
upload(id1, uploadTimestamp = System.currentTimeMillis())
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertSkipTransform(id1, true)
assertSkipTransform(id1, true)
assertRemoteFieldsMatch(id1, id2)
}
// Make sure that files marked as unhashable are all updated together
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
upload(id1)
upload(id2)
clearHashes(id1)
clearHashes(id2)
val file = dataFile(id1)
SignalDatabase.attachments.markDataFileAsUnhashable(file)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id1)!!
assertTrue(dataFileInfo.hashEnd!!.startsWith("UNHASHABLE-"))
}
}
/**
* Various deletion scenarios to ensure that duped files don't deleted while there's still references.
*/
@Test
fun deletions() {
// Delete original then dupe
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
val dataFile = dataFile(id1)
assertDataFilesAreTheSame(id1, id2)
delete(id1)
assertDeleted(id1)
assertRowAndFileExists(id2)
assertTrue(dataFile.exists())
delete(id2)
assertDeleted(id2)
assertFalse(dataFile.exists())
}
// Delete dupe then original
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
val dataFile = dataFile(id1)
assertDataFilesAreTheSame(id1, id2)
delete(id2)
assertDeleted(id2)
assertRowAndFileExists(id1)
assertTrue(dataFile.exists())
delete(id1)
assertDeleted(id1)
assertFalse(dataFile.exists())
}
// Delete original after it was compressed
test {
val id1 = insertWithData(DATA_A)
compress(id1, DATA_A_COMPRESSED)
val id2 = insertWithData(DATA_A)
delete(id1)
assertDeleted(id1)
assertRowAndFileExists(id2)
assertSkipTransform(id2, true)
}
// Quotes are weak references and should not prevent us from deleting the file
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
val dataFile = dataFile(id1)
delete(id1)
assertDeleted(id1)
assertRowExists(id2)
assertFalse(dataFile.exists())
}
}
@Test
fun quotes() {
// Basic quote deduping
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
}
// Making sure remote fields carry
test {
val id1 = insertWithData(DATA_A)
val id2 = insertQuote(id1)
upload(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashStartMatches(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
// Making sure things work for quotes of videos, which have trickier transform properties
test {
val id1 = insertWithData(DATA_A, transformProperties = TransformProperties.forVideoTrim(1, 2))
compress(id1, DATA_A_COMPRESSED)
upload(id1)
val id2 = insertQuote(id1)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertRemoteFieldsMatch(id1, id2)
}
}
/**
* Suite of tests around the migration where we hash all of the attachments and potentially dedupe them.
*/
@Test
fun migration() {
// Verifying that getUnhashedDataFile only returns if there's actually missing hashes
test {
val id = insertWithData(DATA_A)
upload(id)
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// Verifying that getUnhashedDataFile finds the missing hash
test {
val id = insertWithData(DATA_A)
upload(id)
clearHashes(id)
assertNotNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// Verifying that getUnhashedDataFile doesn't return if the file isn't done downloading
test {
val id = insertWithData(DATA_A)
upload(id)
setTransferState(id, AttachmentTable.TRANSFER_PROGRESS_PENDING)
clearHashes(id)
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
}
// If two attachments share the same file, when we backfill the hash, make sure both get their hashes set
test {
val id1 = insertWithData(DATA_A)
val id2 = insertWithData(DATA_A)
upload(id1)
upload(id2)
clearHashes(id1)
clearHashes(id2)
val file = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
assertDataHashEndMatches(id1, id2)
}
// Creates a situation where two different attachments have the same data but wrote to different files, and verifies the migration dedupes it
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
upload(id2)
clearHashes(id2)
assertDataFilesAreDifferent(id1, id2)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
assertDataFilesAreDifferent(id1, id2)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertFalse(file2.exists())
}
// We've got three files now with the same data, with two of them sharing a file. We want to make sure *both* entries that share the same file get deduped.
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
val id3 = insertWithData(DATA_A)
upload(id2)
upload(id3)
clearHashes(id2)
clearHashes(id3)
assertDataFilesAreDifferent(id1, id2)
assertDataFilesAreTheSame(id2, id3)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreTheSame(id1, id2)
assertDataHashEndMatches(id1, id2)
assertDataHashEndMatches(id2, id3)
assertFalse(file2.exists())
}
// We don't want to mess with files that are still downloading, so this makes sure that even if data matches, we don't dedupe and don't delete the file
test {
val id1 = insertWithData(DATA_A)
upload(id1)
clearHashes(id1)
val id2 = insertWithData(DATA_A)
// *not* uploaded
clearHashes(id2)
assertDataFilesAreDifferent(id1, id2)
val file1 = dataFile(id1)
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
assertDataHashEnd(id1, DATA_A_HASH)
val file2 = dataFile(id2)
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
assertDataFilesAreDifferent(id1, id2)
assertTrue(file2.exists())
}
}
private class TestContext {
fun insertWithData(data: ByteArray, transformProperties: TransformProperties = TransformProperties.empty()): AttachmentId {
val uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
val attachment = UriAttachmentBuilder.build(
id = Random.nextLong(),
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = transformProperties
)
return SignalDatabase.attachments.insertAttachmentForPreUpload(attachment).attachmentId
}
fun insertQuote(attachmentId: AttachmentId): AttachmentId {
val originalAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.self())
val messageId = SignalDatabase.messages.insertMessageOutbox(
message = OutgoingMessage(
threadRecipient = Recipient.self(),
sentTimeMillis = System.currentTimeMillis(),
body = "some text",
outgoingQuote = QuoteModel(
id = 123,
author = Recipient.self().id,
text = "Some quote text",
isOriginalMissing = false,
attachments = listOf(originalAttachment),
mentions = emptyList(),
type = QuoteModel.Type.NORMAL,
bodyRanges = null
)
),
threadId = threadId,
forceSms = false,
insertListener = null
)
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId)
return attachments[0].attachmentId
}
fun compress(attachmentId: AttachmentId, newData: ByteArray, mp4FastStart: Boolean = false) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
SignalDatabase.attachments.updateAttachmentData(databaseAttachment, newData.asMediaStream())
SignalDatabase.attachments.markAttachmentAsTransformed(attachmentId, withFastStart = mp4FastStart)
}
fun upload(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()) {
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createPointerAttachment(attachmentId, uploadTimestamp), uploadTimestamp)
}
fun delete(attachmentId: AttachmentId) {
SignalDatabase.attachments.deleteAttachment(attachmentId)
}
fun dataFile(attachmentId: AttachmentId): File {
return SignalDatabase.attachments.getDataFileInfo(attachmentId)!!.file
}
fun setTransferState(attachmentId: AttachmentId, transferState: Int) {
// messageId doesn't actually matter -- that's for notifying listeners
SignalDatabase.attachments.setTransferState(messageId = -1, attachmentId = attachmentId, transferState = transferState)
}
fun clearHashes(id: AttachmentId) {
SignalDatabase.attachments.writableDatabase
.update(AttachmentTable.TABLE_NAME)
.values(
AttachmentTable.DATA_HASH_START to null,
AttachmentTable.DATA_HASH_END to null
)
.where("${AttachmentTable.ID} = ?", id)
.run()
}
fun assertDeleted(attachmentId: AttachmentId) {
assertNull("$attachmentId exists, but it shouldn't!", SignalDatabase.attachments.getAttachment(attachmentId))
}
fun assertRowAndFileExists(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
assertNotNull("$attachmentId does not exist!", databaseAttachment)
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(attachmentId)
assertTrue("The file for $attachmentId does not exist!", dataFileInfo!!.file.exists())
}
fun assertRowExists(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
assertNotNull("$attachmentId does not exist!", databaseAttachment)
}
fun assertDataFilesAreTheSame(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assert(lhsInfo.file.exists())
assert(rhsInfo.file.exists())
assertEquals(lhsInfo.file, rhsInfo.file)
assertEquals(lhsInfo.length, rhsInfo.length)
assertArrayEquals(lhsInfo.random, rhsInfo.random)
}
fun assertDataFilesAreDifferent(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assert(lhsInfo.file.exists())
assert(rhsInfo.file.exists())
assertNotEquals(lhsInfo.file, rhsInfo.file)
}
fun assertDataHashStartMatches(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assertNotNull(lhsInfo.hashStart)
assertEquals("DATA_HASH_START's did not match!", lhsInfo.hashStart, rhsInfo.hashStart)
}
fun assertDataHashEndMatches(lhs: AttachmentId, rhs: AttachmentId) {
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
assertNotNull(lhsInfo.hashEnd)
assertEquals("DATA_HASH_END's did not match!", lhsInfo.hashEnd, rhsInfo.hashEnd)
}
fun assertDataHashEnd(id: AttachmentId, byteArray: ByteArray) {
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id)!!
assertArrayEquals(byteArray, Base64.decode(dataFileInfo.hashEnd!!))
}
fun assertRemoteFieldsMatch(lhs: AttachmentId, rhs: AttachmentId) {
val lhsAttachment = SignalDatabase.attachments.getAttachment(lhs)!!
val rhsAttachment = SignalDatabase.attachments.getAttachment(rhs)!!
assertEquals(lhsAttachment.remoteLocation, rhsAttachment.remoteLocation)
assertEquals(lhsAttachment.remoteKey, rhsAttachment.remoteKey)
assertArrayEquals(lhsAttachment.remoteDigest, rhsAttachment.remoteDigest)
assertArrayEquals(lhsAttachment.incrementalDigest, rhsAttachment.incrementalDigest)
assertEquals(lhsAttachment.incrementalMacChunkSize, rhsAttachment.incrementalMacChunkSize)
assertEquals(lhsAttachment.cdn.cdnNumber, rhsAttachment.cdn.cdnNumber)
}
fun assertDoesNotHaveRemoteFields(attachmentId: AttachmentId) {
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
assertEquals(0, databaseAttachment.uploadTimestamp)
assertNull(databaseAttachment.remoteLocation)
assertNull(databaseAttachment.remoteDigest)
assertNull(databaseAttachment.remoteKey)
assertEquals(0, databaseAttachment.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
)
}
}
private fun test(content: TestContext.() -> Unit) {
SignalDatabase.attachments.deleteAllAttachments()
val context = TestContext()
context.content()
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallLinkTableTest {
companion object {
private val ROOM_ID_A = byteArrayOf(1, 2, 3, 4)
private val ROOM_ID_B = byteArrayOf(2, 2, 3, 4)
private const val TIMESTAMP_A = 1000L
private const val TIMESTAMP_B = 2000L
}
@get:Rule
val harness = SignalActivityRule(createGroup = true)
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteBeforeFirst_thenIExpectNeitherDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A - 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(2, callEvents.size)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteOnFirst_thenIExpectFirstDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_A)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(1, callEvents.size)
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteAfterFirstAndBeforeSecond_thenIExpectFirstDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B - 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(1, callEvents.size)
assertEquals(TIMESTAMP_B, callEvents.first().record.timestamp)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteOnSecond_thenIExpectBothDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(0, callEvents.size)
}
@Test
fun givenTwoNonAdminCallLinks_whenIDeleteAfterSecond_thenIExpectBothDeleted() {
insertTwoNonAdminCallLinksWithEvents()
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(TIMESTAMP_B + 500)
val callEvents = SignalDatabase.calls.getCalls(0, 2, "", CallLogFilter.ALL)
assertEquals(0, callEvents.size)
}
private fun insertTwoNonAdminCallLinksWithEvents() {
insertCallLinkWithEvent(ROOM_ID_A, 1000)
insertCallLinkWithEvent(ROOM_ID_B, 2000)
}
private fun insertCallLinkWithEvent(roomId: ByteArray, timestamp: Long) {
SignalDatabase.callLinks.insertCallLink(
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
roomId = CallLinkRoomId.fromBytes(roomId),
credentials = CallLinkCredentials(
linkKeyBytes = roomId,
adminPassBytes = null
),
state = SignalCallLinkState()
)
)
val callLinkRecipient = SignalDatabase.recipients.getByCallLinkRoomId(CallLinkRoomId.fromBytes(roomId)).get()
SignalDatabase.calls.insertAcceptedGroupCall(
1,
callLinkRecipient,
CallTable.Direction.INCOMING,
timestamp
)
}
}

View File

@@ -10,13 +10,18 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.ringrtc.CallId
import org.signal.ringrtc.CallManager
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallTableTest {
@get:Rule
val harness = SignalActivityRule()
val harness = SignalActivityRule(createGroup = true)
private val groupRecipientId: RecipientId
get() = harness.group!!.recipientId
@Test
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
@@ -24,13 +29,13 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.setTimestamp(callId, harness.others[0], -1L)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
SignalDatabase.calls.setTimestamp(callId, groupRecipientId, -1L)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(-1L, call?.timestamp)
@@ -45,15 +50,15 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.deleteGroupCall(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
@@ -66,12 +71,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -86,12 +91,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
assertEquals(harness.self.id, call?.ringerRecipient)
@@ -103,12 +108,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.JOINED, call?.event)
assertNull(call?.ringerRecipient)
@@ -120,13 +125,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
@@ -134,7 +139,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -143,13 +148,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
@@ -157,7 +162,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -166,13 +171,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
@@ -180,7 +185,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -189,7 +194,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -197,7 +202,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
@@ -205,16 +210,134 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
}
@Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
fun givenAnOutgoingRingCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId = callId,
recipientId = groupRecipientId,
direction = CallTable.Direction.OUTGOING,
timestamp = 1
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenARingingCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAMissedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenADeclinedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAnAcceptedCall_whenIAcceptedOutgoingGroupCall_thenIExpectAccepted() {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(
SignalDatabase.calls.getCallById(callId, groupRecipientId)!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@Test
fun givenAGenericGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -222,7 +345,58 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptOutgoingGroupCall(
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenAJoinedGroupCall_whenIAcceptedOutgoingGroupCall_thenIExpectOutgoingRing() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
SignalDatabase.calls.acceptIncomingGroupCall(
call!!
)
SignalDatabase.calls.acceptOutgoingGroupCall(SignalDatabase.calls.getCallById(callId, groupRecipientId)!!)
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.OUTGOING_RING, acceptedCall?.event)
}
@Test
fun givenNoPriorCallEvent_whenIReceiveAGroupCallUpdateMessage_thenIExpectAGenericGroupCall() {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
peekJoinedUuids = emptyList(),
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
}
@@ -233,7 +407,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -241,13 +415,13 @@ class CallTableTest {
isCallFull = false
)
SignalDatabase.calls.getCallById(callId, harness.others[0]).let {
SignalDatabase.calls.getCallById(callId, groupRecipientId).let {
assertNotNull(it)
assertEquals(now, it?.timestamp)
}
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = 1L,
peekGroupCallEraId = "aaa",
@@ -255,7 +429,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
assertEquals(1L, call?.timestamp)
@@ -266,20 +440,20 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId = callId,
recipientId = harness.others[0],
recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DELETE, call?.event)
}
@@ -290,7 +464,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -300,13 +474,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -319,20 +493,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -344,7 +518,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -354,13 +528,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -372,7 +546,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -382,7 +556,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -390,13 +564,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -409,20 +583,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -434,20 +608,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -458,7 +632,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -468,7 +642,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -476,13 +650,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -493,7 +667,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -503,7 +677,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -511,13 +685,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -528,7 +702,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -538,13 +712,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -556,7 +730,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -564,13 +738,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -582,7 +756,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
@@ -590,13 +764,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -608,20 +782,20 @@ class CallTableTest {
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
}
@@ -631,13 +805,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -649,13 +823,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -667,13 +841,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.CANCELLED_BY_RINGER
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -685,13 +859,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -702,13 +876,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -719,13 +893,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertNotNull(call?.messageId)
@@ -736,15 +910,85 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
assertNotNull(call?.messageId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallB_thenOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallA_thenIDoNotDeleteAnyCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(2, allCallEvents.size)
assertEquals(2, allCallEvents[0].record.callId)
assertEquals(1, allCallEvents[1].record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallA_thenIOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
@Test
fun givenTwoCalls_whenIDeleteAfterCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
private fun insertTwoCallEvents() {
SignalDatabase.calls.insertAcceptedGroupCall(
1,
groupRecipientId,
CallTable.Direction.INCOMING,
1000
)
SignalDatabase.calls.insertAcceptedGroupCall(
2,
groupRecipientId,
CallTable.Direction.OUTGOING,
2000
)
}
}

View File

@@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListRecord
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
class DistributionListTablesTest {

View File

@@ -7,7 +7,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.signal.core.util.delete
import org.signal.core.util.deleteAll
import org.signal.core.util.readToList
import org.signal.core.util.requireLong
import org.signal.core.util.withinTransaction
@@ -33,8 +33,8 @@ class GroupTableTest {
fun setUp() {
groupTable = SignalDatabase.groups
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
}
@Test
@@ -293,22 +293,22 @@ class GroupTableTest {
private fun insertPushGroup(
members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setUuid(Recipient.resolved(harness.others[0]).requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
)
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(members)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
@@ -317,23 +317,23 @@ class GroupTableTest {
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val selfMember: DecryptedMember = DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
val selfMember: DecryptedMember = DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
val otherMembers: List<DecryptedMember> = others.map { id ->
DecryptedMember.newBuilder()
.setUuid(Recipient.resolved(id).requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(id).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
}
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(listOf(selfMember) + otherMembers)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(listOf(selfMember) + otherMembers)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import org.junit.Test
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLongOrNull
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.kem.KEMKeyPair
import org.signal.libsignal.protocol.kem.KEMKeyType
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
class KyberPreKeyTableTest {
private val aci: ACI = ACI.from(UUID.randomUUID())
private val pni: PNI = PNI.from(UUID.randomUUID())
@Test
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
insertTestRecord(aci, id = 1)
insertTestRecord(aci, id = 2)
insertTestRecord(aci, id = 3, staleTime = 42)
insertTestRecord(pni, id = 4)
val now = System.currentTimeMillis()
SignalDatabase.kyberPreKeys.markAllStaleIfNecessary(aci, now)
assertEquals(now, getStaleTime(aci, 1))
assertEquals(now, getStaleTime(aci, 2))
assertEquals(42L, getStaleTime(aci, 3))
assertEquals(0L, getStaleTime(pni, 4))
}
@Test
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 15)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
insertTestRecord(aci, id = 1, staleTime = 0)
insertTestRecord(aci, id = 2, staleTime = 0)
insertTestRecord(aci, id = 3, staleTime = 0)
insertTestRecord(aci, id = 4, staleTime = 0)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 1)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectAccount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(pni, id = 4, staleTime = 10)
insertTestRecord(pni, id = 5, staleTime = 10)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(pni, 4))
assertNotNull(getStaleTime(pni, 5))
}
@Test
fun deleteAllStaleBefore_ignoreLastResortForMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10, lastResort = true)
SignalDatabase.kyberPreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteLastResort() {
insertTestRecord(aci, id = 1, staleTime = 10, lastResort = true)
insertTestRecord(aci, id = 2, staleTime = 10, lastResort = true)
insertTestRecord(aci, id = 3, staleTime = 10, lastResort = true)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
}
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0, lastResort: Boolean = false) {
val kemKeyPair = KEMKeyPair.generate(KEMKeyType.KYBER_1024)
SignalDatabase.kyberPreKeys.insert(
serviceId = account,
keyId = id,
record = KyberPreKeyRecord(
id,
System.currentTimeMillis(),
kemKeyPair,
Curve.generateKeyPair().privateKey.calculateSignature(kemKeyPair.publicKey.serialize())
),
lastResort = lastResort
)
val count = SignalDatabase.rawDatabase
.update(KyberPreKeyTable.TABLE_NAME)
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.run()
assertEquals(1, count)
}
private fun getStaleTime(account: ServiceId, id: Int): Long? {
return SignalDatabase.rawDatabase
.select(KyberPreKeyTable.STALE_TIMESTAMP)
.from(KyberPreKeyTable.TABLE_NAME)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.run()
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
}
}

View File

@@ -0,0 +1,288 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import org.junit.Test
import org.signal.core.util.forEach
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
import org.signal.core.util.updateAll
import org.thoughtcrime.securesms.crash.CrashConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.testing.assertIs
class LogDatabaseTest {
private val db: LogDatabase = LogDatabase.getInstance(ApplicationDependencies.getApplication())
@Test
fun crashTable_matchesNamePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(messagePattern = "Message")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_matchesNameAndMessageAndStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Message", stackTracePattern = "stack")
),
promptThreshold = currentTime
)
foundMatch assertIs true
}
@Test
fun crashTable_doesNotMatchNamePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNameButNotMessagePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", messagePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNameButNotStackTracePattern() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test", stackTracePattern = "Blah")
),
promptThreshold = currentTime
)
foundMatch assertIs false
}
@Test
fun crashTable_matchesNamePatternButPromptedTooRecently() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
db.writableDatabase
.updateAll(LogDatabase.CrashTable.TABLE_NAME)
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
.run()
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime - 100
)
foundMatch assertIs false
}
@Test
fun crashTable_noMatches() {
val currentTime = System.currentTimeMillis()
val foundMatch = db.crashes.anyMatch(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptThreshold = currentTime - 100
)
foundMatch assertIs false
}
@Test
fun crashTable_updatesLastPromptTime() {
val currentTime = System.currentTimeMillis()
db.crashes.saveCrash(
createdAt = currentTime,
name = "TestName",
message = "Test Message",
stackTrace = "test\nstack\ntrace"
)
db.crashes.saveCrash(
createdAt = currentTime,
name = "XXX",
message = "XXX",
stackTrace = "XXX"
)
db.crashes.markAsPrompted(
listOf(
CrashConfig.CrashPattern(namePattern = "Test")
),
promptedAt = currentTime
)
db.writableDatabase
.select(LogDatabase.CrashTable.NAME, LogDatabase.CrashTable.LAST_PROMPTED_AT)
.from(LogDatabase.CrashTable.TABLE_NAME)
.run()
.forEach {
if (it.requireNonNullString(LogDatabase.CrashTable.NAME) == "TestName") {
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs currentTime
} else {
it.requireLong(LogDatabase.CrashTable.LAST_PROMPTED_AT) assertIs 0
}
}
}
}

View File

@@ -10,9 +10,8 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@Suppress("ClassName")
@@ -34,7 +33,7 @@ class MessageTableTest_gifts {
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
}
@Test
@@ -49,7 +48,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -63,7 +62,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -77,13 +76,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -97,13 +96,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
@@ -116,18 +115,18 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -141,18 +140,18 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -166,18 +165,18 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
sentTimeMillis = 2,
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional
@@ -17,7 +17,6 @@ object MmsHelper {
recipient: Recipient = Recipient.UNKNOWN,
body: String = "body",
sentTimeMillis: Long = System.currentTimeMillis(),
subscriptionId: Int = -1,
expiresIn: Long = 0,
viewOnce: Boolean = false,
distributionType: Int = ThreadTable.DistributionTypes.DEFAULT,
@@ -32,7 +31,6 @@ object MmsHelper {
recipient = recipient,
body = body,
timestamp = sentTimeMillis,
subscriptionId = subscriptionId,
expiresIn = expiresIn,
viewOnce = viewOnce,
distributionType = distributionType,
@@ -57,9 +55,9 @@ object MmsHelper {
}
fun insert(
message: IncomingMediaMessage,
message: IncomingMessage,
threadId: Long
): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
return SignalDatabase.messages.insertMessageInbox(message, threadId)
}
}

View File

@@ -13,12 +13,11 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
import java.util.concurrent.TimeUnit
@@ -45,7 +44,7 @@ class MmsTableTest_stories {
SignalStore.account().setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())) }
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
@@ -74,7 +73,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -96,7 +96,8 @@ class MmsTableTest_stories {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -123,7 +124,8 @@ class MmsTableTest_stories {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -155,7 +157,8 @@ class MmsTableTest_stories {
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -169,7 +172,8 @@ class MmsTableTest_stories {
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -214,7 +218,8 @@ class MmsTableTest_stories {
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[0],
sentTimeMillis = 200,
serverTimeMillis = 2,
@@ -253,8 +258,7 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
// WHEN
@@ -319,19 +323,19 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = myStory.id,
sentTimeMillis = 201,
serverTimeMillis = 201,
receivedTimeMillis = 202,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
),
-1
SignalDatabase.threads.getOrCreateThreadIdFor(myStory, ThreadTable.DistributionTypes.DEFAULT)
)
// WHEN

View File

@@ -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
)
)
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import org.junit.Test
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireLongOrNull
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
class OneTimePreKeyTableTest {
private val aci: ACI = ACI.from(UUID.randomUUID())
private val pni: PNI = PNI.from(UUID.randomUUID())
@Test
fun markAllStaleIfNecessary_onlyUpdatesMatchingAccountAndZeroValues() {
insertTestRecord(aci, id = 1)
insertTestRecord(aci, id = 2)
insertTestRecord(aci, id = 3, staleTime = 42)
insertTestRecord(pni, id = 4)
val now = System.currentTimeMillis()
SignalDatabase.oneTimePreKeys.markAllStaleIfNecessary(aci, now)
assertEquals(now, getStaleTime(aci, 1))
assertEquals(now, getStaleTime(aci, 2))
assertEquals(42L, getStaleTime(aci, 3))
assertEquals(0L, getStaleTime(pni, 4))
}
@Test
fun deleteAllStaleBefore_deleteOldBeforeThreshold() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 15)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 0)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_neverDeleteStaleOfZero() {
insertTestRecord(aci, id = 1, staleTime = 0)
insertTestRecord(aci, id = 2, staleTime = 0)
insertTestRecord(aci, id = 3, staleTime = 0)
insertTestRecord(aci, id = 4, staleTime = 0)
insertTestRecord(aci, id = 5, staleTime = 0)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 10, minCount = 0)
assertNotNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectMinCount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(aci, id = 4, staleTime = 10)
insertTestRecord(aci, id = 5, staleTime = 10)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 3)
assertNull(getStaleTime(aci, 1))
assertNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(aci, 4))
assertNotNull(getStaleTime(aci, 5))
}
@Test
fun deleteAllStaleBefore_respectAccount() {
insertTestRecord(aci, id = 1, staleTime = 10)
insertTestRecord(aci, id = 2, staleTime = 10)
insertTestRecord(aci, id = 3, staleTime = 10)
insertTestRecord(pni, id = 4, staleTime = 10)
insertTestRecord(pni, id = 5, staleTime = 10)
SignalDatabase.oneTimePreKeys.deleteAllStaleBefore(aci, threshold = 11, minCount = 2)
assertNull(getStaleTime(aci, 1))
assertNotNull(getStaleTime(aci, 2))
assertNotNull(getStaleTime(aci, 3))
assertNotNull(getStaleTime(pni, 4))
assertNotNull(getStaleTime(pni, 5))
}
private fun insertTestRecord(account: ServiceId, id: Int, staleTime: Long = 0) {
SignalDatabase.oneTimePreKeys.insert(
serviceId = account,
keyId = id,
record = PreKeyRecord(id, Curve.generateKeyPair())
)
val count = SignalDatabase.rawDatabase
.update(OneTimePreKeyTable.TABLE_NAME)
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.run()
assertEquals(1, count)
}
private fun getStaleTime(account: ServiceId, id: Int): Long? {
return SignalDatabase.rawDatabase
.select(OneTimePreKeyTable.STALE_TIMESTAMP)
.from(OneTimePreKeyTable.TABLE_NAME)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.run()
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
}
}

View File

@@ -11,10 +11,8 @@ import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@RunWith(AndroidJUnit4::class)
@@ -24,14 +22,14 @@ class RecipientTableTest {
val harness = SignalActivityRule()
@Test
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIDoNotExpectHiddenToBeReturned() {
fun givenAHiddenRecipient_whenIQueryAllContacts_thenIExpectHiddenToBeReturned() {
val hiddenRecipient = harness.others[0]
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.queryAllContacts("Hidden")!!
assertEquals(0, results.count)
assertEquals(1, results.count)
}
@Test
@@ -59,7 +57,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
SignalDatabase.recipients.markHidden(hiddenRecipient)
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Hidden", false))!!
assertEquals(0, results.count)
}
@@ -130,7 +128,7 @@ class RecipientTableTest {
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
val results = SignalDatabase.recipients.querySignalContacts("Blocked", false)!!
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Blocked", false))!!
assertEquals(0, results.count)
}
@@ -167,16 +165,14 @@ class RecipientTableTest {
@Test
fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
SignalDatabase.recipients.markUnregistered(mainId)
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)
@@ -185,17 +181,15 @@ class RecipientTableTest {
@Test
fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() {
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!)
SignalDatabase.recipients.splitForStorageSyncIfNecessary(mainRecord.aci!!)
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(mainId, byAci)
assertEquals(byE164, byPni)

View File

@@ -0,0 +1,60 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_applyStorageSyncContactUpdate {
@get:Rule
val harness = SignalActivityRule()
@Test
fun insertMessageOnVerifiedToDefault() {
// GIVEN
val identities = ApplicationDependencies.getProtocolStore().aci().identities()
val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other)
identities.setVerified(other.id, harness.othersKeys[0].publicKey, IdentityTable.VerifiedStatus.VERIFIED)
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
val newProto = oldRecord
.toProto()
.newBuilder()
.identityState(ContactRecord.IdentityState.DEFAULT)
.build()
val newRecord = SignalContactRecord(oldRecord.id, newProto)
val update = StorageRecordUpdate<SignalContactRecord>(oldRecord, newRecord)
// WHEN
val oldVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
SignalDatabase.recipients.applyStorageSyncContactUpdate(update)
val newVerifiedStatus: IdentityTable.VerifiedStatus = identities.getIdentityRecord(other.id).get().verifiedStatus
// THEN
oldVerifiedStatus assertIs IdentityTable.VerifiedStatus.VERIFIED
newVerifiedStatus assertIs IdentityTable.VerifiedStatus.DEFAULT
val messages = MessageTableTestUtils.getMessages(SignalDatabase.threads.getThreadIdFor(other.id)!!)
messages.first().isIdentityDefault assertIs true
}
}

View File

@@ -7,13 +7,18 @@ import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.exists
import org.signal.core.util.orNull
import org.signal.core.util.readToSingleBoolean
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
@@ -32,21 +37,17 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_getAndPossiblyMerge {
@@ -55,7 +56,6 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
}
@Test
@@ -83,6 +83,42 @@ class RecipientTableTest_getAndPossiblyMerge {
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni insert") {
val id = process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+aci insert") {
val id = process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni+aci insert") {
val id = process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
}
test("e164+pni+aci insert, pni verified") {
val id = process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
val record = SignalDatabase.recipients.getRecord(id)
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
process(E164_A, PNI_A, ACI_A, pniVerified = false)
expectPniVerified()
}
}
@Test
@@ -92,9 +128,9 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, null, null)
}
test("no match, e164 and pni") {
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
test("no match, pni-only") {
process(null, PNI_A, null)
expect(null, PNI_A, null)
}
test("no match, aci-only") {
@@ -102,6 +138,11 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(null, null, ACI_A)
}
test("no match, e164 and pni") {
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
}
test("no match, e164 and aci") {
process(E164_A, null, ACI_A)
expect(E164_A, null, ACI_A)
@@ -111,6 +152,31 @@ class RecipientTableTest_getAndPossiblyMerge {
process(null, null, null)
}
test("pni matches, pni+aci provided, no pni session") {
given(E164_A, PNI_A, null)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("pni matches, pni+aci provided, pni session") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectSessionSwitchoverEvent(E164_A)
}
test("pni matches, pni+aci provided, pni session, pni-verified") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
expectPniVerified()
}
test("no match, all fields") {
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
@@ -166,6 +232,14 @@ class RecipientTableTest_getAndPossiblyMerge {
expectSessionSwitchoverEvent(E164_A)
}
test("e164 and pni matches, all provided, new aci, existing pni session, pni-verified") {
given(E164_A, PNI_A, null, pniSession = true)
process(E164_A, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
}
test("e164 and aci matches, all provided, new pni") {
given(E164_A, null, ACI_A)
process(E164_A, PNI_A, ACI_A)
@@ -308,6 +382,26 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, pni is changed") {
given(E164_A, PNI_B, ACI_A)
given(E164_B, PNI_A, null)
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, ACI_A)
expect(E164_B, null, null)
}
test("steal, pni is changed, aci left behind") {
given(E164_B, PNI_A, ACI_A)
given(E164_A, PNI_B, null)
process(E164_A, PNI_A, null)
expect(E164_B, null, ACI_A)
expect(E164_A, PNI_A, null)
}
test("steal, e164+pni & e164+pni, no aci provided, no pni session") {
given(E164_A, PNI_B, null)
given(E164_B, PNI_A, null)
@@ -352,7 +446,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, e164 & pni+e164, no aci provided") {
test("steal, e164 & pni+e164, no aci provided, pni session exists") {
val id1 = given(E164_A, null, null)
val id2 = given(E164_B, PNI_A, null, pniSession = true)
@@ -365,6 +459,16 @@ class RecipientTableTest_getAndPossiblyMerge {
expectSessionSwitchoverEvent(id2, E164_B)
}
test("steal, e164 & pni+e164, no aci provided, no pni session") {
given(E164_A, null, null)
given(E164_B, PNI_A, null)
process(E164_A, PNI_A, null)
expect(E164_A, PNI_A, null)
expect(E164_B, null, null)
}
test("steal, e164+pni+aci & e164+aci, no pni provided, change number") {
given(E164_A, PNI_A, ACI_A)
given(E164_B, null, ACI_B)
@@ -377,6 +481,76 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("steal, e164+aci & aci, no pni provided, existing aci session") {
given(E164_A, null, ACI_A, aciSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, null, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci & aci, no pni provided, existing aci session") {
given(E164_A, PNI_A, ACI_A, aciSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, PNI_A, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci & aci, no pni provided, existing pni session") {
given(E164_A, PNI_A, ACI_A, pniSession = true)
given(null, null, ACI_B)
process(E164_A, null, ACI_B)
expect(null, PNI_A, ACI_A)
expect(E164_A, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni & aci, no pni provided, no pni session") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expect(null, PNI_A, null)
expect(E164_A, null, ACI_A)
}
test("steal, e164+pni & aci, no pni provided, pni session") {
given(E164_A, PNI_A, null, pniSession = true)
given(null, null, ACI_A)
process(E164_A, null, ACI_A)
expect(null, PNI_A, null)
expect(E164_A, null, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci * pni+aci, all provided, aci sessions but not pni sessions, no SSE expected") {
given(E164_A, PNI_A, ACI_A, createThread = true, aciSession = true, pniSession = false)
given(null, PNI_B, ACI_B, createThread = false, aciSession = true, pniSession = false)
process(E164_A, PNI_B, ACI_A)
expect(E164_A, PNI_B, ACI_A)
expect(null, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("merge, e164 & pni & aci, all provided") {
given(E164_A, null, null)
given(null, PNI_A, null)
@@ -501,7 +675,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expectThreadMergeEvent(E164_A)
}
test("merge, e164+pni & aci, pni session, thread merge shadows") {
test("merge, e164+pni & aci, pni session, thread merge shadows SSE") {
given(E164_A, PNI_A, null, pniSession = true)
given(null, null, ACI_A)
@@ -533,6 +707,8 @@ class RecipientTableTest_getAndPossiblyMerge {
expectDeleted()
expect(E164_A, PNI_A, ACI_A)
expectPniVerified()
}
test("merge, e164+pni & aci, pni session, pni verified") {
@@ -545,6 +721,7 @@ class RecipientTableTest_getAndPossiblyMerge {
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectPniVerified()
}
test("merge, e164+pni & e164+pni+aci, change number") {
@@ -599,6 +776,41 @@ class RecipientTableTest_getAndPossiblyMerge {
expectThreadMergeEvent(E164_A)
}
test("merge, e164+pni & e164+aci, pni+aci provided, change number") {
given(E164_A, PNI_A, null)
given(E164_B, null, ACI_A)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectChangeNumberEvent()
}
test("merge, e164 + pni reassigned, aci abandoned") {
given(E164_A, PNI_A, ACI_A)
given(E164_B, PNI_B, ACI_B)
process(E164_A, PNI_A, ACI_B)
expect(null, null, ACI_A)
expect(E164_A, PNI_A, ACI_B)
expectChangeNumberEvent()
}
test("merge, e164 follows pni+aci") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectPniVerified()
}
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF)
given(null, null, ACI_A)
@@ -620,6 +832,22 @@ class RecipientTableTest_getAndPossiblyMerge {
process(E164_A, null, ACI_SELF, changeSelf = true)
expect(E164_A, null, ACI_SELF)
}
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
given(E164_SELF, PNI_SELF, ACI_SELF)
process(E164_SELF, PNI_A, ACI_A)
expect(E164_SELF, PNI_SELF, ACI_SELF)
}
test("local user, local e164+aci provided, changeSelf=false, leave pni alone") {
given(E164_SELF, PNI_A, ACI_SELF)
process(E164_SELF, PNI_SELF, ACI_A)
expect(E164_SELF, PNI_A, ACI_SELF)
}
}
/**
@@ -636,9 +864,9 @@ class RecipientTableTest_getAndPossiblyMerge {
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
val smsId3: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
@@ -685,8 +913,8 @@ class RecipientTableTest_getAndPossiblyMerge {
// Thread validation
assertEquals(threadIdAci, retrievedThreadId)
Assert.assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
// SMS validation
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
@@ -730,10 +958,10 @@ class RecipientTableTest_getAndPossiblyMerge {
// Identity validation
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
Assert.assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
// Session validation
Assert.assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
// Reaction validation
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
@@ -758,18 +986,42 @@ class RecipientTableTest_getAndPossiblyMerge {
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = time,
serverTimeMillis = time,
receivedTimeMillis = time,
body = body,
groupId = groupId.orNull(),
isUnidentified = true
)
}
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false)
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
groupId = groupId.orNull(),
body = body,
sentTimeMillis = time,
receivedTimeMillis = time,
serverTimeMillis = time,
isUnidentified = true
)
}
private fun identityKey(value: Byte): IdentityKey {
val byteArray = ByteArray(32)
byteArray[0] = value
return identityKey(byteArray)
}
private fun identityKey(value: ByteArray): IdentityKey {
val bytes = ByteArray(33)
bytes[0] = 0x05
bytes[1] = value
value.copyInto(bytes, 1)
return IdentityKey(bytes)
}
@@ -824,6 +1076,10 @@ class RecipientTableTest_getAndPossiblyMerge {
if (!test.sessionSwitchoverExpected) {
test.expectNoSessionSwitchoverEvent()
}
if (!test.pniVerifiedExpected) {
test.expectPniNotVerified()
}
} catch (e: Throwable) {
if (e.javaClass != exception) {
val error = java.lang.AssertionError("[$name] ${e.message}")
@@ -843,6 +1099,7 @@ class RecipientTableTest_getAndPossiblyMerge {
var changeNumberExpected = false
var threadMergeExpected = false
var sessionSwitchoverExpected = false
var pniVerifiedExpected = false
init {
// Need to delete these first to prevent foreign key crash
@@ -857,6 +1114,7 @@ class RecipientTableTest_getAndPossiblyMerge {
}
ApplicationDependencies.getRecipientCache().clear()
ApplicationDependencies.getRecipientCache().clearSelf()
RecipientId.clearCache()
}
@@ -865,14 +1123,15 @@ class RecipientTableTest_getAndPossiblyMerge {
pni: PNI?,
aci: ACI?,
createThread: Boolean = true,
pniSession: Boolean = false
pniSession: Boolean = false,
aciSession: Boolean = false
): RecipientId {
val id = insert(e164, pni, aci)
generatedIds += id
if (createThread) {
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
val result = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = id, time = (Math.random() * 10000000).toLong(), body = "1"))
SignalDatabase.threads.markAsActiveEarly(result.get().threadId)
}
if (pniSession) {
@@ -883,11 +1142,42 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalDatabase.sessions.store(pni, SignalProtocolAddress(pni.toString(), 1), SessionRecord())
}
if (aciSession) {
if (aci == null) {
throw IllegalArgumentException("aciSession = true but aci is null!")
}
SignalDatabase.sessions.store(aci, SignalProtocolAddress(aci.toString(), 1), SessionRecord())
}
if (aci != null) {
SignalDatabase.identities.saveIdentity(
addressName = aci.toString(),
recipientId = id,
identityKey = identityKey(Util.getSecretBytes(32)),
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
firstUse = true,
timestamp = 0,
nonBlockingApproval = false
)
}
if (pni != null) {
SignalDatabase.identities.saveIdentity(
addressName = pni.toString(),
recipientId = id,
identityKey = identityKey(Util.getSecretBytes(32)),
verifiedStatus = IdentityTable.VerifiedStatus.DEFAULT,
firstUse = true,
timestamp = 0,
nonBlockingApproval = false
)
}
return id
}
fun process(e164: String?, pni: PNI?, aci: ACI?, changeSelf: Boolean = false, pniVerified: Boolean = false): RecipientId {
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(serviceId = aci ?: pni, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
outputRecipientId = SignalDatabase.recipients.getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164, pniVerified = pniVerified, changeSelf = changeSelf)
generatedIds += outputRecipientId
return outputRecipientId
}
@@ -901,15 +1191,15 @@ class RecipientTableTest_getAndPossiblyMerge {
val expected = RecipientTuple(
e164 = e164,
pni = pni,
serviceId = aci ?: pni
aci = aci
)
val actual = RecipientTuple(
e164 = recipient.e164.orElse(null),
pni = recipient.pni.orElse(null),
serviceId = recipient.serviceId.orElse(null)
aci = recipient.aci.orElse(null)
)
assertEquals(expected, actual)
assertEquals("Recipient $id did not match expected result!", expected, actual)
}
fun expectDeleted() {
@@ -917,21 +1207,21 @@ class RecipientTableTest_getAndPossiblyMerge {
}
fun expectDeleted(id: RecipientId) {
SignalDatabase.rawDatabase
.select("1")
.from(RecipientTable.TABLE_NAME)
val found = SignalDatabase.rawDatabase
.exists(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", id)
.run()
.use { !it.moveToFirst() }
assertFalse("Expected $id to be deleted, but it's still present!", found)
}
fun expectChangeNumberEvent() {
assertEquals(1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
assertEquals("Missing change number event!", 1, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = true
}
fun expectNoChangeNumberEvent() {
assertEquals(0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
assertEquals("Unexpected change number event!", 0, SignalDatabase.messages.getChangeNumberMessageCount(outputRecipientId))
changeNumberExpected = false
}
@@ -941,42 +1231,57 @@ class RecipientTableTest_getAndPossiblyMerge {
fun expectSessionSwitchoverEvent(recipientId: RecipientId, e164: String) {
val event: SessionSwitchoverEvent? = getLatestSessionSwitchoverEvent(recipientId)
assertNotNull(event)
assertNotNull("Missing session switchover event! Expected one with e164 = $e164", event)
assertEquals(e164, event!!.e164)
sessionSwitchoverExpected = true
}
fun expectNoSessionSwitchoverEvent() {
assertNull(getLatestSessionSwitchoverEvent(outputRecipientId))
assertNull("Unexpected session switchover event!", getLatestSessionSwitchoverEvent(outputRecipientId))
}
fun expectThreadMergeEvent(previousE164: String) {
val event: ThreadMergeEvent? = getLatestThreadMergeEvent(outputRecipientId)
assertNotNull(event)
assertEquals(previousE164, event!!.previousE164)
assertNotNull("Missing thread merge event! Expected one with e164 = $previousE164", event)
assertEquals("E164 on thread merge event doesn't match!", previousE164, event!!.previousE164)
threadMergeExpected = true
}
fun expectNoThreadMergeEvent() {
assertNull(getLatestThreadMergeEvent(outputRecipientId))
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
}
fun expectPniVerified() {
assertTrue("Expected PNI to be verified!", isPniVerified(outputRecipientId))
pniVerifiedExpected = true
}
fun expectPniNotVerified() {
assertFalse("Expected PNI to be not be verified!", isPniVerified(outputRecipientId))
}
private fun isPniVerified(recipientId: RecipientId): Boolean {
return SignalDatabase.rawDatabase
.select(RecipientTable.PNI_SIGNATURE_VERIFIED)
.from(RecipientTable.TABLE_NAME)
.where("${RecipientTable.ID} = ?", recipientId)
.run()
.readToSingleBoolean(false)
}
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
val serviceIdString: String? = (aci ?: pni)?.toString()
val pniString: String? = pni?.toString()
val id: Long = SignalDatabase.rawDatabase.insert(
RecipientTable.TABLE_NAME,
null,
contentValuesOf(
RecipientTable.PHONE to e164,
RecipientTable.SERVICE_ID to serviceIdString,
RecipientTable.PNI_COLUMN to pniString,
RecipientTable.E164 to e164,
RecipientTable.ACI_COLUMN to aci?.toString(),
RecipientTable.PNI_COLUMN to pni?.toString(),
RecipientTable.REGISTERED to RecipientTable.RegisteredState.REGISTERED.id
)
)
assertTrue("Failed to insert! E164: $e164, ServiceId: $serviceIdString, PNI: $pniString", id > 0)
assertTrue("Failed to insert! E164: $e164, ACI: $aci, PNI: $pni", id > 0)
return RecipientId.from(id)
}
@@ -985,14 +1290,14 @@ class RecipientTableTest_getAndPossiblyMerge {
data class RecipientTuple(
val e164: String?,
val pni: PNI?,
val serviceId: ServiceId?
val aci: ACI?
) {
/**
* The intent here is to give nice diffs with the name of the constants rather than the values.
*/
override fun toString(): String {
return "(${e164.e164String()}, ${pni.pniString()}, ${serviceId.serviceIdString()})"
return "(${e164.e164String()}, ${pni.pniString()}, ${aci.aciString()})"
}
private fun String?.e164String(): String {
@@ -1016,12 +1321,9 @@ class RecipientTableTest_getAndPossiblyMerge {
} ?: "null"
}
private fun ServiceId?.serviceIdString(): String {
private fun ACI?.aciString(): String {
return this?.let {
when (it) {
PNI_A -> "PNI_A"
PNI_B -> "PNI_B"
PNI_SELF -> "PNI_SELF"
ACI_A -> "ACI_A"
ACI_B -> "ACI_B"
ACI_SELF -> "ACI_SELF"
@@ -1042,7 +1344,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
ThreadMergeEvent.parseFrom(bytes)
ThreadMergeEvent.ADAPTER.decode(bytes)
} else {
null
}
@@ -1060,7 +1362,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
SessionSwitchoverEvent.parseFrom(bytes)
SessionSwitchoverEvent.ADAPTER.decode(bytes)
} else {
null
}

View File

@@ -18,13 +18,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@@ -273,18 +270,34 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = wallClock,
serverTimeMillis = wallClock,
receivedTimeMillis = wallClock,
body = body,
groupId = groupId,
isUnidentified = true
)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
groupContext = groupContext,
serverGuid = null
)
}
companion object {
private val aliceServiceId: ServiceId = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
private val bobServiceId: ServiceId = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
private val aliceServiceId: ACI = ACI.from(UUID.fromString("3436efbe-5a76-47fa-a98a-7e72c948a82e"))
private val bobServiceId: ACI = ACI.from(UUID.fromString("8de7f691-0b60-4a68-9cd9-ed2f8453f9ed"))
private val masterKey = GroupMasterKey(Hex.fromStringCondensed("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"))
private val groupId = GroupId.v2(masterKey)

View File

@@ -12,19 +12,24 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendTableTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 0, createGroup = false)
private val distributionId1 = DistributionId.from(UUID.randomUUID())
private val distributionId2 = DistributionId.from(UUID.randomUUID())
private val distributionId3 = DistributionId.from(UUID.randomUUID())
@@ -460,7 +465,7 @@ class StorySendTableTest {
private fun makeRecipients(count: Int): List<RecipientId> {
return (1..count).map {
SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID()))
SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID()))
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@Suppress("ClassName")
class ThreadTableTest_active {
@Rule
@JvmField
val databaseRule = SignalDatabaseRule()
private lateinit var recipient: Recipient
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test
fun givenActiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(1, threads.count)
val record = ThreadTable.StaticReader(threads, InstrumentationRegistry.getInstrumentation().context).getNext()
assertNotNull(record)
assertEquals(record!!.recipient.id, recipient.id)
}
}
@Test
fun givenInactiveUnarchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIGetUnarchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getUnarchivedConversationList(
ConversationFilter.OFF,
false,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
}
@Test
fun givenActiveArchivedThread_whenIGetArchivedConversationList_thenIExpectThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(1, threads.count)
}
}
@Test
fun givenInactiveArchivedThread_whenIGetArchivedConversationList_thenIExpectNoThread() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.threads.setArchived(setOf(threadId), true)
SignalDatabase.threads.getArchivedConversationList(
ConversationFilter.OFF,
0,
10
).use { threads ->
assertEquals(0, threads.count)
}
val threadId2 = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
assertEquals(threadId2, threadId)
}
@Test
fun givenActiveArchivedThread_whenIDeactivateThread_thenIExpectNoMessages() {
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
MmsHelper.insert(recipient = recipient, threadId = threadId)
SignalDatabase.threads.update(threadId, false)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(1, it.count)
}
SignalDatabase.threads.deleteConversation(threadId)
SignalDatabase.messages.getConversation(threadId).use {
assertEquals(0, it.count)
}
}
}

View File

@@ -9,7 +9,7 @@ import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.conversationlist.model.ConversationFilter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@Suppress("ClassName")
@@ -23,7 +23,7 @@ class ThreadTableTest_pinned {
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test

View File

@@ -10,7 +10,7 @@ import org.signal.core.util.CursorUtil
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import java.util.UUID
@Suppress("ClassName")
@@ -25,7 +25,7 @@ class ThreadTableTest_recents {
@Before
fun setUp() {
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ServiceId.from(UUID.randomUUID())))
recipient = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())))
}
@Test

View File

@@ -13,9 +13,9 @@ import okio.ByteString
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.KbsEnclave
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.thoughtcrime.securesms.recipients.LiveRecipientCache
@@ -23,31 +23,25 @@ import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import java.security.KeyStore
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
import java.util.Optional
/**
* Dependency provider used for instrumentation tests (aka androidTests).
*
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess] and
* [KeyBackupService].
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
*/
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
class InstrumentationApplicationDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val keyBackupService: KeyBackupService
private val recipientCache: LiveRecipientCache
init {
@@ -74,20 +68,20 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
signalServiceUrls = arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdnUrlMap = mapOf(
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
emptyArray(),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
networkInterceptors = emptyList(),
dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS),
backupServerPublicParams = Base64.decode(BuildConfig.BACKUP_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {
@@ -96,8 +90,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
on { uncensoredConfiguration } doReturn uncensoredConfiguration
}
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() }
}
@@ -105,10 +97,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
return serviceNetworkAccessMock
}
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
override fun provideRecipientCache(): LiveRecipientCache {
return recipientCache
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobs
import android.net.Uri
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.StreamUtil
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.UriAttachmentBuilder
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
class AttachmentCompressionJobTest {
@get:Rule
val harness = SignalActivityRule()
@Test
fun testCompressionJobsWithDifferentTransformPropertiesCompleteSuccessfully() {
val imageBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("images/sample_image.png").use {
StreamUtil.readFully(it)
}
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication())
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
val firstCompressionJob: AttachmentCompressionJob = AttachmentCompressionJob.fromAttachment(firstDatabaseAttachment, false, -1)
var secondCompressionJob: AttachmentCompressionJob? = null
var firstJobResult: Job.Result? = null
var secondJobResult: Job.Result? = null
val secondJobLatch = CountDownLatch(1)
val jobThread = Thread {
firstCompressionJob.setContext(ApplicationDependencies.getApplication())
firstJobResult = firstCompressionJob.run()
secondJobLatch.await()
secondCompressionJob!!.setContext(ApplicationDependencies.getApplication())
secondJobResult = secondCompressionJob!!.run()
}
jobThread.start()
val secondPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val secondDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondPreUpload)
secondCompressionJob = AttachmentCompressionJob.fromAttachment(secondDatabaseAttachment, false, -1)
secondJobLatch.countDown()
jobThread.join()
firstJobResult!!.isSuccess assertIs true
secondJobResult!!.isSuccess assertIs true
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = transformProperties
)
}
}

View File

@@ -1,212 +0,0 @@
package org.thoughtcrime.securesms.jobs
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.protocol.ecc.Curve
import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.push.PreKeyState
import org.whispersystems.signalservice.internal.push.PreKeyStatus
@RunWith(AndroidJUnit4::class)
class PreKeysSyncJobTest {
@get:Rule
val harness = SignalActivityRule()
private val aciPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().aciPreKeys
private val pniPreKeyMeta: PreKeyMetadataStore
get() = SignalStore.account().pniPreKeys
private lateinit var job: PreKeysSyncJob
@Before
fun setUp() {
job = PreKeysSyncJob()
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
/**
* Create signed prekeys for both identities when both do not have registered prekeys according
* to our local state.
*/
@Test
fun runWithoutRegisteredKeysForBothIdentities() {
// GIVEN
aciPreKeyMeta.isSignedPreKeyRegistered = false
pniPreKeyMeta.isSignedPreKeyRegistered = false
lateinit var aciSignedPreKey: SignedPreKeyEntity
lateinit var pniSignedPreKey: SignedPreKeyEntity
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v2/keys/signed?identity=aci") { r ->
aciSignedPreKey = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/signed?identity=pni") { r ->
pniSignedPreKey = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.isSignedPreKeyRegistered assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
val aciVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.publicKey,
aciSignedPreKey.publicKey.serialize(),
aciSignedPreKey.signature
)
aciVerifySignatureResult assertIs true
val pniVerifySignatureResult = Curve.verifySignature(
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.publicKey,
pniSignedPreKey.publicKey.serialize(),
pniSignedPreKey.signature
)
pniVerifySignatureResult assertIs true
}
/**
* With 100 prekeys registered for each identity, do nothing.
*/
@Test
fun runWithRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(100)) }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIs currentPniKeyId
}
/**
* With 100 prekeys registered for ACI, but no PNI prekeys registered according to local state,
* do nothing for ACI but create PNI prekeys and update local state.
*/
@Test
fun runWithRegisteredKeysForAciIdentityOnly() {
// GIVEN
pniPreKeyMeta.isSignedPreKeyRegistered = false
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(100)) },
Put("/v2/keys/signed?identity=pni") { MockResponse().success() }
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
pniPreKeyMeta.isSignedPreKeyRegistered assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIs currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
}
/**
* With <10 prekeys registered for each identity, upload new.
*/
@Test
fun runWithLowNumberOfRegisteredKeysForBothIdentities() {
// GIVEN
val currentAciKeyId = aciPreKeyMeta.activeSignedPreKeyId
val currentPniKeyId = pniPreKeyMeta.activeSignedPreKeyId
val currentNextAciPreKeyId = aciPreKeyMeta.nextOneTimePreKeyId
val currentNextPniPreKeyId = pniPreKeyMeta.nextOneTimePreKeyId
lateinit var aciPreKeyStateRequest: PreKeyState
lateinit var pniPreKeyStateRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v2/keys?identity=aci") { MockResponse().success(PreKeyStatus(5)) },
Get("/v2/keys?identity=pni") { MockResponse().success(PreKeyStatus(5)) },
Put("/v2/keys/?identity=aci") { r ->
aciPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
},
Put("/v2/keys/?identity=pni") { r ->
pniPreKeyStateRequest = r.parsedRequestBody()
MockResponse().success()
}
)
// WHEN
val result: Job.Result = job.run()
// THEN
result.isSuccess assertIs true
aciPreKeyMeta.activeSignedPreKeyId assertIsNot currentAciKeyId
pniPreKeyMeta.activeSignedPreKeyId assertIsNot currentPniKeyId
aciPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextAciPreKeyId
pniPreKeyMeta.nextOneTimePreKeyId assertIsNot currentNextPniPreKeyId
ApplicationDependencies.getProtocolStore().aci().identityKeyPair.publicKey.let { aciIdentityKey ->
aciPreKeyStateRequest.identityKey assertIs aciIdentityKey
val verifySignatureResult = Curve.verifySignature(
aciIdentityKey.publicKey,
aciPreKeyStateRequest.signedPreKey.publicKey.serialize(),
aciPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
ApplicationDependencies.getProtocolStore().pni().identityKeyPair.publicKey.let { pniIdentityKey ->
pniPreKeyStateRequest.identityKey assertIs pniIdentityKey
val verifySignatureResult = Curve.verifySignature(
pniIdentityKey.publicKey,
pniPreKeyStateRequest.signedPreKey.publicKey.serialize(),
pniPreKeyStateRequest.signedPreKey.signature
)
verifySignatureResult assertIs true
}
}
}

View File

@@ -1,175 +0,0 @@
package org.thoughtcrime.securesms.jobs
import androidx.test.ext.junit.runners.AndroidJUnit4
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.libsignal.usernames.Username
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import org.whispersystems.signalservice.internal.push.WhoAmIResponse
import org.whispersystems.util.Base64UrlSafe
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RefreshOwnProfileJob__checkUsernameIsInSyncTest {
@get:Rule
val harness = SignalActivityRule()
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
SignalStore.phoneNumberPrivacy().clearUsernameOutOfSync()
}
@Test
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
}
@Test
fun givenLocalUsernameDoesNotMatchServerUsername_whenICheckUsernameIsInSync_thenIExpectRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
val serverUsername = "hello.3232"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(serverUsername))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertTrue(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenLocalAndNoServer_whenICheckUsernameIsInSync_thenIExpectRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(WhoAmIResponse())
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertTrue(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenLocalAndServerMatch_whenICheckUsernameIsInSync_thenIExpectNoRetry() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().success(ReserveUsernameResponse(Base64UrlSafe.encodeBytesWithoutPadding(Username.hash(username))))
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertFalse(didReserve)
assertFalse(didConfirm)
assertFalse(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
@Test
fun givenMismatchAndReservationFails_whenICheckUsernameIsInSync_thenIExpectNoConfirm() {
// GIVEN
var didReserve = false
var didConfirm = false
val username = "hello.32"
SignalDatabase.recipients.setUsername(harness.self.id, username)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/accounts/whoami") { r ->
MockResponse().success(
WhoAmIResponse().apply {
usernameHash = Base64UrlSafe.encodeBytesWithoutPadding(Username.hash("${username}23"))
}
)
},
Put("/v1/accounts/username_hash/reserve") { r ->
didReserve = true
MockResponse().failure(418)
},
Put("/v1/accounts/username_hash/confirm") { r ->
didConfirm = true
MockResponse().success()
}
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
// THEN
assertTrue(didReserve)
assertFalse(didConfirm)
assertTrue(SignalStore.phoneNumberPrivacy().isUsernameOutOfSync)
}
}

View File

@@ -22,10 +22,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password() throws IOException {
String pin = "password";
byte[] backupId = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] salt = Hex.fromStringCondensed("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -40,10 +40,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_another_password() throws IOException {
String pin = "anotherpassword";
byte[] backupId = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
byte[] salt = Hex.fromStringCondensed("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("88a787415a2ecd79da0d1016a82a27c5c695c9a19b88b0aa1d35683280aa9a67"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -58,10 +58,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_spaces_diacritics_and_non_arabic_numerals() throws IOException {
String pin = " Pass६örd ";
byte[] backupId = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
byte[] salt = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("9571f3fde1e58588ba49bcf82be1b301ca3859a6f59076f79a8f47181ef952bf"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());
@@ -78,10 +78,10 @@ public final class PinHashing_hashPin_Test {
@Test
public void argon2_hashed_pin_password_with_just_non_arabic_numerals() throws IOException {
String pin = " ६१८ ";
byte[] backupId = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
byte[] salt = Hex.fromStringCondensed("717dc111a98423a57196512606822fca646c653facd037c10728f14ba0be2ab3");
MasterKey masterKey = new MasterKey(Hex.fromStringCondensed("0432d735b32f66d0e3a70d4f9cc821a8529521a4937d26b987715d8eff4e4c54"));
PinHash hashedPin = PinHashUtil.hashPin(pin, new byte[]{});
PinHash hashedPin = PinHashUtil.hashPin(pin, salt);
KbsData kbsData = PinHashUtil.createNewKbsData(hashedPin, masterKey);
assertArrayEquals(hashedPin.accessKey(), kbsData.getKbsAccessKey());

View File

@@ -22,8 +22,9 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.EditMessage
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.SyncMessage
import kotlin.time.Duration.Companion.seconds
@RunWith(AndroidJUnit4::class)
@@ -38,7 +39,6 @@ class EditMessageSyncProcessorTest {
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@@ -46,13 +46,13 @@ class EditMessageSyncProcessorTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var processorV2: MessageContentProcessor
private lateinit var testResult: TestResults
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV2 = MessageContentProcessorV2(harness.context)
processorV2 = MessageContentProcessor(harness.context)
envelopeTimestamp = System.currentTimeMillis()
testResult = TestResults()
}
@@ -67,16 +67,17 @@ class EditMessageSyncProcessorTest {
val content = MessageContentFuzzer.fuzzTextMessage()
val metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, toRecipient.id)
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setTimestamp(originalTimestamp)
.setExpirationStartTimestamp(originalTimestamp)
.setMessage(content.dataMessage)
)
val syncContent = Content.Builder().syncMessage(
SyncMessage.Builder().sent(
SyncMessage.Sent.Builder()
.destinationServiceId(metadata.destinationServiceId.toString())
.timestamp(originalTimestamp)
.expirationStartTimestamp(originalTimestamp)
.message(content.dataMessage)
.build()
).build()
).build()
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage?.expireTimer ?: 0)
val syncTextMessage = TestMessage(
envelope = MessageContentFuzzer.envelope(originalTimestamp),
content = syncContent,
@@ -86,18 +87,20 @@ class EditMessageSyncProcessorTest {
val editTimestamp = originalTimestamp + 200
val editedContent = MessageContentFuzzer.fuzzTextMessage()
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setTimestamp(editTimestamp)
.setExpirationStartTimestamp(editTimestamp)
.setEditMessage(
EditMessage.newBuilder()
.setDataMessage(editedContent.dataMessage)
.setTargetSentTimestamp(originalTimestamp)
val editSyncContent = Content.Builder().syncMessage(
SyncMessage.Builder().sent(
SyncMessage.Sent.Builder()
.destinationServiceId(metadata.destinationServiceId.toString())
.timestamp(editTimestamp)
.expirationStartTimestamp(editTimestamp)
.editMessage(
EditMessage.Builder()
.dataMessage(editedContent.dataMessage)
.targetSentTimestamp(originalTimestamp)
.build()
)
)
.build()
).build()
).build()
val syncEditMessage = TestMessage(
@@ -109,38 +112,38 @@ class EditMessageSyncProcessorTest {
testResult.runSync(listOf(syncTextMessage, syncEditMessage))
SignalDatabase.recipients.setExpireMessages(toRecipient.id, content.dataMessage.expireTimer / 1000)
SignalDatabase.recipients.setExpireMessages(toRecipient.id, (content.dataMessage?.expireTimer ?: 0) / 1000)
val originalTextMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = originalTimestamp,
body = content.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = content.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = content.dataMessage.bodyRangesList.toBodyRangeList()
bodyRanges = content.dataMessage?.bodyRanges.toBodyRangeList()
)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(toRecipient)
val originalMessageId = SignalDatabase.messages.insertMessageOutbox(originalTextMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(originalMessageId, true)
if (content.dataMessage.expireTimer > 0) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(originalMessageId, originalTimestamp)
}
val editMessage = OutgoingMessage(
threadRecipient = toRecipient,
sentTimeMillis = editTimestamp,
body = editedContent.dataMessage.body,
expiresIn = content.dataMessage.expireTimer.seconds.inWholeMilliseconds,
body = editedContent.dataMessage?.body ?: "",
expiresIn = content.dataMessage?.expireTimer?.seconds?.inWholeMilliseconds ?: 0,
isUrgent = true,
isSecure = true,
bodyRanges = editedContent.dataMessage.bodyRangesList.toBodyRangeList(),
bodyRanges = editedContent.dataMessage?.bodyRanges.toBodyRangeList(),
messageToEdit = originalMessageId
)
val editMessageId = SignalDatabase.messages.insertMessageOutbox(editMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(editMessageId, true)
if (content.dataMessage.expireTimer > 0) {
if ((content.dataMessage?.expireTimer ?: 0) > 0) {
SignalDatabase.messages.markExpireStarted(editMessageId, originalTimestamp)
}
testResult.collectLocal()
@@ -167,7 +170,7 @@ class EditMessageSyncProcessorTest {
fun runSync(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasSyncMessage()) {
if (content.syncMessage != null) {
processorV2.process(
envelope,
content,

View File

@@ -1,62 +0,0 @@
package org.thoughtcrime.securesms.messages
import android.app.Application
import androidx.test.core.app.ApplicationProvider
import org.junit.Rule
import org.thoughtcrime.securesms.messages.MessageContentProcessor.ExceptionMetadata
import org.thoughtcrime.securesms.messages.MessageContentProcessor.MessageState
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.TestProtos
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
abstract class MessageContentProcessorTest {
@get:Rule
val harness = SignalActivityRule()
protected fun MessageContentProcessor.doProcess(
messageState: MessageState = MessageState.DECRYPTED_OK,
content: SignalServiceContent,
exceptionMetadata: ExceptionMetadata = ExceptionMetadata("sender", 1),
timestamp: Long = 100L,
smsMessageId: Long = -1L
) {
process(messageState, content, exceptionMetadata, timestamp, smsMessageId)
}
protected fun createNormalContentTestSubject(): MessageContentProcessor {
val context = ApplicationProvider.getApplicationContext<Application>()
return MessageContentProcessor.create(context)
}
/**
* Creates a valid ServiceContentProto with a data message which can be built via
* `injectDataMessage`. This function is intended to be built on-top of for more
* specific scenario in subclasses.
*
* Example can be seen in __handleStoryMessageTest
*/
protected fun createServiceContentWithDataMessage(
messageSender: Recipient = Recipient.resolved(harness.others.first()),
injectDataMessage: SignalServiceProtos.DataMessage.Builder.() -> Unit
): SignalServiceContentProto {
return TestProtos.build {
serviceContent(
localAddress = address(uuid = harness.self.requireServiceId().uuid()).build(),
metadata = metadata(
address = address(uuid = messageSender.requireServiceId().uuid()).build()
).build()
).apply {
content = content().apply {
dataMessage = dataMessage().apply {
injectDataMessage()
}.build()
}.build()
}.build()
}
}
}

View File

@@ -1,313 +0,0 @@
package org.thoughtcrime.securesms.messages
import android.database.Cursor
import android.util.Base64
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.signal.core.util.readToList
import org.signal.core.util.select
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.InMemoryLogger
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.messages.SignalServiceMetadata
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.serialize.SignalServiceAddressProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.SignalServiceMetadataProtobufSerializer
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import java.util.Optional
@RunWith(AndroidJUnit4::class)
class MessageContentProcessorTestV2 {
companion object {
private val TAGS = listOf(MessageContentProcessor.TAG, MessageContentProcessorV2.TAG, AttachmentTable.TAG)
private val GENERALIZE_TAG = mapOf(
MessageContentProcessor.TAG to "MCP",
MessageContentProcessorV2.TAG to "MCP",
AttachmentTable.TAG to AttachmentTable.TAG
)
private val IGNORE_MESSAGE_COLUMNS = listOf(
MessageTable.DATE_RECEIVED,
MessageTable.NOTIFIED_TIMESTAMP,
MessageTable.REACTIONS_LAST_SEEN,
MessageTable.NOTIFIED
)
private val IGNORE_ATTACHMENT_COLUMNS = listOf(
AttachmentTable.UNIQUE_ID,
AttachmentTable.TRANSFER_FILE
)
}
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV1: MessageContentProcessor
private lateinit var processorV2: MessageContentProcessorV2
private lateinit var testResult: TestResults
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV1 = MessageContentProcessor(harness.context)
processorV2 = MessageContentProcessorV2(harness.context)
envelopeTimestamp = System.currentTimeMillis()
testResult = TestResults()
}
@Test
fun textMessage() {
var start = envelopeTimestamp
val messages: List<TestMessage> = (0 until 100).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(messages)
testResult.runV1(messages)
testResult.assert()
}
@Test
fun mediaMessage() {
var start = envelopeTimestamp
val textMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzTextMessage(),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val firstBatchMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageWithBody(textMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val secondBatchNoContentMediaMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoContent(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
val thirdBatchNoTextMediaMessagesMessages: List<TestMessage> = (0 until 10).map {
start += 200
TestMessage(
envelope = MessageContentFuzzer.envelope(start),
content = MessageContentFuzzer.fuzzMediaMessageNoText(textMessages + firstBatchMediaMessages),
metadata = MessageContentFuzzer.envelopeMetadata(harness.others[0], harness.self.id),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(start)
)
}
testResult.runV2(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.runV1(textMessages + firstBatchMediaMessages + secondBatchNoContentMediaMessages + thirdBatchNoTextMediaMessagesMessages)
testResult.assert()
}
private inner class TestResults {
private lateinit var v1Logs: List<Entry>
private lateinit var v1Messages: List<List<Pair<String, String?>>>
private lateinit var v1Attachments: List<List<Pair<String, String?>>>
private lateinit var v2Logs: List<Entry>
private lateinit var v2Messages: List<List<Pair<String, String?>>>
private lateinit var v2Attachments: List<List<Pair<String, String?>>>
fun runV1(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV1.process(
MessageContentProcessor.MessageState.DECRYPTED_OK,
toSignalServiceContent(envelope, content, metadata, serverDeliveredTimestamp),
null,
envelope.timestamp,
-1
)
ThreadUtil.sleep(1)
}
}
v1Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v1Messages = dumpMessages()
v1Attachments = dumpAttachments()
}
fun runV2(messages: List<TestMessage>) {
messages.forEach { (envelope, content, metadata, serverDeliveredTimestamp) ->
if (content.hasDataMessage()) {
processorV2.process(
envelope,
content,
metadata,
serverDeliveredTimestamp,
false
)
ThreadUtil.sleep(1)
}
}
v2Logs = harness.inMemoryLogger.logs()
harness.inMemoryLogger.clear()
v2Messages = dumpMessages()
v2Attachments = dumpAttachments()
cleanup()
}
fun cleanup() {
SignalDatabase.rawDatabase.withinTransaction { db ->
SignalDatabase.threads.deleteAllConversations()
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${MessageTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${ThreadTable.TABLE_NAME}'")
db.execSQL("DELETE FROM sqlite_sequence WHERE name = '${AttachmentTable.TABLE_NAME}'")
}
}
fun assert() {
v2Logs.zip(v1Logs)
.forEach { (v2, v1) ->
GENERALIZE_TAG[v2.tag]!!.assertIs(GENERALIZE_TAG[v1.tag]!!)
if (v2.tag != AttachmentTable.TAG) {
if (v2.message?.startsWith("[") == true && v1.message?.startsWith("[") == false) {
v2.message!!.substring(v2.message!!.indexOf(']') + 2).assertIs(v1.message)
} else {
v2.message.assertIs(v1.message)
}
} else {
if (v2.message?.startsWith("Inserted attachment at ID: AttachmentId::") == true) {
v2.message!!
.substring(0, v2.message!!.indexOf(','))
.assertIs(
v1.message!!
.substring(0, v1.message!!.indexOf(','))
)
} else {
v2.message.assertIs(v1.message)
}
}
v2.throwable.assertIs(v1.throwable)
}
v2Messages.zip(v1Messages)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
v2Attachments.zip(v1Attachments)
.forEach { (v2, v1) ->
v2.assertIs(v1)
}
}
private fun InMemoryLogger.logs(): List<Entry> {
return entries()
.filter { TAGS.contains(it.tag) }
}
private fun dumpMessages(): List<List<Pair<String, String?>>> {
return dumpTable(MessageTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_MESSAGE_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpAttachments(): List<List<Pair<String, String?>>> {
return dumpTable(AttachmentTable.TABLE_NAME)
.map { row ->
val newRow = row.toMutableList()
newRow.removeIf { IGNORE_ATTACHMENT_COLUMNS.contains(it.first) }
newRow
}
}
private fun dumpTable(table: String): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
}
map
}
}
}
private fun toSignalServiceContent(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long): SignalServiceContent {
val localAddress = SignalServiceAddress(metadata.destinationServiceId, Optional.ofNullable(SignalStore.account().e164))
val signalServiceMetadata = SignalServiceMetadata(
SignalServiceAddress(metadata.sourceServiceId, Optional.ofNullable(metadata.sourceE164)),
metadata.sourceDeviceId,
envelope.timestamp,
envelope.serverTimestamp,
serverDeliveredTimestamp,
metadata.sealedSender,
envelope.serverGuid,
Optional.ofNullable(metadata.groupId),
metadata.destinationServiceId.toString()
)
val contentProto = SignalServiceContentProto.newBuilder()
.setLocalAddress(SignalServiceAddressProtobufSerializer.toProtobuf(localAddress))
.setMetadata(SignalServiceMetadataProtobufSerializer.toProtobuf(signalServiceMetadata))
.setContent(content)
.build()
return SignalServiceContent.createFromProto(contentProto)!!
}
}

View File

@@ -1,84 +0,0 @@
package org.thoughtcrime.securesms.messages
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.thoughtcrime.securesms.database.GroupReceiptTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MessageContentProcessorV2__recipientStatusTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var processorV2: MessageContentProcessorV2
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processorV2 = MessageContentProcessorV2(harness.context)
envelopeTimestamp = System.currentTimeMillis()
}
/**
* Process sync group sent text transcript with partial send and then process second sync with recipient update
* flag set to true with the rest of the send completed.
*/
@Test
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember())
val groupContextV2 = GroupContextV2.newBuilder().setRevision(0).setMasterKey(masterKey.serialize().toProtoByteString()).build()
val initialTextMessage = DataMessage.newBuilder().buildWith {
body = MessageContentFuzzer.string()
groupV2 = groupContextV2
timestamp = envelopeTimestamp
}
processorV2.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
val threadId = SignalDatabase.threads.getThreadIdFor(groupRecipientId)!!
val firstSyncMessages = MessageTableTestUtils.getMessages(threadId)
val firstMessageId = firstSyncMessages[0].id
val firstReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
processorV2.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
val secondSyncMessages = MessageTableTestUtils.getMessages(threadId)
val secondReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
firstSyncMessages.size assertIs 1
firstSyncMessages[0].body assertIs initialTextMessage.body
firstReceiptInfo.first { it.recipientId == harness.others[0] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
firstReceiptInfo.first { it.recipientId == harness.others[1] }.status assertIs GroupReceiptTable.STATUS_UNKNOWN
secondSyncMessages.size assertIs 1
secondSyncMessages[0].body assertIs initialTextMessage.body
secondReceiptInfo.first { it.recipientId == harness.others[0] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
secondReceiptInfo.first { it.recipientId == harness.others[1] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
}
}

View File

@@ -1,181 +0,0 @@
package org.thoughtcrime.securesms.messages
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.signal.core.util.requireLong
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
import org.signal.storageservice.protos.groups.local.DecryptedMember
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.MmsHelper
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.TestProtos
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import kotlin.random.Random
@Suppress("ClassName")
class MessageContentProcessor__handleStoryMessageTest : MessageContentProcessorTest() {
@Before
fun setUp() {
SignalDatabase.messages.deleteAllThreads()
}
@After
fun tearDown() {
SignalDatabase.messages.deleteAllThreads()
}
@Test
fun givenContentWithADirectStoryReplyWhenIProcessThenIInsertAReplyInTheCorrectThread() {
val sender = Recipient.resolved(harness.others.first())
val senderThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(sender)
val myStory = Recipient.resolved(SignalDatabase.distributionLists.getRecipientId(DistributionListId.MY_STORY)!!)
val myStoryThread = SignalDatabase.threads.getOrCreateThreadIdFor(myStory)
val expectedSentTime = 200L
val storyMessageId = MmsHelper.insert(
sentTimeMillis = expectedSentTime,
recipient = myStory,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = myStoryThread
)
SignalDatabase.storySends.insert(
messageId = storyMessageId,
recipientIds = listOf(sender.id),
sentTimestamp = expectedSentTime,
allowsReplies = true,
distributionId = DistributionId.MY_STORY
)
val expectedBody = "Hello!"
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
messageSender = sender,
storyAuthor = harness.self,
storySentTimestamp = expectedSentTime
) {
body = expectedBody
}
runTestWithContent(contentProto = storyContent)
val replyId = SignalDatabase.messages.getConversation(senderThreadId, 0, 1).use {
it.moveToFirst()
it.requireLong(MessageTable.ID)
}
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
assertEquals(ParentStoryId.DirectReply(storyMessageId).serialize(), replyRecord.parentStoryId!!.serialize())
assertEquals(expectedBody, replyRecord.body)
SignalDatabase.messages.deleteAllThreads()
}
@Test
fun givenContentWithAGroupStoryReplyWhenIProcessThenIInsertAReplyToTheCorrectStory() {
val sender = Recipient.resolved(harness.others[0])
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(
listOf(
DecryptedMember.newBuilder()
.setUuid(harness.self.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setUuid(sender.requireServiceId().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
.build()
)
)
.setRevision(0)
.build()
val group = SignalDatabase.groups.create(
groupMasterKey,
decryptedGroupState
)
val groupRecipient = Recipient.externalGroupExact(group!!)
val threadForGroup = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
val insertResult = MmsHelper.insert(
message = IncomingMediaMessage(
from = sender.id,
sentTimeMillis = 100L,
serverTimeMillis = 101L,
receivedTimeMillis = 102L,
storyType = StoryType.STORY_WITH_REPLIES
),
threadId = threadForGroup
)
val expectedBody = "Hello, World!"
val storyContent: SignalServiceContentProto = createServiceContentWithStoryContext(
messageSender = sender,
storyAuthor = sender,
storySentTimestamp = 100L
) {
groupV2 = TestProtos.build { groupContextV2(masterKeyBytes = groupMasterKey.serialize()).build() }
body = expectedBody
}
runTestWithContent(storyContent)
val replyId = SignalDatabase.messages.getStoryReplies(insertResult.get().messageId).use { cursor ->
assertEquals(1, cursor.count)
cursor.moveToFirst()
cursor.requireLong(MessageTable.ID)
}
val replyRecord = SignalDatabase.messages.getMessageRecord(replyId) as MediaMmsMessageRecord
assertEquals(ParentStoryId.GroupReply(insertResult.get().messageId).serialize(), replyRecord.parentStoryId?.serialize())
assertEquals(threadForGroup, replyRecord.threadId)
assertEquals(expectedBody, replyRecord.body)
SignalDatabase.messages.deleteGroupStoryReplies(insertResult.get().messageId)
SignalDatabase.messages.deleteAllThreads()
}
/**
* Creates a ServiceContent proto with a StoryContext, and then
* uses `injectDataMessage` to fill in the data message object.
*/
private fun createServiceContentWithStoryContext(
messageSender: Recipient,
storyAuthor: Recipient,
storySentTimestamp: Long,
injectDataMessage: DataMessage.Builder.() -> Unit
): SignalServiceContentProto {
return createServiceContentWithDataMessage(messageSender) {
storyContext = TestProtos.build {
storyContext(
sentTimestamp = storySentTimestamp,
authorUuid = storyAuthor.requireServiceId().toString()
).build()
}
injectDataMessage()
}
}
private fun runTestWithContent(contentProto: SignalServiceContentProto) {
val content = SignalServiceContent.createFromProto(contentProto)
val testSubject = createNormalContentTestSubject()
testSubject.doProcess(content = content!!)
}
}

View File

@@ -1,33 +0,0 @@
package org.thoughtcrime.securesms.messages
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.thoughtcrime.securesms.database.SignalDatabase
import org.whispersystems.signalservice.api.messages.SignalServiceContent
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
@Suppress("ClassName")
class MessageContentProcessor__handleTextMessageTest : MessageContentProcessorTest() {
@Test
fun givenContentWithATextMessageWhenIProcessThenIInsertTheTextMessage() {
val testSubject: MessageContentProcessor = createNormalContentTestSubject()
val expectedBody = "Hello, World!"
val contentProto: SignalServiceContentProto = createServiceContentWithDataMessage {
body = expectedBody
}
val content = SignalServiceContent.createFromProto(contentProto)
// WHEN
testSubject.doProcess(content = content!!)
// THEN
val record = SignalDatabase.messages.getMessageRecord(1)
val threadSize = SignalDatabase.messages.getMessageCountForThread(record.threadId)
assertEquals(1, threadSize)
assertTrue(record.isSecure)
assertEquals(expectedBody, record.body)
}
}

View File

@@ -0,0 +1,84 @@
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import okio.ByteString.Companion.toByteString
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.GroupReceiptTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.GroupContextV2
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class MessageContentProcessor__recipientStatusTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var processor: MessageContentProcessor
private var envelopeTimestamp: Long = 0
@Before
fun setup() {
processor = MessageContentProcessor(harness.context)
envelopeTimestamp = System.currentTimeMillis()
}
/**
* Process sync group sent text transcript with partial send and then process second sync with recipient update
* flag set to true with the rest of the send completed.
*/
@Test
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember())
val groupContextV2 = GroupContextV2.Builder().revision(0).masterKey(masterKey.serialize().toByteString()).build()
val initialTextMessage = DataMessage.Builder().buildWith {
body = MessageContentFuzzer.string()
groupV2 = groupContextV2
timestamp = envelopeTimestamp
}
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
val threadId = SignalDatabase.threads.getThreadIdFor(groupRecipientId)!!
val firstSyncMessages = MessageTableTestUtils.getMessages(threadId)
val firstMessageId = firstSyncMessages[0].id
val firstReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
val secondSyncMessages = MessageTableTestUtils.getMessages(threadId)
val secondReceiptInfo = SignalDatabase.groupReceipts.getGroupReceiptInfo(firstMessageId)
firstSyncMessages.size assertIs 1
firstSyncMessages[0].body assertIs initialTextMessage.body
firstReceiptInfo.first { it.recipientId == harness.others[0] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
firstReceiptInfo.first { it.recipientId == harness.others[1] }.status assertIs GroupReceiptTable.STATUS_UNKNOWN
secondSyncMessages.size assertIs 1
secondSyncMessages[0].body assertIs initialTextMessage.body
secondReceiptInfo.first { it.recipientId == harness.others[0] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
secondReceiptInfo.first { it.recipientId == harness.others[1] }.status assertIs GroupReceiptTable.STATUS_UNDELIVERED
}
}

View File

@@ -6,9 +6,9 @@ import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -25,9 +25,9 @@ import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.FakeClientHelpers
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketProtos.WebSocketRequestMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern
import kotlin.random.Random
import kotlin.time.Duration.Companion.minutes
@@ -37,7 +37,7 @@ import android.util.Log as AndroidLog
/**
* Sends N messages from Bob to Alice to track performance of Alice's processing of messages.
*/
// @Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@RunWith(AndroidJUnit4::class)
class MessageProcessingPerformanceTest {
@@ -58,14 +58,14 @@ class MessageProcessingPerformanceTest {
mockkStatic(UnidentifiedAccessUtil::class)
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkObject(MessageContentProcessorV2)
every { MessageContentProcessorV2.create(harness.application) } returns TimingMessageContentProcessorV2(harness.application)
mockkObject(MessageContentProcessor)
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
}
@After
fun after() {
unmockkStatic(UnidentifiedAccessUtil::class)
unmockkStatic(MessageContentProcessorV2::class)
unmockkStatic(MessageContentProcessor::class)
}
@Test
@@ -92,7 +92,7 @@ class MessageProcessingPerformanceTest {
val messageCount = 100
val envelopes = generateInboundEnvelopes(bobClient, messageCount)
val firstTimestamp = envelopes.first().timestamp
val lastTimestamp = envelopes.last().timestamp
val lastTimestamp = envelopes.last().timestamp ?: 0
// Inject the envelopes into the websocket
Thread {
@@ -106,7 +106,7 @@ class MessageProcessingPerformanceTest {
// Wait until they've all been fully decrypted + processed
harness
.inMemoryLogger
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(lastTimestamp))
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(lastTimestamp))
.awaitFor(1.minutes)
harness.inMemoryLogger.flush()
@@ -125,7 +125,7 @@ class MessageProcessingPerformanceTest {
// Calculate MessageContentProcessor
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessorV2.TAG }.drop(2)
val takeLast: List<Entry> = entries.filter { it.tag == TimingMessageContentProcessor.TAG }.drop(2)
val iterator = takeLast.iterator()
var processCount = 0L
var processDuration = 0L
@@ -141,7 +141,7 @@ class MessageProcessingPerformanceTest {
// Calculate messages per second from "retrieving" first message post session initialization to processing last message
val start = entries.first { it.message == "Retrieved envelope! $firstTimestamp" }
val end = entries.first { it.message == TimingMessageContentProcessorV2.endTag(lastTimestamp) }
val end = entries.first { it.message == TimingMessageContentProcessor.endTag(lastTimestamp) }
val duration = (end.timestamp - start.timestamp).toFloat() / 1000f
val messagePerSecond = messageCount.toFloat() / duration
@@ -156,7 +156,7 @@ class MessageProcessingPerformanceTest {
val aliceProcessFirstMessageLatch = harness
.inMemoryLogger
.getLockForUntil(TimingMessageContentProcessorV2.endTagPredicate(firstPreKeyMessageTimestamp))
.getLockForUntil(TimingMessageContentProcessor.endTagPredicate(firstPreKeyMessageTimestamp))
Thread { aliceClient.process(encryptedEnvelope, System.currentTimeMillis()) }.start()
aliceProcessFirstMessageLatch.awaitFor(15.seconds)
@@ -178,32 +178,19 @@ class MessageProcessingPerformanceTest {
}
private fun webSocketTombstone(): ByteString {
return WebSocketMessage
.newBuilder()
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/queue/empty")
)
.build()
.toByteArray()
.toByteString()
return WebSocketMessage(request = WebSocketRequestMessage(verb = "PUT", path = "/api/v1/queue/empty")).encodeByteString()
}
private fun Envelope.toWebSocketPayload(): ByteString {
return WebSocketMessage
.newBuilder()
.setType(WebSocketMessage.Type.REQUEST)
.setRequest(
WebSocketRequestMessage.newBuilder()
.setVerb("PUT")
.setPath("/api/v1/message")
.setId(Random(System.currentTimeMillis()).nextLong())
.addHeaders("X-Signal-Timestamp: ${this.timestamp}")
.setBody(this.toByteString())
return WebSocketMessage(
type = WebSocketMessage.Type.REQUEST,
request = WebSocketRequestMessage(
verb = "PUT",
path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.encodeByteString()
)
.build()
.toByteArray()
.toByteString()
).encodeByteString()
}
}

View File

@@ -0,0 +1,225 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_readSyncs {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var alice: RecipientId
private lateinit var bob: RecipientId
private lateinit var group: GroupTestingUtils.TestGroupInfo
private lateinit var processor: MessageContentProcessor
@Before
fun setUp() {
alice = harness.others[0]
bob = harness.others[1]
group = harness.group!!
processor = MessageContentProcessor(harness.context)
val threadIdSlot = slot<Long>()
mockkStatic(ThreadUpdateJob::class)
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
SignalDatabase.threads.update(threadIdSlot.captured, false)
}
}
@After
fun tearDown() {
unmockkStatic(ThreadUpdateJob::class)
}
@Test
fun handleSynchronizeReadMessage() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message1Timestamp, alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadMessageMissingTimestamp() {
val messageHelper = MessageHelper()
messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEdits() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.syncReadMessage(alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(message1Timestamp).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(editMessage1Timestamp1).timestamp
val message2Timestamp = messageHelper.incomingMedia().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(alice to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEditsInGroup() {
val messageHelper = MessageHelper()
val message1Timestamp = messageHelper.incomingText(sender = alice, destination = group.recipientId).timestamp
messageHelper.syncReadMessage(alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = alice, destination = group.recipientId).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = alice, destination = group.recipientId).timestamp
val message2Timestamp = messageHelper.incomingMedia(sender = bob, destination = group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(group.recipientId)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(bob to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
private inner class MessageHelper(var startTime: Long = System.currentTimeMillis()) {
fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzStickerMediaMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.editTextMessage(
targetTimestamp = targetTimestamp,
editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
).dataMessage!!
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncReadMessage(vararg reads: Pair<RecipientId, Long>): MessageData {
startTime += 1000
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
}
private data class MessageData(val serverGuid: UUID = UUID.randomUUID(), val timestamp: Long)
}

View File

@@ -1,11 +1,12 @@
package org.thoughtcrime.securesms.messages
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
data class TestMessage(
val envelope: SignalServiceProtos.Envelope,
val content: SignalServiceProtos.Content,
val envelope: Envelope,
val content: Content,
val metadata: EnvelopeMetadata,
val serverDeliveredTimestamp: Long
)

View File

@@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
companion object {
val TAG = Log.tag(TimingMessageContentProcessor::class.java)
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
entry.tag == TAG && entry.message == endTag(timestamp)
}
private fun startTag(timestamp: Long) = "$timestamp start"
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp!!))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
Log.d(TAG, endTag(envelope.timestamp!!))
}
}

View File

@@ -1,26 +0,0 @@
package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
class TimingMessageContentProcessorV2(context: Context) : MessageContentProcessorV2(context) {
companion object {
val TAG = Log.tag(TimingMessageContentProcessorV2::class.java)
fun endTagPredicate(timestamp: Long): LogPredicate = { entry ->
entry.tag == TAG && entry.message == endTag(timestamp)
}
private fun startTag(timestamp: Long) = "$timestamp start"
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean) {
Log.d(TAG, startTag(envelope.timestamp))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent)
Log.d(TAG, endTag(envelope.timestamp))
}
}

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.success
import org.whispersystems.signalservice.api.util.Usernames
import org.whispersystems.signalservice.internal.push.ReserveUsernameResponse
import java.util.concurrent.TimeUnit
@@ -56,27 +57,10 @@ class UsernameEditFragmentTest {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testUsernameCreationInRegistration() {
val scenario = createScenario(true)
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
noViewFoundException.assertIsNull()
val toolbar = view as Toolbar
toolbar.navigationIcon.assertIsNull()
}
onView(withText(R.string.UsernameEditFragment__add_a_username)).check(matches(isDisplayed()))
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
}
@Ignore("Flakey espresso test.")
@Test
fun testUsernameCreationOutsideOfRegistration() {
val scenario = createScenario()
val scenario = createScenario(UsernameEditMode.NORMAL)
scenario.moveToState(Lifecycle.State.RESUMED)
@@ -96,7 +80,7 @@ class UsernameEditFragmentTest {
fun testNicknameUpdateHappyPath() {
val nickname = "Spiderman"
val discriminator = "4578"
val username = "$nickname${UsernameState.DELIMITER}$discriminator"
val username = "$nickname${Usernames.DELIMITER}$discriminator"
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Put("/v1/accounts/username/reserved") {
@@ -107,7 +91,7 @@ class UsernameEditFragmentTest {
}
)
val scenario = createScenario(isInRegistration = true)
val scenario = createScenario(UsernameEditMode.NORMAL)
scenario.moveToState(Lifecycle.State.RESUMED)
onView(withId(R.id.username_text)).perform(typeText(nickname))
@@ -131,8 +115,8 @@ class UsernameEditFragmentTest {
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
}
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
return launchFragmentInContainer(
fragmentArgs = fragmentArgs,
themeResId = R.style.Signal_DayNight_NoActionBar

View File

@@ -6,16 +6,14 @@ import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
@@ -29,35 +27,68 @@ class ContactRecordProcessorTest {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
}
@Test
fun process_splitContact_normalSplit() {
fun process_splitContact_normalSplit_twoRecords() {
// GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setServiceId(ACI_A.toString())
setUnregisteredAtTimestamp(100)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByServiceId(PNI_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(originalId, byAci)
assertEquals(byE164, byPni)
assertNotEquals(byAci, byE164)
}
@Test
fun process_splitContact_normalSplit_oneRecord() {
// GIVEN
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
assertEquals(originalId, byAci)
assertEquals(byE164, byPni)
@@ -70,23 +101,29 @@ class ContactRecordProcessorTest {
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setServiceId(ACI_A.toString())
setUnregisteredAtTimestamp(0)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 0
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setServiceId(PNI_A.toString())
setServicePni(PNI_A.toString())
setServiceE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
aci = PNI_A.toString(),
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
subject.process(listOf(remote1, remote2), StorageSyncHelper.KEY_GENERATOR)
// THEN
val byAci: RecipientId = SignalDatabase.recipients.getByServiceId(ACI_A).get()
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
@@ -95,14 +132,14 @@ class ContactRecordProcessorTest {
assertEquals(byAci, byE164)
}
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
private fun buildRecord(id: StorageId, record: ContactRecord): SignalContactRecord {
return SignalContactRecord(id, record)
}
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId.raw))
.where("${RecipientTable.ID} = ?", recipientId)
.run()
}

View File

@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
/**
* Welcome to Alice's Client.
@@ -27,7 +27,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
private val aliceSenderCertificate = FakeClientHelpers.createCertificateFor(
trustRoot = trustRoot,
uuid = serviceId.uuid(),
uuid = serviceId.rawUuid,
e164 = e164,
deviceId = 1,
identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey,
@@ -40,7 +40,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
ApplicationDependencies.getIncomingMessageObserver()
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() }
?.forEach { ApplicationDependencies.getJobManager().add(it) }
?.forEach { it.enqueue() }
bufferedStore.flushToDisk()
val end = System.currentTimeMillis()

View File

@@ -31,7 +31,7 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -49,7 +49,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
private val serviceAddress = SignalServiceAddress(serviceId, e164)
private val registrationId = KeyHelper.generateRegistrationId(false)
private val aciStore = BobSignalServiceAccountDataStore(registrationId, identityKeyPair)
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.uuid(), e164, 1, identityKeyPair.publicKey.publicKey, 31337)
private val senderCertificate = FakeClientHelpers.createCertificateFor(trustRoot, serviceId.rawUuid, e164, 1, identityKeyPair.publicKey.publicKey, 31337)
private val sessionLock = object : SignalSessionLock {
private val lock = ReentrantLock()
@@ -60,7 +60,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(now: Long): SignalServiceProtos.Envelope {
fun encrypt(now: Long): Envelope {
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
@@ -71,10 +71,10 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
.toEnvelope(envelopeContent.content.get().dataMessage.timestamp, getAliceServiceId())
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
}
fun decrypt(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) {
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}
@@ -143,7 +143,6 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()
@@ -161,13 +160,20 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun storeKyberPreKey(kyberPreKeyId: Int, record: KyberPreKeyRecord?) = throw UnsupportedOperationException()
override fun containsKyberPreKey(kyberPreKeyId: Int): Boolean = throw UnsupportedOperationException()
override fun markKyberPreKeyUsed(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeEcPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeEcPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun storeSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?, record: SenderKeyRecord?) = throw UnsupportedOperationException()
override fun loadSenderKey(sender: SignalProtocolAddress?, distributionId: UUID?): SenderKeyRecord = throw UnsupportedOperationException()
override fun archiveSession(address: SignalProtocolAddress?) = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun getAllAddressesWithActiveSessions(addressNames: MutableList<String>?): MutableMap<SignalProtocolAddress, SessionRecord> = throw UnsupportedOperationException()
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) = throw UnsupportedOperationException()
override fun removeKyberPreKey(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun markAllOneTimeKyberPreKeysStaleIfNecessary(staleTime: Long) = throw UnsupportedOperationException()
override fun deleteAllStaleOneTimeKyberPreKeys(threshold: Long, minCount: Int) = throw UnsupportedOperationException()
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
}
}

View File

@@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.testing
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64
import org.signal.libsignal.internal.Native
import org.signal.libsignal.internal.NativeHandleGuard
import org.signal.libsignal.metadata.certificate.CertificateValidator
@@ -9,16 +11,16 @@ import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.protocol.ecc.ECPublicKey
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeContent
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.OutgoingPushMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.util.Base64
import java.util.Optional
import java.util.UUID
@@ -52,9 +54,9 @@ object FakeClientHelpers {
}
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {
val content = SignalServiceProtos.Content.newBuilder().apply {
setDataMessage(
SignalServiceProtos.DataMessage.newBuilder().apply {
val content = Content.Builder().apply {
dataMessage(
DataMessage.Builder().buildWith {
body = message
timestamp = now
}
@@ -64,16 +66,16 @@ object FakeClientHelpers {
}
fun OutgoingPushMessage.toEnvelope(timestamp: Long, destination: ServiceId): Envelope {
return Envelope.newBuilder()
.setType(Envelope.Type.valueOf(this.type))
.setSourceDevice(1)
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 1)
.setDestinationUuid(destination.toString())
.setServerGuid(UUID.randomUUID().toString())
.setContent(Base64.decode(this.content).toProtoByteString())
.setUrgent(true)
.setStory(false)
return Envelope.Builder()
.type(Envelope.Type.fromValue(this.type))
.sourceDevice(1)
.timestamp(timestamp)
.serverTimestamp(timestamp + 1)
.destinationServiceId(destination.toString())
.serverGuid(UUID.randomUUID().toString())
.content(Base64.decode(this.content).toByteString())
.urgent(true)
.story(false)
.build()
}
}

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.testing
import okio.ByteString.Companion.toByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.storageservice.protos.groups.Member
import org.signal.storageservice.protos.groups.local.DecryptedGroup
@@ -8,27 +9,28 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.internal.push.GroupContextV2
import kotlin.random.Random
/**
* Helper methods for creating groups for message processing tests et al.
*/
object GroupTestingUtils {
fun member(serviceId: ServiceId, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.newBuilder()
.setUuid(serviceId.toByteString())
.setJoinedAtRevision(revision)
.setRole(role)
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.Builder()
.aciBytes(aci.toByteString())
.joinedAtRevision(revision)
.role(role)
.build()
}
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members.toList())
.setRevision(revision)
.setTitle(MessageContentFuzzer.string())
val decryptedGroupState = DecryptedGroup.Builder()
.members(members.toList())
.revision(revision)
.title(MessageContentFuzzer.string())
.build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
@@ -43,8 +45,11 @@ object GroupTestingUtils {
}
fun Recipient.asMember(): DecryptedMember {
return member(serviceId = requireServiceId())
return member(aci = requireAci())
}
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId)
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId) {
val groupV2Context: GroupContextV2
get() = GroupContextV2(masterKey = masterKey.serialize().toByteString(), revision = 0)
}
}

View File

@@ -1,21 +1,21 @@
package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.database.model.toProtoByteString
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.messages.TestMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.EditMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.UUID
import kotlin.random.Random
import kotlin.random.nextInt
@@ -34,22 +34,22 @@ object MessageContentFuzzer {
/**
* Create an [Envelope].
*/
fun envelope(timestamp: Long): Envelope {
return Envelope.newBuilder()
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 5)
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID()))
fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
return Envelope.Builder()
.timestamp(timestamp)
.serverTimestamp(timestamp + 5)
.serverGuid(serverGuid.toString())
.build()
}
/**
* Create metadata to match an [Envelope].
*/
fun envelopeMetadata(source: RecipientId, destination: RecipientId, groupId: GroupId.V2? = null): EnvelopeMetadata {
fun envelopeMetadata(source: RecipientId, destination: RecipientId, sourceDeviceId: Int = 1, groupId: GroupId.V2? = null): EnvelopeMetadata {
return EnvelopeMetadata(
sourceServiceId = Recipient.resolved(source).requireServiceId(),
sourceE164 = null,
sourceDeviceId = 1,
sourceDeviceId = sourceDeviceId,
sealedSender = true,
groupId = groupId?.decodedId,
destinationServiceId = Recipient.resolved(destination).requireServiceId()
@@ -61,21 +61,24 @@ object MessageContentFuzzer {
* - An expire timer value
* - Bold style body ranges
*/
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
body = string()
if (random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
addBodyRanges(
SignalServiceProtos.BodyRange.newBuilder().buildWith {
start = 0
length = 1
style = SignalServiceProtos.BodyRange.Style.BOLD
}
bodyRanges(
listOf(
BodyRange.Builder().buildWith {
start = 0
length = 1
style = BodyRange.Style.BOLD
}
)
)
}
if (groupContextV2 != null) {
@@ -86,6 +89,20 @@ object MessageContentFuzzer {
.build()
}
/**
* Create an edit message.
*/
fun editTextMessage(targetTimestamp: Long, editedDataMessage: DataMessage): Content {
return Content.Builder()
.editMessage(
EditMessage.Builder().buildWith {
targetSentTimestamp = targetTimestamp
dataMessage = editedDataMessage
}
)
.build()
}
/**
* Create a sync sent text message for the given [DataMessage].
*/
@@ -95,17 +112,17 @@ object MessageContentFuzzer {
recipientUpdate: Boolean = false
): Content {
return Content
.newBuilder()
.setSyncMessage(
SyncMessage.newBuilder().buildWith {
sent = SyncMessage.Sent.newBuilder().buildWith {
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
sent = SyncMessage.Sent.Builder().buildWith {
timestamp = textMessage.timestamp
message = textMessage
isRecipientUpdate = recipientUpdate
addAllUnidentifiedStatus(
unidentifiedStatus(
deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
destinationUuid = Recipient.resolved(it).requireServiceId().toString()
SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true
}
}
@@ -115,6 +132,24 @@ object MessageContentFuzzer {
).build()
}
/**
* Create a sync reads message for the given [RecipientId] and message timestamp pairings.
*/
fun syncReadsMessage(timestamps: List<Pair<RecipientId, Long>>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
read = timestamps.map { (senderId, timestamp) ->
SyncMessage.Read.Builder().buildWith {
this.senderAci = Recipient.resolved(senderId).requireAci().toString()
this.timestamp = timestamp
}
}
}
).build()
}
/**
* Create a random media message that may be:
* - A text body
@@ -123,9 +158,9 @@ object MessageContentFuzzer {
* - A message with 0-2 attachment pointers and may contain a text body
*/
fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextBoolean()) {
body = string()
}
@@ -133,28 +168,28 @@ object MessageContentFuzzer {
if (random.nextBoolean() && quoteAble.isNotEmpty()) {
body = string()
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
quote = DataMessage.Quote.Builder().buildWith {
id = quoted.envelope.timestamp
authorUuid = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
addAllAttachments(quoted.content.dataMessage.attachmentsList)
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage?.body
attachments(quoted.content.dataMessage?.attachments ?: emptyList())
bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
type = DataMessage.Quote.Type.NORMAL
}
}
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
authorUuid = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
quote = DataMessage.Quote.Builder().buildWith {
id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage?.body
}
}
if (random.nextFloat() < 0.25) {
val total = random.nextInt(1, 2)
(0..total).forEach { _ -> addAttachments(attachmentPointer()) }
attachments((0..total).map { attachmentPointer() })
}
}
)
@@ -166,15 +201,15 @@ object MessageContentFuzzer {
* - A reaction to a prior message
*/
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.25) {
val reactTo = previousMessages.random(random)
reaction = DataMessage.Reaction.newBuilder().buildWith {
reaction = DataMessage.Reaction.Builder().buildWith {
emoji = emojis.random(random)
remove = false
targetAuthorUuid = reactTo.metadata.sourceServiceId.toString()
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
targetSentTimestamp = reactTo.envelope.timestamp
}
}
@@ -183,22 +218,21 @@ object MessageContentFuzzer {
}
/**
* Create a random media message that can never contain a text body. It may be:
* - A sticker
* Create a random media message that contains a sticker.
*/
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
if (random.nextFloat() < 0.9) {
sticker = DataMessage.Sticker.newBuilder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data = attachmentPointer()
emoji = emojis.random(random)
}
fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data_ = attachmentPointer()
emoji = emojis.random(random)
}
groupV2 = groupContextV2
}
).build()
}
@@ -223,14 +257,14 @@ object MessageContentFuzzer {
* Generate a random [ByteString].
*/
fun byteString(length: Int = 512): ByteString {
return random.nextBytes(length).toProtoByteString()
return random.nextBytes(length).toByteString()
}
/**
* Generate a random [AttachmentPointer].
*/
fun attachmentPointer(): AttachmentPointer {
return AttachmentPointer.newBuilder().run {
return AttachmentPointer.Builder().run {
cdnKey = string()
contentType = mediaTypes.random(random)
key = byteString()

View File

@@ -1,31 +1,15 @@
package org.thoughtcrime.securesms.testing
import io.reactivex.rxjava3.core.Single
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.protocol.util.Medium
import org.signal.libsignal.svr2.PinHash
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PreKeyEntity
@@ -46,7 +30,8 @@ object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
backupCredentials = AuthCredentials.create("username", "password")
svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -83,29 +68,6 @@ object MockProvider {
}
}
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
val tokenData: TokenData = mock {
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
on { basicAuth } doReturn "basicAuth"
on { triesRemaining } doReturn 10
on { tokenResponse } doReturn TokenResponse()
}
kbsRepository.stub {
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
}
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(any(), any()) } doReturn session
}
}
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())

View File

@@ -22,6 +22,8 @@ class Put(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestP
class Post(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("POST", path), responseFactory)
class Delete(path: String, responseFactory: ResponseFactory) : Verb(defaultRequestPredicate("DELETE", path), responseFactory)
fun MockResponse.success(response: Any? = null): MockResponse {
return setResponseCode(200).apply {
if (response != null) {

View File

@@ -29,14 +29,14 @@ import org.thoughtcrime.securesms.registration.RegistrationData
import org.thoughtcrime.securesms.registration.RegistrationRepository
import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.VerifyResponse
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
import java.lang.IllegalArgumentException
import java.util.UUID
/**
@@ -45,7 +45,7 @@ import java.util.UUID
*
* To use: `@get:Rule val harness = SignalActivityRule()`
*/
class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource() {
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication()
@@ -57,6 +57,9 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
private set
lateinit var othersKeys: List<IdentityKeyPair>
var group: GroupTestingUtils.TestGroupInfo? = null
private set
val inMemoryLogger: InMemoryLogger
get() = (application as SignalInstrumentationApplicationContext).inMemoryLogger
@@ -68,6 +71,15 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
others = setupOthers.first
othersKeys = setupOthers.second
if (createGroup && others.size >= 2) {
group = GroupTestingUtils.insertGroup(
revision = 0,
self.asMember(),
others[0].asMember(),
others[1].asMember()
)
}
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@@ -78,6 +90,9 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(Put("/v2/keys") { MockResponse().success() })
@@ -92,13 +107,19 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
),
VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null),
VerifyResponse(
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
),
false
).blockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
SignalStore.svr().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -120,7 +141,7 @@ class SignalActivityRule(private val othersCount: Int = 4) : ExternalResource()
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()

View File

@@ -2,10 +2,12 @@ package org.thoughtcrime.securesms.testing
import org.junit.rules.TestWatcher
import org.junit.runner.Description
import org.signal.core.util.deleteAll
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.ThreadTable
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.UUID
/**
@@ -34,7 +36,8 @@ class SignalDatabaseRule(
private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) {
SignalDatabase.messages.deleteAllThreads()
SignalDatabase.threads.deleteAllConversations()
SignalDatabase.rawDatabase.deleteAll(ThreadTable.TABLE_NAME)
}
}
}

View File

@@ -1,70 +0,0 @@
package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.serialize.protos.AddressProto
import org.whispersystems.signalservice.internal.serialize.protos.MetadataProto
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto
import java.util.UUID
import kotlin.random.Random
class TestProtos private constructor() {
fun address(
uuid: UUID = UUID.randomUUID()
): AddressProto.Builder {
return AddressProto.newBuilder()
.setUuid(ServiceId.from(uuid).toByteString())
}
fun metadata(
address: AddressProto = address().build()
): MetadataProto.Builder {
return MetadataProto.newBuilder()
.setAddress(address)
}
fun groupContextV2(
revision: Int = 0,
masterKeyBytes: ByteArray = Random.Default.nextBytes(GroupMasterKey.SIZE)
): GroupContextV2.Builder {
return GroupContextV2.newBuilder()
.setRevision(revision)
.setMasterKey(ByteString.copyFrom(masterKeyBytes))
}
fun storyContext(
sentTimestamp: Long = Random.nextLong(),
authorUuid: String = UUID.randomUUID().toString()
): DataMessage.StoryContext.Builder {
return DataMessage.StoryContext.newBuilder()
.setAuthorUuid(authorUuid)
.setSentTimestamp(sentTimestamp)
}
fun dataMessage(): DataMessage.Builder {
return DataMessage.newBuilder()
}
fun content(): SignalServiceProtos.Content.Builder {
return SignalServiceProtos.Content.newBuilder()
}
fun serviceContent(
localAddress: AddressProto = address().build(),
metadata: MetadataProto = metadata().build()
): SignalServiceContentProto.Builder {
return SignalServiceContentProto.newBuilder()
.setLocalAddress(localAddress)
.setMetadata(metadata)
}
companion object {
fun <T> build(buildFn: TestProtos.() -> T): T {
return TestProtos().buildFn()
}
}
}

View File

@@ -1,11 +1,19 @@
package org.thoughtcrime.securesms.testing
import android.database.Cursor
import android.util.Base64
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.notNullValue
import org.hamcrest.Matchers.nullValue
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.select
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@@ -53,3 +61,29 @@ fun CountDownLatch.awaitFor(duration: Duration) {
throw TimeoutException("Latch await took longer than ${duration.inWholeMilliseconds}ms")
}
}
fun dumpTableToLogs(tag: String = "TestUtils", table: String) {
dumpTable(table).forEach { Log.d(tag, it.toString()) }
}
fun dumpTable(table: String): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.select()
.from(table)
.run()
.readToList { cursor ->
val map: List<Pair<String, String?>> = cursor.columnNames.map { column ->
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Base64.encodeToString(cursor.getBlob(index), 0)
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
}
map
}
}

View File

@@ -6,6 +6,6 @@ package org.thoughtcrime.securesms.util;
public final class FeatureFlagsAccessor {
public static void forceValue(String key, Object value) {
FeatureFlags.FORCED_VALUES.put(FeatureFlags.PHONE_NUMBER_PRIVACY, true);
FeatureFlags.FORCED_VALUES.put(key, value);
}
}

View File

@@ -38,10 +38,8 @@ object MessageTableTestUtils {
isKeyExchangeType:${type and MessageTypes.KEY_EXCHANGE_BIT != 0L}
isIdentityVerified:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT != 0L}
isIdentityDefault:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT != 0L}
isCorruptedKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CORRUPTED_BIT != 0L}
isInvalidVersionKeyExchange:${type and MessageTypes.KEY_EXCHANGE_INVALID_VERSION_BIT != 0L}
isBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_BUNDLE_BIT != 0L}
isContentBundleKeyExchange:${type and MessageTypes.KEY_EXCHANGE_CONTENT_FORMAT != 0L}
isIdentityUpdate:${type and MessageTypes.KEY_EXCHANGE_IDENTITY_UPDATE_BIT != 0L}
isRateLimited:${type and MessageTypes.MESSAGE_RATE_LIMITED_BIT != 0L}
isExpirationTimerUpdate:${type and MessageTypes.EXPIRATION_TIMER_UPDATE_BIT != 0L}

View File

@@ -6,8 +6,6 @@ import org.signal.benchmark.setup.TestMessages
import org.signal.benchmark.setup.TestUsers
import org.thoughtcrime.securesms.BaseActivity
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient
class BenchmarkSetupActivity : BaseActivity() {
@@ -53,13 +51,6 @@ class BenchmarkSetupActivity : BaseActivity() {
TestMessages.insertOutgoingTextMessage(other = recipient, body = "Test message $i", timestamp = generator.nextTimestamp())
}
val voiceMessageId = TestMessages.insertIncomingVoiceMessage(other = recipient, timestamp = generator.nextTimestamp())
val mmsRecord = SignalDatabase.messages.getMessageRecord(voiceMessageId) as MediaMmsMessageRecord
TestMessages.insertOutgoingImageMessage(other = recipient, body = "test", 2, generator.nextTimestamp())
TestMessages.insertIncomingTextMessage(other = recipient, "reply to the test message", generator.nextTimestamp())
TestMessages.insertIncomingQuoteTextMessage(other = recipient, quote = QuoteModel(mmsRecord.timestamp, recipient.id, "Fake voice message text", false, mmsRecord.slideDeck.asAttachments(), null, QuoteModel.Type.NORMAL, null), body = "Here is a cool quote", timestamp = generator.nextTimestamp())
TestMessages.insertOutgoingTextMessage(other = recipient, body = "longaweorijoaijwerijoiajwer", timestamp = generator.nextTimestamp())
SignalDatabase.threads.update(SignalDatabase.threads.getOrCreateThreadIdFor(recipient = recipient), true)
}
}

View File

@@ -1,17 +1,14 @@
package org.signal.benchmark
import android.content.Context
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.PNI
import org.whispersystems.signalservice.api.push.ServiceIdType
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import java.io.IOException
import java.util.Optional
@@ -37,7 +34,7 @@ class DummyAccountManagerFactory : AccountManagerFactory() {
}
@Throws(IOException::class)
override fun setPreKeys(serviceIdType: ServiceIdType, identityKey: IdentityKey, signedPreKey: SignedPreKeyRecord, oneTimePreKeys: List<PreKeyRecord>) {
override fun setPreKeys(preKeyUpload: PreKeyUpload) {
}
}
}

View File

@@ -1,14 +1,15 @@
package org.signal.benchmark.setup
import org.thoughtcrime.securesms.attachments.Cdn
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TestDbUtils
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.releasechannel.ReleaseChannel
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
@@ -65,7 +66,8 @@ object TestMessages {
return insert
}
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -73,10 +75,11 @@ object TestMessages {
receivedTimeMillis = timestamp ?: System.currentTimeMillis()
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
}
fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -90,28 +93,30 @@ object TestMessages {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
imageAttachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = failed)
return insertIncomingMessage(recipient = other, message = message, failed = failed)
}
fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List<SignalServiceAttachment>))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = false)
return insertIncomingMessage(recipient = other, message = message, failed = false)
}
private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long {
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage, failed: Boolean = false): Long {
val id = insertIncomingMessage(recipient = recipient, message = message)
if (failed) {
setMessageMediaFailed(id)
@@ -122,8 +127,8 @@ object TestMessages {
return id
}
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
}
private fun setMessageMediaFailed(messageId: Long) {
@@ -139,7 +144,7 @@ object TestMessages {
}
private fun imageAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"image/webp",
null,
@@ -148,6 +153,8 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,
@@ -160,7 +167,7 @@ object TestMessages {
private fun voiceAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"audio/aac",
null,
@@ -169,6 +176,8 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.aac"),
true,
false,

View File

@@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.signal.benchmark.DummyAccountManagerFactory
import org.signal.core.util.concurrent.safeBlockingGet
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
@@ -22,7 +23,7 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil
import org.thoughtcrime.securesms.registration.VerifyResponse
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile
import org.whispersystems.signalservice.api.push.ACI
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
@@ -43,6 +44,9 @@ object TestUsers {
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
val registrationData = RegistrationData(
code = "123123",
@@ -54,16 +58,26 @@ object TestUsers {
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
)
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
val verifyResponse = VerifyResponse(
VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
)
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
registrationData,
verifyResponse,
false
).blockingGet()
).safeBlockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
SignalStore.svr().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -86,7 +100,7 @@ object TestUsers {
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()

View File

@@ -0,0 +1,85 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.fragment.findNavController
import androidx.navigation.navGraphViewModels
import org.signal.core.ui.Rows
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.ComposeFragment
/**
* Configuration fragment for the internal conversation test fragment.
*/
class InternalConversationSpringboardFragment : ComposeFragment() {
private val viewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
@Composable
override fun FragmentContent() {
Content(this::navigateBack, this::launchTestFragment, viewModel.hasWallpaper)
}
private fun navigateBack() {
findNavController().popBackStack()
}
private fun launchTestFragment() {
findNavController().navigate(
InternalConversationSpringboardFragmentDirections
.actionInternalConversationSpringboardFragmentToInternalConversationTestFragment()
)
}
}
@Preview
@Composable
private fun ContentPreview() {
val hasWallpaper = remember { mutableStateOf(false) }
SignalTheme(isDarkMode = true) {
Content(onBackPressed = {}, onLaunchTestFragment = {}, hasWallpaper = hasWallpaper)
}
}
@Composable
private fun Content(
onBackPressed: () -> Unit,
onLaunchTestFragment: () -> Unit,
hasWallpaper: MutableState<Boolean>
) {
Scaffolds.Settings(
title = "Conversation Test Springboard",
onNavigationClick = onBackPressed,
navigationIconPainter = rememberVectorPainter(ImageVector.vectorResource(id = R.drawable.symbol_arrow_left_24))
) {
Column(modifier = Modifier.padding(it)) {
Rows.TextRow(
text = "Launch Conversation Test Fragment",
onClick = onLaunchTestFragment
)
Rows.ToggleRow(
checked = hasWallpaper.value,
text = "Enable Wallpaper",
onCheckChanged = { hasWallpaper.value = it }
)
}
}
}

View File

@@ -0,0 +1,13 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class InternalConversationSpringboardViewModel : ViewModel() {
val hasWallpaper = mutableStateOf(false)
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.database.MessageTypes
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import java.security.SecureRandom
import kotlin.time.Duration.Companion.milliseconds
/**
* Generates random conversation messages via the given set of parameters.
*/
class ConversationElementGenerator {
private val mappingModelCache = mutableMapOf<ConversationElementKey, MappingModel<*>>()
private val random = SecureRandom()
private val wordBank = listOf(
"A",
"Test",
"Message",
"To",
"Display",
"Content",
"In",
"Bubbles",
"User",
"Signal",
"The"
)
fun getMappingModel(key: ConversationElementKey): MappingModel<*> {
val cached = mappingModelCache[key]
if (cached != null) {
return cached
}
val messageModel = generateMessage(key)
mappingModelCache[key] = messageModel
return messageModel
}
private fun getIncomingType(): Long {
return MessageTypes.BASE_INBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentOutgoingType(): Long {
return MessageTypes.BASE_SENT_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getSentFailedOutgoingType(): Long {
return MessageTypes.BASE_SENT_FAILED_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun getPendingOutgoingType(): Long {
return MessageTypes.BASE_OUTBOX_TYPE or MessageTypes.SECURE_MESSAGE_BIT
}
private fun generateMessage(key: ConversationElementKey): MappingModel<*> {
val messageId = key.requireMessageId()
val now = getNow()
val testMessageWordLength = random.nextInt(3) + 1
val testMessage = (0 until testMessageWordLength).map {
wordBank.random()
}.joinToString(" ")
val isIncoming = random.nextBoolean()
val record = MmsMessageRecord(
messageId,
if (isIncoming) Recipient.UNKNOWN else Recipient.self(),
0,
if (isIncoming) Recipient.self() else Recipient.UNKNOWN,
now,
now,
now,
true,
1,
testMessage,
SlideDeck(),
if (isIncoming) getIncomingType() else getPendingOutgoingType(),
emptySet(),
emptySet(),
0,
0,
0,
false,
true,
null,
emptyList(),
emptyList(),
false,
emptyList(),
false,
false,
now,
true,
now,
null,
StoryType.NONE,
null,
null,
null,
null,
-1,
null,
null,
0,
false,
null
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
record,
Recipient.UNKNOWN
)
return if (isIncoming) {
IncomingTextOnly(conversationMessage)
} else {
OutgoingTextOnly(conversationMessage)
}
}
private fun getNow(): Long {
val now = System.currentTimeMillis()
return now - random.nextInt(20.milliseconds.inWholeMilliseconds.toInt()).toLong()
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import org.signal.paging.PagedDataSource
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElement
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
import kotlin.math.min
class InternalConversationTestDataSource(
private val size: Int,
private val generator: ConversationElementGenerator
) : PagedDataSource<ConversationElementKey, MappingModel<*>> {
override fun size(): Int = size
override fun load(start: Int, length: Int, totalSize: Int, cancellationSignal: PagedDataSource.CancellationSignal): MutableList<MappingModel<*>> {
val end = min(start + length, totalSize)
return (start until end).map {
load(ConversationElementKey.forMessage(it.toLong()))!!
}.toMutableList()
}
override fun getKey(data: MappingModel<*>): ConversationElementKey {
check(data is ConversationMessageElement)
return ConversationElementKey.forMessage(data.conversationMessage.messageRecord.id)
}
override fun load(key: ConversationElementKey?): MappingModel<*>? {
return key?.let { generator.getMappingModel(it) }
}
}

View File

@@ -0,0 +1,315 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.navGraphViewModels
import com.bumptech.glide.Glide
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager
import org.thoughtcrime.securesms.components.settings.app.internal.conversation.springboard.InternalConversationSpringboardViewModel
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener
import org.thoughtcrime.securesms.conversation.ConversationItem
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.colors.ChatColorsPalette
import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.databinding.ConversationTestFragmentBinding
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.doAfterNextLayout
class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fragment) {
companion object {
private val TAG = Log.tag(InternalConversationTestFragment::class.java)
}
private val binding by ViewBinderDelegate(ConversationTestFragmentBinding::bind)
private val viewModel: InternalConversationTestViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
private val springboardViewModel: InternalConversationSpringboardViewModel by navGraphViewModels(R.id.app_settings)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = ConversationAdapterV2(
lifecycleOwner = viewLifecycleOwner,
requestManager = Glide.with(this),
clickListener = ClickListener(),
hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(),
startExpirationTimeout = {},
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) },
displayDialogFragment = {}
)
if (springboardViewModel.hasWallpaper.value) {
binding.root.setBackgroundColor(0xFF32C7E2.toInt())
}
var startTime = 0L
var firstRender = true
lifecycleDisposable.bindTo(viewLifecycleOwner)
adapter.setPagingController(viewModel.controller)
lifecycleDisposable += viewModel.data.observeOn(AndroidSchedulers.mainThread()).subscribeBy {
if (firstRender) {
startTime = System.currentTimeMillis()
}
adapter.submitList(it) {
if (firstRender) {
firstRender = false
binding.root.doAfterNextLayout {
val endTime = System.currentTimeMillis()
Log.d(TAG, "First render in ${endTime - startTime} millis")
}
}
}
}
binding.recycler.layoutManager = SmoothScrollingLinearLayoutManager(requireContext(), true)
binding.recycler.adapter = adapter
RecyclerViewColorizer(binding.recycler).apply {
setChatColors(ChatColorsPalette.Bubbles.default.withId(ChatColors.Id.Auto))
}
}
private inner class ClickListener : ItemClickListener {
override fun onQuoteClicked(messageRecord: MmsMessageRecord?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onLinkPreviewClicked(linkPreview: LinkPreview) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onQuotedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMoreTextClicked(conversationRecipientId: RecipientId, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onStickerClicked(stickerLocator: StickerLocator) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewOnceMessageClicked(messageRecord: MmsMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSharedContactDetailsClicked(contact: Contact, avatarTransitionView: View) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onAddToContactsClicked(contact: Contact) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteSharedContactClicked(choices: MutableList<Recipient>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onReactionClicked(multiselectPart: MultiselectPart, messageId: Long, isMms: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithErrorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageWithRecaptchaNeededClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onIncomingIdentityMismatchClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRegisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUnregisterVoiceNoteCallbacks(onPlaybackStartObserver: Observer<VoiceNotePlaybackState>) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePause(uri: Uri) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlay(uri: Uri, messageId: Long, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNoteSeekTo(uri: Uri, position: Double) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onVoiceNotePlaybackSpeedChanged(uri: Uri, speed: Float) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGroupMigrationLearnMoreClicked(membershipChange: GroupMigrationMembershipChange) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChatSessionRefreshLearnMoreClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBadDecryptLearnMoreClicked(author: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSafetyNumberLearnMoreClicked(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinGroupCallClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteFriendsToGroupClicked(groupId: GroupId.V2) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEnableCallNotificationsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onPlayInlineContent(conversationMessage: ConversationMessage?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInMemoryMessageClicked(messageRecord: InMemoryMessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onViewGroupDescriptionChange(groupId: GroupId?, description: String, isMessageRequestAccepted: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChangeNumberUpdateContact(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onDonateClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onBlockJoinRequest(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onRecipientNameClicked(target: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onInviteToSignalClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onActivatePaymentsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onSendPaymentClicked(recipientId: RecipientId) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onScheduledIndicatorClicked(view: View, conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onUrlClicked(url: String): Boolean {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
return true
}
override fun onViewGiftBadgeClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onGiftBadgeRevealed(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onJoinCallLink(callLinkRootKey: CallLinkRootKey) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemClick(item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowSafetyTips(forGroup: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onReportSpamLearnMoreClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onMessageRequestAcceptOptionsClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components.settings.app.internal.conversation.test
import androidx.lifecycle.ViewModel
import org.signal.paging.PagedData
import org.signal.paging.PagingConfig
class InternalConversationTestViewModel : ViewModel() {
private val generator = ConversationElementGenerator()
private val dataSource = InternalConversationTestDataSource(
500,
generator
)
private val config = PagingConfig.Builder().setPageSize(25)
.setBufferPages(2)
.build()
private val pagedData = PagedData.createForObservable(dataSource, config)
val controller = pagedData.controller
val data = pagedData.data
}

View File

@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:usesCleartextTraffic="true"
tools:replace="android:usesCleartextTraffic"

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 209 KiB

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