Compare commits

..

962 Commits

Author SHA1 Message Date
Cody Henthorne
bcbe39c85c Bump version to 7.14.1 2024-08-13 16:00:20 -04:00
Cody Henthorne
b2e1f41b0e Update baseline profile. 2024-08-13 15:55:08 -04:00
Cody Henthorne
7cb2c3415a Update translations and other static files. 2024-08-13 15:48:26 -04:00
mtang-signal
d0a06ab3de Fix repeating battery optimization alerts. 2024-08-13 11:32:27 -07:00
Michelle Tang
ae923c9221 Remove immediate screen lock option. 2024-08-13 11:24:08 -07:00
mtang-signal
1a5ce9b4b9 Fix ISE in media gallery. 2024-08-13 11:09:09 -07:00
Greyson Parrelli
748cd00883 Improve network reliability. 2024-08-13 12:47:41 -04:00
Greyson Parrelli
1e4e6b6b41 Potential fix for missing FTS tables. 2024-08-13 11:30:18 -04:00
Cody Henthorne
19f3219224 Fix crash when outgoing call picked up while in the background. 2024-08-13 10:51:12 -04:00
Greyson Parrelli
6358589e19 Add disclaimer about bundletool bug. 2024-08-13 10:36:37 -04:00
Michelle Tang
08d8564c00 Add minor UI updates to screen lock. 2024-08-12 16:22:43 -07:00
mtang-signal
68c7ce5823 Bump version to 7.14.0 2024-08-08 12:51:34 -07:00
mtang-signal
bfcbfed0a6 Update baseline profile. 2024-08-08 12:38:33 -07:00
mtang-signal
8f99930168 Update translations and other static files. 2024-08-08 12:30:12 -07:00
Michelle Tang
21019d1726 Add manufacturer to notification help. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
7807d92825 Display progress for RestoreAttachmentJobs as a Banner. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
4af6e0480a Fix playback position indicator for trimmed video clips. 2024-08-08 12:22:34 -07:00
Alex Hart
1d6917476e Pop the "Create backup now" sheet after different instances of subscribing to backups. 2024-08-08 12:22:34 -07:00
Michelle Tang
3bdbd69a7d Update screen lock. 2024-08-08 12:22:34 -07:00
Alex Hart
c880db0f4a Fix checkout flow activity close on error. 2024-08-08 12:22:34 -07:00
Michelle Tang
385ba3590c Fix device bottom sheet ISE 2024-08-08 12:22:34 -07:00
Alex Hart
c63beb5b2c Hide optimization option if the user doesn't have backups enabled. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
7dc15f8bd3 Wire up event bus in EnterCodeFragment. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
e83c6dc7c2 Prevent ISE during code entry in registration. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
d45acd0e24 Migrate existing Reminders to Banners. 2024-08-08 12:22:34 -07:00
Alex Hart
16a732171a Add 'create a pin' button to backups sub pin entry sheet. 2024-08-08 12:22:34 -07:00
Alex Hart
b9da045f79 Add proper styling for incorrect pin text. 2024-08-08 12:22:34 -07:00
Alex Hart
ccabd9edd8 Add espresso tests for donations flow. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
11d165a17b Introduce Banners. 2024-08-08 12:22:34 -07:00
Nicholas Tinsley
ef2c67d808 Registration: check if phone number is possible.
As opposed to "valid."
2024-08-08 12:19:01 -07:00
Alex Hart
6ac510a156 Add error state for pin entry during backups flow. 2024-08-08 12:19:01 -07:00
Alex Hart
da74874815 Refresh settings screen after disabling backups. 2024-08-08 12:19:01 -07:00
Alex Hart
ff9c77c2e2 Fix backups checkout flow next state. 2024-08-08 12:19:01 -07:00
Greyson Parrelli
2c11a27897 Fix emoji full text search.
Co-authored-by: Nolan Woods <innovate.invent@gmail.com>
2024-08-08 12:19:01 -07:00
Alex Hart
8dc910e71d Move pip above controls when expanded or not enough space in landscape. 2024-08-08 12:19:01 -07:00
Nicholas Tinsley
cbd587f142 Fix post-registration restore closing the app. 2024-08-08 12:19:01 -07:00
Jim Gustafson
ffd2e164bf Update to RingRTC v2.46.0 2024-08-08 12:19:01 -07:00
Greyson Parrelli
ddb367edbe Update WebView fields in AndroidManifest. 2024-08-08 12:19:01 -07:00
Greyson Parrelli
981e7a4270 Tweak when decryption drained state is reset. 2024-08-08 12:19:01 -07:00
Michelle Tang
b46b04fcdf Update strings. 2024-08-08 12:19:01 -07:00
Cody Henthorne
aebaff736c Fix mic usage for api34 when app is backgrounded. 2024-08-08 12:19:01 -07:00
Greyson Parrelli
8932eef991 Convert some SignalServiceAttachment* classes to kotlin. 2024-08-08 12:19:01 -07:00
Fedor Indutny
bb01c0501b Fix info string in deriveMediaId. 2024-08-08 12:19:00 -07:00
Alex Hart
1d1ea01cc1 Add optimize storage setting and sheet. 2024-08-08 12:19:00 -07:00
Cody Henthorne
2677665069 Fix group send error handling for single targets. 2024-08-08 12:19:00 -07:00
Greyson Parrelli
ea215ef488 Fix network interaction in backup service. 2024-08-08 12:19:00 -07:00
Greyson Parrelli
d9586e8d00 Revert "Upgrade Jetpack Compose to 2024.04.00."
This reverts commit f1fbd647685ab51e54863a763b37978505251b8e.
2024-08-08 12:19:00 -07:00
Greyson Parrelli
e1283a66fd Fix issue where mediaName is stored in proto incorrectly. 2024-08-08 12:19:00 -07:00
Alex Hart
fb82703740 Ensure calling pip is in landscape if we leave the activity while in landscape mode. 2024-08-08 12:19:00 -07:00
Greyson Parrelli
5e03e31ffd Fix various AccountData backupV2 import/export bugs.
I'm not including the auto-generated test files yet because I'm still
making tweaks, but these are all valid fixes that got the current
(uncommitted) batch of test files passing.
2024-08-08 12:19:00 -07:00
Nicholas Tinsley
4091af3632 Upgrade Jetpack Compose to 2024.04.00. 2024-08-08 12:19:00 -07:00
Nicholas Tinsley
1b729c42b6 Upgrade AndroidX Lifecycle to 2.8.4.
This also adds a dependency on the lifecycle-runtime-compose artifact.
2024-08-08 12:19:00 -07:00
Alex Hart
5c139aa5b1 Fix reaction positioning in large group calls. 2024-08-08 12:19:00 -07:00
Cody Henthorne
dc7208922c Update target SDK to 34. 2024-08-08 12:19:00 -07:00
Nicholas Tinsley
6424c6bc99 Fixes for restoring a backup after completing registration. 2024-08-08 12:19:00 -07:00
Michelle Tang
57adab858c Add selected photos access. 2024-08-08 12:19:00 -07:00
Alex Hart
4f001a0c95 Default admin approval to on in call links UI. 2024-08-08 12:19:00 -07:00
Jim Gustafson
12529e9fb0 Update to RingRTC v2.45.0 2024-08-08 12:19:00 -07:00
Alex Hart
d406e8f5b8 Add support for several backup alert sheets. 2024-08-08 12:19:00 -07:00
Greyson Parrelli
fb2a332513 Update to latest Backup.proto. 2024-08-08 12:19:00 -07:00
Greyson Parrelli
c2bdac80dc Bump version to 7.13.3 2024-08-06 11:40:32 -04:00
Greyson Parrelli
db00000d66 Update translations and other static files. 2024-08-06 11:38:54 -04:00
Greyson Parrelli
f551a94e58 Fix bug with story sends. 2024-08-06 11:06:31 -04:00
Alex Hart
58a7d3dc08 Bump version to 7.13.2 2024-08-01 16:20:06 -03:00
Alex Hart
3380fa722d Update baseline profile. 2024-08-01 16:06:48 -03:00
Alex Hart
216f57f3ea Update translations and other static files. 2024-08-01 16:03:18 -03:00
Alex Hart
bf4aa85ac3 Fix misplaced call controls when restoring from PIP. 2024-08-01 15:39:02 -03:00
Cody Henthorne
e4966da3ef Fix crash when receiving GSE before state membership updated. 2024-07-31 16:32:46 -04:00
Alex Hart
79c7c2345f Bump version to 7.13.1 2024-07-31 16:35:16 -03:00
Alex Hart
d516037be9 Update baseline profile. 2024-07-31 16:25:15 -03:00
Alex Hart
008b38594d Update translations and other static files. 2024-07-31 16:18:56 -03:00
Cody Henthorne
15b59457f7 Fix expiration clock UI reseting incorrectly on edit. 2024-07-31 14:11:36 -04:00
Cody Henthorne
5ddd1651ee Fix stream reset error handling. 2024-07-30 13:55:08 -04:00
Cody Henthorne
8ca89d2024 Add additional debugging info to group processing lock handling. 2024-07-29 12:37:40 -04:00
Alex Hart
585c8cd863 Fix donation action routing. 2024-07-29 12:24:19 -03:00
Alex Hart
faf6ab233f Remove instance save/restore for the time being. 2024-07-29 10:28:52 -03:00
Nicholas Tinsley
95cbc91bf0 Bump version to 7.13.0 2024-07-26 23:53:15 +02:00
Nicholas Tinsley
9480e23455 Update translations and other static files. 2024-07-26 23:47:28 +02:00
Nicholas Tinsley
727a0f8273 Add additional verification code parser test case. 2024-07-26 23:43:54 +02:00
Nicholas Tinsley
279e55d65f Wire in time remaining for registration lock responses. 2024-07-26 23:43:54 +02:00
Nicholas Tinsley
c36fba2ab7 Handle registration error codes if the user backs out from the enter code screen. 2024-07-26 23:43:54 +02:00
Alex Hart
1d0997379f Add support for several BackupAlert sheet primary actions. 2024-07-26 23:43:54 +02:00
Alex Hart
1a7611d505 Add worker method for determining where to take a user when they press signal backups. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
36846301de Add missing handling for sessions that are already verified. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
b8e81e6677 Add missing registration lock route. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
3d169bffd0 Reserve server-only field in SignalService.proto. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
556a25447e Add logging around registration code autofill. 2024-07-26 23:43:53 +02:00
Alex Hart
b42e48a08a Add ability to long press 'Chats' to get backups subscriber id. 2024-07-26 23:43:53 +02:00
Alex Hart
b1a4e889bc Add support for downgrading backup. 2024-07-26 23:43:53 +02:00
mtang-signal
e6fb01a67b Fix strings. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
6c042f6e47 Add small debugging log statements to ReRegisterWithPinFragment. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
f87ff58701 Convert registration error handling from callbacks to observers. 2024-07-26 23:43:53 +02:00
Cody Henthorne
4fb335de28 Clear drafts when leaving groups. 2024-07-26 23:43:53 +02:00
Alex Hart
725d8dc85d Fixup spinner build. 2024-07-26 23:43:53 +02:00
Alex Hart
e76153b2fd Hide waiting to be let in banner by default. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
0b98901634 Integrate libsignal validator into backup tests. 2024-07-26 23:43:53 +02:00
Nicholas Tinsley
57feb272d2 Clear out any existing registration sessions if the E164 changes. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
7b0badef19 Get shared backup tests working. 2024-07-26 23:43:53 +02:00
Alex Hart
36640edfee Add more error messaging for backups. 2024-07-26 23:43:53 +02:00
Alex Hart
4e07c07ca9 Add backups error string for payment setup errors. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
31ddc5bcc0 Do not crash on invalid sessions. 2024-07-26 23:43:53 +02:00
Alex Hart
c80f459c37 Add CreateBackupBottomSheet. 2024-07-26 23:43:53 +02:00
Alex Hart
2a6dab41f5 Route InAppPaymentType and begin splitting out error messages. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
e6d8e36141 Add new backup testing infrastructure. 2024-07-26 23:43:53 +02:00
Alex Hart
816c9360cd Implement backup receipt generation. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
82c3265be5 Remove now-unnecessary exclusion from apkdiff.py.
Used to need it because baselineprof was non-deterministic, but google
has since fixed this in newer versions of AGP.
2024-07-26 23:43:53 +02:00
Greyson Parrelli
ab03a627da Upgrade libsignal-client to 0.52.5 2024-07-26 23:43:53 +02:00
Greyson Parrelli
81a45ddc09 Fix JobDatabase queue nullability crash. 2024-07-26 23:43:53 +02:00
Cody Henthorne
f1115130b2 Fix incorrect thread used for delete chat history. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
2d557215a0 Don't check for linked devices if not registered. 2024-07-26 23:43:53 +02:00
Alex Hart
cc806a2f79 Add generic payment in progress strings. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
853c934a5a Rotate the crash config feature flag. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
f1ba947a59 Add a "connectivity warning" bottom sheet. 2024-07-26 23:43:53 +02:00
Alex Hart
44b2c62a0e Add finalized strings to strings.xml for backups. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
06d475fb6e Move constraint filtering down into JobStorage to improve perf. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
36dface175 Fix job deletion bug, add performance tests. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
86cf8200b5 Remove cases where all jobs were expected to be in memory. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
973dc72cfa Use a minimal job spec representation in memory. 2024-07-26 23:43:53 +02:00
Greyson Parrelli
eb59afc33c Improve efficiency of sorting jobs in FastJobStorage. 2024-07-26 23:43:53 +02:00
Alex Hart
625ca832b0 Fix bad padding in expired story quotes. 2024-07-26 23:43:53 +02:00
Clark
bc6face873 Fix edit message clearing story reply quote. 2024-07-26 23:43:53 +02:00
Clark
0aeaec8b67 Show edited time instead of original time. 2024-07-22 11:59:12 -04:00
Alex Hart
b70b058925 Fix crash with reused destroyed context. 2024-07-22 11:59:12 -04:00
Greyson Parrelli
e17cf37799 Allow use of the in-app emoji picker when using system emoji. 2024-07-22 11:59:12 -04:00
Alex Hart
330debcf37 Add Snackbar displaying message that user is awaiting entry to an ad hoc call. 2024-07-22 11:59:12 -04:00
mtang-signal
6641cc4806 Update device notification prompt. 2024-07-22 11:59:12 -04:00
Michelle Tang
bd3ab2cc38 Update editing animation. 2024-07-22 11:59:12 -04:00
Clark
fa487e1885 Dont set subscriber data if subscriber is null. 2024-07-22 11:59:12 -04:00
Clark
eb2fc33940 Cap size of group updates. 2024-07-22 11:59:11 -04:00
Alex Hart
c39739bcb4 Add call info treatment for unknown members. 2024-07-22 11:59:11 -04:00
Alex Hart
e7720640d1 Implement landscape calling. 2024-07-22 11:59:11 -04:00
Greyson Parrelli
6e55bc04ab Bump version to 7.12.2 2024-07-22 11:58:41 -04:00
Greyson Parrelli
56ab95b0e1 Update translations and other static files. 2024-07-22 11:58:11 -04:00
Greyson Parrelli
678c1459e9 Fix welcome screen button after pressing back on transfer screen. 2024-07-22 11:40:25 -04:00
Greyson Parrelli
e87b73cc19 Bump version to 7.12.1 2024-07-19 16:09:27 -04:00
Greyson Parrelli
45e1ecd07e Update baseline profile. 2024-07-19 16:09:05 -04:00
Greyson Parrelli
7b043d4143 Update translations and other static files. 2024-07-19 15:50:45 -04:00
Alex Hart
046d439887 Do not restrict parcelized type. 2024-07-19 16:32:29 -03:00
Greyson Parrelli
b34bf4b8b0 Initialize app dependencies earlier. 2024-07-18 12:03:23 -04:00
Nicholas Tinsley
fddc99ab4f Remove noise log statement from ExoPlayer. 2024-07-18 11:07:27 -04:00
Rashad Sookram
1007111310 Update to RingRTC v2.44.4 2024-07-18 08:54:54 -04:00
Greyson Parrelli
3346a1e918 Bump version to 7.12.0 2024-07-17 15:44:50 -04:00
Greyson Parrelli
9c391eb2c9 Update baseline profile. 2024-07-17 15:44:50 -04:00
Greyson Parrelli
f714e038a0 Update translations and other static files. 2024-07-17 15:30:28 -04:00
Greyson Parrelli
61405a62c2 Fix NPE in exoplayer transfer listener. 2024-07-17 15:21:14 -04:00
Alex Hart
d424a60345 Fix group call flickering missed. 2024-07-17 15:21:14 -04:00
Clark
3c10966a36 Add logging for Conversation activity restart due to config changes. 2024-07-17 15:21:14 -04:00
Clark
e210d5939c Dismiss battery saver prompt on continue. 2024-07-17 15:21:14 -04:00
Cody Henthorne
1fafcc69ff Improve large upload over slow connections. 2024-07-17 15:21:14 -04:00
Clark
3184368fa7 Various backup/restore bug fixes. 2024-07-17 15:21:14 -04:00
Greyson Parrelli
c622b7fdb1 Remove all legacy uploads to cdn0. 2024-07-17 15:21:14 -04:00
Alex Hart
29ead80e17 Remove outline on generated call link preview. 2024-07-17 15:21:14 -04:00
Alex Hart
9a72833e06 Filter out outgoing calls and call links in missed call filter. 2024-07-17 15:21:14 -04:00
Alex Hart
d0baf1dc95 Remove appbar offset listener when call log is unbound. 2024-07-17 15:21:14 -04:00
Michelle Tang
7f1227ee19 Add discard draft confirmation when editing. 2024-07-16 17:08:10 -07:00
Nicholas Tinsley
34c95dc082 Audio device logging. 2024-07-16 17:57:05 -04:00
Nicholas Tinsley
10ad73f201 Add logging around data source reading. 2024-07-16 17:48:47 -04:00
Nicholas Tinsley
21fab7c5ba Fix backing out of group story text send. 2024-07-16 17:36:14 -04:00
Nicholas Tinsley
e9c2f96bb9 Increase logging around attachment compression/upload job lifecycle. 2024-07-16 17:31:45 -04:00
Nicholas Tinsley
a950462451 Apply phone number formatting immediately. 2024-07-16 16:54:39 -04:00
Nicholas Tinsley
809317c0fd Run registration UI callbacks in LifeycleScope. 2024-07-16 16:54:39 -04:00
Cody Henthorne
6634540183 Fix generation and update baseline profile. 2024-07-16 16:54:39 -04:00
Alex Hart
89bfba3ee9 Backups subscription flow odds and ends. 2024-07-16 16:54:39 -04:00
Alex Konradi
97974291d2 Update to libsignal 0.52.2 2024-07-16 16:54:38 -04:00
Cody Henthorne
6daee5719b Allow for larger input videos for sending. 2024-07-16 16:54:38 -04:00
Clark
58443c46be Fix username constraint on re-reg from prod->staging. 2024-07-16 16:54:38 -04:00
Nicholas Tinsley
8cbecc2992 Upgrade libphonenumber to 8.13.40. 2024-07-16 16:54:38 -04:00
Cody Henthorne
4c0ca48af3 Handle ChatServiceException in response processors. 2024-07-16 16:54:38 -04:00
Greyson Parrelli
91eeda6c6e Allow RemoteConfig to be lazily initialized. 2024-07-16 16:54:38 -04:00
Greyson Parrelli
04e75c18dd Bump version to 7.11.3 2024-07-16 16:48:08 -04:00
Greyson Parrelli
f5fbfbc7fd Update translations and other static files. 2024-07-16 16:40:44 -04:00
Cody Henthorne
0e1df94b54 Fix processing incoming group invites. 2024-07-16 15:45:22 -04:00
Cody Henthorne
dd923629f6 Bump version to 7.11.2 2024-07-12 17:02:55 -04:00
Cody Henthorne
df8992aaca Update translations and other static files. 2024-07-12 16:57:01 -04:00
Cody Henthorne
63b9700865 Fix group sync message sent bug. 2024-07-12 16:48:12 -04:00
Nicholas Tinsley
d309877d63 Fix ISE in registration. 2024-07-12 16:47:51 -04:00
Nicholas Tinsley
f247fd78c6 Constant bitrate video encoding. 2024-07-12 11:01:03 -04:00
Cody Henthorne
4f96cb7439 Bump version to 7.11.1 2024-07-11 16:25:44 -04:00
Cody Henthorne
6e6e3a5eba Update translations and other static files. 2024-07-11 16:14:04 -04:00
Nicholas Tinsley
9166ed64fb Fix crash in nickname update button. 2024-07-11 16:02:15 -04:00
Nicholas Tinsley
25e4eaa8e8 Log successes in audio device switching. 2024-07-10 13:24:15 -04:00
Nicholas Tinsley
6e742ce770 Prevent crash from showing bottom sheet. 2024-07-09 18:20:15 -04:00
Nicholas Tinsley
c134c3033e Prevent ISE with view binding in registration. 2024-07-09 18:20:15 -04:00
Nicholas Tinsley
fb43a8257c Add more logging for rate limited scenarios. 2024-07-09 16:54:30 -04:00
Nicholas Tinsley
cecfe80d61 Simplify reg v2 nav graph names. 2024-07-09 16:13:22 -04:00
Cody Henthorne
6c302b708a Bump version to 7.11.0 2024-07-09 13:55:58 -04:00
Cody Henthorne
6a22919c50 Update baseline profile. 2024-07-09 13:49:15 -04:00
Cody Henthorne
9073ce5c7b Update translations and other static files. 2024-07-09 13:46:48 -04:00
Michelle Tang
9024c19169 Update device-specific notification support configs. 2024-07-09 13:40:41 -04:00
Arthur-GYT
60a0565ba8 Show max edits warning before editing.
Fixes #13428
Closes #13615

Signed-off-by: Arthur-GYT <a.gayot@ik.me>
2024-07-09 13:40:41 -04:00
Cody Henthorne
383f7556e3 Fix delete for everyone dialog option in note to self. 2024-07-09 13:40:41 -04:00
Greyson Parrelli
94795599e2 Inline the delete sync feature flag. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley
84fbb7c466 Update "lower hand" label to "lower" 2024-07-09 13:40:41 -04:00
Cody Henthorne
c339f39b70 Fix camera manager memory leak. 2024-07-09 13:40:41 -04:00
Cody Henthorne
41a3609f06 Fix edit quote draft loading incorrectly bug. 2024-07-09 13:40:41 -04:00
Cody Henthorne
f5abd7acdf Add Group Send Endorsements support. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley
414368e251 Prevent crash when trying to display Contact Support bottom sheet multiple times. 2024-07-09 13:40:41 -04:00
Jim Gustafson
a3d1197aef Update to RingRTC v2.44.3 2024-07-09 13:40:41 -04:00
Nicholas Tinsley
d91760eefc Upgrade AndroidX Media3 to 1.3.1. 2024-07-09 13:40:41 -04:00
Greyson Parrelli
ee20ced238 Switch MediaName to hex encoding. 2024-07-09 13:40:41 -04:00
Alex Hart
066892c11a Make group subtitles auto-update as names change. 2024-07-09 13:40:41 -04:00
Alex Hart
69fd4f79db Stop reading redemptionTime field. 2024-07-09 13:40:41 -04:00
Alex Hart
f49e2768c1 Fix crash in review card repository. 2024-07-09 13:40:41 -04:00
Greyson Parrelli
70378b85d7 Remove unused capabilities. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley
585401a98e Do not download attachment if we do not have a digest. 2024-07-09 13:40:41 -04:00
Alex Hart
cf7ebfa03d Do not mark update unread if user was ever in the call. 2024-07-09 13:40:41 -04:00
Nicholas Tinsley
aec0a9951a Prevent backups from being scheduled twice within the jitter window.
Fixes #13559.
2024-07-09 13:40:41 -04:00
Nicholas Tinsley
b113eec940 Show "Update" action button on profile name change. 2024-07-09 13:40:41 -04:00
Michelle Tang
a966812bfc Add full send attachments. 2024-07-09 13:40:41 -04:00
Alex Hart
3879a8ffdb Pluralize backup strings. 2024-07-09 13:40:41 -04:00
Alex Hart
5b949b0116 Fix call id serialization. 2024-07-09 13:40:41 -04:00
Rashad Sookram
3c13619ce8 Update to RingRTC v2.44.2 2024-07-09 13:40:41 -04:00
Cody Henthorne
24bba98122 Fix sync thread delete sending another sync back. 2024-07-09 13:40:41 -04:00
Cody Henthorne
a96e5e6ae6 Fix delete sync capability updating on linked devices. 2024-07-09 13:40:41 -04:00
Alex Hart
4cfdfab31e Rename more in-app-payment classes. 2024-07-09 13:40:41 -04:00
Alex Hart
77d3116431 Rename DonationValues to InAppPaymentValues. 2024-07-09 13:40:41 -04:00
Alex Hart
b943df1ce4 Add translatable copy for backup alert fragment. 2024-07-09 13:40:41 -04:00
Alex Hart
8bbb7d56e0 Implements a bunch of missing things in the backup checkout flow stuff. 2024-07-09 13:40:41 -04:00
Clark
079a3d4fee Add import/export tests for contact messages and link previews. 2024-07-09 13:40:41 -04:00
Jim Gustafson
176e0e7765 Update to RingRTC v2.44.1 2024-07-09 13:40:41 -04:00
Greyson Parrelli
c73e80f8d9 Include username link entropy in backups. 2024-07-09 13:40:41 -04:00
Greyson Parrelli
47cd1b568f Add lock screen help dialog. 2024-07-09 13:40:41 -04:00
Clark
058c523329 Add support for import/export of shared contact messages. 2024-07-09 13:40:40 -04:00
Clark
84515482a6 Message backup support for link previews. 2024-07-09 13:40:40 -04:00
Chris Eager
02629020df Remove Option.RECAPTCHA from ProofRequiredException. 2024-07-09 13:40:40 -04:00
Clark
58d769b21f Allow exporting backup tests to binproto. 2024-07-09 13:40:40 -04:00
Greyson Parrelli
9dc67e0466 Do not use quote contents in edit sync messages.
Resolves #13571
2024-07-09 13:40:40 -04:00
Greyson Parrelli
72d02104dc Move StringExtensions to core-util-jvm. 2024-07-09 13:40:40 -04:00
Cody Henthorne
371a39049d Ignore flakey delete sync test. 2024-07-03 14:02:59 -04:00
Clark
47e4a6cf5a Regularly delete any archived media we don't know about. 2024-07-03 14:02:59 -04:00
Clark Chen
4a41e9f9a1 Bump version to 7.10.3 2024-07-02 11:21:45 -04:00
Clark Chen
9fa1b58019 Update translations and other static files. 2024-07-02 11:21:45 -04:00
Michelle Tang
c24473e176 Fix default name for linked devices. 2024-07-01 16:44:53 -04:00
mtang-signal
1311ec498f Default to back camera when linking device. 2024-07-01 08:59:35 -04:00
Nicholas Tinsley
251cec5dee Bump version to 7.10.2 2024-06-28 15:23:01 -04:00
Nicholas Tinsley
1e15a8c1d3 Update translations and other static files. 2024-06-28 15:22:12 -04:00
Michelle Tang
9c5c58794b Fix invalid qr code crash. 2024-06-27 16:12:38 -04:00
Nicholas Tinsley
50063854d7 Bump version to 7.10.1 2024-06-26 13:57:39 -04:00
Nicholas Tinsley
79d2041e46 Update translations and other static files. 2024-06-26 13:45:34 -04:00
Nicholas Tinsley
479b27ce94 Fix benchmark tests. 2024-06-26 13:39:28 -04:00
Cody Henthorne
a66857a7cc Fix incorrect local group state bug. 2024-06-26 13:39:28 -04:00
Alex Hart
37815a3f39 Fix bug with cut off avatars. 2024-06-26 13:39:28 -04:00
Alex Hart
b55ba67b66 Split out backup and subscription error sheet handling. 2024-06-26 13:39:28 -04:00
Michelle Tang
37a2d5fbca Release updated linked devices. 2024-06-26 13:39:28 -04:00
Nicholas Tinsley
d7b5c6bff3 Delete registration V1. 2024-06-26 13:39:28 -04:00
Alex Hart
f11028529e Fix hard coded placeholder in iDEAL dialog. 2024-06-25 10:19:27 -03:00
Greyson Parrelli
93ec322bb9 Bump version to 7.10.0 2024-06-24 15:37:57 -04:00
Greyson Parrelli
71e0468d2c Update translations and other static files. 2024-06-24 15:37:08 -04:00
moiseev-signal
816e3442a0 Adopt libsignal 0.51.1 2024-06-24 15:04:03 -04:00
Cody Henthorne
c37ed722dc Attempt to fix potential draft setting loop. 2024-06-24 15:04:03 -04:00
Michelle Tang
e08c2966c3 Move biometrics check when linking a device. 2024-06-24 15:04:03 -04:00
Cody Henthorne
976f80ff7e Fix conversation not closing after delete bug. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
34a4bda331 Do not send PNI-hello-worlds for new installs. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
a4077ccb4a Add app bundle support.
Co-authored-by: Joshua Lund <josh@signal.org>
2024-06-24 15:04:03 -04:00
Cody Henthorne
21ada2a503 Fix request to rejoin after group updates bug. 2024-06-24 15:04:03 -04:00
Alex Hart
57a70c3085 Quiet down auth check job. 2024-06-24 15:04:03 -04:00
Cody Henthorne
16c8b88f0f Fix multiple text attachments when forwarding to multiple recipients.
Fixes #13593
2024-06-24 15:04:03 -04:00
Michelle Tang
b806952430 Add device-specific support configs. 2024-06-24 15:04:03 -04:00
Alex Hart
c0da0bd272 Add proper payment method type to BackupTypeSettings screen. 2024-06-24 15:04:03 -04:00
Alex Hart
45239c2264 Add payment history screens for backups. 2024-06-24 15:04:03 -04:00
Alex Hart
690236c4e5 Handle manual cancellation UI hint in DonationValues. 2024-06-24 15:04:03 -04:00
Clark
ebee3f72e6 Add test infrastructure for backup binprotos. 2024-06-24 15:04:03 -04:00
Alex Hart
37cec7d44f Implement 1:1 call event delete syncs. 2024-06-24 15:04:03 -04:00
Alex Hart
187fd63a75 Add content for disk full alert for backups. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
362cdfc463 Use a snapshot of the SignalStore during backups. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
863b443317 Convert SignalStore to kotlin. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
341c474610 Remove some indirect database reads from backup export. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
cbb3c0911c Create backups from copies of the database file.
Still more work here to do with regards to certain tables, like
SignalStore and Recipient.
2024-06-24 15:04:03 -04:00
Greyson Parrelli
890facc6f6 Clean up ChatItemExportIterator. 2024-06-24 15:04:03 -04:00
Greyson Parrelli
6fa8337058 Update to the latest backup v2 spec.
Removes some dead protos, removes some sticker details, adds in gift
badges.
2024-06-24 15:04:03 -04:00
Alex Hart
3f1cb65e02 Migrate translatable strings from fragment to xml. 2024-06-24 15:04:03 -04:00
Cody Henthorne
3551e7ec00 Remove rx send remote config and group send using rx always. 2024-06-24 15:04:03 -04:00
Alex Hart
5ecf60a306 Add ability to turn off and delete backups. 2024-06-24 15:04:03 -04:00
Cody Henthorne
6659700a1c Improve delete sync coverage for partial expiring threads. 2024-06-24 15:04:02 -04:00
Cody Henthorne
070174fee6 Add delete sync capability to log section. 2024-06-24 15:04:02 -04:00
Cody Henthorne
09003d85b1 Add single attachment delete sync. 2024-06-24 15:04:02 -04:00
Alex Hart
ea87108def Heal InAppPaymentSubscriber currency if we have a payment with a matching subscriber id. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley
7a696f9a62 Increase pluralization for raised hand snackbar. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley
8ba57a2733 Upgrade OkHttp to 4.12.
Addresses #13491
2024-06-24 15:04:02 -04:00
Cody Henthorne
9824cc2cbe Update delete sync strings. 2024-06-24 15:04:02 -04:00
Michelle Tang
ad60cc72cb Add empty linked device state. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley
1950b80402 Update AndroidX Camera libraries to 1.3.4. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley
2acb47952b Differently pluralize raise hand strings. 2024-06-24 15:04:02 -04:00
Cody Henthorne
14cacaef86 Add verbose tracking to DelteForMeSync test to help finding flakey test. 2024-06-24 15:04:02 -04:00
Michelle Tang
958e815933 Remove ability to scan qr code from gallery. 2024-06-24 15:04:02 -04:00
Alex Hart
6b50be78c0 Implement start of backups payment integration work. 2024-06-24 15:04:02 -04:00
Michelle Tang
680223c4b6 Update permission buttons for contacts.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2024-06-24 15:04:02 -04:00
Greyson Parrelli
1af914d5ef Use SubsamplingImageView for everything except GIFs.
Fixes #10324
2024-06-24 15:04:02 -04:00
Cody Henthorne
a2fc710261 Add support for addressing attachments within a message. 2024-06-24 15:04:02 -04:00
Dan Brunwasser
10922594b3 Improve system emoji rendering across the app with EmojiCompat2.
Resolves #13327
2024-06-24 15:04:02 -04:00
Michelle Tang
abd80c5204 Update linked devices UI. 2024-06-24 15:04:02 -04:00
Alex Hart
ff589e3b91 Fix call link export crash. 2024-06-24 15:04:02 -04:00
Cody Henthorne
c80ccd70ec Add additional delete sync support. 2024-06-24 15:04:02 -04:00
Jim Gustafson
d22d18da47 Update to RingRTC v2.44.0 2024-06-24 15:04:02 -04:00
Clark
75b41c34ea Add import/export for stickers and sticker packs. 2024-06-24 15:04:02 -04:00
Alex Hart
11557e4815 Rewrite fallbackphoto system. 2024-06-24 15:04:02 -04:00
Greyson Parrelli
d698f74d0b Rename FeatureFlags -> RemoteConfig. 2024-06-24 15:04:02 -04:00
Greyson Parrelli
ecbea9fd95 Improve FeatureFlag change detection, use for SVR3. 2024-06-24 15:04:02 -04:00
Greyson Parrelli
13f7a64139 Refactor FeatureFlags. 2024-06-24 15:04:02 -04:00
Michelle Tang
39cb1c638e Remove sms tag from contacts. 2024-06-24 15:04:02 -04:00
Nicholas Tinsley
489b58ad67 Abort transcoding if frame processing gets stuck. 2024-06-24 15:04:02 -04:00
Cody Henthorne
f20fe33af9 Ignore flakey delete sync test. 2024-06-24 15:04:02 -04:00
Clark
6adddf4a0c Add display of last backup time to restore flow. 2024-06-24 15:04:01 -04:00
Clark Chen
16773c9b17 Fix import/export tests with my story. 2024-06-24 15:04:01 -04:00
Michelle Tang
4b50365fa9 Release updated linked devices behind feature flag. 2024-06-24 15:04:01 -04:00
Clark Chen
98766b9ebb Fix backup type dark mode. 2024-06-24 15:04:01 -04:00
Cody Henthorne
45a739ce92 Show notification for group adds. 2024-06-24 15:04:01 -04:00
Clark
c0d7145ada Add handling for "My Story" import/export. 2024-06-24 15:04:01 -04:00
Clark
f94c007af8 Make message backup settings screen update properly. 2024-06-24 15:04:01 -04:00
Michelle Tang
df19cb5795 Increase minimum height of keyboard. 2024-06-24 15:04:01 -04:00
Nicholas Tinsley
e6ceb55092 Match account deletion number by short NSN.
Fixes #13583.
2024-06-24 15:04:01 -04:00
Michelle Tang
bfe2b5cba9 Add loading screen to linked devices. 2024-06-21 09:19:47 -03:00
Alex Hart
571004df50 Tokenize group title search. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
f32b59f0aa Fix crash with AnalyzeDatabaseJob. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
e4019d8595 Ignore deprecated backup tests. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
0b66a8701e Convert FeatureFlags to kotlin. 2024-06-21 09:19:47 -03:00
Clark
e62b8de1bc Fix most of import/export integration tests. 2024-06-21 09:19:47 -03:00
Michelle Tang
d5cd790871 Remove redundant gallery permission ask. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
664c22d8f1 Add mostly-working SVR3 implementation behind flag. 2024-06-21 09:19:47 -03:00
Cody Henthorne
143a61e312 Fix calling error state display bugs. 2024-06-21 09:19:47 -03:00
Michelle Tang
baaad0e475 Fix camera-first qr scans. 2024-06-21 09:19:47 -03:00
Michelle Tang
7086709082 Update devices screen after linking a new device. 2024-06-21 09:19:47 -03:00
Michelle Tang
7bd5ad8c0b Use common compose qr screen for usernames. 2024-06-21 09:19:47 -03:00
Michelle Tang
df19c91ae2 Add padding to quoted messages. 2024-06-21 09:19:47 -03:00
Clark
e5872037e0 Add import/export of chat colors. 2024-06-21 09:19:47 -03:00
Clark
b782fabbb6 Update backup proto with subscriber and recipient changes. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
485b466bd2 Crash on RuntimeExceptions thrown during all Jobs. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
3beac6dfa9 Fix linked device inactive filtering. 2024-06-21 09:19:47 -03:00
Michelle Tang
98290a9fa3 Update max limit string. 2024-06-21 09:19:47 -03:00
Michelle Tang
13dd59f226 Skip biometrics check if unavailable when linking a device. 2024-06-21 09:19:47 -03:00
Michelle Tang
d9c42a4135 Add ability to scan linked device qr code from gallery. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
644b93e5a3 Provide default text background color. 2024-06-21 09:19:47 -03:00
Greyson Parrelli
3ff218f9c6 Make build deprecation more resilient to clock skew. 2024-06-21 09:19:47 -03:00
Alex Hart
f572eb5322 Add CallLink Observed event and handling. 2024-06-21 09:19:47 -03:00
Michelle Tang
d3eb480d31 Update add linked devices screen. 2024-06-21 09:19:47 -03:00
Michelle Tang
ac52b5b992 Update linked devices screen. 2024-06-21 09:19:47 -03:00
Michelle Tang
5c181e774f Prevent editing on stickers. 2024-06-21 09:19:46 -03:00
Michelle Tang
05d25718da Add animation when editing a message. 2024-06-21 09:19:46 -03:00
Clark
66c50bef44 Hook up message backup restore flow to reg v2.
Co-authored-by: Nicholas Tinsley <nicholas@signal.org>
2024-06-21 09:19:46 -03:00
Alex Hart
26bd59c378 Bump version to 7.9.6 2024-06-20 18:13:53 -03:00
Alex Hart
e90eae6080 Update baseline profile. 2024-06-20 18:08:30 -03:00
Alex Hart
86a7db7653 Update translations and other static files. 2024-06-20 18:05:24 -03:00
Cody Henthorne
230de7e9dc Use URL for S3.
Thanks to Oscar Mira <valldrac@molly.im> for bringing this to our attention.
2024-06-20 12:15:49 -04:00
Greyson Parrelli
4b8546a151 Bump version to 7.9.5 2024-06-14 15:16:17 -04:00
Greyson Parrelli
ecd214b91b Update translations and other static files. 2024-06-14 15:15:35 -04:00
Greyson Parrelli
6b5de6e3e5 Only do local donation cancel if it's currently active. 2024-06-14 15:06:13 -04:00
Greyson Parrelli
58b6e49aae Fix NPE when canceling a donation. 2024-06-14 15:06:13 -04:00
Greyson Parrelli
c480512600 Bump version to 7.9.4 2024-06-13 18:34:38 -04:00
Greyson Parrelli
3a5b6476aa Updated language translations. 2024-06-13 18:32:18 -04:00
Alex Hart
cb171092cf Fix crash loop when writing invalid currency . 2024-06-13 18:03:20 -03:00
Nicholas Tinsley
71979b34db Alert user to file system errors during backup restore. 2024-06-13 16:13:43 -04:00
Nicholas Tinsley
73142cea39 Don't hold lazy reference to view binding in delayed runnable. 2024-06-13 15:43:51 -04:00
Nicholas Tinsley
2ab2c6f039 Ensure that substrings match in the registration contact support bottom sheet. 2024-06-13 15:36:41 -04:00
Greyson Parrelli
0bea15c0af Bump version to 7.9.3 2024-06-12 11:23:20 -04:00
Greyson Parrelli
cbd78d78ba Update translations and other static files. 2024-06-12 11:22:44 -04:00
Cody Henthorne
ac0604a753 Fix rare remote megaphone crash. 2024-06-12 11:10:59 -04:00
Nicholas Tinsley
0e57335be1 Split Raise Hand plurals into separate strings. 2024-06-11 11:43:58 -04:00
Greyson Parrelli
c4e64f6fa3 Bump version to 7.9.2 2024-06-10 16:11:51 -04:00
Greyson Parrelli
bf9716f206 Update translations and other static files. 2024-06-10 16:10:42 -04:00
Cody Henthorne
057ffdbaaf Fix conversation memory leak. 2024-06-10 14:54:02 -04:00
Nicholas Tinsley
65dc0d3f34 Disable verbose logging in media converter. 2024-06-10 14:38:19 -04:00
Clark
173ee95e62 Fix backup jitter and add unit tests. 2024-06-10 14:20:56 -04:00
Nicholas Tinsley
789339afa7 Update Raise Hand string. 2024-06-10 11:01:56 -04:00
Nicholas Tinsley
21b518da7a Don't show volume indicator nor switch camera button until incoming call connects. 2024-06-10 11:01:56 -04:00
Nicholas Tinsley
57b6b8dcf1 Improve Raise Hand behavior when in a call with a linked device. 2024-06-07 13:53:58 -04:00
Cody Henthorne
543a85316e Improve FCM check clock skew handling. 2024-06-07 13:02:44 -04:00
Cody Henthorne
2fedb3a0ee Bump version to 7.9.1 2024-06-07 11:59:02 -04:00
Cody Henthorne
ae450aed67 Update baseline profile. 2024-06-07 11:48:08 -04:00
Cody Henthorne
0abb4727fc Update translations and other static files. 2024-06-07 11:30:34 -04:00
Alex Hart
4bc6eb96ff Fix 3DS waiting-for-auth state when launching external application. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley
e6a126d416 Only submit captcha once in Registration V2. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley
fdf858f379 Prevent crash if linked device also raises their hand. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley
4151d123cd Fix crash in registration v2 country code drop down. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley
c8a9759eba Unify Raise Hand copy. 2024-06-07 11:05:09 -04:00
Cody Henthorne
c59b74627f Improve strings for localization. 2024-06-07 11:05:09 -04:00
Nicholas Tinsley
f2191d2996 Adjust text colors in dark mode in Registration V2. 2024-06-07 11:05:09 -04:00
Cody Henthorne
7dfffbd50b Add missing windows aapt2. 2024-06-06 10:21:22 -04:00
Cody Henthorne
329fc52077 Bump version to 7.9.0 2024-06-05 16:20:29 -04:00
Cody Henthorne
8976111f61 Update translations and other static files. 2024-06-05 15:54:32 -04:00
Cody Henthorne
7402959ac6 Fix error handling for resumable uploads to cdn3. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
220d3877a2 Fix phone number autofill in Registration V2. 2024-06-05 15:46:01 -04:00
Clark Chen
380c33642c Clean quote when canceling edit message. 2024-06-05 15:46:01 -04:00
Alex Hart
7acb2bef3d Fix private story listing My Story as a recipient. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
1a103106a5 Catch more stuff in SqlCipherDeletingErrorHandler.
Fixes #13577
2024-06-05 15:46:01 -04:00
Greyson Parrelli
6025e423e8 Fix payment request message text. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
54656ea14e Potentially fix 'design assumption violated' ISE. 2024-06-05 15:46:01 -04:00
Alex Hart
fd00ed71b5 Fix clickable link in donation thanks sheet. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
d4fba5f3c7 Ship Raise Hand. 2024-06-05 15:46:01 -04:00
Alex Hart
ce244f2e8f Fix hit detection for story link previews. 2024-06-05 15:46:01 -04:00
Cody Henthorne
4ad466390f Fix second person translations for group story reactions. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
c5c9b09f7b Turn on Registration V2 and Change Number V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
0638b31c1f More registration lock V2 improvements. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
c3c713a75a Prevent getting stuck on registration lock V2 fragment. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
9af1c72233 Registration V2 restore tweaks. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
500a1e46ad Improve ABS logging. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
4b446877af Change number captcha submission improvement. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
015548613a Fix change view model test.
Co-authored-by: Cody Henthorne <cody@signal.org>
2024-06-05 15:46:01 -04:00
Alex Hart
30b339a482 Remove unnecessary subscriber call for manual cancel. 2024-06-05 15:46:01 -04:00
Cody Henthorne
6dcb2e8d24 Fix androidTest message content fuzer for attachment pointers. 2024-06-05 15:46:01 -04:00
Cody Henthorne
3e8e17526b Fix missing group context on message records. 2024-06-05 15:46:01 -04:00
Clark Chen
30ecaf7aea Remove double remote deleted column from ChatItemImportInserter. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
f761008509 Clean up some stuff around ImportExportTest. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
c3ab8dddd0 Fix runPostSuccessfulTransaction behavior. 2024-06-05 15:46:01 -04:00
Alex Hart
164f089d37 Fix NPE in deleteAll call. 2024-06-05 15:46:01 -04:00
Alex Hart
a021b400bd Fix SafetyNumberBottomSheetRepositoryTest. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
fac8f403be Remove outdated dlist test. 2024-06-05 15:46:01 -04:00
Clark
d85ab37828 Add import and tombstones for mobile coin payments. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
1e35403c87 Change Number V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
b99c2165fa Handle SVR exceptions in Registration V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
303100bb6b Further registration lock improvements in Registration V2. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
b71ba79b8a Flesh out registration lock support for Registration V2. 2024-06-05 15:46:01 -04:00
Clark
54cd84b842 Add handling for import/export of edited messages. 2024-06-05 15:46:01 -04:00
Clark
1565ecdcea Fix multiple scheduled backups due to jitter. 2024-06-05 15:46:01 -04:00
Clark
0a99b68d25 Fix shared contacts avatar double upload. 2024-06-05 15:46:01 -04:00
Alex Hart
f4fac5bd90 Prevent reactions from being overlaid by raise hand. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
f6760b90da Flesh out verification challenge support for registration v2. 2024-06-05 15:46:01 -04:00
Clark
ad9b1f05b4 Disable restore on open if auto-download is off. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
17581a7a5e Update SignalProgressDialog.
Fixes #12949.
2024-06-05 15:46:01 -04:00
Clark
b41bf66133 Disable "Edited" click listener for outgoing messages. 2024-06-05 15:46:01 -04:00
Clark
8bb3d71472 Fix thumbnail not being clickable on initial media receive. 2024-06-05 15:46:01 -04:00
Nicholas Tinsley
295d4b9466 Pin left GIF button to bottom of compose box. 2024-06-05 15:46:01 -04:00
Ahmed El herz
5e490376f4 Fix initial event not triggering onTouchEvent.
Fixes #13351
2024-06-05 15:46:01 -04:00
Greyson Parrelli
fa27531c00 Inline SVR2 feature flag. 2024-06-05 15:46:01 -04:00
Cody Henthorne
2737e5613c Use raw values for learned profile name event. 2024-06-05 15:46:01 -04:00
Greyson Parrelli
d84612ebf4 Revert "Validate full edit message payload."
This reverts commit 268b621667e3144fb3f07099d04aa5609387a5e6.
2024-06-05 15:46:00 -04:00
Greyson Parrelli
96165ad5a8 Fix username getting prematurely removed from recipient. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
19caef057e Update to AGP 8.4.1. 2024-06-05 15:46:00 -04:00
Clark
29cafb11eb Update proto and add payments export without tombstone. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
7e458bfde0 Validate full edit message payload. 2024-06-05 15:46:00 -04:00
Clark
2a3cb80217 Add ui wiring for archive thumbnail support. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
3d382ee15e Use extension functions instead of LibSignalNetwork class. 2024-06-05 15:46:00 -04:00
Clark
6069dfc6f8 Add a separate column for tracking thumbnail restore state. 2024-06-05 15:46:00 -04:00
Clark Chen
dee19ed94a Fix attachment table v231 migration consistency. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
905b0681f5 Update otpk/kpk tests. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
b6a4e1f145 Rewrite the AppDependencies system. 2024-06-05 15:46:00 -04:00
Alex Hart
a0131bf39b Fix db inconsistency. 2024-06-05 15:46:00 -04:00
Alex Hart
7ed77a00df Remove unused method from RecipientTable. 2024-06-05 15:46:00 -04:00
Alex Hart
887c173d8f Move camera flip and improve movement of some ui elements. 2024-06-05 15:46:00 -04:00
Cody Henthorne
6362da7a50 Refactor group state processing. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
1296365bed Upgrade to libsignal 0.47.0 2024-06-05 15:46:00 -04:00
Alex Hart
99ae7c5961 Add search view extension for incognito keyboards. 2024-06-05 15:46:00 -04:00
Clark
5c3ea712fe Add streaming video support for attachment files. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
bc5cb454bf Ship "instant" video playback. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
8a7c2c1e20 Rotate libsignal CDS feature flag. 2024-06-05 15:46:00 -04:00
Cody Henthorne
a81a675d59 Add Delete for Me sync support. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
1c66da7873 Update slow notification debugging info. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
afe3cd1098 Additional error handling for registration v2. 2024-06-05 15:46:00 -04:00
Alex Hart
4f3ee9ca1d Skip the contact links migration if contact permissions are disabled. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
7771aaa501 Sort the language list during build for determinism.
Addresses #13565.
2024-06-05 15:46:00 -04:00
Greyson Parrelli
5ad38c7960 Ensure archive data is copied when deduping. 2024-06-05 15:46:00 -04:00
Alex Hart
0fb1514da2 Consolidate subscription information and manage button to a single row. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
f37efd7e15 Add additional error handling for registration v2. 2024-06-05 15:46:00 -04:00
mtang-signal
1ae2464df1 Update remaining gallery permission UI. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
0425b70d31 Do not show unregistered contacts in search results. 2024-06-05 15:46:00 -04:00
Clark
7b0d3f36dc Ignore digest for downloading archived thumbnails. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
14b917dc7e Fix typo in query resulting in contacts not being unlinked. 2024-06-05 15:46:00 -04:00
Ehren Kret
6184cc0307 Migrate existing raw contacts to add video call links. 2024-06-05 15:46:00 -04:00
Nicholas Tinsley
870aa8e7b0 Fix country code sorting in reg v2. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
d88016669b Shorten groupId string patterns. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
a464b413d9 Use correct label in log. 2024-06-05 15:46:00 -04:00
Alex Hart
d719edf104 Rewrite in-app-payment flows to prepare for backups support. 2024-06-05 15:46:00 -04:00
mtang-signal
b36b00a11c Update camera permission UI for voice calls. 2024-06-05 15:46:00 -04:00
mtang-signal
a99db2b16e Update camera permission UI for usernames. 2024-06-05 15:46:00 -04:00
Greyson Parrelli
2744dec43a Switch to using dateSent for jump-to-calendar.
We use dateSent for date dividers, but were using dateReceived for
calendar date availability, which would occasionally result in a
mismatch. Switched to use the same thing we use for date dividers.
2024-06-05 15:45:59 -04:00
Greyson Parrelli
6f2cce1494 Add acknowledgements from libsigna/ringrtc. 2024-06-05 15:45:59 -04:00
Greyson Parrelli
689ee243aa Fix potential sqlite conflict in dlist on recipient remap. 2024-06-05 15:45:59 -04:00
Cody Henthorne
537fc0ef5c Update to Kotlin 1.9.20, AGP 8.4.0, and Gradle 8.6 2024-06-05 15:45:59 -04:00
Clark Chen
e647b31f29 Explicitly persist message backup tier. 2024-05-17 10:39:09 -04:00
Alex Hart
b59932cd88 Fix compilation error in contacts test app. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley
cfb4377de3 Apply automated ktlint 1.2.1 formatting. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley
e861c022da Disable new ktlint rules with preexisting violations. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley
59006d3182 Upgrade ktlint to 1.2.1. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley
503faea3a9 Support voice verification in registration v2. 2024-05-17 09:33:22 -04:00
Nicholas Tinsley
eb114de5c8 Bump version to 7.8.1 2024-05-16 15:50:48 -04:00
Nicholas Tinsley
1bf9695cff Update translations and other static files. 2024-05-16 15:45:51 -04:00
Clark
241bf065e8 Fix missing thumbnail_file column in media query. 2024-05-16 13:09:58 -04:00
Clark Chen
e0f3b35805 Fix missing archive_thumbnail_cdn column. 2024-05-16 12:30:33 -04:00
Nicholas Tinsley
5741dfc00b Bump version to 7.8.0 2024-05-16 10:24:48 -04:00
Nicholas Tinsley
ec430da772 Update translations and other static files. 2024-05-16 10:20:08 -04:00
Rashad Sookram
5e6d9434de Update to RingRTC v2.42.0 2024-05-16 10:16:10 -04:00
Clark
b72d586748 Add initial thumbnail restore for message backup. 2024-05-16 10:16:10 -04:00
Ehren Kret
757c0fd2ea create video call mimetype for raw contacts links 2024-05-16 10:16:10 -04:00
Nicholas Tinsley
c4e4eaf110 Remove lower hand confirmation dialog. 2024-05-16 10:16:10 -04:00
Nicholas Tinsley
f83275e246 Add customize button to in-call reaction picker. 2024-05-16 10:16:10 -04:00
Greyson Parrelli
d0340d39db Reset backupV2 credentials on 403. 2024-05-16 10:16:10 -04:00
Greyson Parrelli
227a279131 Make sure note to self is included in backupsV2. 2024-05-16 10:16:10 -04:00
mtang-signal
0465fdea62 Update contacts permission UI. 2024-05-16 10:16:10 -04:00
Nicholas Tinsley
13bd4a9c74 Update regv2 result field name. 2024-05-16 10:16:10 -04:00
Greyson Parrelli
f570f1f2c4 Initial test implementation of SVR3. 2024-05-15 15:55:22 -04:00
Nicholas Tinsley
68ced18ea1 Fleshed out session management in registration v2. 2024-05-15 15:55:22 -04:00
Greyson Parrelli
b4a8f01980 Include message timestamp in local send timings. 2024-05-15 15:55:22 -04:00
mtang-signal
c3c743fbb8 Update camera permission UI in media. 2024-05-15 15:55:22 -04:00
Adam Mork
b14eddefc9 Add payment enclave measurements for v6.0.0 2024-05-15 15:55:21 -04:00
Nicholas Tinsley
46638a1948 Bump version to 7.7.2 2024-05-15 15:54:11 -04:00
Nicholas Tinsley
5cee85fcdc Update translations and other static files. 2024-05-15 15:44:52 -04:00
mtang-signal
f97d7e3dfd Fix permissions ask in gallery. 2024-05-15 12:18:50 -07:00
Alex Hart
6da0ecf827 Bump version to 7.7.1 2024-05-10 22:54:45 -03:00
Alex Hart
9803550bba Update baseline profile. 2024-05-10 22:51:55 -03:00
Alex Hart
15284da4c5 Update translations and other static files. 2024-05-10 22:48:55 -03:00
Alex Hart
351c3219e4 Replace RxStore with MutableStateFlow for better lifecycle control. 2024-05-10 22:44:05 -03:00
Alex Hart
ab95dbbc77 Bump version to 7.7.0 2024-05-08 16:43:30 -03:00
Alex Hart
cc6cba45c6 Update baseline profile. 2024-05-08 16:36:44 -03:00
Alex Hart
ce37660df2 Update translations and other static files. 2024-05-08 16:34:17 -03:00
Nicholas Tinsley
ca14ed9b2c Allow for captcha solving for reg v2. 2024-05-08 16:30:53 -03:00
Clark
ba4cdea75d Add cellular backup toggle for message backup. 2024-05-08 16:30:53 -03:00
Clark
83c34dd4cc Integrate swapping backup tiers from backup settings. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley
b6db3802d3 Set raised hand list to be distinct by RecipientID. 2024-05-08 16:30:53 -03:00
Greyson Parrelli
a9a19d3ae0 Add job to upload thumbnails to archive. 2024-05-08 16:30:53 -03:00
Alex Hart
52fb873b1b Specify vibrate attributes to resolve vibrate from background. 2024-05-08 16:30:53 -03:00
moiseev-signal
9a0bb243cd Implement a libsignal-net shadowing web socket. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley
78bbab37fb Show missing FCM dialog in registration V2. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley
9af73b1409 Allow initialization of registration V2 without FCM. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley
9c5bb4aa17 Initial error handling for registration v2. 2024-05-08 16:30:53 -03:00
Clark
49ba83dda8 Integrate message backup frequency. 2024-05-08 16:30:53 -03:00
Clark
de3b0d4ca2 Integrate the backup size into backup settings. 2024-05-08 16:30:53 -03:00
fm-sys
b2efc42357 Add back ability to long press title bar to go to system contact.
Resolves #13372
2024-05-08 16:30:53 -03:00
Cody Henthorne
a71faf674d Cleanup group management code. 2024-05-08 16:30:53 -03:00
moiseev-signal
34faa9003f Upgrade to libsignal 0.46.0. 2024-05-08 16:30:53 -03:00
Clark
bc527a2bc1 Basic settings functionality for message backup. 2024-05-08 16:30:53 -03:00
Nicholas Tinsley
0a3f96935a Support device transfers in restore flow v2. 2024-05-08 16:30:53 -03:00
Alex Konradi
35232a3928 Unwrap ExecutionException from Future observable 2024-05-08 16:30:53 -03:00
Alex Hart
70d74e0bb1 Allow users who have disabled Contacts permission to hide system contacts. 2024-05-08 16:30:53 -03:00
Alex Hart
36c91a95e2 Check spannable intersect when moving between stories. 2024-05-08 16:30:53 -03:00
Greyson Parrelli
4600e38a2a Add partial index to improve unread count perf. 2024-05-08 16:30:52 -03:00
Alex Hart
55abd88a03 Implement better handling for call peeking when opening the calls tab. 2024-05-08 16:30:52 -03:00
mtang-signal
cd880b0879 Expand double tap area. 2024-05-08 16:30:52 -03:00
mtang-signal
bbae6d876f Avoid translating support email strings. 2024-05-08 16:30:52 -03:00
dalamsya50
48a0c5a5a9 Fix git error when running on GitHub Actions.
Fixes #13495
Resolves #13547
2024-05-08 16:30:52 -03:00
BenjaminMuslic
c261df41b0 Added automatic capitalization to profile name fields.
Resolves #13544
2024-05-08 16:30:52 -03:00
Greyson Parrelli
cc98eced27 Short-circuit query if list is empty. 2024-05-08 16:30:52 -03:00
moiseev-signal
452d5960e4 Add test and extra cleanup around usage of incremental mac. 2024-05-08 16:30:52 -03:00
mtang-signal
c95b180728 Update gallery permission UI 2024-05-08 16:30:52 -03:00
Greyson Parrelli
3c380d35fd Attempt to reduce impact of thread updates. 2024-05-08 16:30:52 -03:00
Nicholas Tinsley
41935120e5 DeviceTransferFragment Kotlin rewrite. 2024-05-01 16:45:36 -04:00
Alex Hart
03d8f72c41 Fix group collisions error. 2024-05-01 16:45:36 -04:00
Greyson Parrelli
ab9ecff4d4 Improve timing of query methods. 2024-05-01 16:45:36 -04:00
Alex Hart
e351a0b235 Correct flags for story replies. 2024-05-01 16:45:36 -04:00
Nicholas Tinsley
4a08de370a Fix issue with Mp4Writer with massive time scales. 2024-05-01 16:45:36 -04:00
Cody Henthorne
6d657b449c Convert and update Manage Storage Settings. 2024-05-01 16:45:36 -04:00
Clark
adef572abb Store group snapshot attributes in GroupAttributeBlobs. 2024-05-01 16:45:36 -04:00
Greyson Parrelli
d6f2039bd1 Update attachment cipher tests to use longer inputs. 2024-05-01 16:45:35 -04:00
Clark
1223c3c768 Add support for new backup calls proto and call links. 2024-05-01 16:45:35 -04:00
Greyson Parrelli
333fa22c96 Bump version to 7.6.2 2024-05-01 16:42:29 -04:00
Greyson Parrelli
76c04d8d6d Update translations and other static files. 2024-05-01 16:42:03 -04:00
Greyson Parrelli
c3070f2913 Revert "Expand double tap touch area."
This reverts commit 8c81e47737.
2024-05-01 16:33:33 -04:00
Nicholas Tinsley
234b3967ed Fix button crash in v1 PIN restore fragment. 2024-05-01 10:42:23 -04:00
Greyson Parrelli
89d420cda8 Bump version to 7.6.1 2024-04-30 16:42:44 -04:00
Greyson Parrelli
ced4ece5b8 Update translations and other static files. 2024-04-30 16:41:02 -04:00
mtang-signal
8c81e47737 Expand double tap touch area. 2024-04-30 16:29:33 -04:00
Cody Henthorne
5d15eef61d Improve translations with pluralized string resources. 2024-04-30 16:04:14 -04:00
Greyson Parrelli
8f3e62245f Fix some issues where views were accessed after being destroyed. 2024-04-30 15:22:57 -04:00
Greyson Parrelli
e4ab795c62 Fix stream reading error. 2024-04-30 15:22:57 -04:00
mtang-signal
e4d6f9240f Fix double tap layout warning. 2024-04-30 14:44:00 -04:00
Greyson Parrelli
cfaf40e605 Fix KeyValueDataSet tests. 2024-04-30 11:05:39 -07:00
Greyson Parrelli
bdcf2431e7 Bump version to 7.6.0 2024-04-29 22:04:56 -04:00
Greyson Parrelli
7241283be2 Update baseline profile. 2024-04-29 22:04:56 -04:00
Greyson Parrelli
dde2a8b63a Update translations and other static files. 2024-04-29 22:04:56 -04:00
Greyson Parrelli
f7763a5b82 Be more lenient around long-int conversion in SignalStore. 2024-04-29 22:04:47 -04:00
Greyson Parrelli
c6f4a01001 Hopeful fix for crash in SimpleProgressDialog. 2024-04-29 22:04:31 -04:00
Greyson Parrelli
95a6835988 Improve handling of backup initialization. 2024-04-29 19:26:06 -04:00
moiseev-signal
f9a8f447d2 Support proxy in connections managed by libsignal. 2024-04-29 19:26:06 -04:00
Nicholas Tinsley
d20f588802 Inline the group call reactions feature flag. 2024-04-29 19:25:59 -04:00
Nicholas Tinsley
f23476a4e9 Initial support for restoring backups and skipping SMS in registration v2. 2024-04-29 19:25:59 -04:00
mtang-signal
fd4864b3b1 Update microphone permission UI for calls. 2024-04-29 19:25:59 -04:00
mtang-signal
c5c0c432c4 Update microphone permission UI for voice messages. 2024-04-29 19:25:59 -04:00
Jim Gustafson
69c40a6835 Update to RingRTC v2.41.0 2024-04-29 19:25:59 -04:00
moiseev-signal
7ef7aa65e6 Upgrade to libsignal 0.45.1. 2024-04-29 19:25:59 -04:00
Greyson Parrelli
97c08f0d52 Add additional validations to incremental attachment streams. 2024-04-29 19:25:59 -04:00
mtang-signal
18e6c57e75 Update location permission UI. 2024-04-29 19:25:59 -04:00
mtang-signal
ffc1463cda Add double tap editing feature. 2024-04-29 19:25:59 -04:00
Clark
84e654efb2 Set archive transfer state when archive data is set. 2024-04-29 19:25:59 -04:00
Clark
d983265e08 Persist group state in backup. 2024-04-29 19:25:59 -04:00
Alex Hart
e60b32202e Improved missed call state handling. 2024-04-29 19:25:59 -04:00
moiseev-signal
95fbd7a31c Implement unauthenticated chat web socket connection via libsignal-net. 2024-04-29 19:25:59 -04:00
Nicholas Tinsley
00a91e32fc Multiple skin tones for reaction bursts. 2024-04-29 19:25:59 -04:00
Alex Hart
fa32b7a883 Fix coloring on outgoing calls. 2024-04-24 15:10:12 -03:00
Alex Hart
63e6f955ed Prevent getCallLinks from returning links without root keys. 2024-04-24 14:17:58 -03:00
Alex Hart
7dcb8a425a Handle joined sync message for call links. 2024-04-24 13:31:35 -03:00
Cody Henthorne
f35ce068f9 Change profile fetch REST fallback based on authentication error. 2024-04-24 11:41:31 -04:00
Nicholas Tinsley
881d231a93 Improve group call reactions UI when presented without raise hand.
This also dismisses the custom reaction picker when switching to PiP mode.
2024-04-24 10:13:56 -04:00
Alex Hart
293634c758 Send call link update sync message upon call link creation. 2024-04-24 10:48:00 -03:00
Greyson Parrelli
4134df3f35 Use archive-specific endpoint for attachment backfill. 2024-04-23 16:29:03 -04:00
Clark
f78a019c70 Use seconds instead of millis for redemption time. 2024-04-23 15:56:38 -04:00
Cody Henthorne
d561a1385c Fix extremely long emoji search crash. 2024-04-23 12:29:03 -04:00
moiseev-signal
9b5387e221 Upgrade to libsignal 0.45.0 2024-04-23 12:29:03 -04:00
Cody Henthorne
25b1a814fe Remove legacy keyword search flag from emoji search infra. 2024-04-23 12:29:03 -04:00
Clark
b043b6e458 Schedule message backups when enabled. 2024-04-23 12:29:03 -04:00
Clark
8a972d93e9 Actually use backup jitter in local backups. 2024-04-23 12:29:03 -04:00
Cody Henthorne
8fe66a14c5 Fix multi-window camera crash. 2024-04-23 12:29:03 -04:00
Clark
f82bd64c10 Copy inbound attachments to archive service. 2024-04-23 12:29:03 -04:00
Nicholas Tinsley
4bcab49539 Correct UnopinionatedResponseCodeHandler constant name. 2024-04-23 12:29:03 -04:00
mtang-signal
0f4618ab11 Remove link preview images from shared media. 2024-04-23 12:29:03 -04:00
Cody Henthorne
475ca50fab Fix missing local participant state changes in group calls bug. 2024-04-23 12:29:03 -04:00
Greyson Parrelli
a64a02fa0c Fix issue where structured contact name syncing was delayed. 2024-04-23 12:29:02 -04:00
Clark
f3669a5865 Fix message extra column not being restored properly. 2024-04-23 12:29:02 -04:00
Greyson Parrelli
34dbd11db0 Update file format for backupV2. 2024-04-23 12:29:02 -04:00
Nicholas Tinsley
2e7279c72f Only display "Processing" text on outgoing media. 2024-04-23 12:29:02 -04:00
Nicholas Tinsley
6ad72f00af Fix phone number formatter in Registration V2. 2024-04-23 12:29:02 -04:00
Alex Hart
b771a21518 Add screen for managing backup type. 2024-04-23 12:29:02 -04:00
Greyson Parrelli
04fb459acd Remove unused backup outputstream class. 2024-04-23 12:29:02 -04:00
Greyson Parrelli
690a68f0d0 Remove libweb submodule entirely. 2024-04-23 12:29:02 -04:00
Greyson Parrelli
f34ae8d118 Add padding to the gzipped backup output. 2024-04-23 12:29:02 -04:00
Cody Henthorne
da43ff1e95 Bump version to 7.5.2 2024-04-23 11:42:24 -04:00
Cody Henthorne
f053ebbd51 Update baseline profile. 2024-04-23 11:36:15 -04:00
Cody Henthorne
87606af29c Update translations and other static files. 2024-04-23 11:30:56 -04:00
Cody Henthorne
c811bdcffa Fix benchmark test messages. 2024-04-23 11:26:36 -04:00
Cody Henthorne
0536628da3 Stagger app wake ups due to analyze database alarm. 2024-04-23 10:44:09 -04:00
Nicholas Tinsley
1fa53cfcb8 Prevent crash on attachment delete while voice note system tone is playing. 2024-04-23 10:22:01 -04:00
Cody Henthorne
a9ea3854d2 Bump version to 7.5.1 2024-04-22 17:06:18 -04:00
Cody Henthorne
dc35261e00 Update translations and other static files. 2024-04-22 16:56:39 -04:00
Cody Henthorne
716bc1f5e7 Cleanup dangling domain reference. 2024-04-22 16:52:02 -04:00
Cody Henthorne
db27204084 Validate pni signature message. 2024-04-22 16:33:03 -04:00
Cody Henthorne
42aeceffe2 Revert full usage of ActiveCallManager. 2024-04-22 16:32:27 -04:00
Greyson Parrelli
03845eabaf Bump version to 7.5.0 2024-04-18 16:44:32 -04:00
Greyson Parrelli
62af9dad50 Update translations and other static files. 2024-04-18 16:43:51 -04:00
Cody Henthorne
ee58d47926 Cycle rx message sending flag. 2024-04-18 16:24:13 -04:00
Greyson Parrelli
d74260b536 Improve network reliability. 2024-04-18 16:24:13 -04:00
Alex Hart
15d8a698c5 Add new name collision state management. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
62cf3feeaa Restore a Local Backup v2 2024-04-18 16:24:13 -04:00
Alex Hart
947ab7d48b Implement skeleton for backup sheets. 2024-04-18 16:24:13 -04:00
Greyson Parrelli
a82b9ee25f Add a job to backfill attachment uploads to the archive service. 2024-04-18 16:24:13 -04:00
mtang-signal
1e4d96b7c4 Add camera permission check to group stories. 2024-04-18 16:24:13 -04:00
Alex Hart
735a8e680c Add backupSubscription field to configuration object. 2024-04-18 16:24:13 -04:00
Alex Hart
d9e9fe1d6a Move backups selection code to its own package. 2024-04-18 16:24:13 -04:00
Greyson Parrelli
4bcd1df4f8 Expand account consistency checks. 2024-04-18 16:24:13 -04:00
Greyson Parrelli
9762899272 Remove old thread remappings. 2024-04-18 16:24:13 -04:00
Alex Hart
ce1b73970c Implement BackupStatus widget. 2024-04-18 16:24:13 -04:00
Alex Hart
58282e589b Implement backups settings fragment. 2024-04-18 16:24:13 -04:00
mtang-signal
75bd113545 Fix missing send button for voice notes. 2024-04-18 16:24:13 -04:00
Cody Henthorne
7a6bd0e1f2 Revert "Remove vestigial call camera toggle button."
This reverts commit 7a9c01e6e5.
2024-04-18 16:24:13 -04:00
Greyson Parrelli
f673c4eb83 Remove sql language annotation (for now).
It's broken in newer versions of Android Studio. It doesn't seem to
allow partial-sql anymore, only fully-formed statements. Same with
roomsql.
2024-04-18 16:24:13 -04:00
Jim Gustafson
cbb04e8f0c Update to RingRTC v2.40.0 2024-04-18 16:24:13 -04:00
mtang-signal
cd03da54d5 Fix note to self message detail text. 2024-04-18 16:24:13 -04:00
Clark
5f31f5966c Update backup locator proto. 2024-04-18 16:24:13 -04:00
Clark
d8bbfe2678 Add archived media sync job. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
7a2d408ca2 Stop voice memo playback if the current item is deleted.
Fixes #13502.
2024-04-18 16:24:13 -04:00
Nicholas Tinsley
5e4dfcc65f Add translator notes for some strings. 2024-04-18 16:24:13 -04:00
Clark
7811e51b41 Add CDN number as parameter for read credential call. 2024-04-18 16:24:13 -04:00
Alex Konradi
9703a868e5 Request new ZKC-based auth credential. 2024-04-18 16:24:13 -04:00
Alex Hart
1b7784b01f Update call strings to align with new designs. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
a83abaca1d Order story viewer names alphabetically. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
29b3f09d8a Catch possible ISE at end of re-registration. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
d36b2a23f5 Hide irrelevant rows in self about sheet. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
8f1722c718 Update placeholder label for view once media. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
5416c3b8aa Improve play button display logic on video editor fragment. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
89eeae36c4 Fix signed int overflow in disappearing timer UI message. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
eec2685e67 Registration refactor initial scaffolding. 2024-04-18 16:24:13 -04:00
Clark
318b59a6b2 Do not fallback to REST for resumable upload spec on ratelimit. 2024-04-18 16:24:13 -04:00
Nicholas Tinsley
a2e0468cd9 Remove "lower hand" confirmation dialog. 2024-04-18 16:24:13 -04:00
Clark
689eacd618 Add initial support for backup and restore of message and media to staging.
Co-authored-by: Cody Henthorne <cody@signal.org>
2024-04-18 16:24:13 -04:00
tedgravlin
8617a074ad Update CLA link in PR template. 2024-04-18 16:24:12 -04:00
Greyson Parrelli
046b8da880 Add missing static IPs.
Fixes #13513
2024-04-18 16:24:12 -04:00
Clark Chen
34a36ddfea Bump version to 7.4.2 2024-04-15 16:32:09 -04:00
Clark Chen
9330448198 Update translations and other static files. 2024-04-15 16:24:09 -04:00
Clark Chen
b3336b4d84 Revert "Use existing libsignal proguard rules."
This reverts commit 2ce6ea9a2a.
2024-04-15 10:17:36 -04:00
Alex Hart
9553c94097 Bump version to 7.4.1 2024-04-12 16:38:43 -03:00
Alex Hart
c1845ae1c4 Update baseline profile. 2024-04-12 16:33:11 -03:00
Alex Hart
b6cc3852b0 Update translations and other static files. 2024-04-12 16:28:07 -03:00
Cody Henthorne
eefc86f27e Fix dangling call notification and remove active call manager flag. 2024-04-12 09:38:06 -04:00
Nicholas Tinsley
09404157aa Add processor information to debug log. 2024-04-11 16:09:33 -04:00
Alex Hart
abfd9f8f41 Add proper capitalization settings in nickname activity. 2024-04-11 10:35:47 -03:00
Bishal
e04381fd75 Add fix for missing play button when the audio is not sent in offline mode. 2024-04-11 10:31:12 -03:00
Alex Hart
30cc3ff9fc Bump version to 7.4.0 2024-04-10 16:31:53 -03:00
Alex Hart
6f5f299035 Update baseline profile. 2024-04-10 16:31:47 -03:00
Alex Hart
02eed02cb8 Update translations and other static files. 2024-04-10 16:29:24 -03:00
Greyson Parrelli
c1d29b5c39 Set internalUser=true for nightly builds. 2024-04-10 14:54:35 -04:00
Greyson Parrelli
db4442939d Remove Environment.IS_PNP 2024-04-10 14:52:59 -04:00
tedgravlin
6ece776382 Fix navbar color in multiple instances. 2024-04-10 14:29:58 -03:00
Alex Hart
0eda714755 Send recipients when sending group story sync. 2024-04-10 14:21:34 -03:00
Greyson Parrelli
831d099503 Inline the nicknames feature flag. 2024-04-10 13:18:01 -04:00
Alex Hart
fa23e4ca70 Convert members collection to set to avoid duplicate entries. 2024-04-10 13:45:46 -03:00
Greyson Parrelli
982f602178 Regularly analyze database tables to improve index usage. 2024-04-09 16:55:25 -04:00
Greyson Parrelli
713298109a Specify indexes for mention table queries. 2024-04-09 16:18:21 -04:00
Greyson Parrelli
8793981804 Add a log section for the database schema. 2024-04-09 16:18:21 -04:00
Greyson Parrelli
9bd4e9524c Convert MentionTable to kotlin. 2024-04-09 16:18:21 -04:00
Cody Henthorne
791dc2724f Attempt to fix bad notification for call service shutdown. 2024-04-09 16:18:21 -04:00
Cody Henthorne
ba3473c61a Fix scroll to message when bubble is under toolbar. 2024-04-09 16:18:21 -04:00
moiseev-signal
3ea194255d Add getUsername default method to CredentialsProvider 2024-04-09 16:18:21 -04:00
Cody Henthorne
ea081e981f Treat unregistered user during send as general failure. 2024-04-09 16:18:21 -04:00
Alex Konradi
2ce6ea9a2a Use existing libsignal proguard rules. 2024-04-09 16:18:20 -04:00
Alex Konradi
295c9310e9 Map libsignal CDSI errors to existing exceptions. 2024-04-09 16:18:20 -04:00
Greyson Parrelli
7447ed2eac Add the ability to jump to a specific date in search. 2024-04-09 16:18:20 -04:00
Cody Henthorne
d5bf16b91a Fix incorrect thread body adjustments containing media, mentions, and styling. 2024-04-09 16:18:06 -04:00
Cody Henthorne
76665c1f0d Prevent excessive video toggling in group calls due to server instability. 2024-04-09 16:18:06 -04:00
Cody Henthorne
dd28523b05 Transition full screen call UX to terminal state when call handled by linked device. 2024-04-09 16:18:06 -04:00
Cody Henthorne
16588c401e Reduce verbosity of WebRtcViewModel event logging during calls. 2024-04-09 16:18:06 -04:00
Greyson Parrelli
dbf8a7ca87 Rotate libsignal-net flag. 2024-04-09 16:18:06 -04:00
moiseev-signal
e92c76434e Upgrade to libsignal-client 0.44.0 2024-04-09 16:18:06 -04:00
Greyson Parrelli
7adb581271 Bump version to 7.3.1 2024-04-09 16:17:21 -04:00
Greyson Parrelli
869476a41b Update translations and other static files. 2024-04-09 16:16:47 -04:00
Greyson Parrelli
8daf1bca20 Improve handling of unknown groups. 2024-04-09 15:56:15 -04:00
Greyson Parrelli
d044b3c931 Remove most lazy properties from Recipient. 2024-04-09 15:02:36 -04:00
Cody Henthorne
0fcb19e1cc Fix group recipient resolve race that can cause unknown group recipients in live cache. 2024-04-09 14:59:47 -04:00
Nicholas Tinsley
2a6977da75 Nickname screen copy update. 2024-04-04 09:45:26 -04:00
Nicholas Tinsley
26bd435bf6 Update nickname delete dialog copy. 2024-04-03 16:48:26 -04:00
Greyson Parrelli
91f8d6075c Bump version to 7.3.0 2024-04-03 15:55:34 -04:00
Greyson Parrelli
9ed9a330f4 Update baseline profile. 2024-04-03 15:54:13 -04:00
Greyson Parrelli
8bbf6b790f Update translations and other static files. 2024-04-03 15:47:24 -04:00
Greyson Parrelli
a277e9b307 Fix compilation of benchmark build. 2024-04-03 15:47:24 -04:00
Cody Henthorne
f8e6bcf290 Add username_edit release note cta action. 2024-04-03 14:07:56 -04:00
Greyson Parrelli
3ba2b46bb0 Convert Recipient to kotlin. 2024-04-03 14:02:55 -04:00
Greyson Parrelli
b50eab230d Update strings for 'system contact' -> 'phone contact'. 2024-04-03 14:02:13 -04:00
Alex Hart
3f91824325 Fix bug preventing the review sheet from opening. 2024-04-03 14:02:13 -04:00
Alex Hart
879e05148b Fix database revocation for call links. 2024-04-03 14:02:13 -04:00
moiseev-signal
78e36b85d4 Make sure not more than one libsignal Network instance is ever created
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2024-04-03 14:02:13 -04:00
Alex Hart
544cc06f13 Add chevron to conversation heading. 2024-04-03 14:02:13 -04:00
Cody Henthorne
133b7ef3f1 Fix multiple exception crash in rx message send flow. 2024-04-03 14:02:13 -04:00
Cody Henthorne
08a407dc23 Prevent thread starvation during message sending. 2024-04-03 14:02:13 -04:00
Greyson Parrelli
6c697fad8b Stop reading the PNP capability. 2024-04-03 14:02:13 -04:00
Greyson Parrelli
c904a7aa97 Delete LegacyAttachmentUploadJob.
It's been over 4 months since it was replaced. That's beyond the 90 day
build expiration + 1 day job lifespan. Should be safe to remove.
2024-04-03 14:02:13 -04:00
Greyson Parrelli
ad131d7c65 Enqueue AccountConsistency check when prekey syncs fail. 2024-04-03 14:02:13 -04:00
Alex Hart
e12d2d1e98 Fix local pip movement when in RTL language. 2024-04-03 14:02:12 -04:00
adel-signal
f01e044662 Update to new calling turn info endpoint, add support for turn server ips.
Co-authored-by: Adel Lahlou <adel@signal.com>
2024-04-03 14:02:12 -04:00
Jim Gustafson
03d3ae7043 Update to RingRTC v2.39.3 2024-04-03 14:02:12 -04:00
Greyson Parrelli
6b60a22879 Bump version to 7.2.4 2024-04-03 13:32:38 -04:00
Greyson Parrelli
bbded8caa8 Update translations and other static files. 2024-04-03 13:32:08 -04:00
Greyson Parrelli
3a6352d2a3 Don't show profile name in parens if it's the same as display name. 2024-04-03 13:19:37 -04:00
Greyson Parrelli
8293d6bc4c Allow last-name-only nicknames to be saved. 2024-04-03 11:54:07 -04:00
Greyson Parrelli
56bdb28c2f Fix bug around entering text in the middle of a full note.
There's likely other weirdness, but this at least addresses the most
commond variation, where entering text in the middle of a full note
would start chopping stuff off the end.
2024-04-03 11:20:35 -04:00
Greyson Parrelli
b081fb1e13 Improve recipient shortname selection. 2024-04-03 10:45:47 -04:00
Greyson Parrelli
58c1f64dfe Allow familyName-only nicknames in storage service. 2024-04-03 10:44:04 -04:00
Greyson Parrelli
92b7147dcd Always take the remote nickname. 2024-04-03 10:39:43 -04:00
Greyson Parrelli
fa3a85c948 Bump version to 7.2.3 2024-04-02 15:30:07 -04:00
Greyson Parrelli
9da4513694 Update translations and other static files. 2024-04-02 15:29:14 -04:00
Greyson Parrelli
de520036a9 Allow last-name-only nicknames. 2024-04-02 15:19:44 -04:00
Greyson Parrelli
97ca15a1c0 Allow multi-line entry in note field. 2024-04-02 14:50:13 -04:00
Greyson Parrelli
713a34a5e7 Ensure that conversation count check is on background thread. 2024-04-02 14:36:07 -04:00
Alex Hart
d688280a30 Fix search for users without thread. 2024-04-02 15:27:57 -03:00
Greyson Parrelli
ebbf8fad4b Bump version to 7.2.2 2024-04-01 20:17:55 -04:00
Greyson Parrelli
5891c6fb2d Update translations and other static files. 2024-04-01 20:17:23 -04:00
Greyson Parrelli
7c96319fb6 Fix potential NPE in forwarding flow. 2024-04-01 20:17:23 -04:00
Greyson Parrelli
0d652ccfd6 Listen for recipient name changes in conversation item. 2024-04-01 19:58:05 -04:00
Greyson Parrelli
d3718aa7ef Make nickname FF hot-swappable and default to true. 2024-04-01 19:17:07 -04:00
Cody Henthorne
fcdcb9fd33 Fix linked device nickname change not syncing bug. 2024-04-01 19:06:08 -04:00
Nicholas Tinsley
a8f925def0 Move delete button in Nickname activity. 2024-04-01 17:49:48 -04:00
Nicholas Tinsley
53cb125712 Prevent NPE in video editor. 2024-04-01 14:33:21 -04:00
Nicholas Tinsley
2a5793d96e Allow saving empty notes with empty nicknames. 2024-04-01 14:13:39 -04:00
Nicholas Tinsley
d460fa7ed4 Disable view-once toggle in media caption editor.
Fixes #13492.
2024-04-01 11:37:34 -04:00
Nicholas Tinsley
5272b13c41 Bump version to 7.2.1 2024-03-29 17:35:46 -04:00
Nicholas Tinsley
a66ac42038 Update translations and other static files. 2024-03-29 17:27:15 -04:00
Nicholas Tinsley
0014a2cba7 Hide camera switch icon during calls for devices with 1 or fewer cameras. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
7a9c01e6e5 Remove vestigial call camera toggle button. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
16402e43a5 Make in-app camera compatible with multi-window. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
b1944da58d Show nickname for 1:1 chat bottom sheet. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
9081d3c826 Revise recipient bottom sheet. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
d4ae0ca4cb Update conversation settings string. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
88f6ab915e Do not show nickname field for Note to Self. 2024-03-29 17:21:27 -04:00
Nicholas Tinsley
939024faff Don't show profile name parentheses if we don't have one. 2024-03-29 12:43:58 -04:00
Nicholas Tinsley
4c6e7991df Preserve username exceptions in ProGuard. 2024-03-29 12:40:12 -04:00
Nicholas Tinsley
036d91c039 Align linked device megaphone lifespan.
Thank you to Signal.DE from the community forum.
2024-03-28 15:59:08 -04:00
oguzhandogdu
869c922532 Remove second bullet span function 2024-03-28 14:57:26 -04:00
oguzhandogdu
217d15a853 Add gap width to bullet span 2024-03-28 14:57:26 -04:00
Nicholas Tinsley
931ffd0ba3 Bump version to 7.2.0 2024-03-27 16:01:18 -04:00
Nicholas Tinsley
fecac297fa Update translations and other static files. 2024-03-27 15:57:11 -04:00
Nicholas Tinsley
b0ea8d7df5 Prevent crash on devices with camera killswitches.
Addresses #13450.
2024-03-27 15:54:35 -04:00
Nicholas Tinsley
f126df2120 Put custom controller behind feature flag. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley
42450024fc Add profile name to about sheet. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley
101db6e164 Apply SignalTheme to NicknameActivity. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley
13bef94bf7 Update icons in conversation settings. 2024-03-27 15:54:35 -04:00
Nicholas Tinsley
02792c5a6f Remove extraneous time unit conversion. 2024-03-27 15:54:35 -04:00
Alex Hart
303929090b Implement the majority of the new nicknames and notes feature. 2024-03-27 15:54:35 -04:00
Alex Hart
7a24554b68 Update ContactRecord proto with new nickname fields. 2024-03-27 15:54:35 -04:00
Greyson Parrelli
5b10aa6fa7 Handle 428 for captcha submissions. 2024-03-27 15:54:35 -04:00
Alex Konradi
e6eefac609 Upgrade to libsignal 0.42.0 2024-03-27 15:54:35 -04:00
Alex Hart
5f5a80dcbe Stub out MoreOptionsSheet and RestoreFromBackupFragment. 2024-03-27 15:54:35 -04:00
Cody Henthorne
7802448b24 Fix unblock icon tint in dark theme. 2024-03-27 15:54:35 -04:00
Clark
16d231f718 Persist blur hash with undownloaded attachments. 2024-03-27 15:54:35 -04:00
Cody Henthorne
62ca6cdd2f Fix can't receive audio and video pip render bug. 2024-03-27 15:54:35 -04:00
Cody Henthorne
7d81ed1150 Fix call controls disappearing when returning from system pip. 2024-03-27 15:54:35 -04:00
Greyson Parrelli
27812bb1ec Don't save duplicate queries in Spinner. 2024-03-27 15:54:35 -04:00
Greyson Parrelli
6854f7eb2a Add an 'internal details' screen for message details. 2024-03-27 15:54:35 -04:00
Clark
de86c5622d Integrate more variation in backup test generation. 2024-03-27 15:54:35 -04:00
Jim Gustafson
6bf1a4295f Update to RingRTC v2.39.2 2024-03-27 15:54:35 -04:00
Alex Hart
7de2f0f460 Add nickname and notes fields to the RecipientTable. 2024-03-27 15:54:35 -04:00
Greyson Parrelli
50149a3803 Show a megaphone when a device is about to unlink. 2024-03-27 15:54:35 -04:00
Greyson Parrelli
d7ee9639fd Be more lenient with quality matches when forwarding attachments. 2024-03-19 14:49:56 -04:00
Nicholas Tinsley
7d5627b17b Fix in-app camera rotation in multiview. 2024-03-19 14:48:38 -04:00
Greyson Parrelli
e24c951d83 Convert MiscellaneousValues to kotlin. 2024-03-19 14:47:58 -04:00
Alex Hart
e6a11c1ccf Revert "Fix pip placement in large calls."
This reverts commit aaeba4efe1.
2024-03-19 14:47:58 -04:00
Greyson Parrelli
3f66981359 Do not show username megaphone after a fresh install. 2024-03-19 14:47:58 -04:00
Cody Henthorne
874f808d56 Add process read sync tests. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
450dc2f368 Improve logging around APNG animation disabling. 2024-03-19 14:47:58 -04:00
Alex Hart
7a69df42a7 Add receive support for new call log event data. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
1ce1e30d32 Carry over the sent media quality when forwarding a video. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
011f1d592e Fix bug with quote deduping. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
1d29b0166d Backfill missing attachment hashes. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
6df1a68213 Refactor and improve attachment deduping logic. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley
b7ee6bfcb3 Don't show transfer overlay for scheduled messages. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley
c0cb2b5e12 Allow seeking in video timeline. 2024-03-19 14:47:58 -04:00
Alex Hart
b38865bdc7 Implement UI element refresh on transfer or restore screen. 2024-03-19 14:47:58 -04:00
Alex Hart
6f46331772 Add call log event proto updates. 2024-03-19 14:47:58 -04:00
Clark
989bd662c6 Add tests to generate backup with large amount of messages and chats. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley
359e593481 Add support for hardware camera features in the in-app camera. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley
b7e0fe22db Add CameraX Extensions and update CameraX to 1.3.2. 2024-03-19 14:47:58 -04:00
Alex Hart
61cfbd6852 Allow ringer to ring in certain dnd situations. 2024-03-19 14:47:58 -04:00
Rashad Sookram
02c0d3ed6e Update to RingRTC v2.39.1 2024-03-19 14:47:58 -04:00
Cody Henthorne
e4d6c3aeb2 Do not send viewed receipt for release channel. 2024-03-19 14:47:58 -04:00
Chris Eager
0c6761fcfd Update Option.RECAPTCHA to Option.CAPTCHA 2024-03-19 14:47:58 -04:00
Greyson Parrelli
8f884fdd5c Fix potential crash when parsing PreKeySyncJobData.
Honestly at this point I have no idea how this is happening.
Maybe somehow getting old data that was empty but not null?
A mystery for the ages.
2024-03-19 14:47:58 -04:00
Greyson Parrelli
07cea1818e Ensure that protocol stores are reset after setting ACI/PNI. 2024-03-19 14:47:58 -04:00
Cody Henthorne
132bc15373 Fix ANR when changing the configuration of a foldable. 2024-03-19 14:47:58 -04:00
Clark
d993748753 Generate backup protos with message backup instrumentation tests. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
3372565a39 Improve logging around consistency checks. 2024-03-19 14:47:58 -04:00
Alex Hart
134ac2b2fd Fix display name resolution for my story. 2024-03-19 14:47:58 -04:00
Alex Hart
0e0e91b4fe Hide invite banner when entering conversation search. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
25b50bdb8f Rotate the libsignal-cdsi feature flag. 2024-03-19 14:47:58 -04:00
Alex Konradi
1988085171 Don't strip libsignal.net classes. 2024-03-19 14:47:58 -04:00
Nicholas Tinsley
f892e9baff Update disappearing messages text. 2024-03-19 14:47:58 -04:00
Alex Konradi
4828d84caf Update libsignal to 0.41.2 2024-03-19 14:47:58 -04:00
Greyson Parrelli
aeae6ac292 Remove deprecated blocked field from DeviceContact. 2024-03-19 14:47:58 -04:00
Alex Hart
0544c1f249 Display group call permissions dialog when trying to start a call in annoucment group when not an admin. 2024-03-19 14:47:58 -04:00
Greyson Parrelli
5027159ed8 Improve handling of unregistered states in profile screen. 2024-03-19 14:47:58 -04:00
Cody Henthorne
ce778be895 Resume call PIP on app foreground. 2024-03-19 14:47:58 -04:00
Cody Henthorne
9e349d2b30 Mute video when closing system PIP during a call. 2024-03-19 14:47:58 -04:00
Fumiaki Yoshimatsu
72f19758db Fix chat search when using Japanese IMEs.
Resolves #13467
2024-03-19 14:47:58 -04:00
Greyson Parrelli
55bce1fa12 Fix potential NPE when pinning a PNI chat. 2024-03-19 14:47:58 -04:00
AsamK
5e1ebaa5d4 Fix various storage service issues.
Resolves #13466
2024-03-19 14:47:58 -04:00
Clark
742c348998 Add test restore flow to staging reg. 2024-03-19 14:47:58 -04:00
Clark
9d46b52786 Backup attachments as Attachment locators. 2024-03-19 14:47:57 -04:00
Clark
ef374952ab Add tests for update messages except for groups and calls. 2024-03-19 14:47:57 -04:00
Clark
f8ef4d5985 Add tests for text messages with mentions, quotes, reactions, and ranges. 2024-03-19 14:47:57 -04:00
Cody Henthorne
85929809f0 Bump version to 7.1.3 2024-03-19 14:36:32 -04:00
Cody Henthorne
068540120e Updated baseline profile. 2024-03-19 14:33:44 -04:00
Cody Henthorne
471c4fc200 Update translations and other static files. 2024-03-19 14:28:49 -04:00
Nicholas Tinsley
398c67362d Improve layout for view once toast for older devices. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley
4ceeda5f02 Fix video review page for API <28. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley
2bf6b993fe Somewhat reduce emoji keyboard jankiness in media review fragment. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley
68363c5b82 Disable emoji button for view-once media. 2024-03-19 14:24:26 -04:00
Nicholas Tinsley
9f47a41017 Restore pinch to zoom gesture in in-app camera. 2024-03-19 13:46:19 -04:00
Nicholas Tinsley
ba70101efd Add view-once button to media caption. 2024-03-19 12:00:36 -04:00
Greyson Parrelli
3aa54c9982 Remove some unused permissions. 2024-03-18 19:21:47 -04:00
Greyson Parrelli
825ca0d737 Remove more SMS vestiges. 2024-03-18 19:21:08 -04:00
Clark Chen
6754fef164 Bump version to 7.1.2 2024-03-11 18:50:24 -04:00
Clark Chen
4c079a8c25 Update translations and other static files. 2024-03-11 18:32:59 -04:00
Nicholas Tinsley
6e09d101b5 Debounce camera switcher button. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley
39aa583297 Respect newlines in media review UI. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley
b08db7a8c5 Fix unexpected trimming behavior with long videos. 2024-03-11 18:26:45 -04:00
Alex Hart
865bf0d056 Fix nav bar getting out of sync with keyboard pager. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley
d52c520c02 Explicitly pause video player when not focused. 2024-03-11 18:26:45 -04:00
Nicholas Tinsley
1eabf11cdb Fix tap-to-focus UI for in-app camera. 2024-03-11 18:26:45 -04:00
Cody Henthorne
cfb16d3f17 Fix link rendering under spoilers in read more view. 2024-03-11 18:26:45 -04:00
Alex Hart
d5707638a6 Apply proper theming to FindByActivity. 2024-03-11 18:26:45 -04:00
Alex Hart
5cda5db7f7 Disable text field when view-once is selected. 2024-03-11 11:46:52 -03:00
Alex Hart
5c5d55d265 Introduce glyph fonts to correct spacing. 2024-03-11 11:14:05 -03:00
Alex Hart
4dd3b92eda Prevent crash when review banner wants to display self. 2024-03-11 09:52:24 -03:00
Cody Henthorne
112579079f Fix bad button text wrapping in message request view. 2024-03-08 16:57:15 -05:00
Greyson Parrelli
9897ba4b28 Properly pluralize a string. 2024-03-08 14:39:28 -05:00
Greyson Parrelli
c64dfff4c7 Fix typo in string. 2024-03-07 22:40:26 -05:00
Alex Hart
915b3f0cd3 Bump version to 7.1.1 2024-03-07 17:02:41 -04:00
Alex Hart
c295d11fc4 Updated baseline profile. 2024-03-07 17:01:52 -04:00
Alex Hart
bc47c5436d Update translations and other static files. 2024-03-07 16:56:59 -04:00
Alex Hart
aaeba4efe1 Fix pip placement in large calls. 2024-03-07 16:53:36 -04:00
Alex Hart
3c0eb58381 Apply alpha to v2 conversation item footer content. 2024-03-07 16:53:36 -04:00
Alex Hart
c4f22449f9 Hide thumbnails in specific cases in quote view. 2024-03-07 16:53:36 -04:00
Greyson Parrelli
bca346ec2f Improve copy for unregistered users. 2024-03-07 16:53:36 -04:00
Alex Hart
e0bd60f87c Adjust styling and sizing for rationale dialog. 2024-03-07 16:53:36 -04:00
Alex Hart
aeedab1531 Adjust spacing for contact and verified images on conversation settings page. 2024-03-07 16:53:35 -04:00
Greyson Parrelli
c959f41c68 Improve message send performance. 2024-03-07 16:53:35 -04:00
Alex Hart
9ba755da16 Add section header to find by username / ph row. 2024-03-07 16:53:35 -04:00
Alex Hart
34026c5538 Add proper tinting to refresh and invite rows. 2024-03-07 10:04:16 -04:00
Nicholas Tinsley
ea64425456 Media sending design improvements. 2024-03-07 09:38:13 -04:00
Alex Hart
eb34a20195 Bump version to 7.1.0 2024-03-06 20:50:34 -04:00
Alex Hart
445513cc32 Updated baseline profile. 2024-03-06 20:50:06 -04:00
Alex Hart
e431518a9d Update translations and other static files. 2024-03-06 20:45:13 -04:00
Alex Hart
61df88e094 Fix TestUsers construction in benchmark. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
891c130e12 Sync the PNI identity used in sent transcripts. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
b4ced5278e Fix recipient merging case that causes a change number event. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
10364e9342 Disable next button in FindByActivity when input is blank. 2024-03-06 20:42:01 -04:00
Alex Hart
74dc222a54 Add Recency support for contact search ordering. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
2e4ac7ede1 Always perform CDSI lookups when starting new chats. 2024-03-06 20:42:01 -04:00
Cody Henthorne
184c1b67cc Add learned profile name event. 2024-03-06 20:42:01 -04:00
Alex Hart
f702338129 Fix hide story action state. 2024-03-06 20:42:01 -04:00
Alex Hart
4b4b263423 Usernames 1.01 Fast-Follow Part 1. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
83c16a46de Update assets for image editor. 2024-03-06 20:42:01 -04:00
Clark
6383896a79 Fix incoming group updates showing as updated. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
5fa1560a10 Add stroke to draw tool color bar. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
9bd6ad36cc Round corners of selected region in video trimmer. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
83cc7d5181 Adjust media tool button animation. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
44150673e9 Adjust size of quality selector buttons. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
5092d723a8 Update media send symbols. 2024-03-06 20:42:01 -04:00
Cody Henthorne
218964cbda Add archive media apis. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
ccc9752485 Hoist video editor state out of VideoEditorFragment. 2024-03-06 20:42:01 -04:00
Cody Henthorne
619038f27d Improve local fanout send performance. 2024-03-06 20:42:01 -04:00
Alex Hart
9f197b12ed Add support for call log mark as read. 2024-03-06 20:42:01 -04:00
Jim Gustafson
690608cdf3 Update to RingRTC v2.39.0
Co-authored-by: Alex Hart <alex@signal.org>
2024-03-06 20:42:01 -04:00
Alex Hart
4035932340 Fix find-by slide animations. 2024-03-06 20:42:01 -04:00
Clark
fc9d94701c Disable job manager in instrumentation tests by default. 2024-03-06 20:42:01 -04:00
Alex Hart
6d54ae5f3d Update call link info sheet to match new designs. 2024-03-06 20:42:01 -04:00
Nicholas Tinsley
c53abe0941 Video Sending Redesign 2024-03-06 20:42:01 -04:00
Greyson Parrelli
276e253fdf Fix individual send metrics. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
f160e960be Stop setting pq flag since it's no longer read. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
41b57b9207 Remove unnecessary uniqueness constraint on prekey tables. 2024-03-06 20:42:01 -04:00
Alex Konradi
56eae8c7bf Add libsignal-net CDSI implementation. 2024-03-06 20:42:01 -04:00
Alex Hart
46c8b3b690 Replace full text with call link name as title in call info sheet. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
58b11f3c47 Do a CDS refresh when a new chat is created. 2024-03-06 20:42:01 -04:00
Alex Hart
40b4b316b3 Add new options to share call link details from details fragment. 2024-03-06 20:42:01 -04:00
Clark
7a31f69aea Add tests for import/export of call logs. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
648c99e81d Ensure we are updating last resort metadata after change number. 2024-03-06 20:42:01 -04:00
Greyson Parrelli
56b482a26f Allow scanning QR code from 'Find by username' screen. 2024-03-06 20:42:01 -04:00
Cody Henthorne
c6df4af53a Update bank transfer timeline strings. 2024-03-06 20:42:01 -04:00
Clark
32fe927bfc Add import/export tests for backup of recipients and threads. 2024-03-06 20:42:01 -04:00
Ehren Kret
5740b768d0 Use full organization name in README. 2024-03-06 20:42:01 -04:00
Ehren Kret
d8e74c730a Update README copyright year. 2024-03-06 20:42:00 -04:00
Clark
58846bbf42 Add import/export test for initially account data. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
78d30fc479 Remove deprecated SVR2 enclaves. 2024-03-06 20:42:00 -04:00
Cody Henthorne
86afa988a0 Add ability to scan username qr from gallery. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
6104ef62df Detect username QR codes in our camera-first capture flow. 2024-03-06 20:42:00 -04:00
Cody Henthorne
3f89acf9bd Disable parallel gradle in GH actions. 2024-03-06 20:42:00 -04:00
Cody Henthorne
591d499462 Show system contact icon in more places. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
c31a7152bc Update built-in emoji to v15.1 2024-03-06 20:42:00 -04:00
Greyson Parrelli
343cc3ca67 Update third party licenses. 2024-03-06 20:42:00 -04:00
Cody Henthorne
23e18cee22 Add nobody can find me by number setting warning. 2024-03-06 20:42:00 -04:00
Cody Henthorne
5b2c458bcf Show username in all display name locations if only option. 2024-03-06 20:42:00 -04:00
Cody Henthorne
c10c64a6a6 Prepopulate find number with local user country code. 2024-03-06 20:42:00 -04:00
Cody Henthorne
957221e118 Fix cut off icon in conversation header. 2024-03-06 20:42:00 -04:00
Cody Henthorne
e18e4454e4 Fix multi-invite group create dialog. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
e1067e30de Add support for endpoint checking prekey consistency. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
09b0f15294 Remove unused capabilities. 2024-03-06 20:42:00 -04:00
Greyson Parrelli
b1d6ff4bbd Remove the PNP build variant. 2024-03-06 20:42:00 -04:00
Cody Henthorne
a49e9dd96d Fix crash adjusting constraints during large calls. 2024-03-06 20:42:00 -04:00
Clark
5e428e2c4d Backup and restore mentions. 2024-03-06 20:42:00 -04:00
Clark
0f6ff3c101 Integrate backup file validation to backup playground. 2024-03-06 20:42:00 -04:00
Clark
1ade8b502f Convert and store new group changes in MessageExtras. 2024-03-06 20:42:00 -04:00
Alex Konradi
cc25f0685c Update libsignal version to v0.40.1 2024-03-06 20:42:00 -04:00
Nicholas Tinsley
e91ed88785 CameraX Custom Controller.
Addresses #12817, #13316, #13389
2024-03-06 20:42:00 -04:00
Jon Chambers
39bc6d5eb3 Remove legacy signed prekey endpoint. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley
b7f472b0cd Update Google services libraries. 2024-02-23 16:42:58 -05:00
Cody Henthorne
942f4a45bf Fix reply icon not mirroring in RTL. 2024-02-23 16:42:58 -05:00
Cody Henthorne
767896b14c Fix infinite pending link preview on large previews. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley
8c35628863 Adjust copy depending on PNP settings. 2024-02-23 16:42:58 -05:00
Cody Henthorne
d555370076 Fix edit call link copy in bottom sheet. 2024-02-23 16:42:58 -05:00
Cody Henthorne
bbbe76697d Fix invalid date on group member search results. 2024-02-23 16:42:58 -05:00
Nicholas Tinsley
fc1d60e65b Fallback to matching video decoder by MIME type. 2024-02-23 16:42:57 -05:00
Nicholas Tinsley
9dc856202a Don't use 0 milliseconds as sending retry interval. 2024-02-23 16:42:57 -05:00
Jim Gustafson
6bc41776b1 Update to RingRTC v2.38.0 2024-02-23 16:42:57 -05:00
Greyson Parrelli
940cee0f30 Bump version to 7.0.2 2024-02-23 16:26:12 -05:00
Greyson Parrelli
cdb6c16473 Update translations and other static files. 2024-02-23 16:25:05 -05:00
Greyson Parrelli
c4842ae7c5 Attempt to prevent message retry loops. 2024-02-23 15:36:23 -05:00
Greyson Parrelli
dc32e51ac2 Make a specific crash more clear to improve debuggability. 2024-02-23 15:36:23 -05:00
Greyson Parrelli
43caaf7efc Update a specific recipient case to merge rather than just steal PNI. 2024-02-23 15:36:23 -05:00
Greyson Parrelli
dcd0d433b0 Fix potential charset crash on some devices. 2024-02-23 15:36:23 -05:00
Cody Henthorne
763e891dfd Show username in group invite flow. 2024-02-23 15:36:23 -05:00
Cody Henthorne
c04f761f5a Show rate limit specific error message on username reservation. 2024-02-23 15:36:23 -05:00
Cody Henthorne
b147882e4f Fix text input selection handle colors. 2024-02-23 15:36:23 -05:00
Cody Henthorne
c9f5f91aad Fix black bars on username scan crosshair. 2024-02-23 15:36:23 -05:00
Cody Henthorne
0a3de42729 Fix username QR image generation for multiline usernames. 2024-02-23 15:36:23 -05:00
Greyson Parrelli
64fc0209f4 Remove unused endpoint. 2024-02-23 15:36:23 -05:00
Cody Henthorne
418ad51e77 Fix share your username popup icon tint. 2024-02-23 15:36:23 -05:00
Cody Henthorne
16faf41a84 Fix profile name not updating correctly. 2024-02-23 15:36:23 -05:00
Cody Henthorne
d5cf8d36b3 Fix username recovery UX bugs. 2024-02-23 15:36:23 -05:00
Greyson Parrelli
755fafb0b6 Always use US locale when logging rounded numbers. 2024-02-21 13:08:43 -05:00
Greyson Parrelli
8fc9893ecd Improve logging around retries archiving sessions. 2024-02-21 13:04:42 -05:00
Greyson Parrelli
9071fd0024 Bump version to 7.0.1 2024-02-20 21:45:57 -05:00
Greyson Parrelli
9a3233bb28 Update translations and other static files. 2024-02-20 21:45:30 -05:00
Greyson Parrelli
e77bc9170a Fix RTL rendering of username edit screen. 2024-02-20 21:36:24 -05:00
Greyson Parrelli
23d6a71a3b Update username validation to use libsignal. 2024-02-20 21:36:24 -05:00
Greyson Parrelli
67c3f41dff Fix crash when attempting to start a call via username. 2024-02-20 16:02:57 -05:00
Greyson Parrelli
e22fa499c2 Reduce username debounce rate to 500ms. 2024-02-20 15:25:27 -05:00
Cody Henthorne
fdef13ae92 Apply LQA feedback. 2024-02-20 15:02:39 -05:00
Greyson Parrelli
c0a6f2316c Do not acknowledge retry receipts sent to PNI. 2024-02-20 12:24:32 -05:00
Greyson Parrelli
7d6a87c825 Attempt to fix missing session crash for resends. 2024-02-20 12:13:54 -05:00
Greyson Parrelli
6c863fe99c Clean up capability logging. 2024-02-18 18:34:18 -05:00
2215 changed files with 165768 additions and 61923 deletions

View File

@@ -6,4 +6,15 @@ 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
ktlint_standard_class-naming = disabled
# below rules disabled during ktlint version migration because they were preexisting but should be corrected and re-enabled ASAP
ktlint_function_naming_ignore_when_annotated_with = Composable
ktlint_standard_property-naming = disabled
ktlint_standard_enum-wrapping = disabled
ktlint_standard_multiline-if-else = disabled
ktlint_standard_backing-property-naming = disabled
ktlint_standard_statement-wrapping = disabled
internal:ktlint-suppression = disabled
ktlint_standard_unnecessary-parentheses-before-trailing-lambda = disabled
ktlint_standard_value-parameter-comment = disabled

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

@@ -32,7 +32,7 @@ jobs:
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() }}

View File

@@ -38,7 +38,7 @@ jobs:
- name: Build with Gradle
if: steps.cache-base.outputs.cache-hit != 'true'
run: ./gradlew assemblePlayProdRelease --parallel
run: ./gradlew assemblePlayProdRelease
- name: Copy base apk
if: steps.cache-base.outputs.cache-hit != 'true'
@@ -50,7 +50,7 @@ jobs:
clean: 'false'
- name: Build with Gradle
run: ./gradlew assemblePlayProdRelease --parallel
run: ./gradlew assemblePlayProdRelease
- name: Copy PR apk
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk

3
.gitmodules vendored
View File

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

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

@@ -21,17 +21,10 @@ plugins {
apply(from = "static-ips.gradle.kts")
val canonicalVersionCode = 1394
val canonicalVersionName = "7.0.0"
val postFixSize = 100
val abiPostFix: Map<String, Int> = mapOf(
"universal" to 0,
"armeabi-v7a" to 1,
"arm64-v8a" to 2,
"x86" to 3,
"x86_64" to 4
)
val canonicalVersionCode = 1445
val canonicalVersionName = "7.14.1"
val currentHotfixVersion = 0
val maxHotfixVersions = 100
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
@@ -40,8 +33,6 @@ val selectableVariants = listOf(
"nightlyProdPerf",
"nightlyProdRelease",
"nightlyStagingRelease",
"nightlyPnpPerf",
"nightlyPnpRelease",
"playProdDebug",
"playProdSpinner",
"playProdCanary",
@@ -54,8 +45,6 @@ val selectableVariants = listOf(
"playStagingSpinner",
"playStagingPerf",
"playStagingInstrumentation",
"playPnpDebug",
"playPnpSpinner",
"playStagingRelease",
"websiteProdSpinner",
"websiteProdRelease"
@@ -83,7 +72,7 @@ wire {
}
ktlint {
version.set("0.49.1")
version.set("1.2.1")
}
android {
@@ -96,6 +85,8 @@ android {
useLibrary("org.apache.http.legacy")
testBuildType = "instrumentation"
android.bundle.language.enableSplit = false
kotlinOptions {
jvmTarget = signalKotlinJvmTarget
}
@@ -146,7 +137,7 @@ android {
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")
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", "libsignal_jni_testing.dylib", "signal_jni_testing.dll")
}
}
@@ -156,11 +147,11 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.4"
kotlinCompilerExtensionVersion = "1.5.4"
}
defaultConfig {
versionCode = canonicalVersionCode * postFixSize
versionCode = (canonicalVersionCode * maxHotfixVersions) + currentHotfixVersion
versionName = canonicalVersionName
minSdk = signalMinSdkVersion
@@ -182,7 +173,6 @@ android {
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\"")
buildConfigField("String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\"")
buildConfigField("String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\"")
buildConfigField("String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\"")
buildConfigField("String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\"")
@@ -201,10 +191,9 @@ android {
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_DEPRECATED", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\"")
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/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0I=\"")
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 = ", ")} }")
@@ -213,6 +202,8 @@ android {
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.INFO")
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
@@ -220,6 +211,7 @@ android {
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")
@@ -380,29 +372,21 @@ android {
buildConfigField("String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.signal.org\"")
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"")
buildConfigField("String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"")
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\"")
buildConfigField("String", "SVR2_MRENCLAVE_DEPRECATED", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\"")
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+mPk5vCM=\"")
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==\"")
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
buildConfigField("int", "LIBSIGNAL_LOG_LEVEL", "org.signal.libsignal.protocol.logging.SignalProtocolLogger.DEBUG")
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
}
create("pnp") {
dimension = "environment"
initWith(getByName("staging"))
applicationIdSuffix = ".pnp"
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\"")
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true")
}
}
@@ -418,7 +402,6 @@ android {
.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")) {
@@ -432,14 +415,9 @@ android {
} 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")
if (currentHotfixVersion >= maxHotfixVersions) {
throw AssertionError("Hotfix version is too large!")
}
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
}
@@ -448,6 +426,12 @@ android {
beforeVariants { variant ->
variant.enable = variant.name in selectableVariants
}
onVariants { variant ->
// Include the test-only library on debug builds.
if (variant.buildType != "instrumentation") {
variant.packaging.jniLibs.excludes.add("**/libsignal_jni_testing.so")
}
}
}
val releaseDir = "$projectDir/src/release/java"
@@ -509,9 +493,11 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
implementation(libs.androidx.lifecycle.common.java8)
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
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)
@@ -521,6 +507,7 @@ dependencies {
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.asynclayoutinflater)
implementation(libs.androidx.asynclayoutinflater.appcompat)
implementation(libs.androidx.emoji2)
implementation(libs.firebase.messaging) {
exclude(group = "com.google.firebase", module = "firebase-core")
exclude(group = "com.google.firebase", module = "firebase-analytics")
@@ -550,12 +537,9 @@ dependencies {
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.lottie.compose)
implementation(libs.signal.android.database.sqlcipher)
implementation(libs.androidx.sqlite)
implementation(libs.google.ez.vcard) {
@@ -567,6 +551,7 @@ dependencies {
implementation(libs.accompanist.permissions)
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.coroutines.play.services)
implementation(libs.jackson.module.kotlin)
implementation(libs.rxjava3.rxandroid)
implementation(libs.rxjava3.rxkotlin)
@@ -614,6 +599,7 @@ dependencies {
androidTestImplementation(testLibs.mockito.kotlin)
androidTestImplementation(testLibs.mockk.android)
androidTestImplementation(testLibs.square.okhttp.mockserver)
androidTestImplementation(testLibs.diff.utils)
androidTestUtil(testLibs.androidx.test.orchestrator)
}
@@ -728,7 +714,8 @@ fun Project.languageList(): List<String> {
.map { valuesFolderName -> valuesFolderName.replace("values-", "") }
.filter { valuesFolderName -> valuesFolderName != "values" }
.map { languageCode -> languageCode.replace("-r", "_") }
.distinct() + "en"
.distinct()
.sorted() + "en"
}
fun String.capitalize(): String {

View File

@@ -2,7 +2,9 @@
-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 ** {
@@ -14,6 +16,10 @@
-keep class androidx.window.** { *; }
-keepclassmembers class * extends androidx.constraintlayout.motion.widget.Key {
public <init>();
}
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ import org.signal.core.util.logging.AndroidLogger
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.logging.SignalProtocolLoggerProvider
import org.thoughtcrime.securesms.database.LogDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger
@@ -21,8 +21,8 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
override fun initializeAppDependencies() {
val default = ApplicationDependencyProvider(this)
ApplicationDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
ApplicationDependencies.getDeadlockDetector().start()
AppDependencies.init(this, InstrumentationApplicationDependencyProvider(this, default))
AppDependencies.deadlockDetector.start()
}
override fun initializeLogging() {
@@ -35,4 +35,18 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
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

@@ -1,675 +0,0 @@
/*
* 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,118 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup.v2
import androidx.test.platform.app.InstrumentationRegistry
import com.github.difflib.DiffUtils
import com.github.difflib.UnifiedDiffUtils
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.signal.core.util.Base64
import org.signal.core.util.StreamUtil
import org.signal.libsignal.messagebackup.ComparableBackup
import org.signal.libsignal.messagebackup.MessageBackup
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.push.ServiceId
import java.io.ByteArrayInputStream
import java.util.UUID
import kotlin.random.Random
@RunWith(Parameterized::class)
class ImportExportTestSuite(private val path: String) {
companion object {
val SELF_ACI = ServiceId.ACI.from(UUID.fromString("77770000-b477-4f35-a824-d92987a63641"))
val SELF_PNI = ServiceId.PNI.from(UUID.fromString("77771111-b014-41fb-bf73-05cb2ec52910"))
const val SELF_E164 = "+10000000000"
val SELF_PROFILE_KEY = ProfileKey(Random.nextBytes(32))
val MASTER_KEY = Base64.decode("sHuBMP4ToZk4tcNU+S8eBUeCt8Am5EZnvuqTBJIR4Do")
const val TESTS_FOLDER = "backupTests"
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun data(): Collection<Array<String>> {
val testFiles = InstrumentationRegistry.getInstrumentation().context.resources.assets.list(TESTS_FOLDER)!!
return testFiles
.map { arrayOf(it) }
.toList()
}
}
@Before
fun setup() {
SignalStore.svr.setMasterKey(MasterKey(MASTER_KEY), "1234")
SignalStore.account.setE164(SELF_E164)
SignalStore.account.setAci(SELF_ACI)
SignalStore.account.setPni(SELF_PNI)
SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account.generatePniIdentityKeyIfNecessary()
}
@Test
fun testBinProto() {
val binProtoBytes: ByteArray = InstrumentationRegistry.getInstrumentation().context.resources.assets.open("${TESTS_FOLDER}/$path").use {
StreamUtil.readFully(it)
}
val importResult = import(binProtoBytes)
assertTrue(importResult is ImportResult.Success)
val success = importResult as ImportResult.Success
val generatedBackupData = BackupRepository.debugExport(plaintext = true, currentTime = success.backupTime)
assertEquivalent(binProtoBytes, generatedBackupData)
// Validator expects encrypted data, so we have to export again with encryption to validate
val encryptedBackupData = BackupRepository.debugExport(plaintext = false, currentTime = success.backupTime)
assertPassesValidator(encryptedBackupData)
}
private fun import(importData: ByteArray): ImportResult {
return BackupRepository.import(
length = importData.size.toLong(),
inputStreamFactory = { ByteArrayInputStream(importData) },
selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY),
plaintext = true
)
}
private fun assertPassesValidator(generatedBackupData: ByteArray) {
BackupRepository.validate(
length = generatedBackupData.size.toLong(),
inputStreamFactory = { ByteArrayInputStream(generatedBackupData) },
selfData = BackupRepository.SelfData(SELF_ACI, SELF_PNI, SELF_E164, SELF_PROFILE_KEY)
)
}
private fun assertEquivalent(import: ByteArray, export: ByteArray) {
val importComparable = ComparableBackup.readUnencrypted(MessageBackup.Purpose.REMOTE_BACKUP, import.inputStream(), import.size.toLong())
val exportComparable = ComparableBackup.readUnencrypted(MessageBackup.Purpose.REMOTE_BACKUP, export.inputStream(), import.size.toLong())
if (importComparable.unknownFieldMessages.isNotEmpty()) {
throw AssertionError("Imported backup contains unknown fields: ${importComparable.unknownFieldMessages}")
}
if (exportComparable.unknownFieldMessages.isNotEmpty()) {
throw AssertionError("Imported backup contains unknown fields: ${importComparable.unknownFieldMessages}")
}
val canonicalImport = importComparable.comparableString
val canonicalExport = exportComparable.comparableString
if (canonicalImport != canonicalExport) {
val importLines = canonicalImport.lines()
val exportLines = canonicalExport.lines()
val patch = DiffUtils.diff(importLines, exportLines)
val diff = UnifiedDiffUtils.generateUnifiedDiff("Import", "Export", importLines, patch, 3).joinToString(separator = "\n")
throw AssertionError("Imported backup does not match exported backup. Diff:\n$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

@@ -1,393 +0,0 @@
package org.thoughtcrime.securesms.components.settings.app.changenumber
import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.FlakyTest
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.MockProvider
import org.thoughtcrime.securesms.testing.Post
import org.thoughtcrime.securesms.testing.Put
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.testing.connectionFailure
import org.thoughtcrime.securesms.testing.failure
import org.thoughtcrime.securesms.testing.parsedRequestBody
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.testing.timeout
import org.whispersystems.signalservice.api.account.ChangePhoneNumberRequest
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import org.whispersystems.signalservice.internal.push.MismatchedDevices
import org.whispersystems.signalservice.internal.push.PreKeyState
import java.util.UUID
@RunWith(AndroidJUnit4::class)
class ChangeNumberViewModelTest {
@get:Rule
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
@Before
fun setUp() {
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application)
)
viewModel.setNewCountry(1)
viewModel.setNewNationalNumber("5555550102")
}
}
@After
fun tearDown() {
InstrumentationApplicationDependencyProvider.clearHandlers()
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndNoRegLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
/**
* If we encounter a server error, this means the server ack our request and rejected it. In this
* case we know the change *did not* take on the server and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenServerFailedApiCall() {
// GIVEN
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { MockResponse().failure(500) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs true
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our old details, so we can know the change *did not* take on the server
* and can reset to a clean state.
*/
@Test
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToServer() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { MockResponse().connectionFailure() },
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, oldPni, oldE164)) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs false
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
/**
* If we encounter a non-server error like a timeout or bad SSL, we do not know the state of our change
* number on the server side. We have to do a whoami call to query the server for our details and then
* respond accordingly.
*
* In this case, the whoami is our new details, so we can know the change *did* take on the server
* and need to keep the app in a locked state. The test then uses the ChangeNumberLockActivity to unlock
* and apply the pending state after confirming the change on the server.
*/
@Test
@FlakyTest
@Ignore("Test sometimes requires manual intervention to continue.")
fun testChangeNumber_givenNetworkFailedApiCallEnRouteToClient() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val oldPni = Recipient.self().requirePni()
val oldE164 = Recipient.self().requireE164()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
MockResponse().timeout()
},
Get("/v1/accounts/whoami") { MockResponse().success(MockProvider.createWhoAmIResponse(aci, newPni, "+15555550102")) },
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
val processor: VerifyResponseProcessor = viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet()
// THEN
processor.isServerSentError() assertIs false
Recipient.self().requireE164() assertIs oldE164
Recipient.self().requirePni() assertIs oldPni
SignalStore.misc().isChangeNumberLocked assertIs true
SignalStore.misc().pendingChangeNumberMetadata.assertIsNotNull()
// WHEN AGAIN Processing lock
val scenario = harness.launchActivity<ChangeNumberLockActivity>()
scenario.onActivity {}
ThreadUtil.sleep(500)
// THEN AGAIN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenOnlyPrimaryAndRegistrationLock() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenMismatchedDevicesOnFirstCall() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Get("/v1/devices") { MockResponse().success(MockProvider.primaryOnlyDeviceList) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
@Test
fun testChangeNumber_givenRegLockAndMismatchedDevicesOnFirstTwoCalls() {
// GIVEN
val aci = Recipient.self().requireServiceId()
val newPni = PNI.from(UUID.randomUUID())
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
Put("/v2/accounts/number") { r ->
changeNumberRequest = r.parsedRequestBody()
if (changeNumberRequest.registrationLock.isNullOrEmpty()) {
MockResponse().failure(423, MockProvider.lockedFailure)
} else if (changeNumberRequest.deviceMessages.isEmpty()) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2)
extraDevices = emptyList()
}
)
} else if (changeNumberRequest.deviceMessages.size == 1) {
MockResponse().failure(
409,
MismatchedDevices().apply {
missingDevices = listOf(2, 3)
extraDevices = emptyList()
}
)
} else {
MockResponse().success(MockProvider.createVerifyAccountResponse(aci, newPni))
}
},
Get("/v2/keys/$aci/2") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 2))
},
Get("/v2/keys/$aci/3") {
MockResponse().success(MockProvider.createPreKeyResponse(deviceId = 3))
},
Put("/v2/keys") { r ->
setPreKeysRequest = r.parsedRequestBody()
MockResponse().success()
},
Get("/v1/certificate/delivery") { MockResponse().success(MockProvider.senderCertificate) }
)
// WHEN
viewModel.requestVerificationCode(VerifyAccountRepository.Mode.SMS_WITHOUT_LISTENER, null, null).blockingGet().resultOrThrow
viewModel.verifyCodeWithoutRegistrationLock("123456").blockingGet().also { processor ->
processor.registrationLock() assertIs true
Recipient.self().requirePni() assertIsNot newPni
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock("pin").blockingGet().resultOrThrow
// THEN
assertSuccess(newPni, changeNumberRequest, setPreKeysRequest)
}
private fun assertSuccess(newPni: ServiceId, changeNumberRequest: ChangePhoneNumberRequest, setPreKeysRequest: PreKeyState) {
val pniProtocolStore = ApplicationDependencies.getProtocolStore().pni()
val pniMetadataStore = SignalStore.account().pniPreKeys
Recipient.self().requireE164() assertIs "+15555550102"
Recipient.self().requirePni() assertIs newPni
SignalStore.account().pniRegistrationId assertIs changeNumberRequest.pniRegistrationIds["1"]!!
SignalStore.account().pniIdentityKey.publicKey assertIs changeNumberRequest.pniIdentityKey
pniMetadataStore.activeSignedPreKeyId assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.keyId
val activeSignedPreKey: SignedPreKeyRecord = pniProtocolStore.loadSignedPreKey(pniMetadataStore.activeSignedPreKeyId)
activeSignedPreKey.keyPair.publicKey assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.publicKey
activeSignedPreKey.signature assertIs changeNumberRequest.devicePniSignedPrekeys["1"]!!.signature
setPreKeysRequest.signedPreKey.publicKey assertIs activeSignedPreKey.keyPair.publicKey
setPreKeysRequest.preKeys assertIsSize 100
SignalStore.misc().pendingChangeNumberMetadata.assertIsNull()
}
}

View File

@@ -0,0 +1,131 @@
package org.thoughtcrime.securesms.components.settings.app.subscription.donate
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isSelected
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import okhttp3.mockwebserver.MockResponse
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.Delete
import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.util.JsonUtils
import org.whispersystems.signalservice.api.subscriptions.ActiveSubscription
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import org.whispersystems.signalservice.internal.push.SubscriptionsConfiguration
import java.math.BigDecimal
import java.util.Currency
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.milliseconds
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class CheckoutFlowActivityTest__RecurringDonations {
@get:Rule
val harness = SignalActivityRule(othersCount = 10)
private val intent = CheckoutFlowActivity.createIntent(InstrumentationRegistry.getInstrumentation().targetContext, InAppPaymentType.RECURRING_DONATION)
@Test
fun givenRecurringDonations_whenILoadScreen_thenIExpectMonthlySelected() {
ActivityScenario.launch<CheckoutFlowActivity>(intent)
onView(withId(R.id.monthly)).check(matches(isSelected()))
}
@Test
fun givenNoCurrentDonation_whenILoadScreen_thenIExpectContinueButton() {
ActivityScenario.launch<CheckoutFlowActivity>(intent)
onView(withText("Continue")).check(matches(isDisplayed()))
}
@Test
fun givenACurrentDonation_whenILoadScreen_thenIExpectUpgradeButton() {
initialiseConfigurationResponse()
initialiseActiveSubscription()
ActivityScenario.launch<CheckoutFlowActivity>(intent)
onView(withText(R.string.SubscribeFragment__update_subscription)).check(matches(isDisplayed()))
onView(withText(R.string.SubscribeFragment__cancel_subscription)).check(matches(isDisplayed()))
}
@Test
fun givenACurrentDonation_whenIPressCancel_thenIExpectCancellationDialog() {
initialiseConfigurationResponse()
initialiseActiveSubscription()
ActivityScenario.launch<CheckoutFlowActivity>(intent)
onView(withText(R.string.SubscribeFragment__cancel_subscription)).check(matches(isDisplayed()))
onView(withText(R.string.SubscribeFragment__cancel_subscription)).perform(ViewActions.click())
onView(withText(R.string.SubscribeFragment__confirm_cancellation)).check(matches(isDisplayed()))
onView(withText(R.string.SubscribeFragment__confirm)).perform(ViewActions.click())
onView(withText(R.string.StripePaymentInProgressFragment__cancelling)).check(matches(isDisplayed()))
}
private fun initialiseConfigurationResponse() {
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/subscription/configuration") {
val assets = InstrumentationRegistry.getInstrumentation().context.resources.assets
assets.open("inAppPaymentsTests/configuration.json").use { stream ->
MockResponse().success(JsonUtils.fromJson(stream, SubscriptionsConfiguration::class.java))
}
}
)
}
private fun initialiseActiveSubscription() {
val currency = Currency.getInstance("USD")
val subscriber = InAppPaymentSubscriberRecord(
subscriberId = SubscriberId.generate(),
currency = currency,
type = InAppPaymentSubscriberRecord.Type.DONATION,
requiresCancel = false,
paymentMethodType = InAppPaymentData.PaymentMethodType.CARD
)
InAppPaymentsRepository.setSubscriber(subscriber)
SignalStore.inAppPayments.setSubscriberCurrency(currency, subscriber.type)
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Get("/v1/subscription/${subscriber.subscriberId.serialize()}") {
MockResponse().success(
ActiveSubscription(
ActiveSubscription.Subscription(
200,
currency.currencyCode,
BigDecimal.ONE,
System.currentTimeMillis().milliseconds.inWholeSeconds + 30.days.inWholeSeconds,
true,
System.currentTimeMillis().milliseconds.inWholeSeconds + 30.days.inWholeSeconds,
false,
"active",
"STRIPE",
"CARD",
false
),
null
)
)
},
Delete("/v1/subscription/${subscriber.subscriberId.serialize()}") {
Thread.sleep(10000)
MockResponse().success()
}
)
}
}

View File

@@ -7,6 +7,7 @@ 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
@@ -15,7 +16,6 @@ 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
@@ -137,7 +137,7 @@ class ConversationItemPreviewer {
private fun attachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.CDN_3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"image/webp",
null,
@@ -154,7 +154,8 @@ class ConversationItemPreviewer {
false,
Optional.empty(),
Optional.empty(),
System.currentTimeMillis()
System.currentTimeMillis(),
null
)
}
}

View File

@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.DistributionListPrivacyMode
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
@@ -64,7 +64,7 @@ class SafetyNumberChangeDialogPreviewer {
scenario.onActivity { conversationActivity ->
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(
identityRecords = ApplicationDependencies.getProtocolStore().aci().identities().getIdentityRecords(othersRecipients).identityRecords,
identityRecords = AppDependencies.protocolStore.aci().identities().getIdentityRecords(othersRecipients).identityRecords,
destinations = listOf(ContactSearchKey.RecipientSearchKey(myStoryRecipientId, true))
)
.show(conversationActivity.supportFragmentManager)

View File

@@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.bumptech.glide.RequestManager
import io.mockk.mockk
@@ -203,8 +204,8 @@ class V2ConversationItemShapeTest {
private val colorizer = Colorizer()
override val lifecycleOwner: LifecycleOwner = mockk(relaxed = true)
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true
@@ -289,6 +290,8 @@ class V2ConversationItemShapeTest {
override fun onChangeNumberUpdateContact(recipient: Recipient) = Unit
override fun onChangeProfileNameUpdateContact(recipient: Recipient) = Unit
override fun onCallToAction(action: String) = Unit
override fun onDonateClicked() = Unit
@@ -313,7 +316,7 @@ class V2ConversationItemShapeTest {
override fun goToMediaPreview(parent: ConversationItem?, sharedElement: View?, args: MediaIntentFactory.MediaPreviewArgs?) = Unit
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) = Unit
override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) = Unit
override fun onShowGroupDescriptionClicked(groupName: String, description: String, shouldLinkifyWebLinks: Boolean) = Unit
@@ -328,5 +331,8 @@ class V2ConversationItemShapeTest {
override fun onReportSpamLearnMoreClicked() = Unit
override fun onMessageRequestAcceptOptionsClicked() = Unit
override fun onItemDoubleClick(item: MultiselectPart) = Unit
override fun onPaymentTombstoneClicked() = Unit
}
}

View File

@@ -51,18 +51,16 @@ 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_FILE)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
@@ -79,18 +77,16 @@ 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_FILE)
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
assertNotEquals(attachment1Info, attachment2Info)
}
@@ -121,15 +117,14 @@ class AttachmentTableTest {
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
// WHEN
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData))
// THEN
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA_FILE)!!
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val previousInfo = SignalDatabase.attachments.getDataFileInfo(previousDatabaseAttachmentId)!!
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
assertNotEquals(standardInfo, highInfo)
standardInfo.file assertIs previousInfo.file
highInfo.file assertIsNot standardInfo.file
highInfo.file.exists() assertIs true
}
@@ -158,9 +153,9 @@ class AttachmentTableTest {
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
// THEN
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
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

View File

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

View File

@@ -56,7 +56,7 @@ class CallTableTest {
)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.deleteGroupCall(call!!)
SignalDatabase.calls.markCallDeletedFromSyncEvent(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -69,9 +69,10 @@ class CallTableTest {
@Test
fun givenNoPreExistingEvent_whenIDeleteGroupCall_thenIInsertAndMarkCallDeleted() {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId,
groupRecipientId,
CallTable.Type.GROUP_CALL,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
@@ -438,11 +439,12 @@ class CallTableTest {
@Test
fun givenADeletedCallEvent_whenIReceiveARingUpdate_thenIIgnoreTheRingUpdate() {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
SignalDatabase.calls.insertDeletedCallFromSyncEvent(
callId = callId,
recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis()
timestamp = System.currentTimeMillis(),
type = CallTable.Type.GROUP_CALL
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(

View File

@@ -15,7 +15,7 @@ import org.signal.core.util.getIndexes
import org.signal.core.util.readToList
import org.signal.core.util.requireNonNullString
import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.testing.SignalActivityRule
/**
@@ -30,7 +30,7 @@ class DatabaseConsistencyTest {
@Test
fun testUpgradeConsistency() {
val currentVersionStatements = SignalDatabase.rawDatabase.getAllCreateStatements()
val testHelper = InMemoryTestHelper(ApplicationDependencies.getApplication()).also {
val testHelper = InMemoryTestHelper(AppDependencies.application).also {
it.onUpgrade(it.writableDatabase, 181, SignalDatabaseMigrations.DATABASE_VERSION)
}

View File

@@ -8,7 +8,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.concurrent.SignalExecutors
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
@@ -26,7 +26,7 @@ class DatabaseObserverTest {
@Before
fun setup() {
db = SignalDatabase.instance!!.signalWritableDatabase
observer = ApplicationDependencies.getDatabaseObserver()
observer = AppDependencies.databaseObserver
}
@Test

View File

@@ -25,15 +25,6 @@ class DistributionListTablesTest {
Assert.assertNotNull(id)
}
@Test
fun createList_whenNameConflict_failToInsert() {
val id: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNotNull(id)
val id2: DistributionListId? = distributionDatabase.createList("test", recipientList(1, 2, 3))
Assert.assertNull(id2)
}
@Test
fun getList_returnCorrectList() {
createRecipients(3)

View File

@@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.database
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -75,21 +74,6 @@ class GroupTableTest {
assertEquals(2, groups.size)
}
@Test
fun givenGroups_whenIQueryGroupsByMembership_thenIExpectBothGroups() {
insertPushGroup()
insertMmsGroup(members = listOf(harness.others[1]))
val groups = groupTable.queryGroupsByMembership(
setOf(harness.self.id, harness.others[1]),
includeInactive = false,
excludeV1 = false,
excludeMms = false
)
assertEquals(2, groups.cursor?.count)
}
@Test
fun givenGroups_whenIGetGroups_thenIExpectBothGroups() {
insertPushGroup()
@@ -181,72 +165,10 @@ class GroupTableTest {
assertFalse(actual)
}
@Test
fun givenAGroup_whenIUpdateMembers_thenIExpectUpdatedMembers() {
val v2Group = insertPushGroup()
groupTable.updateMembers(v2Group, listOf(harness.self.id, harness.others[1]))
val groupRecord = groupTable.getGroup(v2Group)
assertEquals(setOf(harness.self.id, harness.others[1]), groupRecord.get().members.toSet())
}
@Test
fun givenAnMmsGroup_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val members: List<RecipientId> = listOf(harness.self.id, harness.others[0])
val other = insertMmsGroup(members + listOf(harness.others[1]))
val mmsGroup = insertMmsGroup(members)
val actual = groupTable.getOrCreateMmsGroupForMembers(members.toSet())
assertNotEquals(other, actual)
assertEquals(mmsGroup, actual)
}
@Test
fun givenMultipleMmsGroups_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1])
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2])
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMultipleMmsGroupsWithDifferentMemberOrders_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val group1Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[1], harness.others[2]).shuffled()
val group2Members: List<RecipientId> = listOf(harness.self.id, harness.others[0], harness.others[2], harness.others[3]).shuffled()
val group1: GroupId = insertMmsGroup(group1Members)
val group2: GroupId = insertMmsGroup(group2Members)
val group1Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group1Members.shuffled().toSet())
val group2Result: GroupId = groupTable.getOrCreateMmsGroupForMembers(group2Members.shuffled().toSet())
assertEquals(group1, group1Result)
assertEquals(group2, group2Result)
assertNotEquals(group1Result, group2Result)
}
@Test
fun givenMmsGroupWithOneMember_whenIGetOrCreateMmsGroup_thenIExpectMyMmsGroup() {
val groupMembers: List<RecipientId> = listOf(harness.self.id)
val group: GroupId = insertMmsGroup(groupMembers)
val groupResult: GroupId = groupTable.getOrCreateMmsGroupForMembers(groupMembers.toSet())
assertEquals(group, groupResult)
}
@Test
fun givenTwoGroupsWithoutMembers_whenIQueryThem_thenIExpectEach() {
val g1 = insertPushGroup(listOf())
val g2 = insertPushGroup(listOf())
val g1 = insertPushGroup(members = emptyList())
val g2 = insertPushGroup(members = emptyList())
val gr1 = groupTable.getGroup(g1)
val gr2 = groupTable.getGroup(g2)
@@ -273,6 +195,85 @@ class GroupTableTest {
assertEquals(groups[0].id, groupInCommon)
}
@Test
fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForTheSharedToken_thenIExpectBothGroups() {
insertPushGroup("Group Alice")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(2, it.cursor?.count)
val firstGroup = it.getNext()
val secondGroup = it.getNext()
assertEquals("Group Alice", firstGroup?.title)
assertEquals("Group Bob", secondGroup?.title)
}
}
@Test
fun givenTwoGroupsWithANameThatSharesAToken_whenISearchForAnUnsharedToken_thenIExpectOneGroup() {
insertPushGroup("Group Alice")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Alice",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(1, it.cursor?.count)
val firstGroup = it.getNext()
assertEquals("Group Alice", firstGroup?.title)
}
}
@Test
fun givenAGroupWithThreeTokens_whenISearchForTheFirstAndLastToken_thenIExpectThatGroup() {
insertPushGroup("Group & Alice")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group Alice",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(1, it.cursor?.count)
val firstGroup = it.getNext()
assertEquals("Group & Alice", firstGroup?.title)
}
}
@Test
fun givenTwoGroupsWithSharedTokens_whenISearchForAnExactMatch_thenIExpectThatGroupFirst() {
insertPushGroup("Group Alice Bob")
insertPushGroup("Group Bob")
SignalDatabase.groups.queryGroupsByTitle(
inputQuery = "Group Bob",
includeInactive = false,
excludeV1 = false,
excludeMms = false
).use {
assertEquals(2, it.cursor?.count)
val firstGroup = it.getNext()
val second = it.getNext()
assertEquals("Group Bob", firstGroup?.title)
assertEquals("Group Alice Bob", second?.title)
}
}
private fun insertThread(groupId: GroupId): Long {
val groupRecipient = SignalDatabase.recipients.getByGroupId(groupId).get()
return SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(groupRecipient))
@@ -292,6 +293,7 @@ class GroupTableTest {
}
private fun insertPushGroup(
title: String = "Test Group",
members: List<DecryptedMember> = listOf(
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
@@ -307,11 +309,12 @@ class GroupTableTest {
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.Builder()
.title(title)
.members(members)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
}
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
@@ -336,6 +339,6 @@ class GroupTableTest {
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
return groupTable.create(groupMasterKey, decryptedGroupState, null)!!
}
}

View File

@@ -159,7 +159,7 @@ class KyberPreKeyTableTest {
val count = SignalDatabase.rawDatabase
.update(KyberPreKeyTable.TABLE_NAME)
.values(KyberPreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
assertEquals(1, count)
@@ -169,8 +169,15 @@ class KyberPreKeyTableTest {
return SignalDatabase.rawDatabase
.select(KyberPreKeyTable.STALE_TIMESTAMP)
.from(KyberPreKeyTable.TABLE_NAME)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account)
.where("${KyberPreKeyTable.ACCOUNT_ID} = ? AND ${KyberPreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
.readToSingleObject { it.requireLongOrNull(KyberPreKeyTable.STALE_TIMESTAMP) }
}
private fun ServiceId.toAccountId(): String {
return when (this) {
is ACI -> this.toString()
is PNI -> KyberPreKeyTable.PNI_ACCOUNT_ID
}
}
}

View File

@@ -12,12 +12,12 @@ 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.dependencies.AppDependencies
import org.thoughtcrime.securesms.testing.assertIs
class LogDatabaseTest {
private val db: LogDatabase = LogDatabase.getInstance(ApplicationDependencies.getApplication())
private val db: LogDatabase = LogDatabase.getInstance(AppDependencies.application)
@Test
fun crashTable_matchesNamePattern() {

View File

@@ -30,8 +30,8 @@ class MessageTableTest_gifts {
mms.deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
SignalStore.account.setAci(localAci)
SignalStore.account.setPni(localPni)
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
}

View File

@@ -40,14 +40,14 @@ class MmsTableTest_stories {
mms.deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
SignalStore.account.setAci(localAci)
SignalStore.account.setPni(localPni)
myStory = Recipient.resolved(SignalDatabase.recipients.getOrInsertFromDistributionListId(DistributionListId.MY_STORY))
recipients = (0 until 5).map { SignalDatabase.recipients.getOrInsertFromServiceId(ACI.from(UUID.randomUUID())) }
releaseChannelRecipient = Recipient.resolved(SignalDatabase.recipients.insertReleaseChannelRecipient())
SignalStore.releaseChannelValues().setReleaseChannelRecipientId(releaseChannelRecipient.id)
SignalStore.releaseChannel.setReleaseChannelRecipientId(releaseChannelRecipient.id)
}
@Test

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

@@ -120,7 +120,7 @@ class OneTimePreKeyTableTest {
val count = SignalDatabase.rawDatabase
.update(OneTimePreKeyTable.TABLE_NAME)
.values(OneTimePreKeyTable.STALE_TIMESTAMP to staleTime)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
assertEquals(1, count)
@@ -130,8 +130,15 @@ class OneTimePreKeyTableTest {
return SignalDatabase.rawDatabase
.select(OneTimePreKeyTable.STALE_TIMESTAMP)
.from(OneTimePreKeyTable.TABLE_NAME)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account)
.where("${OneTimePreKeyTable.ACCOUNT_ID} = ? AND ${OneTimePreKeyTable.KEY_ID} = $id", account.toAccountId())
.run()
.readToSingleObject { it.requireLongOrNull(OneTimePreKeyTable.STALE_TIMESTAMP) }
}
private fun ServiceId.toAccountId(): String {
return when (this) {
is ACI -> this.toString()
is PNI -> OneTimePreKeyTable.PNI_ACCOUNT_ID
}
}
}

View File

@@ -57,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)
}
@@ -128,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)
}

View File

@@ -9,7 +9,7 @@ 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.dependencies.AppDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageRecordUpdate
import org.thoughtcrime.securesms.storage.StorageSyncModels
@@ -28,7 +28,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
@Test
fun insertMessageOnVerifiedToDefault() {
// GIVEN
val identities = ApplicationDependencies.getProtocolStore().aci().identities()
val identities = AppDependencies.protocolStore.aci().identities()
val other = Recipient.resolved(harness.others[0])
MmsHelper.insert(recipient = other)

View File

@@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMessage
@@ -53,9 +53,9 @@ class RecipientTableTest_getAndPossiblyMerge {
@Before
fun setup() {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
SignalStore.account.setE164(E164_SELF)
SignalStore.account.setAci(ACI_SELF)
SignalStore.account.setPni(PNI_SELF)
}
@Test
@@ -776,6 +776,18 @@ 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)
@@ -788,6 +800,17 @@ class RecipientTableTest_getAndPossiblyMerge {
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)
@@ -1090,8 +1113,8 @@ class RecipientTableTest_getAndPossiblyMerge {
SignalDatabase.rawDatabase.execSQL("DELETE FROM $table")
}
ApplicationDependencies.getRecipientCache().clear()
ApplicationDependencies.getRecipientCache().clearSelf()
AppDependencies.recipientCache.clear()
AppDependencies.recipientCache.clearSelf()
RecipientId.clearCache()
}

View File

@@ -10,7 +10,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Hex
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.database.model.databaseprotos.addMember
import org.thoughtcrime.securesms.database.model.databaseprotos.addRequestingMember
import org.thoughtcrime.securesms.database.model.databaseprotos.deleteRequestingMember
@@ -44,8 +46,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
recipients = SignalDatabase.recipients
sms = SignalDatabase.messages
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
SignalStore.account.setAci(localAci)
SignalStore.account.setPni(localPni)
alice = recipients.getOrInsertFromServiceId(aliceServiceId)
bob = recipients.getOrInsertFromServiceId(bobServiceId)
@@ -286,11 +288,18 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
val updateDescription = GV2UpdateDescription(
gv2ChangeDescription = groupContext,
groupChangeUpdate = GroupsV2UpdateMessageConverter.translateDecryptedChangeUpdate(SignalStore.account.getServiceIds(), groupContext)
)
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
groupContext = groupContext,
update = updateDescription,
isGroupAdd = false,
serverGuid = null
)
}

View File

@@ -5,6 +5,7 @@ import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.audio.AudioHash
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.stickers.StickerLocator
import java.util.UUID
object UriAttachmentBuilder {
fun build(
@@ -22,23 +23,28 @@ object UriAttachmentBuilder {
stickerLocator: StickerLocator? = null,
blurHash: BlurHash? = null,
audioHash: AudioHash? = null,
transformProperties: AttachmentTable.TransformProperties? = null
transformProperties: AttachmentTable.TransformProperties? = null,
uuid: UUID? = UUID.randomUUID()
): UriAttachment {
return UriAttachment(
uri,
contentType,
transferState,
size,
fileName,
voiceNote,
borderless,
videoGif,
quote,
caption,
stickerLocator,
blurHash,
audioHash,
transformProperties
dataUri = uri,
contentType = contentType,
transferState = transferState,
size = size,
width = 0,
height = 0,
fileName = fileName,
fastPreflightId = null,
voiceNote = voiceNote,
borderless = borderless,
videoGif = videoGif,
quote = quote,
caption = caption,
stickerLocator = stickerLocator,
blurHash = blurHash,
audioHash = audioHash,
transformProperties = transformProperties,
uuid = uuid
)
}
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.readToSingleObject
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
import org.signal.core.util.update
import org.signal.donations.InAppPaymentType
import org.thoughtcrime.securesms.components.settings.app.subscription.DonationSerializationHelper.toDecimalValue
import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.FiatValue
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.testing.SignalDatabaseRule
import org.thoughtcrime.securesms.testing.assertIs
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.math.BigDecimal
import java.util.Currency
@RunWith(AndroidJUnit4::class)
class FixInAppCurrencyIfAbleTest {
@get:Rule
val harness = SignalDatabaseRule(deleteAllThreadsOnEachRun = false)
@Test
fun givenNoSubscribers_whenIMigrate_thenIDoNothing() {
migrate()
}
@Test
fun givenASubscriberButNoPayment_whenIMigrate_thenIDoNothing() {
val subscriber = insertSubscriber("USD")
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndMismatchedPayment_whenIMigrate_thenIDoNothing() {
val subscriber = insertSubscriber("USD")
val otherSubscriber = insertSubscriber("EUR")
insertPayment(otherSubscriber)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndPaymentWithNoSubscriber_whenIMigrate_thenDoNothing() {
val subscriber = insertSubscriber("USD")
insertPayment(null)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs ""
}
@Test
fun givenASubscriberAndMatchingPayment_whenIMigrate_thenUpdateCurrencyCode() {
val subscriber = insertSubscriber("USD")
insertPayment(subscriber)
clearCurrencyCode(subscriber)
migrate()
getCurrencyCode(subscriber) assertIs "USD"
}
@Test
fun givenASupercededSubscriber_whenIMigrate_thenIDoNothing() {
val oldSubscriber = insertSubscriber("USD")
insertPayment(oldSubscriber)
clearCurrencyCode(oldSubscriber)
insertSubscriber("USD")
migrate()
}
private fun migrate() {
V236_FixInAppSubscriberCurrencyIfAble.migrate(
context = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application,
db = SignalDatabase.rawDatabase,
oldVersion = 0,
newVersion = 0
)
}
private fun insertSubscriber(currencyCode: String): InAppPaymentSubscriberRecord {
val record = InAppPaymentSubscriberRecord(
subscriberId = SubscriberId.generate(),
currency = Currency.getInstance(currencyCode),
type = InAppPaymentSubscriberRecord.Type.DONATION,
requiresCancel = false,
paymentMethodType = InAppPaymentData.PaymentMethodType.PAYPAL
)
SignalDatabase.inAppPaymentSubscribers.insertOrReplace(record)
return record
}
private fun clearCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord) {
SignalDatabase.rawDatabase.update(InAppPaymentSubscriberTable.TABLE_NAME)
.values(InAppPaymentSubscriberTable.CURRENCY_CODE to "")
.where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
.run()
}
private fun getCurrencyCode(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord): String {
return SignalDatabase.rawDatabase.select(InAppPaymentSubscriberTable.CURRENCY_CODE)
.from(InAppPaymentSubscriberTable.TABLE_NAME)
.where("${InAppPaymentSubscriberTable.SUBSCRIBER_ID} = ?", inAppPaymentSubscriberRecord.subscriberId.serialize())
.run()
.readToSingleObject { it.requireNonNullString(InAppPaymentSubscriberTable.CURRENCY_CODE) }!!
}
private fun insertPayment(inAppPaymentSubscriberRecord: InAppPaymentSubscriberRecord?): InAppPaymentTable.InAppPayment {
val id = SignalDatabase.inAppPayments.insert(
type = InAppPaymentType.RECURRING_DONATION,
state = InAppPaymentTable.State.END,
subscriberId = inAppPaymentSubscriberRecord?.subscriberId,
endOfPeriod = null,
inAppPaymentData = InAppPaymentData(
amount = FiatValue(
currencyCode = inAppPaymentSubscriberRecord?.currency?.currencyCode ?: "USD",
amount = BigDecimal.ONE.toDecimalValue()
),
level = 200,
paymentMethodType = inAppPaymentSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
)
)
return SignalDatabase.inAppPayments.getById(id)!!
}
}

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.dependencies
import android.app.Application
import io.mockk.spyk
import okhttp3.ConnectionSpec
import okhttp3.Response
import okhttp3.WebSocket
@@ -23,6 +24,9 @@ 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.whispersystems.signalservice.api.SignalServiceDataStore
import org.whispersystems.signalservice.api.SignalServiceMessageSender
import org.whispersystems.signalservice.api.SignalWebSocket
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
@@ -37,12 +41,13 @@ import java.util.Optional
*
* 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) : AppDependencies.Provider by default {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val recipientCache: LiveRecipientCache
private var signalServiceMessageSender: SignalServiceMessageSender? = null
init {
runSync {
@@ -53,18 +58,21 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
Get("/v1/websocket/?login=") {
MockResponse().success().withWebSocketUpgrade(mockIdentifiedWebSocket)
},
Get("/v1/websocket", { !it.path.contains("login") }) {
Get("/v1/websocket", {
val path = it.path
return@Get path == null || !path.contains("login")
}) {
MockResponse().success().withWebSocketUpgrade(object : WebSocketListener() {})
}
)
}
webServer.setDispatcher(object : Dispatcher() {
webServer.dispatcher = object : Dispatcher() {
override fun dispatch(request: RecordedRequest): MockResponse {
val handler = handlers.firstOrNull { it.requestPredicate(request) }
return handler?.responseFactory?.invoke(request) ?: MockResponse().setResponseCode(500)
}
})
}
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
@@ -101,6 +109,17 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
return recipientCache
}
override fun provideSignalServiceMessageSender(
signalWebSocket: SignalWebSocket,
protocolStore: SignalServiceDataStore,
signalServiceConfiguration: SignalServiceConfiguration
): SignalServiceMessageSender {
if (signalServiceMessageSender == null) {
signalServiceMessageSender = spyk(objToCopy = default.provideSignalServiceMessageSender(signalWebSocket, protocolStore, signalServiceConfiguration))
}
return signalServiceMessageSender!!
}
class MockWebSocket : WebSocketListener() {
private val TAG = "MockWebSocket"

View File

@@ -16,7 +16,7 @@ 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.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
@@ -38,7 +38,7 @@ class AttachmentCompressionJobTest {
StreamUtil.readFully(it)
}
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(ApplicationDependencies.getApplication())
val blob = BlobProvider.getInstance().forData(imageBytes).createForSingleSessionOnDisk(AppDependencies.application)
val firstPreUpload = createAttachment(1, blob, AttachmentTable.TransformProperties.empty())
val firstDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(firstPreUpload)
@@ -51,12 +51,12 @@ class AttachmentCompressionJobTest {
val secondJobLatch = CountDownLatch(1)
val jobThread = Thread {
firstCompressionJob.setContext(ApplicationDependencies.getApplication())
firstCompressionJob.setContext(AppDependencies.application)
firstJobResult = firstCompressionJob.run()
secondJobLatch.await()
secondCompressionJob!!.setContext(ApplicationDependencies.getApplication())
secondCompressionJob!!.setContext(AppDependencies.application)
secondJobResult = secondCompressionJob!!.run()
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobs
import android.app.Application
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.EventTimer
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.JobDatabase.Companion.getInstance
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobmanager.JobMigrator
import org.thoughtcrime.securesms.jobmanager.JobTracker
import org.thoughtcrime.securesms.util.TextSecurePreferences
import java.util.concurrent.CountDownLatch
import kotlin.random.Random
@Ignore("This is just for testing performance, not correctness, and they can therefore take a long time. Run them manually when you need to.")
@RunWith(AndroidJUnit4::class)
class JobManagerPerformanceTests {
companion object {
val TAG = Log.tag(JobManagerPerformanceTests::class.java)
}
@Test
fun testPerformance_singleQueue() {
runTest("singleQueue", 2000) { TestJob(queue = "queue") }
}
@Test
fun testPerformance_fourQueues() {
runTest("fourQueues", 2000) { TestJob(queue = "queue-${Random.nextInt(1, 5)}") }
}
@Test
fun testPerformance_noQueues() {
runTest("noQueues", 2000) { TestJob(queue = null) }
}
private fun runTest(name: String, count: Int, jobCreator: () -> TestJob) {
val context = AppDependencies.application
val jobManager = testJobManager(context)
jobManager.beginJobLoop()
val eventTimer = EventTimer()
val latch = CountDownLatch(count)
var seenStart = false
jobManager.addListener({ it.factoryKey == TestJob.KEY }) { _, state ->
if (!seenStart && state == JobTracker.JobState.RUNNING) {
// Adding the jobs can take a while (and runs on a background thread), so we want to reset the timer the first time we see a job run so the first job
// doesn't have a skewed time
eventTimer.reset()
seenStart = true
}
if (state.isComplete) {
eventTimer.emit("job")
latch.countDown()
if (latch.count % 100 == 0L) {
Log.d(TAG, "[$name] Finished ${count - latch.count}/$count jobs")
}
}
}
Log.i(TAG, "[$name] Adding jobs...")
jobManager.addAll((1..count).map { jobCreator() })
Log.i(TAG, "[$name] Waiting for jobs to complete...")
latch.await()
Log.i(TAG, "[$name] Jobs complete!")
Log.i(TAG, eventTimer.stop().summary)
}
private fun testJobManager(context: Application): JobManager {
val config = JobManager.Configuration.Builder()
.setJobFactories(
JobManagerFactories.getJobFactories(context) + mapOf(
TestJob.KEY to TestJob.Factory()
)
)
.setConstraintFactories(JobManagerFactories.getConstraintFactories(context))
.setConstraintObservers(JobManagerFactories.getConstraintObservers(context))
.setJobStorage(FastJobStorage(getInstance(context)))
.setJobMigrator(JobMigrator(TextSecurePreferences.getJobManagerVersion(context), JobManager.CURRENT_VERSION, JobManagerFactories.getJobMigrations(context)))
.build()
return JobManager(context, config)
}
private class TestJob(params: Parameters) : Job(params) {
companion object {
const val KEY = "test"
}
constructor(queue: String?) : this(Parameters.Builder().setQueue(queue).build())
override fun serialize(): ByteArray? = null
override fun getFactoryKey(): String = KEY
override fun run(): Result = Result.success()
override fun onFailure() = Unit
class Factory : Job.Factory<TestJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): TestJob {
return TestJob(parameters)
}
}
}
}

View File

@@ -52,7 +52,7 @@ class MessageContentProcessor__recipientStatusTest {
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)
@@ -64,7 +64,7 @@ class MessageContentProcessor__recipientStatusTest {
processor.process(
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
)

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import android.net.Uri
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.UriAttachmentBuilder
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription
import org.thoughtcrime.securesms.jobs.ThreadUpdateJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.UUID
import kotlin.random.Random
/**
* Makes inserting messages through the "normal" code paths simpler. Mostly focused on incoming messages.
*/
class MessageHelper(private val harness: SignalActivityRule, var startTime: Long = System.currentTimeMillis()) {
val alice: RecipientId = harness.others[0]
val bob: RecipientId = harness.others[1]
val group: GroupTestingUtils.TestGroupInfo = harness.group!!
val processor: MessageContentProcessor = MessageContentProcessor(harness.context)
init {
val threadIdSlot = slot<Long>()
mockkStatic(ThreadUpdateJob::class)
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
SignalDatabase.threads.update(threadIdSlot.captured, false)
}
}
fun tearDown() {
unmockkStatic(ThreadUpdateJob::class)
}
fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = sender, timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null,
allowExpireTimeChanges = false
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun outgoingText(conversationId: RecipientId = alice, successfulSend: Boolean = true, updateMessage: ((OutgoingMessage) -> OutgoingMessage)? = null): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val threadRecipient = Recipient.resolved(conversationId)
val message = OutgoingMessage(
threadRecipient = threadRecipient,
body = MessageContentFuzzer.string(),
sentTimeMillis = messageData.timestamp,
isUrgent = true,
isSecure = true
).let { updateMessage?.invoke(it) ?: it }
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
if (successfulSend) {
SignalDatabase.messages.markAsSent(messageId, true)
}
return messageData.copy(messageId = messageId)
}
fun outgoingMessage(conversationId: RecipientId = alice, updateMessage: OutgoingMessage.() -> OutgoingMessage): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val threadRecipient = Recipient.resolved(conversationId)
val message = OutgoingMessage(
threadRecipient = threadRecipient,
sentTimeMillis = messageData.timestamp,
isUrgent = true,
isSecure = true
).apply { updateMessage() }
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(threadRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(message, threadId, false, null)
return messageData.copy(messageId = messageId)
}
fun outgoingAttachment(data: ByteArray, uuid: UUID? = UUID.randomUUID()): Attachment {
val uri: Uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
val attachment: UriAttachment = UriAttachmentBuilder.build(
id = Random.nextLong(),
uri = uri,
contentType = MediaUtil.IMAGE_JPEG,
transformProperties = AttachmentTable.TransformProperties(),
uuid = uuid
)
return attachment
}
fun outgoingGroupChange(): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = harness.self.id, timestamp = startTime)
val groupRecipient = Recipient.resolved(group.recipientId)
val decryptedGroupV2Context = DecryptedGroupV2Context(
context = group.groupV2Context,
groupState = SignalDatabase.groups.getGroup(group.groupId).get().requireV2GroupProperties().decryptedGroup
)
val updateDescription = GV2UpdateDescription.Builder()
.gv2ChangeDescription(decryptedGroupV2Context)
.groupChangeUpdate(GroupsV2UpdateMessageConverter.translateDecryptedChange(SignalStore.account.getServiceIds(), decryptedGroupV2Context))
.build()
val outgoingMessage = OutgoingMessage.groupUpdateMessage(groupRecipient, updateDescription, startTime)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(groupRecipient)
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, null)
SignalDatabase.messages.markAsSent(messageId, true)
return messageData.copy(messageId = messageId)
}
fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = sender, timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.fuzzStickerMediaMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
startTime = nextStartTime()
val messageData = MessageData(author = sender, timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.editTextMessage(
targetTimestamp = targetTimestamp,
editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
sentTimestamp = messageData.timestamp,
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
).dataMessage!!
),
metadata = MessageContentFuzzer.envelopeMetadata(
source = sender,
destination = harness.self.id,
groupId = if (destination == group.recipientId) group.groupId else null
),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncReadMessage(vararg reads: Pair<RecipientId, Long>): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeMessage(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeMessage(deletes.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeConversation(vararg deletes: MessageContentFuzzer.DeleteForMeSync): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeConversation(deletes.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeLocalOnlyConversation(vararg conversations: RecipientId): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeLocalOnlyConversation(conversations.toList()),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair<RecipientId, Long>, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): MessageData {
startTime = nextStartTime()
val messageData = MessageData(timestamp = startTime)
processor.process(
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
content = MessageContentFuzzer.syncDeleteForMeAttachment(conversationId, message, uuid, digest, plainTextHash),
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
serverDeliveredTimestamp = messageData.timestamp + 10
)
return messageData
}
/**
* Get the next "sentTimestamp" for current + [nextMessageOffset]th message. Useful for early message processing and future message timestamps.
*/
fun nextStartTime(nextMessageOffset: Int = 1): Long {
return startTime + 1000 * nextMessageOffset
}
data class MessageData(
val author: RecipientId = RecipientId.UNKNOWN,
val serverGuid: UUID = UUID.randomUUID(),
val timestamp: Long,
val messageId: Long = -1L
)
}

View File

@@ -16,7 +16,7 @@ import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.AliceClient
@@ -55,8 +55,8 @@ class MessageProcessingPerformanceTest {
@Before
fun setup() {
mockkStatic(UnidentifiedAccessUtil::class)
every { UnidentifiedAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkStatic(SealedSenderAccessUtil::class)
every { SealedSenderAccessUtil.getCertificateValidator() } returns FakeClientHelpers.noOpCertificateValidator
mockkObject(MessageContentProcessor)
every { MessageContentProcessor.create(harness.application) } returns TimingMessageContentProcessor(harness.application)
@@ -64,7 +64,7 @@ class MessageProcessingPerformanceTest {
@After
fun after() {
unmockkStatic(UnidentifiedAccessUtil::class)
unmockkStatic(SealedSenderAccessUtil::class)
unmockkStatic(MessageContentProcessor::class)
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_readSyncs {
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var messageHelper: MessageHelper
@Before
fun setUp() {
messageHelper = MessageHelper(harness)
}
@After
fun tearDown() {
messageHelper.tearDown()
}
@Test
fun handleSynchronizeReadMessage() {
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadMessageMissingTimestamp() {
messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEdits() {
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(message1Timestamp).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(editMessage1Timestamp1).timestamp
val message2Timestamp = messageHelper.incomingMedia().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.alice to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
@Test
fun handleSynchronizeReadWithEditsInGroup() {
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.syncReadMessage(messageHelper.alice to message1Timestamp)
val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
val message2Timestamp = messageHelper.incomingMedia(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 2
messageHelper.syncReadMessage(messageHelper.bob to message2Timestamp, messageHelper.alice to editMessage1Timestamp1, messageHelper.alice to editMessage1Timestamp2)
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
threadRecord.unreadCount assertIs 0
}
}

View File

@@ -0,0 +1,703 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.Matchers.greaterThan
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.signal.core.util.withinTransaction
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.CallTable
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.SessionSwitchoverEvent
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.MessageContentFuzzer.DeleteForMeSync
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assert
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.thoughtcrime.securesms.testing.assertIsSize
import org.thoughtcrime.securesms.util.IdentityUtil
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class SyncMessageProcessorTest_synchronizeDeleteForMe {
companion object {
private val TAG = "SyncDeleteForMeTest"
}
@get:Rule
val harness = SignalActivityRule(createGroup = true)
private lateinit var messageHelper: MessageHelper
@Before
fun setUp() {
messageHelper = MessageHelper(harness)
}
@After
fun tearDown() {
messageHelper.tearDown()
}
@Test
fun singleMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText().timestamp
messageHelper.incomingText()
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun singleOutgoingMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.outgoingText().timestamp
messageHelper.incomingText()
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, harness.self.id to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun singleGroupMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 3
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
}
@Test
fun multipleGroupMessageDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp
messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId)
val message3Timestamp = messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 3
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.group.recipientId, messageHelper.alice to message1Timestamp, messageHelper.bob to message3Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun allMessagesDelete() {
// GIVEN
val message1Timestamp = messageHelper.incomingText().timestamp
val message2Timestamp = messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to message1Timestamp, messageHelper.alice to message2Timestamp)
)
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 0
val threadRecord = SignalDatabase.threads.getThreadRecord(threadId)
threadRecord assertIs null
}
@Test
fun earlyMessagesDelete() {
// GIVEN
messageHelper.incomingText().timestamp
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
// WHEN
val nextTextMessageTimestamp = messageHelper.nextStartTime(2)
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to nextTextMessageTimestamp)
)
messageHelper.incomingText()
// THEN
messageCount = SignalDatabase.messages.getMessageCountForThread(threadId)
messageCount assertIs 1
}
@Test
fun multipleConversationMessagesDelete() {
// GIVEN
messageHelper.incomingText(sender = messageHelper.alice)
val aliceMessage2 = messageHelper.incomingText(sender = messageHelper.alice).timestamp
messageHelper.incomingText(sender = messageHelper.bob)
val bobMessage2 = messageHelper.incomingText(sender = messageHelper.bob).timestamp
val aliceThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
var aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
aliceMessageCount assertIs 2
val bobThreadId = SignalDatabase.threads.getThreadIdFor(messageHelper.bob)!!
var bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
bobMessageCount assertIs 2
// WHEN
messageHelper.syncDeleteForMeMessage(
DeleteForMeSync(conversationId = messageHelper.alice, messageHelper.alice to aliceMessage2),
DeleteForMeSync(conversationId = messageHelper.bob, messageHelper.bob to bobMessage2)
)
// THEN
aliceMessageCount = SignalDatabase.messages.getMessageCountForThread(aliceThreadId)
aliceMessageCount assertIs 1
bobMessageCount = SignalDatabase.messages.getMessageCountForThread(bobThreadId)
bobMessageCount assertIs 1
}
@Test
fun singleConversationDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun singleConversationNoRecentsFoundDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
val randomFutureMessages = (1..5).map {
messageHelper.alice to messageHelper.nextStartTime(it)
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, isFullDelete = true)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Unable to find most recent received at timestamp") == true }.size assertIs 1
}
@Test
fun singleConversationNoRecentsFoundNonExpiringRecentsFoundDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
val nonExpiringMessages = messages.takeLast(5).map { it.recipientId to it.timetamp }
val randomFutureMessages = (1..5).map {
messageHelper.alice to messageHelper.nextStartTime(it)
}
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, randomFutureMessages, nonExpiringMessages, true)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Using backup non-expiring messages") == true }.size assertIs 1
}
@Test
fun localOnlyRemainingAfterConversationDeleteWithFullDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
Log.v(TAG, "Adding normal messages")
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val alice = Recipient.resolved(messageHelper.alice)
Log.v(TAG, "Adding identity message")
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
Log.v(TAG, "Adding profile message")
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
Log.v(TAG, "Adding call message")
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
// WHEN
Log.v(TAG, "Processing sync message")
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun localOnlyRemainingAfterConversationDeleteWithoutFullDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val alice = Recipient.resolved(messageHelper.alice)
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 23
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.alice,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = false
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 3
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
}
@Test
fun groupConversationDelete() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 50) {
messages += when (i % 3) {
1 -> MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText(sender = messageHelper.alice, destination = messageHelper.group.recipientId).timestamp)
2 -> MessageTable.SyncMessageId(messageHelper.bob, messageHelper.incomingText(sender = messageHelper.bob, destination = messageHelper.group.recipientId).timestamp)
else -> MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(messageHelper.group.recipientId).timestamp)
}
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.group.recipientId)!!
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(
conversationId = messageHelper.group.recipientId,
messages = messages.takeLast(5).map { it.recipientId to it.timetamp },
isFullDelete = true
)
)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
@Test
fun multipleConversationDelete() {
// GIVEN
val allMessages = mapOf<RecipientId, MutableList<MessageTable.SyncMessageId>>(
messageHelper.alice to mutableListOf(),
messageHelper.bob to mutableListOf()
)
allMessages.forEach { (conversation, messages) ->
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(conversation, messageHelper.incomingText(sender = conversation).timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText(conversationId = conversation).timestamp)
}
}
val threadIds = allMessages.keys.map { SignalDatabase.threads.getThreadIdFor(it)!! }
threadIds.forEach { SignalDatabase.messages.getMessageCountForThread(it) assertIs 20 }
// WHEN
messageHelper.syncDeleteForMeConversation(
DeleteForMeSync(conversationId = messageHelper.alice, allMessages[messageHelper.alice]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true),
DeleteForMeSync(conversationId = messageHelper.bob, allMessages[messageHelper.bob]!!.takeLast(5).map { it.recipientId to it.timetamp }, isFullDelete = true)
)
// THEN
threadIds.forEach {
SignalDatabase.messages.getMessageCountForThread(it) assertIs 0
SignalDatabase.threads.getThreadRecord(it) assertIs null
}
}
@Test
fun singleLocalOnlyConversation() {
// GIVEN
val alice = Recipient.resolved(messageHelper.alice)
// Insert placeholder message to prevent early thread update deletes
val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
// Cleanup and confirm setup
SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assert greaterThan(0)
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
// THEN
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
}
@Ignore("counts are consistent for some reason")
@Test
fun multipleLocalOnlyConversation() {
// GIVEN
val alice = Recipient.resolved(messageHelper.alice)
// Insert placeholder messages in group and alice thread to prevent early thread update deletes
val groupPlaceholderMessage = messageHelper.outgoingText(conversationId = messageHelper.group.recipientId).messageId
val oneToOnePlaceHolderMessage = messageHelper.outgoingText().messageId
val aliceThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.alice, isGroup = false)
val groupThreadId = SignalDatabase.threads.getOrCreateThreadIdFor(messageHelper.group.recipientId, isGroup = true)
// Identity changes
IdentityUtil.markIdentityVerified(harness.context, alice, true, true)
IdentityUtil.markIdentityVerified(harness.context, alice, false, true)
IdentityUtil.markIdentityVerified(harness.context, alice, true, false)
IdentityUtil.markIdentityVerified(harness.context, alice, false, false)
IdentityUtil.markIdentityUpdate(harness.context, alice.id)
// Calls
SignalDatabase.calls.insertOneToOneCall(1, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.OUTGOING, CallTable.Event.ACCEPTED)
SignalDatabase.calls.insertOneToOneCall(2, System.currentTimeMillis(), alice.id, CallTable.Type.VIDEO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED)
SignalDatabase.calls.insertOneToOneCall(3, System.currentTimeMillis(), alice.id, CallTable.Type.AUDIO_CALL, CallTable.Direction.INCOMING, CallTable.Event.MISSED_NOTIFICATION_PROFILE)
SignalDatabase.calls.insertAcceptedGroupCall(4, messageHelper.group.recipientId, CallTable.Direction.INCOMING, System.currentTimeMillis())
SignalDatabase.calls.insertDeclinedGroupCall(5, messageHelper.group.recipientId, System.currentTimeMillis())
// Detected changes
SignalDatabase.messages.insertProfileNameChangeMessages(alice, "new name", "previous name")
SignalDatabase.messages.insertLearnedProfileNameChangeMessage(alice, null, "username.42")
SignalDatabase.messages.insertNumberChangeMessages(alice.id)
SignalDatabase.messages.insertSmsExportMessage(alice.id, SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!)
SignalDatabase.messages.insertSessionSwitchoverEvent(alice.id, aliceThreadId, SessionSwitchoverEvent())
// Sent failed
SignalDatabase.messages.markAsSending(messageHelper.outgoingText().messageId)
SignalDatabase.messages.markAsSentFailed(messageHelper.outgoingText().messageId)
messageHelper.outgoingText().let {
SignalDatabase.messages.markAsSending(it.messageId)
SignalDatabase.messages.markAsRateLimited(it.messageId)
}
// Group change
messageHelper.outgoingGroupChange()
// Cleanup and confirm setup
SignalDatabase.messages.deleteMessage(messageId = oneToOnePlaceHolderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.messages.deleteMessage(messageId = groupPlaceholderMessage, threadId = aliceThreadId, notify = false, updateThread = false)
SignalDatabase.rawDatabase.withinTransaction {
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 16
SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 10
}
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice, messageHelper.group.recipientId)
// THEN
SignalDatabase.messages.getMessageCountForThread(aliceThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(aliceThreadId) assertIs null
SignalDatabase.messages.getMessageCountForThread(groupThreadId) assertIs 0
SignalDatabase.threads.getThreadRecord(groupThreadId) assertIs null
}
@Test
fun singleLocalOnlyConversationHasAddressable() {
// GIVEN
val messages = mutableListOf<MessageTable.SyncMessageId>()
for (i in 0 until 10) {
messages += MessageTable.SyncMessageId(messageHelper.alice, messageHelper.incomingText().timestamp)
messages += MessageTable.SyncMessageId(harness.self.id, messageHelper.outgoingText().timestamp)
}
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
// WHEN
messageHelper.syncDeleteForMeLocalOnlyConversation(messageHelper.alice)
// THEN
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 20
SignalDatabase.threads.getThreadRecord(threadId).assertIsNotNull()
harness.inMemoryLogger.flush()
harness.inMemoryLogger.entries().filter { it.message?.contains("Thread is not local only") == true }.size assertIs 1
}
@Test
fun singleAttachmentDeletes() {
// GIVEN
val message1 = messageHelper.outgoingText { message ->
message.copy(
attachments = listOf(
messageHelper.outgoingAttachment(byteArrayOf(1, 2, 3)),
messageHelper.outgoingAttachment(byteArrayOf(2, 3, 4), null),
messageHelper.outgoingAttachment(byteArrayOf(5, 6, 7), null),
messageHelper.outgoingAttachment(byteArrayOf(10, 11, 12))
)
)
}
var attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
attachments assertIsSize 4
val threadId = SignalDatabase.threads.getThreadIdFor(messageHelper.alice)!!
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
// Has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[0].attachmentId,
attachment = attachments[0].copy(digest = byteArrayOf(attachments[0].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
// Missing uuid and digest
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[1].attachmentId,
attachment = attachments[1],
uploadTimestamp = message1.timestamp + 1
)
// Missing uuid and plain text
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[2].attachmentId,
attachment = attachments[2].copy(digest = byteArrayOf(attachments[2].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
SignalDatabase.rawDatabase.update(AttachmentTable.TABLE_NAME).values(AttachmentTable.DATA_HASH_END to null).where("${AttachmentTable.ID} = ?", attachments[2].attachmentId).run()
// Different has all three
SignalDatabase.attachments.finalizeAttachmentAfterUpload(
id = attachments[3].attachmentId,
attachment = attachments[3].copy(digest = byteArrayOf(attachments[3].attachmentId.id.toByte())),
uploadTimestamp = message1.timestamp + 1
)
attachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
// WHEN
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[0].uuid,
attachments[0].remoteDigest,
attachments[0].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
var updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 3
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[0].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[1].uuid,
attachments[1].remoteDigest,
attachments[1].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 2
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[1].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[2].uuid,
attachments[2].remoteDigest,
attachments[2].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 1
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 1
updatedAttachments.forEach { it.attachmentId assertIsNot attachments[2].attachmentId }
messageHelper.syncDeleteForMeAttachment(
conversationId = messageHelper.alice,
message = message1.author to message1.timestamp,
attachments[3].uuid,
attachments[3].remoteDigest,
attachments[3].dataHash
)
SignalDatabase.messages.getMessageCountForThread(threadId) assertIs 0
updatedAttachments = SignalDatabase.attachments.getAttachmentsForMessage(message1.messageId)
updatedAttachments assertIsSize 0
SignalDatabase.threads.getThreadRecord(threadId) assertIs null
}
private fun DatabaseAttachment.copy(
uuid: UUID? = this.uuid,
digest: ByteArray? = this.remoteDigest
): Attachment {
return DatabaseAttachment(
attachmentId = this.attachmentId,
mmsId = this.mmsId,
hasData = this.hasData,
hasThumbnail = false,
hasArchiveThumbnail = false,
contentType = this.contentType,
transferProgress = this.transferState,
size = this.size,
fileName = this.fileName,
cdn = this.cdn,
location = this.remoteLocation,
key = this.remoteKey,
digest = digest,
incrementalDigest = this.incrementalDigest,
incrementalMacChunkSize = this.incrementalMacChunkSize,
fastPreflightId = this.fastPreflightId,
voiceNote = this.voiceNote,
borderless = this.borderless,
videoGif = this.videoGif,
width = this.width,
height = this.height,
quote = this.quote,
caption = this.caption,
stickerLocator = this.stickerLocator,
blurHash = this.blurHash,
audioHash = this.audioHash,
transformProperties = this.transformProperties,
displayOrder = this.displayOrder,
uploadTimestamp = this.uploadTimestamp,
dataHash = this.dataHash,
archiveCdn = this.archiveCdn,
archiveThumbnailCdn = this.archiveThumbnailCdn,
archiveMediaName = this.archiveMediaName,
archiveMediaId = this.archiveMediaId,
thumbnailRestoreState = this.thumbnailRestoreState,
uuid = uuid
)
}
}

View File

@@ -0,0 +1,55 @@
package org.thoughtcrime.securesms.migrations
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.count
import org.signal.core.util.readToSingleInt
import org.signal.donations.PaymentSourceType
import org.thoughtcrime.securesms.database.InAppPaymentSubscriberTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNotNull
import org.whispersystems.signalservice.api.subscriptions.SubscriberId
import java.util.Currency
@RunWith(AndroidJUnit4::class)
class SubscriberIdMigrationJobTest {
private val testSubject = SubscriberIdMigrationJob()
@Test
fun givenNoSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectNoDatabaseEntries() {
testSubject.run()
val actual = SignalDatabase.inAppPaymentSubscribers.readableDatabase.count()
.from(InAppPaymentSubscriberTable.TABLE_NAME)
.run()
.readToSingleInt()
actual assertIs 0
}
@Test
fun givenUSDSubscriber_whenIRunSubscriberIdMigrationJob_thenIExpectASingleEntry() {
val subscriberId = SubscriberId.generate()
SignalStore.inAppPayments.setSubscriberCurrency(Currency.getInstance("USD"), InAppPaymentSubscriberRecord.Type.DONATION)
SignalStore.inAppPayments.setSubscriber("USD", subscriberId)
SignalStore.inAppPayments.setSubscriptionPaymentSourceType(PaymentSourceType.PayPal)
SignalStore.inAppPayments.shouldCancelSubscriptionBeforeNextSubscribeAttempt = true
testSubject.run()
val actual = SignalDatabase.inAppPaymentSubscribers.getByCurrencyCode("USD", InAppPaymentSubscriberRecord.Type.DONATION)
actual.assertIsNotNull()
actual!!.subscriberId.bytes assertIs subscriberId.bytes
actual.paymentMethodType assertIs InAppPaymentData.PaymentMethodType.PAYPAL
actual.requiresCancel assertIs true
actual.currency assertIs Currency.getInstance("USD")
actual.type assertIs InAppPaymentSubscriberRecord.Type.DONATION
}
}

View File

@@ -90,10 +90,10 @@ class SafetyNumberBottomSheetRepositoryTest {
subjectUnderTest.removeFromStories(toRemove, listOf(destinationKey)).subscribe()
testSubscriber.request(1)
testScheduler.triggerActions()
testSubscriber.awaitCount(3)
testSubscriber.awaitCount(2)
// THEN
testSubscriber.assertValueAt(2) { map ->
testSubscriber.assertValueAt(1) { map ->
assertMatch(
map,
mapOf(
@@ -116,10 +116,10 @@ class SafetyNumberBottomSheetRepositoryTest {
subjectUnderTest.removeAllFromStory(distributionListMembers, distributionList).subscribe()
testSubscriber.request(1)
testScheduler.triggerActions()
testSubscriber.awaitCount(3)
testSubscriber.awaitCount(2)
// THEN
testSubscriber.assertValueAt(2) { map ->
testSubscriber.assertValueAt(1) { map ->
assertMatch(map, mapOf())
}
}

View File

@@ -24,9 +24,9 @@ class ContactRecordProcessorTest {
@Before
fun setup() {
SignalStore.account().setE164(E164_SELF)
SignalStore.account().setAci(ACI_SELF)
SignalStore.account().setPni(PNI_SELF)
SignalStore.account.setE164(E164_SELF)
SignalStore.account.setAci(ACI_SELF)
SignalStore.account.setPni(PNI_SELF)
}
@Test

View File

@@ -3,8 +3,7 @@ package org.thoughtcrime.securesms.testing
import org.signal.core.util.logging.Log
import org.signal.libsignal.protocol.ecc.ECKeyPair
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messages.protocol.BufferedProtocolStore
import org.thoughtcrime.securesms.recipients.Recipient
@@ -30,17 +29,17 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
uuid = serviceId.rawUuid,
e164 = e164,
deviceId = 1,
identityKey = SignalStore.account().aciIdentityKey.publicKey.publicKey,
identityKey = SignalStore.account.aciIdentityKey.publicKey.publicKey,
expires = 31337
)
fun process(envelope: Envelope, serverDeliveredTimestamp: Long) {
val start = System.currentTimeMillis()
val bufferedStore = BufferedProtocolStore.create()
ApplicationDependencies.getIncomingMessageObserver()
AppDependencies.incomingMessageObserver
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() }
?.forEach { ApplicationDependencies.getJobManager().add(it) }
?.forEach { it.enqueue() }
bufferedStore.flushToDisk()
val end = System.currentTimeMillis()
@@ -48,9 +47,9 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
}
fun encrypt(now: Long, destination: Recipient): Envelope {
return ApplicationDependencies.getSignalServiceMessageSender().getEncryptedMessage(
return AppDependencies.signalServiceMessageSender.getEncryptedMessage(
SignalServiceAddress(destination.requireServiceId(), destination.requireE164()),
FakeClientHelpers.getTargetUnidentifiedAccess(ProfileKeyUtil.getSelfProfileKey(), ProfileKey(destination.profileKey), aliceSenderCertificate),
FakeClientHelpers.getSealedSenderAccess(ProfileKey(destination.profileKey), aliceSenderCertificate),
1,
FakeClientHelpers.encryptedTextMessage(now),
false

View File

@@ -17,7 +17,7 @@ import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.zkgroup.profiles.ProfileKey
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil
import org.thoughtcrime.securesms.crypto.SealedSenderAccessUtil
import org.thoughtcrime.securesms.database.OneTimePreKeyTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.SignedPreKeyTable
@@ -25,14 +25,13 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.SignalSessionLock
import org.whispersystems.signalservice.api.crypto.SealedSenderAccess
import org.whispersystems.signalservice.api.crypto.SignalServiceCipher
import org.whispersystems.signalservice.api.crypto.SignalSessionBuilder
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.Envelope
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -75,12 +74,12 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, SealedSenderAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}
private fun getAliceServiceId(): ServiceId {
return SignalStore.account().requireAci()
return SignalStore.account.requireAci()
}
private fun getAlicePreKeyBundle(): PreKeyBundle {
@@ -103,7 +102,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
val selfSignedPreKeyRecord = SignalDatabase.signedPreKeys.get(getAliceServiceId(), selfSignedPreKeyId)!!
return PreKeyBundle(
SignalStore.account().registrationId,
SignalStore.account.registrationId,
1,
selfPreKeyId,
selfPreKeyRecord.keyPair.publicKey,
@@ -115,19 +114,19 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
private fun getAliceProtocolAddress(): SignalProtocolAddress {
return SignalProtocolAddress(SignalStore.account().requireAci().toString(), 1)
return SignalProtocolAddress(SignalStore.account.requireAci().toString(), 1)
}
private fun getAlicePublicKey(): IdentityKey {
return SignalStore.account().aciIdentityKey.publicKey
return SignalStore.account.aciIdentityKey.publicKey
}
private fun getAliceProfileKey(): ProfileKey {
return ProfileKeyUtil.getSelfProfileKey()
}
private fun getAliceUnidentifiedAccess(): Optional<UnidentifiedAccess> {
return FakeClientHelpers.getTargetUnidentifiedAccess(profileKey, getAliceProfileKey(), senderCertificate)
private fun getAliceUnidentifiedAccess(): SealedSenderAccess? {
return FakeClientHelpers.getSealedSenderAccess(getAliceProfileKey(), senderCertificate)
}
private class BobSignalServiceAccountDataStore(private val registrationId: Int, private val identityKeyPair: IdentityKeyPair) : SignalServiceAccountDataStore {
@@ -139,10 +138,12 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun isTrustedIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?, direction: IdentityKeyStore.Direction?): Boolean = true
override fun loadSession(address: SignalProtocolAddress?): SessionRecord = aliceSessionRecord ?: SessionRecord()
override fun saveIdentity(address: SignalProtocolAddress?, identityKey: IdentityKey?): Boolean = false
override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) { aliceSessionRecord = record }
override fun storeSession(address: SignalProtocolAddress?, record: SessionRecord?) {
aliceSessionRecord = record
}
override fun getSubDeviceSessions(name: String?): List<Int> = emptyList()
override fun containsSession(address: SignalProtocolAddress?): Boolean = aliceSessionRecord != null
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account().aciIdentityKey.publicKey
override fun getIdentity(address: SignalProtocolAddress?): IdentityKey = SignalStore.account.aciIdentityKey.publicKey
override fun loadPreKey(preKeyId: Int): PreKeyRecord = throw UnsupportedOperationException()
override fun storePreKey(preKeyId: Int, record: PreKeyRecord?) = throw UnsupportedOperationException()
override fun containsPreKey(preKeyId: Int): Boolean = throw UnsupportedOperationException()

View File

@@ -14,8 +14,8 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey
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.SealedSenderAccess
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
@@ -46,11 +46,10 @@ object FakeClientHelpers {
}
}
fun getTargetUnidentifiedAccess(myProfileKey: ProfileKey, theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): Optional<UnidentifiedAccess> {
val selfUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(myProfileKey)
val themUnidentifiedAccessKey = UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey)
fun getSealedSenderAccess(theirProfileKey: ProfileKey, senderCertificate: SenderCertificate): SealedSenderAccess? {
val themUnidentifiedAccessKey = UnidentifiedAccess(UnidentifiedAccess.deriveAccessKeyFrom(theirProfileKey), senderCertificate.serialized, false)
return UnidentifiedAccessPair(UnidentifiedAccess(selfUnidentifiedAccessKey, senderCertificate.serialized, false), UnidentifiedAccess(themUnidentifiedAccessKey, senderCertificate.serialized, false)).targetUnidentifiedAccess
return SealedSenderAccess.forIndividual(themUnidentifiedAccessKey)
}
fun encryptedTextMessage(now: Long, message: String = "Test body message"): EnvelopeContent {

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
@@ -9,6 +10,7 @@ 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.ACI
import org.whispersystems.signalservice.internal.push.GroupContextV2
import kotlin.random.Random
/**
@@ -31,7 +33,7 @@ object GroupTestingUtils {
.title(MessageContentFuzzer.string())
.build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState, null)!!
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
SignalDatabase.recipients.setProfileSharing(groupRecipientId, true)
@@ -46,5 +48,8 @@ object GroupTestingUtils {
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

@@ -2,16 +2,20 @@ package org.thoughtcrime.securesms.testing
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.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.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
@@ -33,22 +37,22 @@ object MessageContentFuzzer {
/**
* Create an [Envelope].
*/
fun envelope(timestamp: Long): Envelope {
fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
return Envelope.Builder()
.timestamp(timestamp)
.serverTimestamp(timestamp + 5)
.serverGuid(UUID.randomUUID().toString())
.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()
@@ -60,12 +64,13 @@ object MessageContentFuzzer {
* - An expire timer value
* - Bold style body ranges
*/
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null, allowExpireTimeChanges: Boolean = true): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
timestamp = sentTimestamp
body = string()
if (random.nextBoolean()) {
if (allowExpireTimeChanges && random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
@@ -87,6 +92,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].
*/
@@ -116,6 +135,139 @@ 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()
}
fun syncDeleteForMeMessage(allDeletes: List<DeleteForMeSync>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
messageDeletes = allDeletes.map { (conversationId, conversationDeletes) ->
val conversation = Recipient.resolved(conversationId)
SyncMessage.DeleteForMe.MessageDeletes(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
messages = conversationDeletes.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
}
)
}
)
)
).build()
}
fun syncDeleteForMeConversation(allDeletes: List<DeleteForMeSync>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
conversationDeletes = allDeletes.map { delete ->
val conversation = Recipient.resolved(delete.conversationId)
SyncMessage.DeleteForMe.ConversationDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
mostRecentMessages = delete.messages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
mostRecentNonExpiringMessages = delete.nonExpiringMessages.map { (author, timestamp) ->
SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(author).requireAci().toString(),
sentTimestamp = timestamp
)
},
isFullDelete = delete.isFullDelete
)
}
)
)
).build()
}
fun syncDeleteForMeLocalOnlyConversation(conversations: List<RecipientId>): Content {
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
localOnlyConversationDeletes = conversations.map { conversationId ->
val conversation = Recipient.resolved(conversationId)
SyncMessage.DeleteForMe.LocalOnlyConversationDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
}
)
}
)
)
).build()
}
fun syncDeleteForMeAttachment(conversationId: RecipientId, message: Pair<RecipientId, Long>, uuid: UUID?, digest: ByteArray?, plainTextHash: String?): Content {
val conversation = Recipient.resolved(conversationId)
return Content
.Builder()
.syncMessage(
SyncMessage(
deleteForMe = SyncMessage.DeleteForMe(
attachmentDeletes = listOf(
SyncMessage.DeleteForMe.AttachmentDelete(
conversation = if (conversation.isGroup) {
SyncMessage.DeleteForMe.ConversationIdentifier(threadGroupId = conversation.requireGroupId().decodedId.toByteString())
} else {
SyncMessage.DeleteForMe.ConversationIdentifier(threadServiceId = conversation.requireAci().toString())
},
targetMessage = SyncMessage.DeleteForMe.AddressableMessage(
authorServiceId = Recipient.resolved(message.first).requireAci().toString(),
sentTimestamp = message.second
),
uuid = uuid?.let { UuidUtil.toByteString(it) },
fallbackDigest = digest?.toByteString(),
fallbackPlaintextHash = plainTextHash?.let { Base64.decodeOrNull(it)?.toByteString() }
)
)
)
)
).build()
}
/**
* Create a random media message that may be:
* - A text body
@@ -184,22 +336,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 {
fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.9) {
sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data_ = attachmentPointer()
emoji = emojis.random(random)
}
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()
}
@@ -245,7 +396,7 @@ object MessageContentFuzzer {
caption = string(allowNullString = true)
blurHash = string()
uploadTimestamp = random.nextLong()
cdnNumber = 1
cdnNumber = 2
build()
}
@@ -257,4 +408,14 @@ object MessageContentFuzzer {
fun fuzzServerDeliveredTimestamp(envelopeTimestamp: Long): Long {
return envelopeTimestamp + 10
}
data class DeleteForMeSync(
val conversationId: RecipientId,
val messages: List<Pair<RecipientId, Long>>,
val nonExpiringMessages: List<Pair<RecipientId, Long>> = emptyList(),
val isFullDelete: Boolean = true,
val attachments: List<Pair<Long, AttachmentTable.SyncAttachmentId>> = emptyList()
) {
constructor(conversationId: RecipientId, vararg messages: Pair<RecipientId, Long>) : this(conversationId, messages.toList())
}
}

View File

@@ -31,7 +31,7 @@ object MockProvider {
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null
svr2Credentials = AuthCredentials.create("username", "password")
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -68,7 +68,7 @@ object MockProvider {
}
}
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
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

@@ -55,5 +55,5 @@ inline fun <reified T> RecordedRequest.parsedRequestBody(): T {
}
private fun defaultRequestPredicate(verb: String, path: String, predicate: RequestPredicate = { true }): RequestPredicate = { request ->
request.method == verb && request.path.startsWith("/$path") && predicate(request)
request.method == verb && request.path?.startsWith("/$path") == true && predicate(request)
}

View File

@@ -19,7 +19,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.IdentityTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.profiles.ProfileName
@@ -47,7 +47,7 @@ import java.util.UUID
*/
class SignalActivityRule(private val othersCount: Int = 4, private val createGroup: Boolean = false) : ExternalResource() {
val application: Application = ApplicationDependencies.getApplication()
val application: Application = AppDependencies.application
lateinit var context: Context
private set
@@ -90,8 +90,8 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account.generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
@@ -111,19 +111,19 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
verifyAccountResponse = VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
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.svr().optOut()
SignalStore.svr.optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
SignalStore.settings().isMessageNotificationsEnabled = false
SignalStore.settings.isMessageNotificationsEnabled = false
return Recipient.self()
}
@@ -141,11 +141,11 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
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, false))
SignalDatabase.recipients.setProfileSharing(recipientId, true)
SignalDatabase.recipients.markRegistered(recipientId, aci)
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
others += recipientId
othersKeys += otherIdentity
}
@@ -158,14 +158,14 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
}
fun changeIdentityKey(recipient: Recipient, identityKey: IdentityKey = IdentityKeyUtil.generateIdentityKeyPair().publicKey) {
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0), identityKey)
}
fun getIdentity(recipient: Recipient): IdentityKey {
return ApplicationDependencies.getProtocolStore().aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
return AppDependencies.protocolStore.aci().identities().getIdentity(SignalProtocolAddress(recipient.requireServiceId().toString(), 0))
}
fun setVerified(recipient: Recipient, status: IdentityTable.VerifiedStatus) {
ApplicationDependencies.getProtocolStore().aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
AppDependencies.protocolStore.aci().identities().setVerified(recipient.id, getIdentity(recipient), IdentityTable.VerifiedStatus.VERIFIED)
}
}

View File

@@ -26,8 +26,8 @@ class SignalDatabaseRule(
override fun starting(description: Description?) {
deleteAllThreads()
SignalStore.account().setAci(localAci)
SignalStore.account().setPni(localPni)
SignalStore.account.setAci(localAci)
SignalStore.account.setPni(localPni)
}
override fun finished(description: Description?) {

View File

@@ -1,13 +1,14 @@
package org.thoughtcrime.securesms.testing
import android.database.Cursor
import android.util.Base64
import org.hamcrest.Matcher
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.Hex
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.select
@@ -56,33 +57,41 @@ infix fun <E, T : Collection<E>> T.assertIsSize(expected: Int) {
assertThat(this, hasSize(expected))
}
infix fun <T : Any> T.assert(matcher: Matcher<T>) {
assertThat(this, matcher)
}
fun CountDownLatch.awaitFor(duration: Duration) {
if (!await(duration.inWholeMilliseconds, TimeUnit.MILLISECONDS)) {
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 dumpTableToLogs(tag: String = "TestUtils", table: String, columns: Set<String>? = null) {
dumpTable(table, columns).forEach { Log.d(tag, it.toString()) }
}
fun dumpTable(table: String): List<List<Pair<String, String?>>> {
fun dumpTable(table: String, columns: Set<String>?): List<List<Pair<String, String?>>> {
return SignalDatabase.rawDatabase
.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))
}
val map: List<Pair<String, String?>> = cursor.columnNames.mapNotNull { column ->
if (columns == null || columns.contains(column)) {
val index = cursor.getColumnIndex(column)
var data: String? = when (cursor.getType(index)) {
Cursor.FIELD_TYPE_BLOB -> Hex.toStringCondensed(cursor.getBlob(index))
else -> cursor.getString(index)
}
if (table == MessageTable.TABLE_NAME && column == MessageTable.TYPE) {
data = MessageTableTestUtils.typeColumnToString(cursor.getLong(index))
}
column to data
column to data
} else {
null
}
}
map
}

View File

@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.util;
/**
* A class that allows us to inject feature flags during tests.
*/
public final class FeatureFlagsAccessor {
public static void forceValue(String key, Object value) {
FeatureFlags.FORCED_VALUES.put(key, value);
}
}

View File

@@ -61,7 +61,7 @@ object MessageTableTestUtils {
isProfileChange:${type == MessageTypes.PROFILE_CHANGE_TYPE}
isGroupV1MigrationEvent:${type == MessageTypes.GV1_MIGRATION_TYPE}
isChangeNumber:${type == MessageTypes.CHANGE_NUMBER_TYPE}
isBoostRequest:${type == MessageTypes.BOOST_REQUEST_TYPE}
isDonationChannelDonationRequest:${type == MessageTypes.RELEASE_CHANNEL_DONATION_REQUEST_TYPE}
isThreadMerge:${type == MessageTypes.THREAD_MERGE_TYPE}
isSmsExport:${type == MessageTypes.SMS_EXPORT_TYPE}
isGroupV2LeaveOnly:${type and MessageTypes.GROUP_V2_LEAVE_BITS == MessageTypes.GROUP_V2_LEAVE_BITS}

View File

@@ -2,9 +2,9 @@ package org.signal.benchmark
import android.content.Context
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.push.AccountManagerFactory
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.RemoteConfig
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -16,15 +16,15 @@ import java.util.Optional
class DummyAccountManagerFactory : AccountManagerFactory() {
override fun createAuthenticated(context: Context, aci: ACI, pni: PNI, number: String, deviceId: Int, password: String): SignalServiceAccountManager {
return DummyAccountManager(
ApplicationDependencies.getSignalServiceNetworkAccess().getConfiguration(number),
AppDependencies.signalServiceNetworkAccess.getConfiguration(number),
aci,
pni,
number,
deviceId,
password,
BuildConfig.SIGNAL_AGENT,
FeatureFlags.okHttpAutomaticRetry(),
FeatureFlags.groupLimits().hardLimit
RemoteConfig.okHttpAutomaticRetry,
RemoteConfig.groupLimits.hardLimit
)
}

View File

@@ -1,5 +1,6 @@
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
@@ -9,7 +10,6 @@ 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
@@ -144,7 +144,7 @@ object TestMessages {
}
private fun imageAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"image/webp",
null,
@@ -161,13 +161,14 @@ object TestMessages {
false,
Optional.empty(),
Optional.empty(),
System.currentTimeMillis()
System.currentTimeMillis(),
null
)
}
private fun voiceAttachment(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(
ReleaseChannel.CDN_NUMBER,
Cdn.S3.cdnNumber,
SignalServiceAttachmentRemoteId.from(""),
"audio/aac",
null,
@@ -184,7 +185,8 @@ object TestMessages {
false,
Optional.empty(),
Optional.empty(),
System.currentTimeMillis()
System.currentTimeMillis(),
null
)
}

View File

@@ -10,7 +10,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor
import org.thoughtcrime.securesms.profiles.ProfileName
@@ -35,7 +35,7 @@ object TestUsers {
private var generatedOthers: Int = 0
fun setupSelf(): Recipient {
val application: Application = ApplicationDependencies.getApplication()
val application: Application = AppDependencies.application
DeviceTransferBlockingInterceptor.getInstance().blockNetwork()
PreferenceManager.getDefaultSharedPreferences(application).edit().putBoolean("pref_prompted_push_registration", true).commit()
@@ -44,8 +44,8 @@ object TestUsers {
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
SignalStore.account.generateAciIdentityKeyIfNecessary()
SignalStore.account.generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
val registrationData = RegistrationData(
@@ -63,8 +63,8 @@ object TestUsers {
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)
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account.aciIdentityKey, SignalStore.account.pniPreKeys)
)
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
@@ -77,7 +77,7 @@ object TestUsers {
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.svr().optOut()
SignalStore.svr.optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))
@@ -100,11 +100,11 @@ object TestUsers {
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
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()
ApplicationDependencies.getProtocolStore().aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
AppDependencies.protocolStore.aci().saveIdentity(SignalProtocolAddress(aci.toString(), 0), otherIdentity.publicKey)
others += recipientId
}

View File

@@ -12,7 +12,7 @@ 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.dependencies.AppDependencies
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel
@@ -123,7 +123,7 @@ class ConversationElementGenerator {
)
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
ApplicationDependencies.getApplication(),
AppDependencies.application,
record,
Recipient.UNKNOWN
)

View File

@@ -67,7 +67,8 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(),
startExpirationTimeout = {},
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) }
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) },
displayDialogFragment = {}
)
if (springboardViewModel.hasWallpaper.value) {
@@ -230,6 +231,10 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onChangeProfileNameUpdateContact(recipient: Recipient) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onCallToAction(action: String) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
@@ -279,7 +284,7 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onEditedIndicatorClicked(messageRecord: MessageRecord) {
override fun onEditedIndicatorClicked(conversationMessage: ConversationMessage) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
@@ -299,6 +304,14 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onItemDoubleClick(item: MultiselectPart) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onPaymentTombstoneClicked() {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}
override fun onShowSafetyTips(forGroup: Boolean) {
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
}

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"

View File

@@ -22,8 +22,6 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
@@ -45,16 +43,10 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<!-- For sending/receiving events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
@@ -71,17 +63,14 @@
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<!-- For fixing MMS -->
<!-- For device transfer -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
@@ -94,6 +83,15 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
<!-- For services -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<!-- For vestigial telecom integration service -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
@@ -126,13 +124,14 @@
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />
<activity android:name=".WebRtcCallActivity"
android:theme="@style/TextSecure.DarkTheme.WebRTCCall"
android:excludeFromRecents="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:configChanges="screenSize|smallestScreenSize|screenLayout"
android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"
@@ -174,10 +173,6 @@
</intent-filter>
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize"
@@ -625,7 +620,7 @@
<activity android:name=".conversation.v2.ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:parentActivityName=".MainActivity"
android:resizeableActivity="true"
android:exported="false">
@@ -676,7 +671,7 @@
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
@@ -719,6 +714,7 @@
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
@@ -762,13 +758,6 @@
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity
android:name=".backup.v2.ui.MessageBackupsFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".stories.settings.StorySettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -811,13 +800,6 @@
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity
android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity
android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -844,7 +826,14 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".registration.RegistrationNavigationActivity"
<activity android:name=".registration.ui.RegistrationActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".restore.RestoreActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
@@ -940,19 +929,25 @@
</activity>
<activity android:name="org.thoughtcrime.securesms.webrtc.VoiceCallShare"
android:exported="true"
android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:exported="true"
android:excludeFromRecents="true"
android:permission="android.permission.CALL_PHONE"
android:theme="@style/NoAnimation.Theme.BlackScreen"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call" />
</intent-filter>
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.videocall" />
</intent-filter>
</activity>
<activity android:name=".mediasend.AvatarSelectionActivity"
@@ -976,11 +971,20 @@
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".registration.ui.restore.RemoteRestoreActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".profiles.manage.EditProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".nicknames.NicknameActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity
android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
@@ -1084,30 +1088,43 @@
android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.QrImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrScannerActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".reactions.edit.EditReactionsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.app.subscription.donate.CheckoutFlowActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<service
android:enabled="true"
android:name=".service.webrtc.WebRtcCallService"
android:foregroundServiceType="camera|microphone"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
android:enabled="true"
android:exported="false"
android:name=".service.KeyCachingService" />
android:name=".service.KeyCachingService"
android:foregroundServiceType="remoteMessaging"/>
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$ForegroundService"
android:foregroundServiceType="remoteMessaging"
android:exported="false"/>
<service
@@ -1118,6 +1135,7 @@
<service
android:name=".service.webrtc.AndroidCallConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:foregroundServiceType="phoneCall"
android:exported="true">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
@@ -1140,19 +1158,6 @@
</intent-filter>
</receiver>
<service android:name=".service.QuickResponseService"
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sms" />
<data android:scheme="smsto" />
<data android:scheme="mms" />
<data android:scheme="mmsto" />
</intent-filter>
</service>
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
@@ -1170,10 +1175,17 @@
<service
android:name=".service.GenericForegroundService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
android:name=".service.AttachmentProgressService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
android:name=".service.BackupProgressService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
@@ -1182,6 +1194,7 @@
<service
android:name=".gcm.FcmFetchForegroundService"
android:foregroundServiceType="remoteMessaging"
android:exported="false"/>
<service android:name=".gcm.FcmReceiveService" android:exported="true">
@@ -1190,13 +1203,6 @@
</intent-filter>
</service>
<receiver android:name=".service.SmsDeliveryListener"
android:exported="true">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.services.MESSAGE_SENT"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">
@@ -1256,11 +1262,6 @@
android:exported="false"
android:grantUriPermissions="true" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}.mms" />
<provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
@@ -1308,6 +1309,12 @@
</intent-filter>
</receiver>
<receiver android:name=".service.AnalyzeDatabaseAlarmListener" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundServiceUtil$Receiver" android:exported="false" />
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
@@ -1372,7 +1379,11 @@
</intent-filter>
</receiver>
<service android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallForegroundService" android:exported="false" />
<service
android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallForegroundService"
android:exported="false"
android:foregroundServiceType="dataSync|microphone" />
<receiver android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallManager$ActiveCallServiceReceiver" android:exported="false">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.service.webrtc.ActiveCallAction.DENY"/>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 208 KiB

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