Compare commits

..

498 Commits

Author SHA1 Message Date
Cody Henthorne
89d7c0b0d0 Bump version to 6.40.0 2023-11-08 20:11:41 -05:00
Cody Henthorne
d2ec62d681 Updated baseline profile. 2023-11-08 20:02:59 -05:00
Cody Henthorne
b6d38fe8f1 Update translations and other static files. 2023-11-08 19:57:56 -05:00
Cody Henthorne
1edc256148 Rotate ideal and sepa flags. 2023-11-08 19:51:46 -05:00
Cody Henthorne
24ac385898 Fix dark theme issues with compose bottom sheets and donation bank name typo. 2023-11-08 19:51:46 -05:00
Cody Henthorne
f062e58f7b Flesh out monthly iDEAL donation flow. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
96aec401b9 Fix username link settings navigation. 2023-11-08 19:51:46 -05:00
Nicholas Tinsley
7ff0b7aa3c Increase clickable area of media download button. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
e5ab5241d5 Centralize username logic in UsernameRepository. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
0f4f87067e Add some detailed username docs to UsernameRepository. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
3f32f816b0 Convert the UsernameRepository to an object. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
73de2dfda7 Fix opening username links. 2023-11-08 19:51:46 -05:00
Nicholas
d6fd6cb5a3 Optimize thread ID DB query. 2023-11-08 19:51:46 -05:00
Nicholas
39fbbe896f Batch insert group receipts. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
29c70acf4e Leave attachment insert early if there are no attachments. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
5cd2568776 Fix foreground service crash with state tracking. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
60a6535a12 Add internal test buttons to corrupt username state. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
f48b389449 Fix padding in edit profile screen. 2023-11-08 19:51:46 -05:00
Greyson Parrelli
316dd210a0 Minor improvements to username tooltip. 2023-11-07 22:11:08 -05:00
Greyson Parrelli
a60712c09d If both usernames hashes are empty, consider valid. 2023-11-07 14:44:46 -05:00
Nicholas Tinsley
482cd564ff Lower priority of ConversationShortcutUpdateJob. 2023-11-07 13:37:21 -05:00
Greyson Parrelli
ac1171d43b Allow install of nightlies with the same version code but newer upload dates. 2023-11-07 12:51:09 -05:00
Greyson Parrelli
ed8953c430 Fix logging around username link reset failures. 2023-11-07 12:11:22 -05:00
Cody Henthorne
9a8aecaf3f Improve donation strings localization. 2023-11-07 11:56:01 -05:00
Greyson Parrelli
423719e7bc Fix username QR code sharing. 2023-11-07 11:43:40 -05:00
Cody Henthorne
7f2b6a874e Flesh out iDEAL sad path UX and address UI polish feedback. 2023-11-07 11:04:36 -05:00
Greyson Parrelli
cfe5ea3f9b Add the ability to download the current perfetto trace in Spinner. 2023-11-07 09:07:59 -05:00
Greyson Parrelli
07aa058a46 Update username consistency error handling. 2023-11-06 14:49:51 -05:00
Nicholas Tinsley
6cadf93c43 Forward touch events in timestamp of text message. 2023-11-06 14:48:35 -05:00
Cody Henthorne
60eb1332d2 Fix lifespan typo for ExternalLaunchDonationJob. 2023-11-06 11:04:24 -05:00
Nicholas Tinsley
a9ee7e93fd Increase IdentityKey cache size. 2023-11-06 10:46:53 -05:00
Clark
2782216e52 Remove slow getResourceAsStream when loading the Conscrypt provider. 2023-11-06 09:56:11 -05:00
Nicholas Tinsley
d22537c5f2 Fix LocalMetrics for text sends. 2023-11-03 15:24:36 -04:00
Nicholas Tinsley
57aa6c19e1 Set silent group updates to low job priority. 2023-11-03 15:20:38 -04:00
Nicholas Tinsley
761553d392 Avoid unnecessary lock acquisition. 2023-11-03 15:12:29 -04:00
Greyson Parrelli
29350ab7b0 Add a QR code link and tooltip in the profile settings. 2023-11-03 14:33:07 -04:00
Cody Henthorne
528ccc1e9d Navigate to main donation screen if user leaves for external app. 2023-11-03 12:56:03 -04:00
Cody Henthorne
20d26ad7ca Expand spinner timestamp conversion to job tables. 2023-11-03 12:51:17 -04:00
Cody Henthorne
5d23c5c902 Increase sepa receipt request lifespan to cover at least 14 business days. 2023-11-03 12:49:19 -04:00
Greyson Parrelli
145794bf04 Add the ability to set job priority. 2023-11-03 12:21:27 -04:00
Greyson Parrelli
d00f2aa8d0 Convert EditProfileFragment to kotlin. 2023-11-03 10:40:13 -04:00
Greyson Parrelli
3a20375567 Update profile edit screen to remove subtitles. 2023-11-03 09:25:09 -04:00
Greyson Parrelli
7be93a8a44 Rename profile fragments so they make sense. 2023-11-03 09:14:17 -04:00
Jim Gustafson
b5e4c4e92a Update to RingRTC v2.34.3 2023-11-02 21:30:07 -04:00
Greyson Parrelli
20285796bd Fix username link sharing toolbar. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
7826ff94e3 Also check PNI prekey age on message send. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
f1dccbb64d Consider empty usernames as absent. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
528e301ce4 Improve username creation error debouncing. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
af016a9c79 Fix username error message text wrapping. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
cbd5738543 Fix some username creation tinting issues in dark theme. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
2dd0899a3d Fix nightly updates. 2023-11-02 19:19:00 -04:00
Greyson Parrelli
e486a4baef Bump version to 6.39.1 2023-11-02 19:18:37 -04:00
Greyson Parrelli
5fc11baf9e Update translations and other static files. 2023-11-02 19:18:37 -04:00
Nicholas
157777cac1 Batch update DB upon group receipt. 2023-11-02 19:18:37 -04:00
Greyson Parrelli
99d0ee6725 Fix cursor crash in ConversationSettings.
Best way to fix a cursor crash it to... stop using cursors.

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

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

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

Fixes #13172
2023-09-14 17:02:52 -04:00
Greyson Parrelli
09ba6d834a Ensure signed prekeys are rotated even if someone turns their clock back. 2023-09-14 13:20:55 -04:00
Alex Hart
61654f815d Bump version to 6.33.0 2023-09-13 14:25:11 -03:00
Alex Hart
bf450766b2 Updated baseline profile. 2023-09-13 13:40:12 -03:00
Alex Hart
2f813f3d91 Update translations and other static files. 2023-09-13 13:35:45 -03:00
Clark
51e46db42d Fail websocket drain if keepalive token is removed. 2023-09-13 13:28:43 -03:00
Cody Henthorne
11e0dd18d3 Remove use of legacy hangup in sending flow. 2023-09-13 13:28:43 -03:00
Nicholas Tinsley
ff5b024074 Fix crash when closing app during voice memo. 2023-09-13 13:28:43 -03:00
Nicholas
2f53c1a860 Display error when initial attachment selection is too large. 2023-09-13 13:28:43 -03:00
Alex Hart
53b1544b58 Fix bad feeling on-back behavior for in-conversation search. 2023-09-13 13:28:43 -03:00
Alex Hart
846fc9008c Fix issue on lower API devices with insets.
Fixes #13156
2023-09-13 13:28:43 -03:00
Alex Hart
cf7455c661 Do not display clear filter when no filter is applied in call log. 2023-09-12 10:58:39 -03:00
Alex Hart
ea52bbea42 Fix footer padding for very narrow bodies. 2023-09-12 10:48:00 -03:00
Alex Hart
712c41d927 Remove busy log line from call link processing. 2023-09-12 10:31:44 -03:00
Greyson Parrelli
c33da4a5ae Do not block CDS if PIN creation failed. 2023-09-12 09:28:13 -04:00
Alex Hart
10aecb9390 Order call links in reverse chron on calls tab. 2023-09-12 10:16:49 -03:00
Alex Hart
a1eafe311e Set correct color on join button in SignalCallRow. 2023-09-12 10:13:46 -03:00
Alex Hart
df416be43e Rotate CIV2 flag. 2023-09-12 10:01:10 -03:00
Alex Hart
08035bf8a5 Rotate edit message flag. 2023-09-12 10:00:25 -03:00
Alex Hart
05a990e228 Add CIV2 callback for clicking an edited message footer. 2023-09-12 10:00:02 -03:00
Nicholas Tinsley
4d7d1699f9 Simplify VoiceNote playback logic. 2023-09-11 17:12:26 -04:00
Alex Hart
92b0ebb6f6 Call Link single-user request sheet. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
e41accf52d Fix bug where name wouldn't refresh when getting a message request. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
1cca60fa53 Include decryption time in success log. 2023-09-11 17:07:12 -03:00
Alex Hart
69f489ffc5 Fix reactions position when parent isn't re-laid-out. 2023-09-11 17:07:12 -03:00
Alex Hart
903e305519 Drop verbose logs from ConversationItemAnimator. 2023-09-11 17:07:12 -03:00
Cody Henthorne
9ed3e8befb Fix infinite spinner on scheduled link preview bug. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
cd38c99f7e Reduce websocket timeout if we have no network. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
3fc26733ad Add more logging to forwarding bottom sheet. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
e24134ff6f Reduce AvatarProvider logging. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
901063f4c9 Improve third-party license display. 2023-09-11 17:07:12 -03:00
Cody Henthorne
be8742f69e Add promote PNI change profile keys to change processing. 2023-09-11 17:07:12 -03:00
Alex Hart
dbd6b4bd52 Update recipient after related call link has a state change. 2023-09-11 17:07:12 -03:00
Alex Hart
8a39e8094c Force CallLinkInfoSheet to always be dark mode. 2023-09-11 17:07:12 -03:00
Alex Hart
a154a6cce5 Add 'edit' state for call name button. 2023-09-11 17:07:12 -03:00
Alex Hart
052ec14a6b Rotate ad-hoc calling flag. 2023-09-11 17:07:12 -03:00
Alex Hart
fa9034d57b Add logic for handling disconnect reason while connected to call link. 2023-09-11 17:07:12 -03:00
Alex Hart
266adf788c Add support for received call link previews. 2023-09-11 17:07:12 -03:00
Greyson Parrelli
b19aedd17c Upgrade CameraX to 1.3.0-rc01 2023-09-11 17:07:12 -03:00
Greyson Parrelli
f959543c19 Add UI for prompting about crashes. 2023-09-11 17:07:11 -03:00
Alex Hart
0a6c3baf24 Bump version to 6.32.4 2023-09-11 17:03:34 -03:00
Alex Hart
5a33c1eed6 Updated baseline profile. 2023-09-11 16:59:24 -03:00
Alex Hart
ce1196e17a Updated language translations. 2023-09-11 16:56:56 -03:00
Nicholas Tinsley
a9fd5a3162 Fix DB query for incremental media. 2023-09-11 14:56:49 -04:00
Alex Hart
18b33a7776 Ensure lower api levels do not try to use Uri based IconCompat. 2023-09-11 15:27:58 -03:00
Cody Henthorne
b72fe0d7a2 Fix unread mention indicator showing incorrectly bug. 2023-09-11 13:53:42 -04:00
Nicholas Tinsley
551e5a0a25 Bump version to 6.32.3 2023-09-09 13:08:03 -04:00
Nicholas Tinsley
92d4a580c1 Updated language translations. 2023-09-09 13:07:48 -04:00
Greyson Parrelli
b367701a96 Fix bug where default reactions were dropped in AccountRecord. 2023-09-08 19:42:04 -04:00
Nicholas Tinsley
8595863afe Bump version to 6.32.2 2023-09-08 17:54:55 -04:00
Nicholas Tinsley
21492ed88e Updated language translations. 2023-09-08 17:54:36 -04:00
Nicholas Tinsley
4dc14ab7f9 Fix translation tool postprocessing bug. 2023-09-08 17:50:50 -04:00
Nicholas Tinsley
5caf3409db Match incremental MAC calculation. 2023-09-08 17:50:50 -04:00
Greyson Parrelli
1565c32162 Fix crash when opening license screen. 2023-09-07 13:51:55 -04:00
Nicholas Tinsley
45edb4e5da Bump version to 6.32.1 2023-09-07 11:06:31 -04:00
Nicholas Tinsley
5bf1c4f433 Updated baseline profile. 2023-09-07 11:04:46 -04:00
Nicholas Tinsley
3cc692d3fb Rotate the edit message feature flag. 2023-09-07 10:28:54 -04:00
Alex Hart
e42b2490f0 Rotate flag for civ2-text-only views. 2023-09-07 11:27:34 -03:00
Alex Hart
454b1f69ed Suppress LayoutTransition during scroll events. 2023-09-07 11:26:45 -03:00
Greyson Parrelli
b410756dfd Remove dashes from help option. 2023-09-07 10:17:09 -04:00
Nicholas
1458919549 Voice note playlist improvements. 2023-09-07 10:02:16 -04:00
Alex Hart
48ae8c2465 Utilize Bitmap shortcut on API29 and under. 2023-09-07 10:42:39 -03:00
Alex Hart
0a78bcb374 Remove payments beta tag. 2023-09-06 16:27:51 -04:00
Greyson Parrelli
61cdb48273 Fix issue where notification settings were slow to open. 2023-09-06 16:23:14 -04:00
Nicholas Tinsley
b3350b22b6 Remove extraneous "Learn more" from Payments screen. 2023-09-06 15:12:42 -04:00
Nicholas Tinsley
d35d22c7d8 Fix voice note playback for single voice notes. 2023-09-06 11:53:41 -04:00
Alex Hart
24cd11152b Prevent several re-layout calls in ConversationItem. 2023-09-06 11:18:36 -03:00
Nicholas Tinsley
d21254ac02 Bump version to 6.32.0 2023-09-06 10:15:12 -04:00
Nicholas Tinsley
70f08c806a Updated baseline profile. 2023-09-06 10:08:51 -04:00
Nicholas Tinsley
e7c3fb02e8 Updated language translations. 2023-09-06 09:57:00 -04:00
Alex Hart
3d3cf1d76e Add logging for slide enqueue and move dropped animation message to verbose. 2023-09-06 09:34:49 -04:00
Nicholas
2bf385fe38 Upgrade libsignal to 0.32.0 2023-09-06 09:34:49 -04:00
Nicholas
7ba595be55 Ignore Bluetooth devices with Watch in their product name.
Addresses #13141
2023-09-06 09:34:49 -04:00
Alex Hart
c45e79c588 Split reaction view updates to separate width from adding views. 2023-09-06 09:34:49 -04:00
Alex Hart
f37568b050 Stopgap for reaction display in conversation item v2. 2023-09-06 09:34:49 -04:00
Nicholas Tinsley
b5afc1cd1c Fix AttachmentCipherTest 2023-09-06 09:34:49 -04:00
Alex Hart
e9777ccfc6 Fix scroll button when only one giant message is displayed. 2023-09-06 09:34:49 -04:00
Alex Hart
898404fc65 Fix poor spacing of footer in short group text messages. 2023-09-06 09:34:49 -04:00
Alex Hart
131212b158 Fix improper bubble spacing caused by swipe to reply icon. 2023-09-06 09:34:49 -04:00
Greyson Parrelli
3f1d3149e9 Attempt to open db as read-write during error recovery.
Relates to #13034
2023-09-06 09:34:49 -04:00
Nicholas
bfc8b199b6 Hopefully prevent VoiceNotePlaybackService startup crash.
Addresses #13140
2023-09-06 09:34:49 -04:00
Alex Hart
6d4b487428 Update shortcut drawable to use content id. 2023-09-06 09:34:49 -04:00
Cody Henthorne
9337201ffb Prevent okhttp from auto-retrying attachment uploads. 2023-09-06 09:34:49 -04:00
Greyson Parrelli
494b2c6786 Add an index specifically for improving thread count perf. 2023-09-06 09:34:49 -04:00
Alex Hart
bc1c8032c1 Add support for shade and arbitrary overlay drawables to CIV2 Media items. 2023-09-06 09:34:49 -04:00
Alex Hart
21b0a4d370 Fix UriChatWallpaper loading issue where wrong thread was used for setting the imageView resource. 2023-09-06 09:34:49 -04:00
Alex Hart
133effccfc Move delegate creation to a lazy field. 2023-09-06 09:34:49 -04:00
Cody Henthorne
62b4ebc4a9 Fix mention excessive haptic feedback bug. 2023-09-06 09:34:49 -04:00
Cody Henthorne
12941ea19e Fix attachment editor and schedule message bar UI overlap bug. 2023-09-06 09:34:49 -04:00
Alex Hart
f94bd706a4 Fix sender name color. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
3cbbc29c00 Rotate the edit message feature flag. 2023-09-06 09:34:48 -04:00
Cody Henthorne
0827c18eeb Update edit message awareness bottom sheet copy. 2023-09-06 09:34:48 -04:00
Cody Henthorne
6c4ebc9f58 Fix incorrect type value being used for unknown storage records. 2023-09-06 09:34:48 -04:00
Alex Hart
1f2bfe8245 Replace internal setting for CIV2 TextOnly with a FeatureFlag. 2023-09-06 09:34:48 -04:00
Jim Gustafson
305d7485c1 Update to RingRTC v2.31.2 2023-09-06 09:34:48 -04:00
Alex Hart
4ded05bbd1 Implement groundwork for proper ConversationItemV2 payload processing. 2023-09-06 09:34:48 -04:00
Alex Hart
540a2b1876 ConversationItemV2 Quote support and various fixes. 2023-09-06 09:34:48 -04:00
Cody Henthorne
153d3ad388 Fix story group replies layout in RTL. 2023-09-06 09:34:48 -04:00
Alex Hart
a3e36d2453 Update target API to 33 2023-09-06 09:34:48 -04:00
Nicholas Tinsley
b9449a798b Increase Glide exception coverage. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
9da149a868 Convert DateUtils to kotlin, improve perf with caching. 2023-09-06 09:34:48 -04:00
Cody Henthorne
d505c00403 Add CDN3 upload and download support. 2023-09-06 09:34:48 -04:00
Nicholas Tinsley
4d7a0a361f Dismiss Voice Note player notification upon completion. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
e08e02ae80 Update Stopwatch to log fractional milliseconds. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
95c6f569d6 Fetch data in ConversationDataSource in parallel. 2023-09-06 09:34:48 -04:00
Nicholas Tinsley
e46759f436 Update view-once Toast string. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
b42dd5289b Remove unnecessary context args in slide creation. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
a911a007d2 Change job scheduling to be relative rather than absolute. 2023-09-06 09:34:48 -04:00
Nicholas
64babe2e42 Streamable Video. 2023-09-06 09:34:48 -04:00
Greyson Parrelli
099c94c215 Fix handling of some PNI initial contact flows. 2023-08-31 14:33:54 -04:00
Alex Hart
75b81a0fd2 Add the groundwork for the ConversationItemV2 Media item. 2023-08-31 14:33:54 -04:00
Greyson Parrelli
f9ab5d4013 Fix SVR2 typo. 2023-08-31 14:33:53 -04:00
Cody Henthorne
b83080e2d7 Fix payments spinning forever. 2023-08-31 14:33:53 -04:00
Cody Henthorne
6a21106347 Convert StorageService protos to wire. 2023-08-31 14:33:53 -04:00
Greyson Parrelli
9a7d8c858d Convert JobDatabase to Kotlin. 2023-08-31 14:33:53 -04:00
Greyson Parrelli
8339c0d8de Convert JobManager tests to kotlin. 2023-08-29 09:33:45 -04:00
Greyson Parrelli
2b1136ea02 Fix loading states for username editing. 2023-08-29 09:33:45 -04:00
Greyson Parrelli
84b4d69913 Fix error display when entering invalid username characters.
Also convert UsernameEditViewModel to kotlin.
2023-08-29 09:33:45 -04:00
Alex Hart
3fe9ce378e Mock out glideRequests dependency for instrumented test. 2023-08-29 09:33:45 -04:00
Greyson Parrelli
57b9571d86 Don't store blank usernames. 2023-08-29 09:33:45 -04:00
Alex Hart
ae3071d318 Fix bottom constraint of sender photo in civ2. 2023-08-29 09:33:45 -04:00
Greyson Parrelli
8a93814bac Update to the new username link spec. 2023-08-29 09:33:45 -04:00
Alex Hart
a6dd4345ab Rewrite quote view using constraint layout and stubs. 2023-08-29 09:33:45 -04:00
Greyson Parrelli
c71456444f Bump version to 6.31.2 2023-08-28 18:58:47 -04:00
Greyson Parrelli
b916605a24 Updated language translations. 2023-08-28 18:58:23 -04:00
Alex Hart
553da1e7e8 Speed up AvatarProvider. 2023-08-28 18:51:43 -04:00
Cody Henthorne
847651ead7 Revert "Update to RingRTC v2.31.1"
This reverts commit 4ab82c99a8.
2023-08-28 11:55:41 -04:00
Alex Hart
f977f261d6 Utilize iconless person objects until we can fix AvatarProvider. 2023-08-28 12:52:45 -03:00
Greyson Parrelli
3fa9e89e8e Fix typo when reading feature flag. 2023-08-28 10:01:58 -04:00
1695 changed files with 74291 additions and 37344 deletions

View File

@@ -18,6 +18,8 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: set up JDK 17
uses: actions/setup-java@v3

View File

@@ -15,6 +15,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.sha }}
- name: set up JDK 17
@@ -45,6 +46,7 @@ jobs:
- uses: actions/checkout@v3
with:
submodules: true
clean: 'false'
- name: Build with Gradle

3
.gitignore vendored
View File

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

3
.gitmodules vendored Normal file
View File

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

View File

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

View File

@@ -3,7 +3,6 @@ import com.android.build.api.dsl.ManagedVirtualDevice
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.protobuf'
id 'androidx.navigation.safeargs'
id 'org.jlleitschuh.gradle.ktlint'
id 'org.jetbrains.kotlin.android'
@@ -11,25 +10,11 @@ plugins {
id 'kotlin-parcelize'
id 'com.squareup.wire'
id 'translations'
id 'licenses'
}
apply from: 'static-ips.gradle'
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.18.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
wire {
kotlin {
javaInterop = true
@@ -40,7 +25,7 @@ wire {
}
protoPath {
srcDir "${project.rootDir}/libsignal/service/src/main/protowire"
srcDir "${project.rootDir}/libsignal-service/src/main/protowire"
}
}
@@ -48,8 +33,8 @@ ktlint {
version = "0.49.1"
}
def canonicalVersionCode = 1321
def canonicalVersionName = "6.31.1"
def canonicalVersionCode = 1356
def canonicalVersionName = "6.40.0"
def postFixSize = 100
def abiPostFix = ['universal' : 0,
@@ -184,6 +169,7 @@ android {
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2.signal.org\""
buildConfigField "String", "SIGNAL_CDN3_URL", "\"https://cdn3.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
@@ -198,7 +184,7 @@ android {
buildConfigField "String[]", "SIGNAL_STORAGE_IPS", storage_ips
buildConfigField "String[]", "SIGNAL_CDN_IPS", cdn_ips
buildConfigField "String[]", "SIGNAL_CDN2_IPS", cdn2_ips
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
buildConfigField "String[]", "SIGNAL_CDN3_IPS", cdn3_ips
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String[]", "SIGNAL_CDSI_IPS", cdsi_ips
@@ -206,12 +192,6 @@ android {
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"0cedba03535b41b67729ce9924185f831d7767928a1d1689acb689bc079c375f\", " +
"\"187d2739d22be65e74b65f0055e74d31310e4267e5fac2b1246cc8beba81af39\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\""
@@ -339,26 +319,27 @@ android {
play {
dimension 'distribution'
isDefault true
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "boolean", "MANAGES_APP_UPDATES", "false"
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "null"
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"play\""
}
website {
dimension 'distribution'
ext.websiteUpdateUrl = "https://updates.signal.org/android"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
buildConfigField "boolean", "MANAGES_APP_UPDATES", "true"
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "\"https://updates.signal.org/android/latest.json\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"website\""
}
nightly {
def apkUpdateManifestUrl = "<unset>"
if (file("${project.rootDir}/nightly-url.txt").exists()) {
apkUpdateManifestUrl = file("${project.rootDir}/nightly-url.txt").text.trim()
}
dimension 'distribution'
versionNameSuffix "-nightly-untagged-${getDateSuffix()}"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
buildConfigField "boolean", "MANAGES_APP_UPDATES", "true"
buildConfigField "String", "APK_UPDATE_MANIFEST_URL", "\"${apkUpdateManifestUrl}\""
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"nightly\""
}
@@ -380,16 +361,11 @@ android {
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN3_URL", "\"https://cdn3-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"ee1d0d972b7ea903615670de43ab1b6e7a825e811c70a29bb5fe0f819e0975fa\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
buildConfigField "org.thoughtcrime.securesms.KbsEnclave[]", "KBS_FALLBACKS", "new org.thoughtcrime.securesms.KbsEnclave[] { new org.thoughtcrime.securesms.KbsEnclave(\"dd6f66d397d9e8cf6ec6db238e59a7be078dd50e9715427b9c89b409ffe53f99\", " +
"\"4200003414528c151e2dccafbc87aa6d3d66a5eb8f8c05979a6e97cb33cd493a\", " +
"\"ee19f1965b1eefa3dc4204eb70c04f397755f771b8c1909d080c04dad2a6a9ba\") }"
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUj\""
buildConfigField "String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\""
@@ -428,6 +404,9 @@ android {
tag = tag.substring(1)
}
output.versionNameOverride = tag
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
}
} else {
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
@@ -532,13 +511,11 @@ dependencies {
implementation project(':sms-exporter')
implementation project(':sticky-header-grid')
implementation project(':photoview')
implementation project(':glide-webp')
implementation libs.libsignal.android
implementation libs.google.protobuf.javalite
implementation(libs.mobilecoin) {
exclude group: 'com.google.protobuf'
}
implementation libs.mobilecoin
implementation libs.signal.ringrtc
@@ -593,6 +570,7 @@ dependencies {
testImplementation testLibs.robolectric.shadows.multidex
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
testImplementation (testLibs.bouncycastle.bcpkix.jdk15on) { version { strictly "1.70" } } // Used by roboelectric
testImplementation testLibs.conscrypt.openjdk.uber // Used by robolectric
testImplementation testLibs.hamcrest.hamcrest
testImplementation testLibs.mockk
@@ -688,6 +666,24 @@ tasks.withType(Test) {
}
}
project.tasks.configureEach { task ->
if (task.name.toLowerCase().contains("nightly") && task.name != 'checkNightlyParams') {
task.dependsOn checkNightlyParams
}
}
tasks.register('checkNightlyParams') {
doFirst {
if (project.gradle.startParameter.taskNames.any { it.toLowerCase().contains("nightly") }) {
if (!file("${project.rootDir}/nightly-url.txt").exists()) {
throw new GradleException("Cannot fine 'nightly-url.txt' for nightly build! It must exist in the root of this project and contain the location of the nightly manifest.")
}
}
}
}
def loadKeystoreProperties(filename) {
def keystorePropertiesFile = file("${project.rootDir}/${filename}")
if (keystorePropertiesFile.exists()) {

View File

@@ -4,6 +4,7 @@
-keep class org.whispersystems.** { *; }
-keep class org.signal.libsignal.protocol.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keep class org.signal.donations.json.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}

View File

@@ -26,15 +26,13 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
}
override fun initializeLogging() {
persistentLogger = PersistentLogger(this)
Log.initialize({ true }, AndroidLogger(), persistentLogger, inMemoryLogger)
Log.initialize({ true }, AndroidLogger(), PersistentLogger(this), inMemoryLogger)
SignalProtocolLoggerProvider.setProvider(CustomSignalProtocolLogger())
SignalExecutors.UNBOUNDED.execute {
Log.blockUntilAllWritesFinished()
LogDatabase.getInstance(this).trimToSize()
LogDatabase.getInstance(this).logs.trimToSize()
}
}
}

View File

@@ -50,7 +50,6 @@ class ChangeNumberViewModelTest {
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
@@ -231,8 +230,6 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },
@@ -319,8 +316,6 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
Put("/v1/verification/session/${MockProvider.sessionMetadataJson.id}/code") { MockResponse().success(MockProvider.sessionMetadataJson) },

View File

@@ -9,8 +9,9 @@ import org.junit.runner.RunWith
import org.signal.core.util.ThreadUtil
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.conversation.v2.ConversationActivity
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
@@ -64,7 +65,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -73,7 +75,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
ThreadUtil.sleep(1)
}
@@ -83,7 +85,8 @@ class ConversationItemPreviewer {
attachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = System.currentTimeMillis(),
@@ -92,7 +95,7 @@ class ConversationItemPreviewer {
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
val insert = SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
val insert = SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get()
SignalDatabase.attachments.getAttachmentsForMessage(insert.messageId).forEachIndexed { index, attachment ->
// if (index != 1) {
@@ -144,6 +147,7 @@ class ConversationItemPreviewer {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,

View File

@@ -8,7 +8,7 @@ package org.thoughtcrime.securesms.conversation.v2.items
import android.net.Uri
import android.view.View
import androidx.lifecycle.Observer
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
@@ -29,7 +29,6 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange
import org.thoughtcrime.securesms.linkpreview.LinkPreview
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.GlideRequests
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -48,7 +47,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(),
isGroupThread = false,
adapterPosition = 5
@@ -70,7 +68,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.END
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@@ -92,7 +89,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.START
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
@@ -116,7 +112,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.MIDDLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@@ -138,7 +133,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@@ -160,7 +154,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(prev),
isGroupThread = false,
adapterPosition = 5
@@ -184,7 +177,6 @@ class V2ConversationItemShapeTest {
val expected = V2ConversationItemShape.MessageShape.SINGLE
val actual = testSubject.setMessageShape(
isLtr = true,
currentMessage = getMessageRecord(now),
isGroupThread = false,
adapterPosition = 5
@@ -211,13 +203,14 @@ class V2ConversationItemShapeTest {
private val colorizer = Colorizer()
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.STANDARD
override val displayMode: ConversationItemDisplayMode = ConversationItemDisplayMode.Standard
override val clickListener: ConversationAdapter.ItemClickListener = FakeConversationItemClickListener
override val selectedItems: Set<MultiselectPart> = emptySet()
override val isMessageRequestAccepted: Boolean = true
override val searchQuery: String? = null
override val glideRequests: GlideRequests = GlideApp.with(InstrumentationRegistry.getInstrumentation().context)
override val glideRequests: GlideRequests = mockk()
override val isParentInScroll: Boolean = false
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit

View File

@@ -293,22 +293,22 @@ class GroupTableTest {
private fun insertPushGroup(
members: List<DecryptedMember> = listOf(
DecryptedMember.newBuilder()
.setAciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build(),
DecryptedMember.newBuilder()
.setAciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(harness.others[0]).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
)
): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(members)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!
@@ -317,23 +317,23 @@ class GroupTableTest {
private fun insertPushGroupWithSelfAndOthers(others: List<RecipientId>): GroupId {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val selfMember: DecryptedMember = DecryptedMember.newBuilder()
.setAciBytes(harness.self.requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
val selfMember: DecryptedMember = DecryptedMember.Builder()
.aciBytes(harness.self.requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
val otherMembers: List<DecryptedMember> = others.map { id ->
DecryptedMember.newBuilder()
.setAciBytes(Recipient.resolved(id).requireAci().toByteString())
.setJoinedAtRevision(0)
.setRole(Member.Role.DEFAULT)
DecryptedMember.Builder()
.aciBytes(Recipient.resolved(id).requireAci().toByteString())
.joinedAtRevision(0)
.role(Member.Role.DEFAULT)
.build()
}
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(listOf(selfMember) + otherMembers)
.setRevision(0)
val decryptedGroupState = DecryptedGroup.Builder()
.members(listOf(selfMember) + otherMembers)
.revision(0)
.build()
return groupTable.create(groupMasterKey, decryptedGroupState)!!

View File

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

View File

@@ -48,7 +48,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -62,7 +62,7 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -76,13 +76,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId))
@@ -96,13 +96,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val result = mms.setOutgoingGiftsRevealed(listOf(messageId, messageId2))
@@ -115,13 +115,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
@@ -140,13 +140,13 @@ class MessageTableTest_gifts {
val messageId = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(
@@ -165,13 +165,13 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
giftBadge = GiftBadge()
)
val messageId3 = MmsHelper.insert(

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import java.util.Optional
@@ -55,9 +55,9 @@ object MmsHelper {
}
fun insert(
message: IncomingMediaMessage,
message: IncomingMessage,
threadId: Long
): Optional<MessageTable.InsertResult> {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, threadId)
return SignalDatabase.messages.insertMessageInbox(message, threadId)
}
}

View File

@@ -13,7 +13,7 @@ import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.ParentStoryId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -73,7 +73,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -95,7 +96,8 @@ class MmsTableTest_stories {
// GIVEN
val sender = recipients[0]
val messageId = MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -122,7 +124,8 @@ class MmsTableTest_stories {
// GIVEN
val messageIds = recipients.take(5).map {
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = it,
sentTimeMillis = 2,
serverTimeMillis = 2,
@@ -154,7 +157,8 @@ class MmsTableTest_stories {
val unviewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -168,7 +172,8 @@ class MmsTableTest_stories {
val viewedIds: List<Long> = (0 until 5).map {
Thread.sleep(5)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[it],
sentTimeMillis = System.currentTimeMillis(),
serverTimeMillis = 2,
@@ -213,7 +218,8 @@ class MmsTableTest_stories {
fun givenNoOutgoingStories_whenICheckIsOutgoingStoryAlreadyInDatabase_thenIExpectFalse() {
// GIVEN
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = recipients[0],
sentTimeMillis = 200,
serverTimeMillis = 2,
@@ -321,7 +327,8 @@ class MmsTableTest_stories {
)
MmsHelper.insert(
IncomingMediaMessage(
IncomingMessage(
type = MessageType.NORMAL,
from = myStory.id,
sentTimeMillis = 201,
serverTimeMillis = 201,

View File

@@ -38,8 +38,8 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
val newProto = oldRecord
.toProto()
.toBuilder()
.setIdentityState(ContactRecord.IdentityState.DEFAULT)
.newBuilder()
.identityState(ContactRecord.IdentityState.DEFAULT)
.build()
val newRecord = SignalContactRecord(oldRecord.id, newProto)

View File

@@ -14,8 +14,10 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.exists
import org.signal.core.util.orNull
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.select
@@ -34,12 +36,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.ThreadMergeEvent
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.thoughtcrime.securesms.util.Util
@@ -142,6 +142,30 @@ class RecipientTableTest_getAndPossiblyMerge {
process(null, null, null)
}
test("pni matches, pni+aci provided, no pni session") {
given(E164_A, PNI_A, null)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("pni matches, pni+aci provided, pni session") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
expectSessionSwitchoverEvent(E164_A)
}
test("pni matches, pni+aci provided, pni session, pni-verified") {
given(E164_A, PNI_A, null, pniSession = true)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectNoSessionSwitchoverEvent()
}
test("no match, all fields") {
process(E164_A, PNI_A, ACI_A)
expect(E164_A, PNI_A, ACI_A)
@@ -502,6 +526,18 @@ class RecipientTableTest_getAndPossiblyMerge {
expectNoSessionSwitchoverEvent()
}
test("steal, e164+pni+aci * pni+aci, all provided, aci sessions but not pni sessions, no SSE expected") {
given(E164_A, PNI_A, ACI_A, createThread = true, aciSession = true, pniSession = false)
given(null, PNI_B, ACI_B, createThread = false, aciSession = true, pniSession = false)
process(E164_A, PNI_B, ACI_A)
expect(E164_A, PNI_B, ACI_A)
expect(null, null, ACI_B)
expectNoSessionSwitchoverEvent()
}
test("merge, e164 & pni & aci, all provided") {
given(E164_A, null, null)
given(null, PNI_A, null)
@@ -789,9 +825,9 @@ class RecipientTableTest_getAndPossiblyMerge {
val smsId2: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdE164, time = 1, body = "1")).get().messageId
val smsId3: Long = SignalDatabase.messages.insertMessageInbox(smsMessage(sender = recipientIdAci, time = 2, body = "2")).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertSecureDecryptedMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val mmsId1: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 3, body = "3"), -1).get().messageId
val mmsId2: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdE164, time = 4, body = "4"), -1).get().messageId
val mmsId3: Long = SignalDatabase.messages.insertMessageInbox(mmsMessage(sender = recipientIdAci, time = 5, body = "5"), -1).get().messageId
val threadIdAci: Long = SignalDatabase.threads.getThreadIdFor(recipientIdAci)!!
val threadIdE164: Long = SignalDatabase.threads.getThreadIdFor(recipientIdE164)!!
@@ -911,12 +947,30 @@ class RecipientTableTest_getAndPossiblyMerge {
MatcherAssert.assertThat("Distribution list should have updated $recipientIdE164 to $recipientIdAci", updatedList.members, Matchers.containsInAnyOrder(recipientIdAci, recipientIdAciB))
}
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingTextMessage {
return IncomingTextMessage(sender, 1, time, time, time, body, groupId, 0, true, null)
private fun smsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = time,
serverTimeMillis = time,
receivedTimeMillis = time,
body = body,
groupId = groupId.orNull(),
isUnidentified = true
)
}
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMediaMessage {
return IncomingMediaMessage(sender, groupId, body, time, time, time, emptyList(), 0, 0, false, false, true, Optional.empty(), false, false)
private fun mmsMessage(sender: RecipientId, time: Long = 0, body: String = "", groupId: Optional<GroupId> = Optional.empty()): IncomingMessage {
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
groupId = groupId.orNull(),
body = body,
sentTimeMillis = time,
receivedTimeMillis = time,
serverTimeMillis = time,
isUnidentified = true
)
}
private fun identityKey(value: Byte): IdentityKey {
@@ -1228,7 +1282,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
ThreadMergeEvent.parseFrom(bytes)
ThreadMergeEvent.ADAPTER.decode(bytes)
} else {
null
}
@@ -1246,7 +1300,7 @@ class RecipientTableTest_getAndPossiblyMerge {
.use { cursor: Cursor ->
if (cursor.moveToFirst()) {
val bytes = Base64.decode(cursor.requireNonNullString(MessageTable.BODY))
SessionSwitchoverEvent.parseFrom(bytes)
SessionSwitchoverEvent.ADAPTER.decode(bytes)
} else {
null
}

View File

@@ -18,12 +18,10 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.groupChange
import org.thoughtcrime.securesms.database.model.databaseprotos.groupContext
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage
import org.thoughtcrime.securesms.sms.IncomingTextMessage
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
import java.util.Optional
import java.util.UUID
@Suppress("ClassName", "TestFunctionName")
@@ -272,13 +270,28 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
assertThat("latest message should be deleted", sms.getMessageRecordOrNull(latestMessage.messageId), nullValue())
}
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingTextMessage {
private fun smsMessage(sender: RecipientId, body: String? = ""): IncomingMessage {
wallClock++
return IncomingTextMessage(sender, 1, wallClock, wallClock, wallClock, body, Optional.of(groupId), 0, true, null)
return IncomingMessage(
type = MessageType.NORMAL,
from = sender,
sentTimeMillis = wallClock,
serverTimeMillis = wallClock,
receivedTimeMillis = wallClock,
body = body,
groupId = groupId,
isUnidentified = true
)
}
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingGroupUpdateMessage {
return IncomingGroupUpdateMessage(smsMessage(sender, null), groupContext)
private fun groupUpdateMessage(sender: RecipientId, groupContext: DecryptedGroupV2Context): IncomingMessage {
wallClock++
return IncomingMessage.groupUpdate(
from = sender,
timestamp = wallClock,
groupId = groupId,
groupContext = groupContext
)
}
companion object {

View File

@@ -13,9 +13,9 @@ import okio.ByteString
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.KbsEnclave
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess
import org.thoughtcrime.securesms.push.SignalServiceTrustStore
import org.thoughtcrime.securesms.recipients.LiveRecipientCache
@@ -23,32 +23,25 @@ import org.thoughtcrime.securesms.testing.Get
import org.thoughtcrime.securesms.testing.Verb
import org.thoughtcrime.securesms.testing.runSync
import org.thoughtcrime.securesms.testing.success
import org.thoughtcrime.securesms.util.Base64
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.TrustStore
import org.whispersystems.signalservice.internal.configuration.SignalCdnUrl
import org.whispersystems.signalservice.internal.configuration.SignalCdsiUrl
import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalServiceConfiguration
import org.whispersystems.signalservice.internal.configuration.SignalServiceUrl
import org.whispersystems.signalservice.internal.configuration.SignalStorageUrl
import org.whispersystems.signalservice.internal.configuration.SignalSvr2Url
import java.security.KeyStore
import java.util.Optional
/**
* Dependency provider used for instrumentation tests (aka androidTests).
*
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess] and
* [KeyBackupService].
* 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 {
private val serviceTrustStore: TrustStore
private val uncensoredConfiguration: SignalServiceConfiguration
private val serviceNetworkAccessMock: SignalServiceNetworkAccess
private val keyBackupService: KeyBackupService
private val recipientCache: LiveRecipientCache
init {
@@ -80,7 +73,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
signalKeyBackupServiceUrls = arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalStorageUrls = arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdsiUrls = arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalSvr2Urls = arrayOf(SignalSvr2Url(baseUrl, serviceTrustStore, "localhost", ConnectionSpec.CLEARTEXT)),
@@ -97,8 +89,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
on { uncensoredConfiguration } doReturn uncensoredConfiguration
}
keyBackupService = mock()
recipientCache = LiveRecipientCache(application) { r -> r.run() }
}
@@ -106,10 +96,6 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
return serviceNetworkAccessMock
}
override fun provideKeyBackupService(signalServiceAccountManager: SignalServiceAccountManager, keyStore: KeyStore, enclave: KbsEnclave): KeyBackupService {
return keyBackupService
}
override fun provideRecipientCache(): LiveRecipientCache {
return recipientCache
}

View File

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

View File

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

View File

@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.messages
import androidx.test.ext.junit.runners.AndroidJUnit4
import okio.ByteString.Companion.toByteString
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.GroupReceiptTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.toProtoByteString
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.testing.GroupTestingUtils
import org.thoughtcrime.securesms.testing.GroupTestingUtils.asMember
@@ -15,8 +15,8 @@ import org.thoughtcrime.securesms.testing.MessageContentFuzzer
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.util.MessageTableTestUtils
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.GroupContextV2
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
@@ -41,9 +41,9 @@ class MessageContentProcessor__recipientStatusTest {
@Test
fun syncGroupSentTextMessageWithRecipientUpdateFollowup() {
val (groupId, masterKey, groupRecipientId) = GroupTestingUtils.insertGroup(revision = 0, harness.self.asMember(), harness.others[0].asMember(), harness.others[1].asMember())
val groupContextV2 = GroupContextV2.newBuilder().setRevision(0).setMasterKey(masterKey.serialize().toProtoByteString()).build()
val groupContextV2 = GroupContextV2.Builder().revision(0).masterKey(masterKey.serialize().toByteString()).build()
val initialTextMessage = DataMessage.newBuilder().buildWith {
val initialTextMessage = DataMessage.Builder().buildWith {
body = MessageContentFuzzer.string()
groupV2 = groupContextV2
timestamp = envelopeTimestamp

View File

@@ -6,7 +6,6 @@ import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.After
import org.junit.Before
import org.junit.Ignore
@@ -26,7 +25,7 @@ import org.thoughtcrime.securesms.testing.Entry
import org.thoughtcrime.securesms.testing.FakeClientHelpers
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.thoughtcrime.securesms.testing.awaitFor
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.websocket.WebSocketMessage
import org.whispersystems.signalservice.internal.websocket.WebSocketRequestMessage
import java.util.regex.Pattern
@@ -93,7 +92,7 @@ class MessageProcessingPerformanceTest {
val messageCount = 100
val envelopes = generateInboundEnvelopes(bobClient, messageCount)
val firstTimestamp = envelopes.first().timestamp
val lastTimestamp = envelopes.last().timestamp
val lastTimestamp = envelopes.last().timestamp ?: 0
// Inject the envelopes into the websocket
Thread {
@@ -190,7 +189,7 @@ class MessageProcessingPerformanceTest {
path = "/api/v1/message",
id = Random(System.currentTimeMillis()).nextLong(),
headers = listOf("X-Signal-Timestamp: ${this.timestamp}"),
body = this.toByteArray().toByteString()
body = this.encodeByteString()
)
).encodeByteString()
}

View File

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

View File

@@ -5,7 +5,8 @@ import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.Envelope
class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(context) {
companion object {
@@ -19,9 +20,9 @@ class TimingMessageContentProcessor(context: Context) : MessageContentProcessor(
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp))
override fun process(envelope: Envelope, content: Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp!!))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
Log.d(TAG, endTag(envelope.timestamp))
Log.d(TAG, endTag(envelope.timestamp!!))
}
}

View File

@@ -6,12 +6,12 @@ import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.signal.core.util.Base64
import org.signal.core.util.update
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.Base64
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.FeatureFlagsAccessor
import org.whispersystems.signalservice.api.push.ServiceId.ACI
@@ -38,15 +38,21 @@ class ContactRecordProcessorTest {
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setAci(ACI_A.toString())
setUnregisteredAtTimestamp(100)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 100
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setPni(PNI_A.toString())
setE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
@@ -69,16 +75,22 @@ class ContactRecordProcessorTest {
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
setStorageId(originalId, STORAGE_ID_A)
val remote1 = buildRecord(STORAGE_ID_B) {
setAci(ACI_A.toString())
setUnregisteredAtTimestamp(0)
}
val remote1 = buildRecord(
STORAGE_ID_B,
ContactRecord(
aci = ACI_A.toString(),
unregisteredAtTimestamp = 0
)
)
val remote2 = buildRecord(STORAGE_ID_C) {
setAci(PNI_A.toString())
setPni(PNI_A.toString())
setE164(E164_A)
}
val remote2 = buildRecord(
STORAGE_ID_C,
ContactRecord(
aci = PNI_A.toString(),
pni = PNI_A.toString(),
e164 = E164_A
)
)
// WHEN
val subject = ContactRecordProcessor()
@@ -94,14 +106,14 @@ class ContactRecordProcessorTest {
assertEquals(byAci, byE164)
}
private fun buildRecord(id: StorageId, applyParams: ContactRecord.Builder.() -> ContactRecord.Builder): SignalContactRecord {
return SignalContactRecord(id, ContactRecord.getDefaultInstance().toBuilder().applyParams().build())
private fun buildRecord(id: StorageId, record: ContactRecord): SignalContactRecord {
return SignalContactRecord(id, record)
}
private fun setStorageId(recipientId: RecipientId, storageId: StorageId) {
SignalDatabase.rawDatabase
.update(RecipientTable.TABLE_NAME)
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeBytes(storageId.raw))
.values(RecipientTable.STORAGE_SERVICE_ID to Base64.encodeWithPadding(storageId.raw))
.where("${RecipientTable.ID} = ?", recipientId)
.run()
}

View File

@@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.testing.FakeClientHelpers.toEnvelope
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.Envelope
/**
* Welcome to Alice's Client.

View File

@@ -31,11 +31,10 @@ import org.whispersystems.signalservice.api.crypto.UnidentifiedAccess
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.Envelope
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
import kotlin.UnsupportedOperationException
/**
* Welcome to Bob's Client.
@@ -61,7 +60,7 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
/** Inspired by SignalServiceMessageSender#getEncryptedMessage */
fun encrypt(now: Long): SignalServiceProtos.Envelope {
fun encrypt(now: Long): Envelope {
val envelopeContent = FakeClientHelpers.encryptedTextMessage(now)
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, null)
@@ -72,10 +71,10 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
}
return cipher.encrypt(getAliceProtocolAddress(), getAliceUnidentifiedAccess(), envelopeContent)
.toEnvelope(envelopeContent.content.get().dataMessage.timestamp, getAliceServiceId())
.toEnvelope(envelopeContent.content.get().dataMessage!!.timestamp!!, getAliceServiceId())
}
fun decrypt(envelope: SignalServiceProtos.Envelope, serverDeliveredTimestamp: Long) {
fun decrypt(envelope: Envelope, serverDeliveredTimestamp: Long) {
val cipher = SignalServiceCipher(serviceAddress, 1, aciStore, sessionLock, UnidentifiedAccessUtil.getCertificateValidator())
cipher.decrypt(envelope, serverDeliveredTimestamp)
}

View File

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

View File

@@ -16,19 +16,19 @@ import kotlin.random.Random
*/
object GroupTestingUtils {
fun member(aci: ACI, revision: Int = 0, role: Member.Role = Member.Role.ADMINISTRATOR): DecryptedMember {
return DecryptedMember.newBuilder()
.setAciBytes(aci.toByteString())
.setJoinedAtRevision(revision)
.setRole(role)
return DecryptedMember.Builder()
.aciBytes(aci.toByteString())
.joinedAtRevision(revision)
.role(role)
.build()
}
fun insertGroup(revision: Int = 0, vararg members: DecryptedMember): TestGroupInfo {
val groupMasterKey = GroupMasterKey(Random.nextBytes(GroupMasterKey.SIZE))
val decryptedGroupState = DecryptedGroup.newBuilder()
.addAllMembers(members.toList())
.setRevision(revision)
.setTitle(MessageContentFuzzer.string())
val decryptedGroupState = DecryptedGroup.Builder()
.members(members.toList())
.revision(revision)
.title(MessageContentFuzzer.string())
.build()
val groupId = SignalDatabase.groups.create(groupMasterKey, decryptedGroupState)!!

View File

@@ -1,21 +1,20 @@
package org.thoughtcrime.securesms.testing
import com.google.protobuf.ByteString
import org.thoughtcrime.securesms.database.model.toProtoByteString
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.messages.SignalServiceProtoUtil.buildWith
import org.thoughtcrime.securesms.messages.TestMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.AttachmentPointer
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Content
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.DataMessage
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.Envelope
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContextV2
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage
import org.whispersystems.signalservice.internal.push.AttachmentPointer
import org.whispersystems.signalservice.internal.push.BodyRange
import org.whispersystems.signalservice.internal.push.Content
import org.whispersystems.signalservice.internal.push.DataMessage
import org.whispersystems.signalservice.internal.push.Envelope
import org.whispersystems.signalservice.internal.push.GroupContextV2
import org.whispersystems.signalservice.internal.push.SyncMessage
import java.util.UUID
import kotlin.random.Random
import kotlin.random.nextInt
@@ -35,10 +34,10 @@ object MessageContentFuzzer {
* Create an [Envelope].
*/
fun envelope(timestamp: Long): Envelope {
return Envelope.newBuilder()
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 5)
.setServerGuidBytes(UuidUtil.toByteString(UUID.randomUUID()))
return Envelope.Builder()
.timestamp(timestamp)
.serverTimestamp(timestamp + 5)
.serverGuid(UUID.randomUUID().toString())
.build()
}
@@ -62,20 +61,22 @@ object MessageContentFuzzer {
* - Bold style body ranges
*/
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
body = string()
if (random.nextBoolean()) {
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
}
if (random.nextBoolean()) {
addBodyRanges(
SignalServiceProtos.BodyRange.newBuilder().buildWith {
start = 0
length = 1
style = SignalServiceProtos.BodyRange.Style.BOLD
}
bodyRanges(
listOf(
BodyRange.Builder().buildWith {
start = 0
length = 1
style = BodyRange.Style.BOLD
}
)
)
}
if (groupContextV2 != null) {
@@ -95,16 +96,16 @@ object MessageContentFuzzer {
recipientUpdate: Boolean = false
): Content {
return Content
.newBuilder()
.setSyncMessage(
SyncMessage.newBuilder().buildWith {
sent = SyncMessage.Sent.newBuilder().buildWith {
.Builder()
.syncMessage(
SyncMessage.Builder().buildWith {
sent = SyncMessage.Sent.Builder().buildWith {
timestamp = textMessage.timestamp
message = textMessage
isRecipientUpdate = recipientUpdate
addAllUnidentifiedStatus(
unidentifiedStatus(
deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
SyncMessage.Sent.UnidentifiedDeliveryStatus.Builder().buildWith {
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true
}
@@ -123,9 +124,9 @@ object MessageContentFuzzer {
* - A message with 0-2 attachment pointers and may contain a text body
*/
fun fuzzMediaMessageWithBody(quoteAble: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextBoolean()) {
body = string()
}
@@ -133,28 +134,28 @@ object MessageContentFuzzer {
if (random.nextBoolean() && quoteAble.isNotEmpty()) {
body = string()
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
quote = DataMessage.Quote.Builder().buildWith {
id = quoted.envelope.timestamp
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
addAllAttachments(quoted.content.dataMessage.attachmentsList)
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
text = quoted.content.dataMessage?.body
attachments(quoted.content.dataMessage?.attachments ?: emptyList())
bodyRanges(quoted.content.dataMessage?.bodyRanges ?: emptyList())
type = DataMessage.Quote.Type.NORMAL
}
}
if (random.nextFloat() < 0.1 && quoteAble.isNotEmpty()) {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
quote = DataMessage.Quote.Builder().buildWith {
id = random.nextLong(quoted.envelope.timestamp!! - 1000000, quoted.envelope.timestamp!!)
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
text = quoted.content.dataMessage?.body
}
}
if (random.nextFloat() < 0.25) {
val total = random.nextInt(1, 2)
(0..total).forEach { _ -> addAttachments(attachmentPointer()) }
attachments((0..total).map { attachmentPointer() })
}
}
)
@@ -166,12 +167,12 @@ object MessageContentFuzzer {
* - A reaction to a prior message
*/
fun fuzzMediaMessageNoContent(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.25) {
val reactTo = previousMessages.random(random)
reaction = DataMessage.Reaction.newBuilder().buildWith {
reaction = DataMessage.Reaction.Builder().buildWith {
emoji = emojis.random(random)
remove = false
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
@@ -187,15 +188,15 @@ object MessageContentFuzzer {
* - A sticker
*/
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
return Content.newBuilder()
.setDataMessage(
DataMessage.newBuilder().buildWith {
return Content.Builder()
.dataMessage(
DataMessage.Builder().buildWith {
if (random.nextFloat() < 0.9) {
sticker = DataMessage.Sticker.newBuilder().buildWith {
sticker = DataMessage.Sticker.Builder().buildWith {
packId = byteString(length = 24)
packKey = byteString(length = 128)
stickerId = random.nextInt()
data = attachmentPointer()
data_ = attachmentPointer()
emoji = emojis.random(random)
}
}
@@ -223,14 +224,14 @@ object MessageContentFuzzer {
* Generate a random [ByteString].
*/
fun byteString(length: Int = 512): ByteString {
return random.nextBytes(length).toProtoByteString()
return random.nextBytes(length).toByteString()
}
/**
* Generate a random [AttachmentPointer].
*/
fun attachmentPointer(): AttachmentPointer {
return AttachmentPointer.newBuilder().run {
return AttachmentPointer.Builder().run {
cdnKey = string()
contentType = mediaTypes.random(random)
key = byteString()

View File

@@ -1,22 +1,12 @@
package org.thoughtcrime.securesms.testing
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.util.KeyHelper
import org.signal.libsignal.protocol.util.Medium
import org.signal.libsignal.svr2.PinHash
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.SvrPinData
import org.whispersystems.signalservice.api.kbs.MasterKey
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.push.SignedPreKeyEntity
@@ -78,18 +68,6 @@ object MockProvider {
}
}
fun mockGetRegistrationLockStringFlow() {
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): SvrPinData = SvrPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(anyOrNull(), anyOrNull()) } doReturn session
}
}
fun createPreKeyResponse(identity: IdentityKeyPair = SignalStore.account().aciIdentityKey, deviceId: Int): PreKeyResponse {
val signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(SecureRandom().nextInt(Medium.MAX_VALUE), identity.privateKey)
val oneTimePreKey = PreKeyRecord(SecureRandom().nextInt(Medium.MAX_VALUE), Curve.generateKeyPair())

View File

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

View File

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

View File

@@ -2,9 +2,10 @@ package org.signal.benchmark.setup
import org.thoughtcrime.securesms.attachments.PointerAttachment
import org.thoughtcrime.securesms.database.AttachmentTable
import org.thoughtcrime.securesms.database.MessageType
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.TestDbUtils
import org.thoughtcrime.securesms.mms.IncomingMediaMessage
import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.mms.QuoteModel
import org.thoughtcrime.securesms.recipients.Recipient
@@ -65,7 +66,8 @@ object TestMessages {
return insert
}
fun insertIncomingTextMessage(other: Recipient, body: String, timestamp: Long? = null) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -73,10 +75,11 @@ object TestMessages {
receivedTimeMillis = timestamp ?: System.currentTimeMillis()
)
SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(other)).get().messageId
}
fun insertIncomingQuoteTextMessage(other: Recipient, body: String, quote: QuoteModel, timestamp: Long?) {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
body = body,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
@@ -90,28 +93,30 @@ object TestMessages {
val attachments: List<SignalServiceAttachmentPointer> = (0 until attachmentCount).map {
imageAttachment()
}
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(attachments))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = failed)
return insertIncomingMessage(recipient = other, message = message, failed = failed)
}
fun insertIncomingVoiceMessage(other: Recipient, timestamp: Long? = null): Long {
val message = IncomingMediaMessage(
val message = IncomingMessage(
type = MessageType.NORMAL,
from = other.id,
sentTimeMillis = timestamp ?: System.currentTimeMillis(),
serverTimeMillis = timestamp ?: System.currentTimeMillis(),
receivedTimeMillis = timestamp ?: System.currentTimeMillis(),
attachments = PointerAttachment.forPointers(Optional.of(Collections.singletonList(voiceAttachment()) as List<SignalServiceAttachment>))
)
return insertIncomingMediaMessage(recipient = other, message = message, failed = false)
return insertIncomingMessage(recipient = other, message = message, failed = false)
}
private fun insertIncomingMediaMessage(recipient: Recipient, message: IncomingMediaMessage, failed: Boolean = false): Long {
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage, failed: Boolean = false): Long {
val id = insertIncomingMessage(recipient = recipient, message = message)
if (failed) {
setMessageMediaFailed(id)
@@ -122,8 +127,8 @@ object TestMessages {
return id
}
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMediaMessage): Long {
return SignalDatabase.messages.insertSecureDecryptedMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
private fun insertIncomingMessage(recipient: Recipient, message: IncomingMessage): Long {
return SignalDatabase.messages.insertMessageInbox(message, SignalDatabase.threads.getOrCreateThreadIdFor(recipient)).get().messageId
}
private fun setMessageMediaFailed(messageId: Long) {
@@ -149,6 +154,7 @@ object TestMessages {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.jpg"),
false,
false,
@@ -171,6 +177,7 @@ object TestMessages {
1024,
Optional.empty(),
Optional.empty(),
0,
Optional.of("/not-there.aac"),
true,
false,

View File

@@ -44,6 +44,8 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
@@ -94,6 +96,10 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@@ -246,6 +252,7 @@
<activity-alias android:name=".RoutingActivity"
android:targetActivity=".MainActivity"
android:resizeableActivity="true"
android:exported="true">
<intent-filter>
@@ -624,6 +631,7 @@
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity"
android:resizeableActivity="true"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
@@ -955,7 +963,7 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".profiles.edit.EditProfileActivity"
<activity android:name=".profiles.edit.CreateProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
@@ -965,7 +973,7 @@
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".profiles.manage.ManageProfileActivity"
<activity android:name=".profiles.manage.EditProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
@@ -1023,6 +1031,7 @@
<activity android:name=".MainActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:resizeableActivity="true"
android:exported="false"/>
<activity android:name=".pin.PinRestoreActivity"
@@ -1180,6 +1189,10 @@
android:name=".service.GenericForegroundService"
android:exported="false"/>
<service
android:name=".service.AttachmentProgressService"
android:exported="false"/>
<service
android:name=".gcm.FcmFetchBackgroundService"
android:exported="false"/>
@@ -1194,18 +1207,6 @@
</intent-filter>
</service>
<receiver android:name=".service.SmsListener"
android:permission="android.permission.BROADCAST_SMS"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.SMS_DELIVER"/>
</intent-filter>
</receiver>
<receiver android:name=".service.SmsDeliveryListener"
android:exported="true">
<intent-filter>
@@ -1213,20 +1214,6 @@
</intent-filter>
</receiver>
<receiver android:name=".service.MmsListener"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BROADCAST_WAP_PUSH">
<intent-filter android:priority="1001">
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
<intent-filter>
<action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
android:exported="false">

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,800 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conscrypt;
import java.nio.ByteBuffer;
import java.security.KeyManagementException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLContextSpi;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* Core API for creating and configuring all Conscrypt types.
* This is identical to the original Conscrypt.java, except with the slow
* version initialization code removed.
*/
@SuppressWarnings("unused")
public final class ConscryptSignal {
private ConscryptSignal() {}
/**
* Returns {@code true} if the Conscrypt native library has been successfully loaded.
*/
public static boolean isAvailable() {
try {
checkAvailability();
return true;
} catch (Throwable e) {
return false;
}
}
// BEGIN MODIFICATION
/*public static class Version {
private final int major;
private final int minor;
private final int patch;
private Version(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
}
public int major() { return major; }
public int minor() { return minor; }
public int patch() { return patch; }
}
private static final Version VERSION;
static {
int major = -1;
int minor = -1;
int patch = -1;
InputStream stream = null;
try {
stream = Conscrypt.class.getResourceAsStream("conscrypt.properties");
if (stream != null) {
Properties props = new Properties();
props.load(stream);
major = Integer.parseInt(props.getProperty("org.conscrypt.version.major", "-1"));
minor = Integer.parseInt(props.getProperty("org.conscrypt.version.minor", "-1"));
patch = Integer.parseInt(props.getProperty("org.conscrypt.version.patch", "-1"));
}
} catch (IOException e) {
// TODO(prb): This should probably be fatal or have some fallback behaviour
} finally {
IoUtils.closeQuietly(stream);
}
if ((major >= 0) && (minor >= 0) && (patch >= 0)) {
VERSION = new Version(major, minor, patch);
} else {
VERSION = null;
}
}
/**
* Returns the version of this distribution of Conscrypt. If version information is
* unavailable, returns {@code null}.
*/
/*public static Version version() {
return VERSION;
}*/
// END MODIFICATION
/**
* Checks that the Conscrypt support is available for the system.
*
* @throws UnsatisfiedLinkError if unavailable
*/
public static void checkAvailability() {
NativeCrypto.checkAvailability();
}
/**
* Indicates whether the given {@link Provider} was created by this distribution of Conscrypt.
*/
public static boolean isConscrypt(Provider provider) {
return provider instanceof OpenSSLProvider;
}
/**
* Constructs a new {@link Provider} with the default name.
*/
public static Provider newProvider() {
checkAvailability();
return new OpenSSLProvider();
}
/**
* Constructs a new {@link Provider} with the given name.
*
* @deprecated Use {@link #newProviderBuilder()} instead.
*/
@Deprecated
public static Provider newProvider(String providerName) {
checkAvailability();
return newProviderBuilder().setName(providerName).build();
}
public static class ProviderBuilder {
private String name = Platform.getDefaultProviderName();
private boolean provideTrustManager = Platform.provideTrustManagerByDefault();
private String defaultTlsProtocol = NativeCrypto.SUPPORTED_PROTOCOL_TLSV1_3;
private ProviderBuilder() {}
/**
* Sets the name of the Provider to be built.
*/
public ProviderBuilder setName(String name) {
this.name = name;
return this;
}
/**
* Causes the returned provider to provide an implementation of
* {@link javax.net.ssl.TrustManagerFactory}.
* @deprecated Use provideTrustManager(true)
*/
@Deprecated
public ProviderBuilder provideTrustManager() {
return provideTrustManager(true);
}
/**
* Specifies whether the returned provider will provide an implementation of
* {@link javax.net.ssl.TrustManagerFactory}.
*/
public ProviderBuilder provideTrustManager(boolean provide) {
this.provideTrustManager = provide;
return this;
}
/**
* Specifies what the default TLS protocol should be for SSLContext identifiers
* {@code TLS}, {@code SSL}, and {@code Default}.
*/
public ProviderBuilder defaultTlsProtocol(String defaultTlsProtocol) {
this.defaultTlsProtocol = defaultTlsProtocol;
return this;
}
public Provider build() {
return new OpenSSLProvider(name, provideTrustManager, defaultTlsProtocol);
}
}
public static ProviderBuilder newProviderBuilder() {
return new ProviderBuilder();
}
/**
* Returns the maximum length (in bytes) of an encrypted packet.
*/
public static int maxEncryptedPacketLength() {
return NativeConstants.SSL3_RT_MAX_PACKET_SIZE;
}
/**
* Gets the default X.509 trust manager.
*/
@ExperimentalApi
public static X509TrustManager getDefaultX509TrustManager() throws KeyManagementException {
checkAvailability();
return SSLParametersImpl.getDefaultX509TrustManager();
}
/**
* Indicates whether the given {@link SSLContext} was created by this distribution of Conscrypt.
*/
public static boolean isConscrypt(SSLContext context) {
return context.getProvider() instanceof OpenSSLProvider;
}
/**
* Constructs a new instance of the preferred {@link SSLContextSpi}.
*/
public static SSLContextSpi newPreferredSSLContextSpi() {
checkAvailability();
return OpenSSLContextImpl.getPreferred();
}
/**
* Sets the client-side persistent cache to be used by the context.
*/
public static void setClientSessionCache(SSLContext context, SSLClientSessionCache cache) {
SSLSessionContext clientContext = context.getClientSessionContext();
if (!(clientContext instanceof ClientSessionContext)) {
throw new IllegalArgumentException(
"Not a conscrypt client context: " + clientContext.getClass().getName());
}
((ClientSessionContext) clientContext).setPersistentCache(cache);
}
/**
* Sets the server-side persistent cache to be used by the context.
*/
public static void setServerSessionCache(SSLContext context, SSLServerSessionCache cache) {
SSLSessionContext serverContext = context.getServerSessionContext();
if (!(serverContext instanceof ServerSessionContext)) {
throw new IllegalArgumentException(
"Not a conscrypt client context: " + serverContext.getClass().getName());
}
((ServerSessionContext) serverContext).setPersistentCache(cache);
}
/**
* Indicates whether the given {@link SSLSocketFactory} was created by this distribution of
* Conscrypt.
*/
public static boolean isConscrypt(SSLSocketFactory factory) {
return factory instanceof OpenSSLSocketFactoryImpl;
}
private static OpenSSLSocketFactoryImpl toConscrypt(SSLSocketFactory factory) {
if (!isConscrypt(factory)) {
throw new IllegalArgumentException(
"Not a conscrypt socket factory: " + factory.getClass().getName());
}
return (OpenSSLSocketFactoryImpl) factory;
}
/**
* Configures the default socket to be created for all socket factory instances.
*/
@ExperimentalApi
public static void setUseEngineSocketByDefault(boolean useEngineSocket) {
OpenSSLSocketFactoryImpl.setUseEngineSocketByDefault(useEngineSocket);
OpenSSLServerSocketFactoryImpl.setUseEngineSocketByDefault(useEngineSocket);
}
/**
* Configures the socket to be created for the given socket factory instance.
*/
@ExperimentalApi
public static void setUseEngineSocket(SSLSocketFactory factory, boolean useEngineSocket) {
toConscrypt(factory).setUseEngineSocket(useEngineSocket);
}
/**
* Indicates whether the given {@link SSLServerSocketFactory} was created by this distribution
* of Conscrypt.
*/
public static boolean isConscrypt(SSLServerSocketFactory factory) {
return factory instanceof OpenSSLServerSocketFactoryImpl;
}
private static OpenSSLServerSocketFactoryImpl toConscrypt(SSLServerSocketFactory factory) {
if (!isConscrypt(factory)) {
throw new IllegalArgumentException(
"Not a conscrypt server socket factory: " + factory.getClass().getName());
}
return (OpenSSLServerSocketFactoryImpl) factory;
}
/**
* Configures the socket to be created for the given server socket factory instance.
*/
@ExperimentalApi
public static void setUseEngineSocket(SSLServerSocketFactory factory, boolean useEngineSocket) {
toConscrypt(factory).setUseEngineSocket(useEngineSocket);
}
/**
* Indicates whether the given {@link SSLSocket} was created by this distribution of Conscrypt.
*/
public static boolean isConscrypt(SSLSocket socket) {
return socket instanceof AbstractConscryptSocket;
}
private static AbstractConscryptSocket toConscrypt(SSLSocket socket) {
if (!isConscrypt(socket)) {
throw new IllegalArgumentException(
"Not a conscrypt socket: " + socket.getClass().getName());
}
return (AbstractConscryptSocket) socket;
}
/**
* This method enables Server Name Indication (SNI) and overrides the hostname supplied
* during socket creation. If the hostname is not a valid SNI hostname, the SNI extension
* will be omitted from the handshake.
*
* @param socket the socket
* @param hostname the desired SNI hostname, or null to disable
*/
public static void setHostname(SSLSocket socket, String hostname) {
toConscrypt(socket).setHostname(hostname);
}
/**
* Returns either the hostname supplied during socket creation or via
* {@link #setHostname(SSLSocket, String)}. No DNS resolution is attempted before
* returning the hostname.
*/
public static String getHostname(SSLSocket socket) {
return toConscrypt(socket).getHostname();
}
/**
* This method attempts to create a textual representation of the peer host or IP. Does
* not perform a reverse DNS lookup. This is typically used during session creation.
*/
public static String getHostnameOrIP(SSLSocket socket) {
return toConscrypt(socket).getHostnameOrIP();
}
/**
* This method enables session ticket support.
*
* @param socket the socket
* @param useSessionTickets True to enable session tickets
*/
public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTickets) {
toConscrypt(socket).setUseSessionTickets(useSessionTickets);
}
/**
* Enables/disables TLS Channel ID for the given server-side socket.
*
* <p>This method needs to be invoked before the handshake starts.
*
* @param socket the socket
* @param enabled Whether to enable channel ID.
* @throws IllegalStateException if this is a client socket or if the handshake has already
* started.
*/
public static void setChannelIdEnabled(SSLSocket socket, boolean enabled) {
toConscrypt(socket).setChannelIdEnabled(enabled);
}
/**
* Gets the TLS Channel ID for the given server-side socket. Channel ID is only available
* once the handshake completes.
*
* @param socket the socket
* @return channel ID or {@code null} if not available.
* @throws IllegalStateException if this is a client socket or if the handshake has not yet
* completed.
* @throws SSLException if channel ID is available but could not be obtained.
*/
public static byte[] getChannelId(SSLSocket socket) throws SSLException {
return toConscrypt(socket).getChannelId();
}
/**
* Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket.
*
* <p>This method needs to be invoked before the handshake starts.
*
* @param socket the socket
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key
* (disables TLS Channel ID).
* The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka
* SECG secp256r1 or ANSI
* X9.62 prime256v1).
* @throws IllegalStateException if this is a server socket or if the handshake has already
* started.
*/
public static void setChannelIdPrivateKey(SSLSocket socket, PrivateKey privateKey) {
toConscrypt(socket).setChannelIdPrivateKey(privateKey);
}
/**
* Returns the ALPN protocol agreed upon by client and server.
*
* @param socket the socket
* @return the selected protocol or {@code null} if no protocol was agreed upon.
*/
public static String getApplicationProtocol(SSLSocket socket) {
return toConscrypt(socket).getApplicationProtocol();
}
/**
* Sets an application-provided ALPN protocol selector. If provided, this will override
* the list of protocols set by {@link #setApplicationProtocols(SSLSocket, String[])}.
*
* @param socket the socket
* @param selector the ALPN protocol selector
*/
public static void setApplicationProtocolSelector(SSLSocket socket,
ApplicationProtocolSelector selector) {
toConscrypt(socket).setApplicationProtocolSelector(selector);
}
/**
* Sets the application-layer protocols (ALPN) in prioritization order.
*
* @param socket the socket being configured
* @param protocols the protocols in descending order of preference. If empty, no protocol
* indications will be used. This array will be copied.
* @throws IllegalArgumentException - if protocols is null, or if any element in a non-empty
* array is null or an empty (zero-length) string
*/
public static void setApplicationProtocols(SSLSocket socket, String[] protocols) {
toConscrypt(socket).setApplicationProtocols(protocols);
}
/**
* Gets the application-layer protocols (ALPN) in prioritization order.
*
* @param socket the socket
* @return the protocols in descending order of preference, or an empty array if protocol
* indications are not being used. Always returns a new array.
*/
public static String[] getApplicationProtocols(SSLSocket socket) {
return toConscrypt(socket).getApplicationProtocols();
}
/**
* Returns the tls-unique channel binding value for this connection, per RFC 5929. This
* will return {@code null} if there is no such value available, such as if the handshake
* has not yet completed or this connection is closed.
*/
public static byte[] getTlsUnique(SSLSocket socket) {
return toConscrypt(socket).getTlsUnique();
}
/**
* Exports a value derived from the TLS master secret as described in RFC 5705.
*
* @param label the label to use in calculating the exported value. This must be
* an ASCII-only string.
* @param context the application-specific context value to use in calculating the
* exported value. This may be {@code null} to use no application context, which is
* treated differently than an empty byte array.
* @param length the number of bytes of keying material to return.
* @return a value of the specified length, or {@code null} if the handshake has not yet
* completed or the connection has been closed.
* @throws SSLException if the value could not be exported.
*/
public static byte[] exportKeyingMaterial(SSLSocket socket, String label, byte[] context,
int length) throws SSLException {
return toConscrypt(socket).exportKeyingMaterial(label, context, length);
}
/**
* Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt.
*/
public static boolean isConscrypt(SSLEngine engine) {
return engine instanceof AbstractConscryptEngine;
}
private static AbstractConscryptEngine toConscrypt(SSLEngine engine) {
if (!isConscrypt(engine)) {
throw new IllegalArgumentException(
"Not a conscrypt engine: " + engine.getClass().getName());
}
return (AbstractConscryptEngine) engine;
}
/**
* Provides the given engine with the provided bufferAllocator.
* @throws IllegalArgumentException if the provided engine is not a Conscrypt engine.
* @throws IllegalStateException if the provided engine has already begun its handshake.
*/
@ExperimentalApi
public static void setBufferAllocator(SSLEngine engine, BufferAllocator bufferAllocator) {
toConscrypt(engine).setBufferAllocator(bufferAllocator);
}
/**
* Provides the given socket with the provided bufferAllocator. If the given socket is a
* Conscrypt socket but does not use buffer allocators, this method does nothing.
* @throws IllegalArgumentException if the provided socket is not a Conscrypt socket.
* @throws IllegalStateException if the provided socket has already begun its handshake.
*/
@ExperimentalApi
public static void setBufferAllocator(SSLSocket socket, BufferAllocator bufferAllocator) {
AbstractConscryptSocket s = toConscrypt(socket);
if (s instanceof ConscryptEngineSocket) {
((ConscryptEngineSocket) s).setBufferAllocator(bufferAllocator);
}
}
/**
* Configures the default {@link BufferAllocator} to be used by all future
* {@link SSLEngine} instances from this provider.
*/
@ExperimentalApi
public static void setDefaultBufferAllocator(BufferAllocator bufferAllocator) {
ConscryptEngine.setDefaultBufferAllocator(bufferAllocator);
}
/**
* This method enables Server Name Indication (SNI) and overrides the hostname supplied
* during engine creation.
*
* @param engine the engine
* @param hostname the desired SNI hostname, or {@code null} to disable
*/
public static void setHostname(SSLEngine engine, String hostname) {
toConscrypt(engine).setHostname(hostname);
}
/**
* Returns either the hostname supplied during socket creation or via
* {@link #setHostname(SSLEngine, String)}. No DNS resolution is attempted before
* returning the hostname.
*/
public static String getHostname(SSLEngine engine) {
return toConscrypt(engine).getHostname();
}
/**
* Returns the maximum overhead, in bytes, of sealing a record with SSL.
*/
public static int maxSealOverhead(SSLEngine engine) {
return toConscrypt(engine).maxSealOverhead();
}
/**
* Sets a listener on the given engine for completion of the TLS handshake
*/
public static void setHandshakeListener(SSLEngine engine, HandshakeListener handshakeListener) {
toConscrypt(engine).setHandshakeListener(handshakeListener);
}
/**
* Enables/disables TLS Channel ID for the given server-side engine.
*
* <p>This method needs to be invoked before the handshake starts.
*
* @param engine the engine
* @param enabled Whether to enable channel ID.
* @throws IllegalStateException if this is a client engine or if the handshake has already
* started.
*/
public static void setChannelIdEnabled(SSLEngine engine, boolean enabled) {
toConscrypt(engine).setChannelIdEnabled(enabled);
}
/**
* Gets the TLS Channel ID for the given server-side engine. Channel ID is only available
* once the handshake completes.
*
* @param engine the engine
* @return channel ID or {@code null} if not available.
* @throws IllegalStateException if this is a client engine or if the handshake has not yet
* completed.
* @throws SSLException if channel ID is available but could not be obtained.
*/
public static byte[] getChannelId(SSLEngine engine) throws SSLException {
return toConscrypt(engine).getChannelId();
}
/**
* Sets the {@link PrivateKey} to be used for TLS Channel ID by this client engine.
*
* <p>This method needs to be invoked before the handshake starts.
*
* @param engine the engine
* @param privateKey private key (enables TLS Channel ID) or {@code null} for no key
* (disables TLS Channel ID).
* The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka
* SECG secp256r1 or ANSI X9.62 prime256v1).
* @throws IllegalStateException if this is a server engine or if the handshake has already
* started.
*/
public static void setChannelIdPrivateKey(SSLEngine engine, PrivateKey privateKey) {
toConscrypt(engine).setChannelIdPrivateKey(privateKey);
}
/**
* Extended unwrap method for multiple source and destination buffers.
*
* @param engine the target engine for the unwrap
* @param srcs the source buffers
* @param dsts the destination buffers
* @return the result of the unwrap operation
* @throws SSLException thrown if an SSL error occurred
*/
public static SSLEngineResult unwrap(SSLEngine engine, final ByteBuffer[] srcs,
final ByteBuffer[] dsts) throws SSLException {
return toConscrypt(engine).unwrap(srcs, dsts);
}
/**
* Exteneded unwrap method for multiple source and destination buffers.
*
* @param engine the target engine for the unwrap.
* @param srcs the source buffers
* @param srcsOffset the offset in the {@code srcs} array of the first source buffer
* @param srcsLength the number of source buffers starting at {@code srcsOffset}
* @param dsts the destination buffers
* @param dstsOffset the offset in the {@code dsts} array of the first destination buffer
* @param dstsLength the number of destination buffers starting at {@code dstsOffset}
* @return the result of the unwrap operation
* @throws SSLException thrown if an SSL error occurred
*/
public static SSLEngineResult unwrap(SSLEngine engine, final ByteBuffer[] srcs, int srcsOffset,
final int srcsLength, final ByteBuffer[] dsts, final int dstsOffset,
final int dstsLength) throws SSLException {
return toConscrypt(engine).unwrap(
srcs, srcsOffset, srcsLength, dsts, dstsOffset, dstsLength);
}
/**
* This method enables session ticket support.
*
* @param engine the engine
* @param useSessionTickets True to enable session tickets
*/
public static void setUseSessionTickets(SSLEngine engine, boolean useSessionTickets) {
toConscrypt(engine).setUseSessionTickets(useSessionTickets);
}
/**
* Sets the application-layer protocols (ALPN) in prioritization order.
*
* @param engine the engine being configured
* @param protocols the protocols in descending order of preference. If empty, no protocol
* indications will be used. This array will be copied.
* @throws IllegalArgumentException - if protocols is null, or if any element in a non-empty
* array is null or an empty (zero-length) string
*/
public static void setApplicationProtocols(SSLEngine engine, String[] protocols) {
toConscrypt(engine).setApplicationProtocols(protocols);
}
/**
* Gets the application-layer protocols (ALPN) in prioritization order.
*
* @param engine the engine
* @return the protocols in descending order of preference, or an empty array if protocol
* indications are not being used. Always returns a new array.
*/
public static String[] getApplicationProtocols(SSLEngine engine) {
return toConscrypt(engine).getApplicationProtocols();
}
/**
* Sets an application-provided ALPN protocol selector. If provided, this will override
* the list of protocols set by {@link #setApplicationProtocols(SSLEngine, String[])}.
*
* @param engine the engine
* @param selector the ALPN protocol selector
*/
public static void setApplicationProtocolSelector(SSLEngine engine,
ApplicationProtocolSelector selector) {
toConscrypt(engine).setApplicationProtocolSelector(selector);
}
/**
* Returns the ALPN protocol agreed upon by client and server.
*
* @param engine the engine
* @return the selected protocol or {@code null} if no protocol was agreed upon.
*/
public static String getApplicationProtocol(SSLEngine engine) {
return toConscrypt(engine).getApplicationProtocol();
}
/**
* Returns the tls-unique channel binding value for this connection, per RFC 5929. This
* will return {@code null} if there is no such value available, such as if the handshake
* has not yet completed or this connection is closed.
*/
public static byte[] getTlsUnique(SSLEngine engine) {
return toConscrypt(engine).getTlsUnique();
}
/**
* Exports a value derived from the TLS master secret as described in RFC 5705.
*
* @param label the label to use in calculating the exported value. This must be
* an ASCII-only string.
* @param context the application-specific context value to use in calculating the
* exported value. This may be {@code null} to use no application context, which is
* treated differently than an empty byte array.
* @param length the number of bytes of keying material to return.
* @return a value of the specified length, or {@code null} if the handshake has not yet
* completed or the connection has been closed.
* @throws SSLException if the value could not be exported.
*/
public static byte[] exportKeyingMaterial(SSLEngine engine, String label, byte[] context,
int length) throws SSLException {
return toConscrypt(engine).exportKeyingMaterial(label, context, length);
}
/**
* Indicates whether the given {@link TrustManager} was created by this distribution of
* Conscrypt.
*/
public static boolean isConscrypt(TrustManager trustManager) {
return trustManager instanceof TrustManagerImpl;
}
private static TrustManagerImpl toConscrypt(TrustManager trustManager) {
if (!isConscrypt(trustManager)) {
throw new IllegalArgumentException(
"Not a Conscrypt trust manager: " + trustManager.getClass().getName());
}
return (TrustManagerImpl) trustManager;
}
/**
* Set the default hostname verifier that will be used for HTTPS endpoint identification by
* Conscrypt trust managers. If {@code null} (the default), endpoint identification will use
* the default hostname verifier set in
* {@link HttpsURLConnection#setDefaultHostnameVerifier(javax.net.ssl.HostnameVerifier)}.
*/
public synchronized static void setDefaultHostnameVerifier(ConscryptHostnameVerifier verifier) {
TrustManagerImpl.setDefaultHostnameVerifier(verifier);
}
/**
* Returns the currently-set default hostname verifier for Conscrypt trust managers.
*
* @see #setDefaultHostnameVerifier(ConscryptHostnameVerifier)
*/
public synchronized static ConscryptHostnameVerifier getDefaultHostnameVerifier(TrustManager trustManager) {
return TrustManagerImpl.getDefaultHostnameVerifier();
}
/**
* Set the hostname verifier that will be used for HTTPS endpoint identification by the
* given trust manager. If {@code null} (the default), endpoint identification will use the
* default hostname verifier set in {@link #setDefaultHostnameVerifier(ConscryptHostnameVerifier)}.
*
* @throws IllegalArgumentException if the provided trust manager is not a Conscrypt trust
* manager per {@link #isConscrypt(TrustManager)}
*/
public static void setHostnameVerifier(TrustManager trustManager, ConscryptHostnameVerifier verifier) {
toConscrypt(trustManager).setHostnameVerifier(verifier);
}
/**
* Returns the currently-set hostname verifier for the given trust manager.
*
* @throws IllegalArgumentException if the provided trust manager is not a Conscrypt trust
* manager per {@link #isConscrypt(TrustManager)}
*
* @see #setHostnameVerifier(TrustManager, ConscryptHostnameVerifier)
*/
public static ConscryptHostnameVerifier getHostnameVerifier(TrustManager trustManager) {
return toConscrypt(trustManager).getHostnameVerifier();
}
/**
* Wraps the HttpsURLConnection.HostnameVerifier into a ConscryptHostnameVerifier
*/
public static ConscryptHostnameVerifier wrapHostnameVerifier(final HostnameVerifier verifier) {
return new ConscryptHostnameVerifier() {
@Override
public boolean verify(X509Certificate[] certificates, String hostname, SSLSession session) {
return verifier.verify(hostname, session);
}
};
}
}

View File

@@ -26,10 +26,11 @@ import androidx.multidex.MultiDexApplication;
import com.google.android.gms.security.ProviderInstaller;
import org.conscrypt.Conscrypt;
import org.conscrypt.ConscryptSignal;
import org.greenrobot.eventbus.EventBus;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.signal.core.util.MemoryTracker;
import org.signal.core.util.concurrent.AnrDetector;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log;
@@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob;
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
import org.thoughtcrime.securesms.jobs.ExternalLaunchDonationJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.FontDownloaderJob;
import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
@@ -83,15 +85,13 @@ import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.apkupdate.ApkUpdateRefreshListener;
import org.thoughtcrime.securesms.service.webrtc.AndroidTelecomUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.PowerManagerCompat;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SignalLocalMetrics;
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -110,6 +110,7 @@ import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
import io.reactivex.rxjava3.exceptions.UndeliverableException;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;
import io.reactivex.rxjava3.schedulers.Schedulers;
import kotlin.Unit;
import rxdogtag2.RxDogTag;
/**
@@ -124,9 +125,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
private static final String TAG = Log.tag(ApplicationContext.class);
@VisibleForTesting
protected PersistentLogger persistentLogger;
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
@@ -155,6 +153,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
initializeLogging();
Log.i(TAG, "onCreate()");
})
.addBlocking("anr-detector", this::startAnrDetector)
.addBlocking("security-provider", this::initializeSecurityProvider)
.addBlocking("crash-handling", this::initializeCrashHandling)
.addBlocking("rx-init", this::initializeRx)
@@ -169,7 +168,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addBlocking("proxy-init", () -> {
if (SignalStore.proxy().isProxyEnabled()) {
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
Conscrypt.setUseEngineSocketByDefault(true);
ConscryptSignal.setUseEngineSocketByDefault(true);
}
})
.addBlocking("blob-provider", this::initializeBlobProvider)
@@ -229,7 +228,9 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
ApplicationDependencies.getDeadlockDetector().start();
SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary();
ExternalLaunchDonationJob.enqueueIfNecessary();
FcmFetchManager.onForeground(this);
startAnrDetector();
SignalExecutors.BOUNDED.execute(() -> {
FeatureFlags.refreshIfNecessary();
@@ -263,10 +264,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getShakeToReport().disable();
ApplicationDependencies.getDeadlockDetector().stop();
MemoryTracker.stop();
}
public PersistentLogger getPersistentLogger() {
return persistentLogger;
AnrDetector.stop();
}
public void checkBuildExpiration() {
@@ -276,6 +274,17 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
}
}
/**
* Note: this is purposefully "started" twice -- once during application create, and once during foreground.
* This is so we can capture ANR's that happen on boot before the foreground event.
*/
private void startAnrDetector() {
AnrDetector.start(TimeUnit.SECONDS.toMillis(5), FeatureFlags::internalUser, (dumps) -> {
LogDatabase.getInstance(this).anrs().save(System.currentTimeMillis(), dumps);
return Unit.INSTANCE;
});
}
private void initializeSecurityProvider() {
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
@@ -285,7 +294,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
throw new ProviderInitializationException();
}
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
int conscryptPosition = Security.insertProviderAt(ConscryptSignal.newProvider(), 2);
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
if (conscryptPosition < 0) {
@@ -295,14 +304,14 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
@VisibleForTesting
protected void initializeLogging() {
persistentLogger = new PersistentLogger(this);
org.signal.core.util.logging.Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), persistentLogger);
Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), new PersistentLogger(this));
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
SignalExecutors.UNBOUNDED.execute(() -> {
Log.blockUntilAllWritesFinished();
LogDatabase.getInstance(this).trimToSize();
LogDatabase.getInstance(this).logs().trimToSize();
LogDatabase.getInstance(this).crashes().trimToSize();
});
}
@@ -406,8 +415,8 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
RotateSenderCertificateListener.schedule(this);
RoutineMessageFetchReceiver.startOrUpdateAlarm(this);
if (BuildConfig.PLAY_STORE_DISABLED) {
UpdateApkRefreshListener.schedule(this);
if (BuildConfig.MANAGES_APP_UPDATES) {
ApkUpdateRefreshListener.schedule(this);
}
}

View File

@@ -6,7 +6,6 @@ import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.transition.TransitionInflater;
import android.view.View;

View File

@@ -5,7 +5,6 @@ import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
@@ -18,7 +17,6 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.util.AppStartup;
import org.thoughtcrime.securesms.util.ConfigurationUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;

View File

@@ -57,6 +57,10 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void setEventListener(@Nullable EventListener listener);
default void setParentScrolling(boolean isParentScrolling) {
// Intentionally Blank.
}
default void updateTimestamps() {
// Intentionally Blank.
}

View File

@@ -35,7 +35,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
@@ -50,6 +49,8 @@ import androidx.transition.TransitionManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.RxExtensions;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
@@ -70,10 +71,11 @@ import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.groups.SelectionLimits;
import org.thoughtcrime.securesms.groups.ui.GroupLimitDialog;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository;
import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameAciFetchResult;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -85,6 +87,7 @@ import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -145,6 +148,8 @@ public final class ContactSelectionListFragment extends LoggingFragment {
private Set<RecipientId> currentSelection;
private boolean isMulti;
private boolean canSelectSelf;
private boolean resetPositionOnCommit = false;
private ListClickListener listClickListener = new ListClickListener();
@Nullable private SwipeRefreshLayout.OnRefreshListener onRefreshListener;
@@ -423,6 +428,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
onRefreshListener = null;
}
public int getSelectedMembersSize() {
return contactSearchMediator.getSelectedMembersSize();
}
private @NonNull Bundle safeArguments() {
return getArguments() != null ? getArguments() : new Bundle();
}
@@ -519,12 +528,21 @@ public final class ContactSelectionListFragment extends LoggingFragment {
}
public void setQueryFilter(String filter) {
this.cursorFilter = filter;
if (Objects.equals(filter, this.cursorFilter)) {
return;
}
this.resetPositionOnCommit = true;
this.cursorFilter = filter;
contactSearchMediator.onFilterChanged(filter);
}
public void resetQueryFilter() {
setQueryFilter(null);
this.resetPositionOnCommit = true;
swipeRefresh.setRefreshing(false);
}
@@ -542,11 +560,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
headerActionView.setVisibility(View.GONE);
}
public void setRecyclerViewPaddingBottom(@Px int paddingBottom) {
ViewUtil.setPaddingBottom(recyclerView, paddingBottom);
}
private void onLoadFinished(int count) {
if (resetPositionOnCommit) {
resetPositionOnCommit = false;
recyclerView.scrollToPosition(0);
}
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
@@ -666,11 +685,18 @@ public final class ContactSelectionListFragment extends LoggingFragment {
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
return UsernameUtil.fetchAciForUsername(username);
}, uuid -> {
try {
return RxExtensions.safeBlockingGet(UsernameRepository.fetchAciForUsername(UsernameUtil.sanitizeUsernameFromSearch(username)));
} catch (InterruptedException e) {
Log.w(TAG, "Interrupted?", e);
return UsernameAciFetchResult.NetworkError.INSTANCE;
}
}, result -> {
loadingDialog.dismiss();
if (uuid.isPresent()) {
Recipient recipient = Recipient.externalUsername(uuid.get(), username);
// TODO Could be more specific with errors
if (result instanceof UsernameAciFetchResult.Success success) {
Recipient recipient = Recipient.externalUsername(success.getAci(), username);
SelectedContact selected = SelectedContact.forUsername(recipient.getId(), username);
if (onContactSelectedListener != null) {

View File

@@ -5,13 +5,11 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@@ -32,7 +30,7 @@ import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.Base64;
import org.signal.core.util.Base64;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;

View File

@@ -25,7 +25,7 @@ import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSh
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
import org.thoughtcrime.securesms.notifications.SlowNotificationsViewModel;
import org.thoughtcrime.securesms.notifications.VitalsViewModel;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup;
@@ -45,7 +45,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
private VoiceNoteMediaController mediaController;
private ConversationListTabsViewModel conversationListTabsViewModel;
private SlowNotificationsViewModel slowNotificationsViewModel;
private VitalsViewModel vitalsViewModel;
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
@@ -99,25 +99,27 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
conversationListTabsViewModel = new ViewModelProvider(this, factory).get(ConversationListTabsViewModel.class);
updateTabVisibility();
slowNotificationsViewModel = new ViewModelProvider(this).get(SlowNotificationsViewModel.class);
vitalsViewModel = new ViewModelProvider(this).get(VitalsViewModel.class);
lifecycleDisposable.add(
slowNotificationsViewModel
.getSlowNotificationState()
.subscribe(this::presentSlowNotificationState)
vitalsViewModel
.getVitalsState()
.subscribe(this::presentVitalsState)
);
}
@SuppressLint("NewApi")
private void presentSlowNotificationState(SlowNotificationsViewModel.State slowNotificationState) {
switch (slowNotificationState) {
private void presentVitalsState(VitalsViewModel.State state) {
switch (state) {
case NONE:
break;
case PROMPT_BATTERY_SAVER_DIALOG:
PromptBatterySaverDialogFragment.show(getSupportFragmentManager());
break;
case PROMPT_DEBUGLOGS:
DebugLogsPromptDialogFragment.show(this, getSupportFragmentManager());
case PROMPT_DEBUGLOGS_FOR_NOTIFICATIONS:
DebugLogsPromptDialogFragment.show(this, DebugLogsPromptDialogFragment.Purpose.NOTIFICATIONS);
case PROMPT_DEBUGLOGS_FOR_CRASH:
DebugLogsPromptDialogFragment.show(this, DebugLogsPromptDialogFragment.Purpose.CRASH);
break;
}
}
@@ -168,7 +170,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
updateTabVisibility();
slowNotificationsViewModel.checkSlowNotificationHeuristics();
vitalsViewModel.checkSlowNotificationHeuristics();
}
@Override

View File

@@ -21,7 +21,6 @@ import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;

View File

@@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.lock.v2.CreateSvrPinActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.pin.PinRestoreActivity;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
import org.thoughtcrime.securesms.profiles.edit.CreateProfileActivity;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
@@ -228,7 +228,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
}
private Intent getCreateProfileNameIntent() {
Intent intent = EditProfileActivity.getIntentForUserProfile(this);
Intent intent = CreateProfileActivity.getIntentForUserProfile(this);
return getRoutedIntent(intent, getIntent());
}

View File

@@ -53,11 +53,12 @@ import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor;
import org.thoughtcrime.securesms.components.webrtc.CallLinkInfoSheet;
import org.thoughtcrime.securesms.components.webrtc.CallLinkProfileKeySender;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow;
@@ -73,6 +74,7 @@ import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
import org.thoughtcrime.securesms.components.webrtc.WebRtcControls;
import org.thoughtcrime.securesms.components.webrtc.WifiToCellularPopupWindow;
import org.thoughtcrime.securesms.components.webrtc.participantslist.CallParticipantsListDialog;
import org.thoughtcrime.securesms.components.webrtc.requests.CallLinkIncomingRequestSheet;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
@@ -98,6 +100,7 @@ import org.thoughtcrime.securesms.webrtc.CallParticipantsViewState;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -774,6 +777,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
return;
}
if (state.isCallLink()) {
CallLinkProfileKeySender.onSendAnyway(new HashSet<>(changedRecipients));
}
if (state.getGroupCallState().isConnected()) {
ApplicationDependencies.getSignalCallManager().groupApproveSafetyChange(changedRecipients);
} else {
@@ -1088,6 +1095,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
public void onLaunchPendingRequestsSheet() {
new PendingParticipantsBottomSheet().show(getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
}
@Override
public void onLaunchRecipientSheet(@NonNull Recipient pendingRecipient) {
CallLinkIncomingRequestSheet.show(getSupportFragmentManager(), pendingRecipient.getId());
}
}
private class WindowLayoutInfoConsumer implements Consumer<WindowLayoutInfo> {

View File

@@ -1,10 +1,10 @@
package org.thoughtcrime.securesms.absbackup.backupables
import com.google.protobuf.InvalidProtocolBufferException
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.AndroidBackupItem
import org.thoughtcrime.securesms.absbackup.protos.SvrAuthToken
import org.thoughtcrime.securesms.keyvalue.SignalStore
import java.io.IOException
/**
* This backs up the not-secret KBS Auth tokens, which can be combined with a PIN to prove ownership of a phone number in order to complete the registration process.
@@ -30,7 +30,7 @@ object SvrAuthTokens : AndroidBackupItem {
val proto = SvrAuthToken.ADAPTER.decode(data)
SignalStore.svr().putAuthTokenList(proto.tokens)
} catch (e: InvalidProtocolBufferException) {
} catch (e: IOException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* Provided to the DownloadManager as a callback receiver for when it has finished downloading the APK we're trying to install.
*
* Registered in the manifest to list to [DownloadManager.ACTION_DOWNLOAD_COMPLETE].
*/
class ApkUpdateDownloadManagerReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdateDownloadManagerReceiver::class.java)
}
override fun onReceive(context: Context, intent: Intent) {
Log.i(TAG, "onReceive()")
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE != intent.action) {
Log.i(TAG, "Unexpected action: " + intent.action)
return
}
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -2)
if (downloadId != SignalStore.apkUpdate().downloadId) {
Log.w(TAG, "downloadId doesn't match the one we're waiting for! Ignoring.")
return
}
ApkUpdateInstaller.installOrPromptForInstall(context, downloadId, userInitiated = false)
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.StreamUtil
import org.signal.core.util.getDownloadManager
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Environment
import org.thoughtcrime.securesms.util.FileUtils
import java.io.FileInputStream
import java.io.IOException
import java.io.InputStream
import java.security.MessageDigest
object ApkUpdateInstaller {
private val TAG = Log.tag(ApkUpdateInstaller::class.java)
/**
* Installs the downloaded APK silently if possible. If not, prompts the user with a notification to install.
* May show errors instead under certain conditions.
*
* A common pattern you may see is that this is called with [userInitiated] = false (or some other state
* that prevents us from auto-updating, like the app being in the foreground), causing this function
* to show an install prompt notification. The user clicks that notification, calling this with
* [userInitiated] = true, and then everything installs.
*/
fun installOrPromptForInstall(context: Context, downloadId: Long, userInitiated: Boolean) {
if (downloadId != SignalStore.apkUpdate().downloadId) {
Log.w(TAG, "DownloadId doesn't match the one we're waiting for! We likely have newer data. Ignoring.")
return
}
val digest = SignalStore.apkUpdate().digest
if (digest == null) {
Log.w(TAG, "DownloadId matches, but digest is null! Inconsistent state. Failing and clearing state.")
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
return
}
if (!isMatchingDigest(context, downloadId, digest)) {
Log.w(TAG, "DownloadId matches, but digest does not! Bad download or inconsistent state. Failing and clearing state.")
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
return
}
if (!userInitiated && !shouldAutoUpdate()) {
Log.w(TAG, "Not user-initiated and not eligible for auto-update. Prompting. (API=${Build.VERSION.SDK_INT}, Foreground=${ApplicationDependencies.getAppForegroundObserver().isForegrounded}, AutoUpdate=${SignalStore.apkUpdate().autoUpdate})")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
try {
installApk(context, downloadId, userInitiated)
} catch (e: IOException) {
Log.w(TAG, "Hit IOException when trying to install APK!", e)
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
} catch (e: SecurityException) {
Log.w(TAG, "Hit SecurityException when trying to install APK!", e)
SignalStore.apkUpdate().clearDownloadAttributes()
ApkUpdateNotifications.showInstallFailed(context, ApkUpdateNotifications.FailureReason.UNKNOWN)
}
}
@Throws(IOException::class, SecurityException::class)
private fun installApk(context: Context, downloadId: Long, userInitiated: Boolean) {
val apkInputStream: InputStream? = getDownloadedApkInputStream(context, downloadId)
if (apkInputStream == null) {
Log.w(TAG, "Could not open download APK input stream!")
return
}
Log.d(TAG, "Beginning APK install...")
val packageInstaller: PackageInstaller = context.packageManager.packageInstaller
val sessionParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL).apply {
// At this point, we always want to set this if possible, since we've already prompted the user with our own notification when necessary.
// This lets us skip the system-generated notification.
if (Build.VERSION.SDK_INT >= 31) {
setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
}
Log.d(TAG, "Creating install session...")
val sessionId: Int = packageInstaller.createSession(sessionParams)
val session: PackageInstaller.Session = packageInstaller.openSession(sessionId)
Log.d(TAG, "Writing APK data...")
session.use { activeSession ->
val sessionOutputStream = activeSession.openWrite(context.packageName, 0, -1)
StreamUtil.copy(apkInputStream, sessionOutputStream)
}
val installerPendingIntent = PendingIntent.getBroadcast(
context,
sessionId,
Intent(context, ApkUpdatePackageInstallerReceiver::class.java).apply {
putExtra(ApkUpdatePackageInstallerReceiver.EXTRA_USER_INITIATED, userInitiated)
putExtra(ApkUpdatePackageInstallerReceiver.EXTRA_DOWNLOAD_ID, downloadId)
},
PendingIntentFlags.mutable() or PendingIntentFlags.updateCurrent()
)
Log.d(TAG, "Committing session...")
session.commit(installerPendingIntent.intentSender)
}
private fun getDownloadedApkInputStream(context: Context, downloadId: Long): InputStream? {
return try {
FileInputStream(context.getDownloadManager().openDownloadedFile(downloadId).fileDescriptor)
} catch (e: IOException) {
Log.w(TAG, e)
null
}
}
private fun isMatchingDigest(context: Context, downloadId: Long, expectedDigest: ByteArray): Boolean {
return try {
FileInputStream(context.getDownloadManager().openDownloadedFile(downloadId).fileDescriptor).use { stream ->
val digest = FileUtils.getFileDigest(stream)
MessageDigest.isEqual(digest, expectedDigest)
}
} catch (e: IOException) {
Log.w(TAG, e)
false
}
}
private fun shouldAutoUpdate(): Boolean {
// TODO Auto-updates temporarily restricted to nightlies. Once we have designs for allowing users to opt-out of auto-updates, we can re-enable this
return Environment.IS_NIGHTLY && Build.VERSION.SDK_INT >= 31 && SignalStore.apkUpdate().autoUpdate && !ApplicationDependencies.getAppForegroundObserver().isForegrounded
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.signal.core.util.logging.Log
/**
* Receiver that is triggered based on various notification actions that can be taken on update-related notifications.
*/
class ApkUpdateNotificationReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdateNotificationReceiver::class.java)
const val ACTION_INITIATE_INSTALL = "signal.apk_update_notification.initiate_install"
const val EXTRA_DOWNLOAD_ID = "signal.download_id"
}
override fun onReceive(context: Context, intent: Intent?) {
if (intent == null) {
Log.w(TAG, "Null intent")
return
}
val downloadId: Long = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -2)
when (val action: String? = intent.action) {
ACTION_INITIATE_INSTALL -> handleInstall(context, downloadId)
else -> Log.w(TAG, "Unrecognized notification action: $action")
}
}
private fun handleInstall(context: Context, downloadId: Long) {
Log.i(TAG, "Got action to install.")
ApkUpdateInstaller.installOrPromptForInstall(context, downloadId, userInitiated = true)
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.util.ServiceUtil
object ApkUpdateNotifications {
val TAG = Log.tag(ApkUpdateNotifications::class.java)
/**
* Shows a notification to prompt the user to install the app update. Only shown when silently auto-updating is not possible or are disabled by the user.
* Note: This is an 'ongoing' notification (i.e. not-user dismissable) and never dismissed programatically. This is because the act of installing the APK
* will dismiss it for us.
*/
@SuppressLint("LaunchActivityFromNotification")
fun showInstallPrompt(context: Context, downloadId: Long) {
val pendingIntent = PendingIntent.getBroadcast(
context,
1,
Intent(context, ApkUpdateNotificationReceiver::class.java).apply {
action = ApkUpdateNotificationReceiver.ACTION_INITIATE_INSTALL
putExtra(ApkUpdateNotificationReceiver.EXTRA_DOWNLOAD_ID, downloadId)
},
PendingIntentFlags.immutable()
)
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setOngoing(true)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_prompt_install_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_prompt_install_body))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_PROMPT_INSTALL, notification)
}
fun showInstallFailed(context: Context, reason: FailureReason) {
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntentFlags.immutable()
)
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_failed_general_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_failed_general_body))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_FAILED_INSTALL, notification)
}
fun showAutoUpdateSuccess(context: Context) {
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntentFlags.immutable()
)
val appVersionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName
val notification = NotificationCompat.Builder(context, NotificationChannels.getInstance().APP_UPDATES)
.setContentTitle(context.getString(R.string.ApkUpdateNotifications_auto_update_success_title))
.setContentText(context.getString(R.string.ApkUpdateNotifications_auto_update_success_body, appVersionName))
.setSmallIcon(R.drawable.ic_notification)
.setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
ServiceUtil.getNotificationManager(context).notify(NotificationIds.APK_UPDATE_SUCCESSFUL_INSTALL, notification)
}
enum class FailureReason {
UNKNOWN,
ABORTED,
BLOCKED,
INCOMPATIBLE,
INVALID,
CONFLICT,
STORAGE,
TIMEOUT
}
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import org.signal.core.util.getParcelableExtraCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.apkupdate.ApkUpdateNotifications.FailureReason
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* This is the receiver that is triggered by the [PackageInstaller] to notify of various events. Package installation is initiated
* in [ApkUpdateInstaller].
*/
class ApkUpdatePackageInstallerReceiver : BroadcastReceiver() {
companion object {
private val TAG = Log.tag(ApkUpdatePackageInstallerReceiver::class.java)
const val EXTRA_USER_INITIATED = "signal.user_initiated"
const val EXTRA_DOWNLOAD_ID = "signal.download_id"
}
override fun onReceive(context: Context, intent: Intent?) {
val statusCode: Int = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) ?: -1
val statusMessage: String? = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
val userInitiated = intent?.getBooleanExtra(EXTRA_USER_INITIATED, false) ?: false
Log.w(TAG, "[onReceive] Status: $statusCode, Message: $statusMessage")
when (statusCode) {
PackageInstaller.STATUS_SUCCESS -> {
Log.i(TAG, "Update installed successfully!")
SignalStore.apkUpdate().lastApkUploadTime = SignalStore.apkUpdate().pendingApkUploadTime
ApkUpdateNotifications.showAutoUpdateSuccess(context)
}
PackageInstaller.STATUS_PENDING_USER_ACTION -> handlePendingUserAction(context, userInitiated, intent!!)
PackageInstaller.STATUS_FAILURE_ABORTED -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.ABORTED)
PackageInstaller.STATUS_FAILURE_BLOCKED -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.BLOCKED)
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.INCOMPATIBLE)
PackageInstaller.STATUS_FAILURE_INVALID -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.INVALID)
PackageInstaller.STATUS_FAILURE_CONFLICT -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.CONFLICT)
PackageInstaller.STATUS_FAILURE_STORAGE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.STORAGE)
PackageInstaller.STATUS_FAILURE_TIMEOUT -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.TIMEOUT)
PackageInstaller.STATUS_FAILURE -> ApkUpdateNotifications.showInstallFailed(context, FailureReason.UNKNOWN)
else -> Log.w(TAG, "Unknown status! $statusCode")
}
}
private fun handlePendingUserAction(context: Context, userInitiated: Boolean, intent: Intent) {
val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -2)
if (!userInitiated) {
Log.w(TAG, "Not user-initiated, but needs user action! Showing prompt notification.")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
val promptIntent: Intent? = intent.getParcelableExtraCompat(Intent.EXTRA_INTENT, Intent::class.java)
if (promptIntent == null) {
Log.w(TAG, "Missing prompt intent! Showing prompt notification instead.")
ApkUpdateNotifications.showInstallPrompt(context, downloadId)
return
}
promptIntent.apply {
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending")
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(promptIntent)
}
}

View File

@@ -1,22 +1,28 @@
package org.thoughtcrime.securesms.service;
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.apkupdate;
import android.content.Context;
import android.content.Intent;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.UpdateApkJob;
import org.thoughtcrime.securesms.jobs.ApkUpdateJob;
import org.thoughtcrime.securesms.service.PersistentAlarmManagerListener;
import org.thoughtcrime.securesms.util.Environment;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.concurrent.TimeUnit;
public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
public class ApkUpdateRefreshListener extends PersistentAlarmManagerListener {
private static final String TAG = Log.tag(UpdateApkRefreshListener.class);
private static final String TAG = Log.tag(ApkUpdateRefreshListener.class);
private static final long INTERVAL = TimeUnit.HOURS.toMillis(6);
private static final long INTERVAL = Environment.IS_NIGHTLY ? TimeUnit.HOURS.toMillis(2) : TimeUnit.HOURS.toMillis(6);
@Override
protected long getNextScheduledExecutionTime(Context context) {
@@ -27,9 +33,9 @@ public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
protected long onAlarm(Context context, long scheduledTime) {
Log.i(TAG, "onAlarm...");
if (scheduledTime != 0 && BuildConfig.PLAY_STORE_DISABLED) {
if (scheduledTime != 0 && BuildConfig.MANAGES_APP_UPDATES) {
Log.i(TAG, "Queueing APK update job...");
ApplicationDependencies.getJobManager().add(new UpdateApkJob());
ApplicationDependencies.getJobManager().add(new ApkUpdateJob());
}
long newTime = System.currentTimeMillis() + INTERVAL;
@@ -39,7 +45,7 @@ public class UpdateApkRefreshListener extends PersistentAlarmManagerListener {
}
public static void schedule(Context context) {
new UpdateApkRefreshListener().onReceive(context, getScheduleIntent());
new ApkUpdateRefreshListener().onReceive(context, getScheduleIntent());
}
}

View File

@@ -1,17 +1,28 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.ParcelCompat;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.ParcelUtil;
public abstract class Attachment {
import java.util.Objects;
public abstract class Attachment implements Parcelable {
@NonNull
private final String contentType;
@@ -48,6 +59,7 @@ public abstract class Attachment {
private final int height;
private final boolean quote;
private final long uploadTimestamp;
private final int incrementalMacChunkSize;
@Nullable
private final String caption;
@@ -80,6 +92,7 @@ public abstract class Attachment {
boolean videoGif,
int width,
int height,
int incrementalMacChunkSize,
boolean quote,
long uploadTimestamp,
@Nullable String caption,
@@ -88,31 +101,95 @@ public abstract class Attachment {
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
this.contentType = contentType;
this.transferState = transferState;
this.size = size;
this.fileName = fileName;
this.cdnNumber = cdnNumber;
this.location = location;
this.key = key;
this.relay = relay;
this.digest = digest;
this.incrementalDigest = incrementalDigest;
this.fastPreflightId = fastPreflightId;
this.voiceNote = voiceNote;
this.borderless = borderless;
this.videoGif = videoGif;
this.width = width;
this.height = height;
this.quote = quote;
this.uploadTimestamp = uploadTimestamp;
this.stickerLocator = stickerLocator;
this.caption = caption;
this.blurHash = blurHash;
this.audioHash = audioHash;
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
this.contentType = contentType;
this.transferState = transferState;
this.size = size;
this.fileName = fileName;
this.cdnNumber = cdnNumber;
this.location = location;
this.key = key;
this.relay = relay;
this.digest = digest;
this.incrementalDigest = incrementalDigest;
this.fastPreflightId = fastPreflightId;
this.voiceNote = voiceNote;
this.borderless = borderless;
this.videoGif = videoGif;
this.width = width;
this.height = height;
this.incrementalMacChunkSize = incrementalMacChunkSize;
this.quote = quote;
this.uploadTimestamp = uploadTimestamp;
this.stickerLocator = stickerLocator;
this.caption = caption;
this.blurHash = blurHash;
this.audioHash = audioHash;
this.transformProperties = transformProperties != null ? transformProperties : TransformProperties.empty();
}
protected Attachment(Parcel in) {
this.contentType = Objects.requireNonNull(in.readString());
this.transferState = in.readInt();
this.size = in.readLong();
this.fileName = in.readString();
this.cdnNumber = in.readInt();
this.location = in.readString();
this.key = in.readString();
this.relay = in.readString();
this.digest = ParcelUtil.readByteArray(in);
this.incrementalDigest = ParcelUtil.readByteArray(in);
this.fastPreflightId = in.readString();
this.voiceNote = ParcelUtil.readBoolean(in);
this.borderless = ParcelUtil.readBoolean(in);
this.videoGif = ParcelUtil.readBoolean(in);
this.width = in.readInt();
this.height = in.readInt();
this.incrementalMacChunkSize = in.readInt();
this.quote = ParcelUtil.readBoolean(in);
this.uploadTimestamp = in.readLong();
this.stickerLocator = ParcelCompat.readParcelable(in, StickerLocator.class.getClassLoader(), StickerLocator.class);
this.caption = in.readString();
this.blurHash = ParcelCompat.readParcelable(in, BlurHash.class.getClassLoader(), BlurHash.class);
this.audioHash = ParcelCompat.readParcelable(in, AudioHash.class.getClassLoader(), AudioHash.class);
this.transformProperties = Objects.requireNonNull(ParcelCompat.readParcelable(in, TransformProperties.class.getClassLoader(), TransformProperties.class));
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
AttachmentCreator.writeSubclass(dest, this);
dest.writeString(contentType);
dest.writeInt(transferState);
dest.writeLong(size);
dest.writeString(fileName);
dest.writeInt(cdnNumber);
dest.writeString(location);
dest.writeString(key);
dest.writeString(relay);
ParcelUtil.writeByteArray(dest, digest);
ParcelUtil.writeByteArray(dest, incrementalDigest);
dest.writeString(fastPreflightId);
ParcelUtil.writeBoolean(dest, voiceNote);
ParcelUtil.writeBoolean(dest, borderless);
ParcelUtil.writeBoolean(dest, videoGif);
dest.writeInt(width);
dest.writeInt(height);
dest.writeInt(incrementalMacChunkSize);
ParcelUtil.writeBoolean(dest, quote);
dest.writeLong(uploadTimestamp);
dest.writeParcelable(stickerLocator, 0);
dest.writeString(caption);
dest.writeParcelable(blurHash, 0);
dest.writeParcelable(audioHash, 0);
dest.writeParcelable(transformProperties, 0);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Attachment> CREATOR = AttachmentCreator.INSTANCE;
@Nullable
public abstract Uri getUri();
@@ -172,7 +249,11 @@ public abstract class Attachment {
@Nullable
public byte[] getIncrementalDigest() {
return incrementalDigest;
if (incrementalDigest != null && incrementalDigest.length > 0) {
return incrementalDigest;
} else {
return null;
}
}
@Nullable
@@ -200,6 +281,10 @@ public abstract class Attachment {
return height;
}
public int getIncrementalMacChunkSize() {
return incrementalMacChunkSize;
}
public boolean isQuote() {
return quote;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.attachments
import android.os.Parcel
import android.os.Parcelable
/**
* Parcelable Creator for Attachments. Encapsulates the logic around dealing with
* subclasses, since Attachment is abstract and has several children.
*/
object AttachmentCreator : Parcelable.Creator<Attachment> {
enum class Subclass(val clazz: Class<out Attachment>, val code: String) {
DATABASE(DatabaseAttachment::class.java, "database"),
MMS_NOTIFICATION(MmsNotificationAttachment::class.java, "mms_notification"),
POINTER(PointerAttachment::class.java, "pointer"),
TOMBSTONE(TombstoneAttachment::class.java, "tombstone"),
URI(UriAttachment::class.java, "uri")
}
@JvmStatic
fun writeSubclass(dest: Parcel, instance: Attachment) {
val subclass = Subclass.values().firstOrNull { it.clazz == instance::class.java } ?: error("Unexpected subtype ${instance::class.java.simpleName}")
dest.writeString(subclass.code)
}
override fun createFromParcel(source: Parcel): Attachment {
val rawCode = source.readString()!!
return when (Subclass.values().first { rawCode == it.code }) {
Subclass.DATABASE -> DatabaseAttachment(source)
Subclass.MMS_NOTIFICATION -> MmsNotificationAttachment(source)
Subclass.POINTER -> PointerAttachment(source)
Subclass.TOMBSTONE -> TombstoneAttachment(source)
Subclass.URI -> UriAttachment(source)
}
}
override fun newArray(size: Int): Array<Attachment?> = arrayOfNulls(size)
}

View File

@@ -1,14 +1,19 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.ParcelCompat;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentTable.TransformProperties;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.ParcelUtil;
import java.util.Comparator;
@@ -34,6 +39,7 @@ public class DatabaseAttachment extends Attachment {
String relay,
byte[] digest,
byte[] incrementalDigest,
int incrementalMacChunkSize,
String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -49,7 +55,7 @@ public class DatabaseAttachment extends Attachment {
int displayOrder,
long uploadTimestamp)
{
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.hasThumbnail = hasThumbnail;
@@ -57,10 +63,29 @@ public class DatabaseAttachment extends Attachment {
this.displayOrder = displayOrder;
}
protected DatabaseAttachment(Parcel in) {
super(in);
this.attachmentId = ParcelCompat.readParcelable(in, AttachmentId.class.getClassLoader(), AttachmentId.class);
this.hasData = ParcelUtil.readBoolean(in);
this.hasThumbnail = ParcelUtil.readBoolean(in);
this.mmsId = in.readLong();
this.displayOrder = in.readInt();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(attachmentId, 0);
ParcelUtil.writeBoolean(dest, hasData);
ParcelUtil.writeBoolean(dest, hasThumbnail);
dest.writeLong(mmsId);
dest.writeInt(displayOrder);
}
@Override
@Nullable
public Uri getUri() {
if (hasData) {
if (hasData || (FeatureFlags.instantVideoPlayback() && getIncrementalDigest() != null)) {
return PartAuthority.getAttachmentDataUri(attachmentId);
} else {
return null;

View File

@@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.AttachmentTable;
@@ -11,7 +13,11 @@ import org.thoughtcrime.securesms.database.MessageTable;
public class MmsNotificationAttachment extends Attachment {
public MmsNotificationAttachment(int status, long size) {
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, false, 0, null, null, null, null, null);
super("application/mms", getTransferStateFromStatus(status), size, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, false, 0, null, null, null, null, null);
}
protected MmsNotificationAttachment(Parcel in) {
super(in);
}
@Nullable

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -8,12 +9,12 @@ import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.blurhash.BlurHash;
import org.thoughtcrime.securesms.database.AttachmentTable;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.Base64;
import org.signal.core.util.Base64;
import org.whispersystems.signalservice.api.InvalidMessageStructureException;
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
import org.whispersystems.signalservice.api.util.AttachmentPointerUtil;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.DataMessage;
import java.util.LinkedList;
import java.util.List;
@@ -31,6 +32,7 @@ public class PointerAttachment extends Attachment {
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
int incrementalMacChunkSize,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -42,7 +44,11 @@ public class PointerAttachment extends Attachment {
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash)
{
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, incrementalDigest, fastPreflightId, voiceNote, borderless, videoGif, width, height, incrementalMacChunkSize, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
}
protected PointerAttachment(Parcel in) {
super(in);
}
@Nullable
@@ -102,7 +108,7 @@ public class PointerAttachment extends Attachment {
String encodedKey = null;
if (pointer.get().asPointer().getKey() != null) {
encodedKey = Base64.encodeBytes(pointer.get().asPointer().getKey());
encodedKey = Base64.encodeWithPadding(pointer.get().asPointer().getKey());
}
return Optional.of(new PointerAttachment(pointer.get().getContentType(),
@@ -111,9 +117,11 @@ public class PointerAttachment extends Attachment {
pointer.get().asPointer().getFileName().orElse(null),
pointer.get().asPointer().getCdnNumber(),
pointer.get().asPointer().getRemoteId().toString(),
encodedKey, null,
encodedKey,
null,
pointer.get().asPointer().getDigest().orElse(null),
pointer.get().asPointer().getIncrementalDigest().orElse(null),
pointer.get().asPointer().getIncrementalMacChunkSize(),
fastPreflightId,
pointer.get().asPointer().getVoiceNote(),
pointer.get().asPointer().isBorderless(),
@@ -136,10 +144,11 @@ public class PointerAttachment extends Attachment {
pointer.getFileName(),
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeWithPadding(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0,
null,
false,
false,
@@ -152,24 +161,25 @@ public class PointerAttachment extends Attachment {
null));
}
public static Optional<Attachment> forPointer(SignalServiceProtos.DataMessage.Quote.QuotedAttachment quotedAttachment) {
public static Optional<Attachment> forPointer(DataMessage.Quote.QuotedAttachment quotedAttachment) {
SignalServiceAttachment thumbnail;
try {
thumbnail = quotedAttachment.hasThumbnail() ? AttachmentPointerUtil.createSignalAttachmentPointer(quotedAttachment.getThumbnail()) : null;
thumbnail = quotedAttachment.thumbnail != null ? AttachmentPointerUtil.createSignalAttachmentPointer(quotedAttachment.thumbnail) : null;
} catch (InvalidMessageStructureException e) {
return Optional.empty();
}
return Optional.of(new PointerAttachment(quotedAttachment.getContentType(),
return Optional.of(new PointerAttachment(quotedAttachment.contentType,
AttachmentTable.TRANSFER_PROGRESS_PENDING,
thumbnail != null ? thumbnail.asPointer().getSize().orElse(0) : 0,
quotedAttachment.getFileName(),
quotedAttachment.fileName,
thumbnail != null ? thumbnail.asPointer().getCdnNumber() : 0,
thumbnail != null ? thumbnail.asPointer().getRemoteId().toString() : "0",
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeWithPadding(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getIncrementalDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getIncrementalMacChunkSize() : 0,
null,
false,
false,

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -16,7 +17,11 @@ import org.thoughtcrime.securesms.database.AttachmentTable;
public class TombstoneAttachment extends Attachment {
public TombstoneAttachment(@NonNull String contentType, boolean quote) {
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, quote, 0, null, null, null, null, null);
super(contentType, AttachmentTable.TRANSFER_PROGRESS_DONE, 0, null, 0, null, null, null, null, null, null, false, false, false, 0, 0, 0, quote, 0, null, null, null, null, null);
}
protected TombstoneAttachment(Parcel in) {
super(in);
}
@Override

View File

@@ -1,9 +1,11 @@
package org.thoughtcrime.securesms.attachments;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.os.ParcelCompat;
import org.thoughtcrime.securesms.audio.AudioHash;
import org.thoughtcrime.securesms.blurhash.BlurHash;
@@ -52,10 +54,21 @@ public class UriAttachment extends Attachment {
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
super(contentType, transferState, size, fileName, 0, null, null, null, null, null, fastPreflightId, voiceNote, borderless, videoGif, width, height, 0, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.dataUri = Objects.requireNonNull(dataUri);
}
protected UriAttachment(Parcel in) {
super(in);
this.dataUri = Objects.requireNonNull(ParcelCompat.readParcelable(in, Uri.class.getClassLoader(), Uri.class));
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(dataUri, 0);
}
@Override
@NonNull
public Uri getUri() {

View File

@@ -2,19 +2,19 @@ package org.thoughtcrime.securesms.audio;
import androidx.annotation.NonNull;
import com.google.protobuf.ByteString;
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
import java.util.concurrent.TimeUnit;
import okio.ByteString;
public class AudioFileInfo {
private final long durationUs;
private final byte[] waveFormBytes;
private final float[] waveForm;
public static @NonNull AudioFileInfo fromDatabaseProtobuf(@NonNull AudioWaveFormData audioWaveForm) {
return new AudioFileInfo(audioWaveForm.getDurationUs(), audioWaveForm.getWaveForm().toByteArray());
return new AudioFileInfo(audioWaveForm.durationUs, audioWaveForm.waveForm.toByteArray());
}
AudioFileInfo(long durationUs, byte[] waveFormBytes) {
@@ -37,9 +37,9 @@ public class AudioFileInfo {
}
public @NonNull AudioWaveFormData toDatabaseProtobuf() {
return AudioWaveFormData.newBuilder()
.setDurationUs(durationUs)
.setWaveForm(ByteString.copyFrom(waveFormBytes))
.build();
return new AudioWaveFormData.Builder()
.durationUs(durationUs)
.waveForm(ByteString.of(waveFormBytes))
.build();
}
}

View File

@@ -1,17 +1,22 @@
package org.thoughtcrime.securesms.audio;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.model.databaseprotos.AudioWaveFormData;
import org.whispersystems.util.Base64;
import org.thoughtcrime.securesms.util.ParcelUtil;
import org.signal.core.util.Base64;
import java.io.IOException;
import java.util.Objects;
/**
* An AudioHash is a compact string representation of the wave form and duration for an audio file.
*/
public final class AudioHash {
public final class AudioHash implements Parcelable {
@NonNull private final String hash;
@NonNull private final AudioWaveFormData audioWaveForm;
@@ -22,13 +27,46 @@ public final class AudioHash {
}
public AudioHash(@NonNull AudioWaveFormData audioWaveForm) {
this(Base64.encodeBytes(audioWaveForm.toByteArray()), audioWaveForm);
this(Base64.encodeWithPadding(audioWaveForm.encode()), audioWaveForm);
}
protected AudioHash(Parcel in) {
hash = Objects.requireNonNull(in.readString());
try {
audioWaveForm = AudioWaveFormData.ADAPTER.decode(Objects.requireNonNull(ParcelUtil.readByteArray(in)));
} catch (IOException e) {
throw new AssertionError();
}
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(hash);
ParcelUtil.writeByteArray(dest, audioWaveForm.encode());
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<AudioHash> CREATOR = new Creator<>() {
@Override
public AudioHash createFromParcel(Parcel in) {
return new AudioHash(in);
}
@Override
public AudioHash[] newArray(int size) {
return new AudioHash[size];
}
};
public static @Nullable AudioHash parseOrNull(@Nullable String hash) {
if (hash == null) return null;
try {
return new AudioHash(hash, AudioWaveFormData.parseFrom(Base64.decode(hash)));
return new AudioHash(hash, AudioWaveFormData.ADAPTER.decode(Base64.decode(hash)));
} catch (IOException e) {
return null;
}

View File

@@ -107,7 +107,7 @@ object AudioWaveForms {
private fun generateWaveForm(context: Context, uri: Uri, cacheKey: String, attachmentId: AttachmentId): CacheCheckResult {
try {
val startTime = System.currentTimeMillis()
SignalDatabase.attachments.writeAudioHash(attachmentId, AudioWaveFormData.getDefaultInstance())
SignalDatabase.attachments.writeAudioHash(attachmentId, AudioWaveFormData())
Log.i(TAG, "Starting wave form generation ($cacheKey)")
val fileInfo: AudioFileInfo = AudioWaveFormGenerator.generateWaveForm(context, uri)

View File

@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration
import org.thoughtcrime.securesms.groups.ParcelableGroupId
import org.thoughtcrime.securesms.mediasend.AvatarSelectionActivity
import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.permissions.PermissionCompat
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
@@ -238,7 +239,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
@Suppress("DEPRECATION")
private fun openGallery() {
Permissions.with(this)
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
.request(*PermissionCompat.forImages())
.ifNecessary()
.onAllGranted {
val intent = AvatarSelectionActivity.getIntentForGallery(requireContext())

View File

@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import org.signal.core.util.Conversions;
import org.signal.core.util.StreamUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.kdf.HKDF;
import org.signal.libsignal.protocol.util.ByteUtil;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
@@ -27,6 +28,9 @@ import javax.crypto.spec.SecretKeySpec;
class BackupRecordInputStream extends FullBackupBase.BackupStream {
private final String TAG = Log.tag(BackupRecordInputStream.class);
private final int MAX_BUFFER_SIZE = 8192;
private final int version;
private final InputStream in;
private final Cipher cipher;
@@ -92,6 +96,35 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
return readFrame(in);
}
boolean validateFrame() throws InvalidAlgorithmParameterException, IOException, InvalidKeyException {
int frameLength = decryptFrameLength(in);
if (frameLength <= 0) {
Log.i(TAG, "Backup frame is not valid due to negative frame length. This is likely because the decryption passphrase was wrong.");
return false;
}
int bufferSize = Math.min(MAX_BUFFER_SIZE, frameLength);
byte[] buffer = new byte[bufferSize];
byte[] theirMac = new byte[10];
while (frameLength > 0) {
int read = in.read(buffer, 0, Math.min(buffer.length, frameLength));
if (read == -1) return false;
if (read < MAX_BUFFER_SIZE) {
final int frameEndIndex = read - 10;
mac.update(buffer, 0, frameEndIndex);
System.arraycopy(buffer, frameEndIndex, theirMac, 0, theirMac.length);
} else {
mac.update(buffer, 0, read);
}
frameLength -= read;
}
byte[] ourMac = ByteUtil.trim(mac.doFinal(), 10);
return MessageDigest.isEqual(ourMac, theirMac);
}
void readAttachmentTo(OutputStream out, int length) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
@@ -142,24 +175,7 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
private BackupFrame readFrame(InputStream in) throws IOException {
try {
byte[] length = new byte[4];
StreamUtil.readFully(in, length);
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
int frameLength;
if (BackupVersions.isFrameLengthEncrypted(version)) {
mac.update(length);
// this depends upon cipher being a stream cipher mode in order to get back the length without needing a full AES block-size input
byte[] decryptedLength = cipher.update(length);
if (decryptedLength.length != length.length) {
throw new IOException("Cipher was not a stream cipher!");
}
frameLength = Conversions.byteArrayToInt(decryptedLength);
} else {
frameLength = Conversions.byteArrayToInt(length);
}
int frameLength = decryptFrameLength(in);
byte[] frame = new byte[frameLength];
StreamUtil.readFully(in, frame);
@@ -182,5 +198,27 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
}
}
private int decryptFrameLength(InputStream inputStream) throws IOException, InvalidAlgorithmParameterException, InvalidKeyException {
byte[] length = new byte[4];
StreamUtil.readFully(inputStream, length);
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
int frameLength;
if (BackupVersions.isFrameLengthEncrypted(version)) {
mac.update(length);
// this depends upon cipher being a stream cipher mode in order to get back the length without needing a full AES block-size input
byte[] decryptedLength = cipher.update(length);
if (decryptedLength.length != length.length) {
throw new IOException("Cipher was not a stream cipher!");
}
frameLength = Conversions.byteArrayToInt(decryptedLength);
} else {
frameLength = Conversions.byteArrayToInt(length);
}
return frameLength;
}
static class BadMacException extends IOException {}
}

View File

@@ -45,6 +45,8 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -63,6 +65,24 @@ public class FullBackupImporter extends FullBackupBase {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(FullBackupImporter.class);
public static boolean validatePassphrase(@NonNull Context context,
@NonNull Uri uri,
@NonNull String passphrase)
throws IOException
{
try (InputStream is = getInputStream(context, uri)) {
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
return inputStream.validateFrame();
} catch (InvalidAlgorithmParameterException e) {
Log.w(TAG, "Invalid algorithm parameter exception in backup passphrase validation.", e);
return false;
} catch (InvalidKeyException e) {
Log.w(TAG, "Invalid key exception in backup passphrase validation.", e);
return false;
}
}
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
throws IOException

View File

@@ -8,6 +8,7 @@ import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSize
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.glide.GiftBadgeModel
import org.thoughtcrime.securesms.mms.GlideApp
@@ -31,6 +32,10 @@ class BadgeImageView @JvmOverloads constructor(
isClickable = false
}
constructor(context: Context, badgeImageSize: BadgeImageSize) : this(context) {
badgeSize = badgeImageSize.sizeCode
}
override fun setOnClickListener(l: OnClickListener?) {
val wasClickable = isClickable
super.setOnClickListener(l)

View File

@@ -101,16 +101,16 @@ object Badges {
@JvmStatic
fun toDatabaseBadge(badge: Badge): BadgeList.Badge {
return BadgeList.Badge.newBuilder()
.setId(badge.id)
.setCategory(badge.category.code)
.setDescription(badge.description)
.setExpiration(badge.expirationTimestamp)
.setVisible(badge.visible)
.setName(badge.name)
.setImageUrl(badge.imageUrl.toString())
.setImageDensity(badge.imageDensity)
.build()
return BadgeList.Badge(
id = badge.id,
category = badge.category.code,
description = badge.description,
expiration = badge.expirationTimestamp,
visible = badge.visible,
name = badge.name,
imageUrl = badge.imageUrl.toString(),
imageDensity = badge.imageDensity
)
}
@JvmStatic

View File

@@ -79,12 +79,11 @@ class GiftMessageView @JvmOverloads constructor(
}
actionView.setText(
when (giftBadge.redemptionState ?: GiftBadge.RedemptionState.UNRECOGNIZED) {
when (giftBadge.redemptionState) {
GiftBadge.RedemptionState.PENDING -> R.string.GiftMessageView__redeem
GiftBadge.RedemptionState.STARTED -> R.string.GiftMessageView__redeeming
GiftBadge.RedemptionState.REDEEMED -> R.string.GiftMessageView__redeemed
GiftBadge.RedemptionState.FAILED -> R.string.GiftMessageView__redeem
GiftBadge.RedemptionState.UNRECOGNIZED -> R.string.GiftMessageView__redeem
}
)
}

View File

@@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.badges.gifts
import android.content.Context
import org.signal.core.util.Base64
import org.signal.libsignal.zkgroup.InvalidInputException
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.Base64
import java.lang.Integer.min
import java.util.concurrent.TimeUnit
@@ -32,7 +32,7 @@ object Gifts {
): OutgoingMessage {
return OutgoingMessage(
threadRecipient = recipient,
body = Base64.encodeBytes(giftBadge.toByteArray()),
body = Base64.encodeWithPadding(giftBadge.encode()),
isSecure = true,
sentTimeMillis = sentTimestamp,
expiresIn = expiresIn,

View File

@@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.donate.Do
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationCheckoutDelegate
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonationProcessorAction
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayRequest
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.gateway.GatewayResponse
import org.thoughtcrime.securesms.components.settings.app.subscription.errors.DonationErrorSource
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.components.settings.conversation.preferences.RecipientPreference
@@ -83,7 +84,7 @@ class GiftFlowConfirmationFragment :
keyboardPagerViewModel.setOnlyPage(KeyboardPage.EMOJI)
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, DonationErrorSource.GIFT)
donationCheckoutDelegate = DonationCheckoutDelegate(this, this, viewModel.uiSessionKey, DonationErrorSource.GIFT)
processingDonationPaymentDialog = MaterialAlertDialogBuilder(requireContext())
.setView(R.layout.processing_payment_dialog)
@@ -106,6 +107,7 @@ class GiftFlowConfirmationFragment :
GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToGatewaySelectorBottomSheet(
with(viewModel.snapshot) {
GatewayRequest(
uiSessionKey = viewModel.uiSessionKey,
donateToSignalType = DonateToSignalType.GIFT,
badge = giftBadge!!,
label = getString(R.string.preferences__one_time),
@@ -262,6 +264,14 @@ class GiftFlowConfirmationFragment :
findNavController().safeNavigate(GiftFlowConfirmationFragmentDirections.actionGiftFlowConfirmationFragmentToCreditCardFragment(gatewayRequest))
}
override fun navigateToIdealDetailsFragment(gatewayRequest: GatewayRequest) {
error("Unsupported operation")
}
override fun navigateToBankTransferMandate(gatewayResponse: GatewayResponse) {
error("Unsupported operation")
}
override fun onPaymentComplete(gatewayRequest: GatewayRequest) {
val mainActivityIntent = MainActivity.clearTop(requireContext())
@@ -275,7 +285,8 @@ class GiftFlowConfirmationFragment :
}
override fun onProcessorActionProcessed() = Unit
override fun onUserCancelledPaymentFlow() {
findNavController().popBackStack(R.id.giftFlowConfirmationFragment, false)
}
override fun onUserLaunchedAnExternalApplication() = Unit
override fun navigateToDonationPending(gatewayRequest: GatewayRequest) = error("Unsupported operation")
}

View File

@@ -39,6 +39,7 @@ class GiftFlowViewModel(
val state: Flowable<GiftFlowState> = store.stateFlowable
val events: Observable<DonationEvent> = eventPublisher
val snapshot: GiftFlowState get() = store.state
val uiSessionKey: Long = System.currentTimeMillis()
init {
refresh()

View File

@@ -63,7 +63,7 @@ class ViewReceivedGiftBottomSheet : DSLSettingsBottomSheetFragment() {
ViewReceivedGiftBottomSheet().apply {
arguments = Bundle().apply {
putParcelable(ARG_SENT_FROM, messageRecord.fromRecipient.id)
putByteArray(ARG_GIFT_BADGE, messageRecord.giftBadge!!.toByteArray())
putByteArray(ARG_GIFT_BADGE, messageRecord.giftBadge!!.encode())
putLong(ARG_MESSAGE_ID, messageRecord.id)
}
show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)

View File

@@ -34,7 +34,7 @@ class ViewSentGiftBottomSheet : DSLSettingsBottomSheetFragment() {
ViewSentGiftBottomSheet().apply {
arguments = Bundle().apply {
putParcelable(ARG_SENT_TO, messageRecord.toRecipient.id)
putByteArray(ARG_GIFT_BADGE, messageRecord.giftBadge!!.toByteArray())
putByteArray(ARG_GIFT_BADGE, messageRecord.giftBadge!!.encode())
}
show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
}
@@ -45,7 +45,7 @@ class ViewSentGiftBottomSheet : DSLSettingsBottomSheetFragment() {
get() = requireArguments().getParcelableCompat(ARG_SENT_TO, RecipientId::class.java)!!
private val giftBadge: GiftBadge
get() = GiftBadge.parseFrom(requireArguments().getByteArray(ARG_GIFT_BADGE))
get() = GiftBadge.ADAPTER.decode(requireArguments().getByteArray(ARG_GIFT_BADGE)!!)
private val lifecycleDisposable = LifecycleDisposable()

View File

@@ -6,6 +6,7 @@ import android.os.Parcelable
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.compose.runtime.Stable
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
@@ -25,6 +26,7 @@ typealias OnBadgeClicked = (Badge, Boolean, Boolean) -> Unit
/**
* A Badge that can be collected and displayed by a user.
*/
@Stable
@Parcelize
data class Badge(
val id: String,

View File

@@ -4,6 +4,7 @@ import androidx.fragment.app.FragmentManager
import org.signal.core.util.DimensionUnit
import org.signal.core.util.logging.Log
import org.signal.donations.StripeDeclineCode
import org.signal.donations.StripeFailureCode
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.badges.models.ExpiredBadge
@@ -38,6 +39,7 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
val badge: Badge = args.badge
val cancellationReason = UnexpectedSubscriptionCancellation.fromStatus(args.cancelationReason)
val declineCode: StripeDeclineCode? = args.chargeFailure?.let { StripeDeclineCode.getFromCode(it) }
val failureCode: StripeFailureCode? = args.chargeFailure?.let { StripeFailureCode.getFromCode(it) }
val isLikelyASustainer = SignalStore.donationsValues().isLikelyASustainer()
val inactive = cancellationReason == UnexpectedSubscriptionCancellation.INACTIVE
@@ -69,6 +71,12 @@ class ExpiredBadgeBottomSheetDialogFragment : DSLSettingsBottomSheetFragment(
getString(declineCode.mapToErrorStringResource()),
badge.name
)
} else if (failureCode != null) {
getString(
R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_canceled_s,
getString(failureCode.mapToErrorStringResource()),
badge.name
)
} else if (inactive) {
getString(R.string.ExpiredBadgeBottomSheetDialogFragment__your_recurring_monthly_donation_was_automatically, badge.name)
} else {

View File

@@ -58,7 +58,7 @@ class BecomeASustainerFragment : DSLSettingsBottomSheetFragment() {
space(DimensionUnit.DP.toPixels(32f).toInt())
tonalButton(
tonalWrappedButton(
text = DSLSettingsText.from(
R.string.BecomeASustainerMegaphone__become_a_sustainer
),

View File

@@ -7,12 +7,16 @@ package org.thoughtcrime.securesms.calls.links
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.annotation.ColorRes
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.ContextCompat
import com.google.android.material.button.MaterialButton
import org.thoughtcrime.securesms.R
/**
* ConversationItem action button for joining a call link.
*/
class CallLinkJoinButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
@@ -22,10 +26,19 @@ class CallLinkJoinButton @JvmOverloads constructor(
inflate(context, R.layout.call_link_join_button, this)
}
private val joinStroke: View = findViewById(R.id.join_stroke)
private val joinButton: MaterialButton = findViewById(R.id.join_button)
fun setTextColor(@ColorRes textColorResId: Int) {
joinButton.setTextColor(ContextCompat.getColor(context, textColorResId))
val color = ContextCompat.getColor(context, textColorResId)
joinButton.setTextColor(color)
}
fun setStrokeColor(@ColorRes strokeColorResId: Int) {
val color = ContextCompat.getColor(context, strokeColorResId)
joinStroke.setBackgroundColor(color)
}
fun setJoinClickListener(onClickListener: OnClickListener) {

View File

@@ -23,7 +23,7 @@ import java.net.URLDecoder
object CallLinks {
private const val ROOT_KEY = "key"
private const val HTTPS_LINK_PREFIX = "https://signal.link/call/#key="
private const val SNGL_LINK_PREFIX = "sgnl://signal.link/#key="
private const val SNGL_LINK_PREFIX = "sgnl://signal.link/call/#key="
private val TAG = Log.tag(CallLinks::class.java)
@@ -58,8 +58,7 @@ object CallLinks {
return false
}
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
Log.w(TAG, "Invalid url prefix.")
if (!url.startsWith(HTTPS_LINK_PREFIX) || !url.startsWith(SNGL_LINK_PREFIX)) {
return false
}

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -132,6 +133,10 @@ fun SignalCallRow(
Buttons.Small(
onClick = onJoinClicked,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onSurface
),
modifier = Modifier.align(CenterVertically)
) {
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))

View File

@@ -106,7 +106,13 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
Spacer(modifier = Modifier.height(12.dp))
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name),
text = stringResource(
id = if (callLink.state.name.isEmpty()) {
R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name
} else {
R.string.CreateCallLinkBottomSheetDialogFragment__edit_call_name
}
),
onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked
)

View File

@@ -246,7 +246,13 @@ private fun CallLinkDetails(
if (state.callLink.credentials?.adminPassBytes != null) {
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__add_call_name),
text = stringResource(
id = if (state.callLink.state.name.isEmpty()) {
R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name
} else {
R.string.CreateCallLinkBottomSheetDialogFragment__edit_call_name
}
),
onClick = callback::onEditNameClicked
)

View File

@@ -17,6 +17,7 @@ import androidx.core.app.SharedElementCallback
import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.transition.TransitionInflater
@@ -57,6 +58,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.tabs.ConversationListTab
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.doAfterNextLayout
import org.thoughtcrime.securesms.util.fragments.requireListener
@@ -74,7 +76,7 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
private val TAG = Log.tag(CallLogFragment::class.java)
}
private val viewModel: CallLogViewModel by viewModels()
private val viewModel: CallLogViewModel by activityViewModels()
private val binding: CallLogFragmentBinding by ViewBinderDelegate(CallLogFragmentBinding::bind)
private val disposables = LifecycleDisposable()
private val callLogContextMenu = CallLogContextMenu(this, this)
@@ -230,6 +232,13 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
val count = callLogActionMode.getCount()
MaterialAlertDialogBuilder(requireContext())
.setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, count, count))
.setMessage(
if (FeatureFlags.adHocCalling()) {
getString(R.string.CallLogFragment__call_links_youve_created)
} else {
null
}
)
.setPositiveButton(R.string.CallLogFragment__delete) { _, _ ->
performDeletion(count, viewModel.stageSelectionDeletion())
callLogActionMode.end()
@@ -303,6 +312,12 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
val progress = 1 - verticalOffset.toFloat() / -layout.height
binding.pullView.onUserDrag(progress)
}
if (viewModel.filterSnapshot != CallLogFilter.ALL) {
binding.root.doAfterNextLayout {
binding.pullView.openImmediate()
}
}
}
override fun onCreateACallLinkClicked() {
@@ -363,6 +378,13 @@ class CallLogFragment : Fragment(R.layout.call_log_fragment), CallLogAdapter.Cal
override fun deleteCall(call: CallLogRow) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(resources.getQuantityString(R.plurals.CallLogFragment__delete_d_calls, 1, 1))
.setMessage(
if (FeatureFlags.adHocCalling()) {
getString(R.string.CallLogFragment__call_links_youve_created)
} else {
null
}
)
.setPositiveButton(R.string.CallLogFragment__delete) { _, _ ->
performDeletion(1, viewModel.stageCallDeletion(call))
}

View File

@@ -63,7 +63,7 @@ class CallLogPagedDataSource(
remaining -= callEvents.size
}
if (start <= clearFilterStart && remaining > 0) {
if (hasFilter && start <= clearFilterStart && remaining > 0) {
callLogRows.add(CallLogRow.ClearFilter)
}

View File

@@ -86,18 +86,22 @@ class CallLogRepository(
/**
* Delete all call events / unowned links and enqueue clear history job, and then
* emit a clear history message.
*
* This explicitly drops failed call link revocations of call links, and those call links
* will remain visible to the user. This is safe because the clear history sync message should
* only clear local history and then poll link status from the server.
*/
fun deleteAllCallLogsOnOrBeforeNow(): Single<Int> {
return Single.fromCallable {
SignalDatabase.rawDatabase.withinTransaction {
val now = System.currentTimeMillis()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(now)
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(now)
ApplicationDependencies.getJobManager().add(CallLogEventSendJob.forClearHistory(now))
val latestTimestamp = SignalDatabase.calls.getLatestTimestamp()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(latestTimestamp)
SignalDatabase.callLinks.deleteNonAdminCallLinksOnOrBefore(latestTimestamp)
ApplicationDependencies.getJobManager().add(CallLogEventSendJob.forClearHistory(latestTimestamp))
}
SignalDatabase.callLinks.getAllAdminCallLinksExcept(emptySet())
}.flatMap(this::revokeAndCollectResults).map { -1 }.subscribeOn(Schedulers.io())
}.flatMap(this::revokeAndCollectResults).map { 0 }.subscribeOn(Schedulers.io())
}
/**

View File

@@ -93,11 +93,11 @@ sealed class CallLogRow {
return FULL
}
if (groupCallUpdateDetails.inCallUuidsList.contains(Recipient.self().requireAci().rawUuid.toString())) {
if (groupCallUpdateDetails.inCallUuids.contains(Recipient.self().requireAci().rawUuid.toString())) {
return LOCAL_USER_JOINED
}
return if (groupCallUpdateDetails.inCallUuidsCount > 0) {
return if (groupCallUpdateDetails.inCallUuids.isNotEmpty()) {
ACTIVE
} else {
NONE

View File

@@ -1,3 +1,8 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components;
import android.content.Context;
@@ -12,10 +17,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.transfercontrols.TransferControlView;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideClickListener;
import org.thoughtcrime.securesms.mms.SlidesClickedListener;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.views.Stub;
import java.util.List;
@@ -24,13 +31,15 @@ public class AlbumThumbnailView extends FrameLayout {
private @Nullable SlideClickListener thumbnailClickListener;
private @Nullable SlidesClickedListener downloadClickListener;
private @Nullable SlidesClickedListener cancelDownloadClickListener;
private @Nullable SlideClickListener playVideoClickListener;
private int currentSizeClass;
private final int[] corners = new int[4];
private ViewGroup albumCellContainer;
private Stub<TransferControlView> transferControls;
private final ViewGroup albumCellContainer;
private final Stub<TransferControlView> transferControlsStub;
private final SlideClickListener defaultThumbnailClickListener = (v, slide) -> {
if (thumbnailClickListener != null) {
@@ -42,19 +51,18 @@ public class AlbumThumbnailView extends FrameLayout {
public AlbumThumbnailView(@NonNull Context context) {
super(context);
initialize();
inflate(getContext(), R.layout.album_thumbnail_view, this);
albumCellContainer = findViewById(R.id.album_cell_container);
transferControlsStub = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
}
public AlbumThumbnailView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
inflate(getContext(), R.layout.album_thumbnail_view, this);
albumCellContainer = findViewById(R.id.album_cell_container);
transferControls = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
albumCellContainer = findViewById(R.id.album_cell_container);
transferControlsStub = new Stub<>(findViewById(R.id.album_transfer_controls_stub));
}
public void setSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides, boolean showControls) {
@@ -63,16 +71,17 @@ public class AlbumThumbnailView extends FrameLayout {
}
if (showControls) {
transferControls.get().setShowDownloadText(true);
transferControls.get().setSlides(slides);
transferControls.get().setDownloadClickListener(v -> {
if (downloadClickListener != null) {
downloadClickListener.onClick(v, slides);
}
});
transferControlsStub.get().setShowSecondaryText(true);
transferControlsStub.get().setDownloadClickListener(
v -> {
if (downloadClickListener != null) {
downloadClickListener.onClick(v, slides);
}
});
transferControlsStub.get().setSlides(slides);
} else {
if (transferControls.resolved()) {
transferControls.get().setVisibility(GONE);
if (transferControlsStub.resolved()) {
transferControlsStub.get().setVisibility(GONE);
}
}
@@ -85,6 +94,7 @@ public class AlbumThumbnailView extends FrameLayout {
showSlides(glideRequests, slides);
applyCorners();
forceLayout();
}
public void setCellBackgroundColor(@ColorInt int color) {
@@ -101,10 +111,19 @@ public class AlbumThumbnailView extends FrameLayout {
thumbnailClickListener = listener;
}
public void setDownloadClickListener(@Nullable SlidesClickedListener listener) {
downloadClickListener = listener;
public void setDownloadClickListener(SlidesClickedListener listener) {
this.downloadClickListener = listener;
}
public void setCancelDownloadClickListener(SlidesClickedListener listener) {
this.cancelDownloadClickListener = listener;
}
public void setPlayVideoClickListener(SlideClickListener listener) {
this.playVideoClickListener = listener;
}
public void setRadii(int topLeft, int topRight, int bottomRight, int bottomLeft) {
corners[0] = topLeft;
corners[1] = topRight;
@@ -117,23 +136,46 @@ public class AlbumThumbnailView extends FrameLayout {
private void inflateLayout(int sizeClass) {
albumCellContainer.removeAllViews();
int resId;
switch (sizeClass) {
case 2:
inflate(getContext(), R.layout.album_thumbnail_2, albumCellContainer);
resId = R.layout.album_thumbnail_2;
break;
case 3:
inflate(getContext(), R.layout.album_thumbnail_3, albumCellContainer);
resId = R.layout.album_thumbnail_3;
break;
case 4:
inflate(getContext(), R.layout.album_thumbnail_4, albumCellContainer);
resId = R.layout.album_thumbnail_4;
break;
case 5:
inflate(getContext(), R.layout.album_thumbnail_5, albumCellContainer);
resId = R.layout.album_thumbnail_5;
break;
default:
inflate(getContext(), R.layout.album_thumbnail_many, albumCellContainer);
resId = R.layout.album_thumbnail_many;
break;
}
inflate(getContext(), resId, albumCellContainer);
if (transferControlsStub.resolved()) {
int size;
switch (sizeClass) {
case 2:
size = R.dimen.album_2_total_height;
break;
case 3:
size = R.dimen.album_3_total_height;
break;
case 4:
size = R.dimen.album_4_total_height;
break;
default:
size = R.dimen.album_5_total_height;
break;
}
ViewGroup.LayoutParams params = transferControlsStub.get().getLayoutParams();
params.height = getContext().getResources().getDimensionPixelSize(size);
transferControlsStub.get().setLayoutParams(params);
}
}
private void applyCorners() {
@@ -214,19 +256,20 @@ public class AlbumThumbnailView extends FrameLayout {
}
private void showSlides(@NonNull GlideRequests glideRequests, @NonNull List<Slide> slides) {
setSlide(glideRequests, slides.get(0), R.id.album_cell_1);
setSlide(glideRequests, slides.get(1), R.id.album_cell_2);
boolean showControls = TransferControlView.containsPlayableSlides(slides);
setSlide(glideRequests, slides.get(0), R.id.album_cell_1, showControls);
setSlide(glideRequests, slides.get(1), R.id.album_cell_2, showControls);
if (slides.size() >= 3) {
setSlide(glideRequests, slides.get(2), R.id.album_cell_3);
setSlide(glideRequests, slides.get(2), R.id.album_cell_3, showControls);
}
if (slides.size() >= 4) {
setSlide(glideRequests, slides.get(3), R.id.album_cell_4);
setSlide(glideRequests, slides.get(3), R.id.album_cell_4, showControls);
}
if (slides.size() >= 5) {
setSlide(glideRequests, slides.get(4), R.id.album_cell_5);
setSlide(glideRequests, slides.get(4), R.id.album_cell_5, showControls && slides.size() == 5);
}
if (slides.size() > 5) {
@@ -235,11 +278,17 @@ public class AlbumThumbnailView extends FrameLayout {
}
}
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id) {
private void setSlide(@NonNull GlideRequests glideRequests, @NonNull Slide slide, @IdRes int id, boolean showControls) {
ThumbnailView cell = findViewById(id);
cell.setImageResource(glideRequests, slide, false, false);
cell.showSecondaryText(false);
cell.setThumbnailClickListener(defaultThumbnailClickListener);
cell.setDownloadClickListener(downloadClickListener);
cell.setCancelDownloadClickListener(cancelDownloadClickListener);
if (MediaUtil.isInstantVideoSupported(slide)) {
cell.setPlayVideoClickListener(playVideoClickListener);
}
cell.setOnLongClickListener(defaultLongClickListener);
cell.setImageResource(glideRequests, slide, showControls, false);
}
private int sizeClass(int size) {

View File

@@ -15,12 +15,16 @@ import androidx.annotation.Px;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.FragmentActivity;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.MultiTransformation;
import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition;
import org.signal.core.util.logging.Log;
@@ -32,6 +36,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.conversation.colors.ChatColors;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequest;
import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -54,6 +59,8 @@ public final class AvatarImageView extends AppCompatImageView {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(AvatarImageView.class);
private final RequestListener<Drawable> redownloadRequestListener = new RedownloadRequestListener();
private int size;
private boolean inverted;
private OnClickListener listener;
@@ -198,7 +205,8 @@ public final class AvatarImageView extends AppCompatImageView {
.error(fallbackContactPhotoDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.downsample(DownsampleStrategy.CENTER_INSIDE)
.transform(new MultiTransformation<>(transforms));
.transform(new MultiTransformation<>(transforms))
.addListener(redownloadRequestListener);
if (avatarOptions.fixedSize > 0) {
fixedSizeTarget = new FixedSizeTarget(avatarOptions.fixedSize);
@@ -363,4 +371,19 @@ public final class AvatarImageView extends AppCompatImageView {
}
}
}
private static class RedownloadRequestListener implements RequestListener<Drawable> {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
if (model instanceof ProfileContactPhoto) {
RetrieveProfileAvatarJob.enqueueForceUpdate(((ProfileContactPhoto) model).getRecipient());
}
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
}
}

View File

@@ -322,12 +322,12 @@ public class ConversationItemFooter extends ConstraintLayout {
} else {
long timestamp = messageRecord.getTimestamp();
if (messageRecord.isEditMessage()) {
if (displayMode == ConversationItemDisplayMode.EDIT_HISTORY) {
if (displayMode == ConversationItemDisplayMode.EditHistory.INSTANCE) {
timestamp = messageRecord.getDateSent();
}
}
String date = DateUtils.getSimpleRelativeTimeSpanString(getContext(), locale, timestamp);
if (displayMode != ConversationItemDisplayMode.DETAILED && messageRecord.isEditMessage() && messageRecord.isLatestRevision()) {
String date = DateUtils.getDatelessRelativeTimeSpanString(getContext(), locale, timestamp);
if (displayMode != ConversationItemDisplayMode.Detailed.INSTANCE && messageRecord.isEditMessage() && messageRecord.isLatestRevision()) {
date = getContext().getString(R.string.ConversationItem_edited_timestamp_footer, date);
}
dateView.setText(date);

View File

@@ -1,3 +1,8 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components
import android.content.Context
@@ -86,7 +91,7 @@ class ConversationItemThumbnail @JvmOverloads constructor(
}
}
override fun onSaveInstanceState(): Parcelable? {
override fun onSaveInstanceState(): Parcelable {
val root = super.onSaveInstanceState()
return bundleOf(
STATE_ROOT to root,
@@ -255,6 +260,24 @@ class ConversationItemThumbnail @JvmOverloads constructor(
state.applyState(thumbnail, album)
}
fun setPlayVideoClickListener(listener: SlideClickListener?) {
state = state.copy(
thumbnailViewState = state.thumbnailViewState.copy(playVideoClickListener = listener),
albumViewState = state.albumViewState.copy(playVideoClickListener = listener)
)
state.applyState(thumbnail, album)
}
fun setCancelDownloadClickListener(listener: SlidesClickedListener?) {
state = state.copy(
thumbnailViewState = state.thumbnailViewState.copy(cancelDownloadClickListener = listener),
albumViewState = state.albumViewState.copy(cancelDownloadClickListener = listener)
)
state.applyState(thumbnail, album)
}
private fun setThumbnailBounds(bounds: IntArray) {
val (minWidth, maxWidth, minHeight, maxHeight) = bounds
state = state.copy(

View File

@@ -1,3 +1,8 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components
import android.graphics.Color
@@ -31,6 +36,10 @@ data class ConversationItemThumbnailState(
@IgnoredOnParcel
private val downloadClickListener: SlidesClickedListener? = null,
@IgnoredOnParcel
private val cancelDownloadClickListener: SlidesClickedListener? = null,
@IgnoredOnParcel
private val playVideoClickListener: SlideClickListener? = null,
@IgnoredOnParcel
private val longClickListener: OnLongClickListener? = null,
private val visibility: Int = View.GONE,
private val minWidth: Int = -1,
@@ -55,6 +64,8 @@ data class ConversationItemThumbnailState(
thumbnailView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
thumbnailView.get().setThumbnailClickListener(clickListener)
thumbnailView.get().setDownloadClickListener(downloadClickListener)
thumbnailView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
thumbnailView.get().setPlayVideoClickListener(playVideoClickListener)
thumbnailView.get().setOnLongClickListener(longClickListener)
thumbnailView.get().setBounds(minWidth, maxWidth, minHeight, maxHeight)
}
@@ -69,6 +80,10 @@ data class ConversationItemThumbnailState(
@IgnoredOnParcel
private val downloadClickListener: SlidesClickedListener? = null,
@IgnoredOnParcel
private val cancelDownloadClickListener: SlidesClickedListener? = null,
@IgnoredOnParcel
private val playVideoClickListener: SlideClickListener? = null,
@IgnoredOnParcel
private val longClickListener: OnLongClickListener? = null,
private val visibility: Int = View.GONE,
private val cellBackgroundColor: Int = Color.TRANSPARENT,
@@ -89,6 +104,8 @@ data class ConversationItemThumbnailState(
albumView.get().setRadii(cornerTopLeft, cornerTopRight, cornerBottomRight, cornerBottomLeft)
albumView.get().setThumbnailClickListener(clickListener)
albumView.get().setDownloadClickListener(downloadClickListener)
albumView.get().setCancelDownloadClickListener(cancelDownloadClickListener)
albumView.get().setPlayVideoClickListener(playVideoClickListener)
albumView.get().setOnLongClickListener(longClickListener)
albumView.get().setCellBackgroundColor(cellBackgroundColor)
}

View File

@@ -5,19 +5,20 @@
package org.thoughtcrime.securesms.components
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import org.signal.core.util.ResourceUtil
import org.signal.core.util.concurrent.LifecycleDisposable
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.PromptLogsBottomSheetBinding
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.CommunicationActions
@@ -27,14 +28,25 @@ import org.thoughtcrime.securesms.util.SupportEmailUtil
class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
companion object {
private const val KEY_PURPOSE = "purpose"
@JvmStatic
fun show(context: Context, fragmentManager: FragmentManager) {
if (NetworkUtil.isConnected(context) && fragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
fun show(activity: AppCompatActivity, purpose: Purpose) {
if (!activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
return
}
if (NetworkUtil.isConnected(activity) && activity.supportFragmentManager.findFragmentByTag(BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) == null) {
DebugLogsPromptDialogFragment().apply {
arguments = bundleOf()
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
SignalStore.uiHints().lastNotificationLogsPrompt = System.currentTimeMillis()
arguments = bundleOf(
KEY_PURPOSE to purpose.serialized
)
}.show(activity.supportFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
when (purpose) {
Purpose.NOTIFICATIONS -> SignalStore.uiHints().lastNotificationLogsPrompt = System.currentTimeMillis()
Purpose.CRASH -> SignalStore.uiHints().lastCrashPrompt = System.currentTimeMillis()
}
}
}
}
@@ -44,7 +56,12 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
private val binding by ViewBinderDelegate(PromptLogsBottomSheetBinding::bind)
private lateinit var viewModel: PromptLogsViewModel
private val viewModel: PromptLogsViewModel by viewModels(
factoryProducer = {
val purpose = Purpose.deserialize(requireArguments().getInt(KEY_PURPOSE))
PromptLogsViewModel.Factory(ApplicationDependencies.getApplication(), purpose)
}
)
private val disposables: LifecycleDisposable = LifecycleDisposable()
@@ -55,43 +72,63 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
disposables.bindTo(viewLifecycleOwner)
viewModel = ViewModelProvider(this).get(PromptLogsViewModel::class.java)
val purpose = Purpose.deserialize(requireArguments().getInt(KEY_PURPOSE))
when (purpose) {
Purpose.NOTIFICATIONS -> {
binding.title.setText(R.string.PromptLogsSlowNotificationsDialog__title)
}
Purpose.CRASH -> {
binding.title.setText(R.string.PromptLogsSlowNotificationsDialog__title_crash)
}
}
binding.submit.setOnClickListener {
val progressDialog = SignalProgressDialog.show(requireContext())
disposables += viewModel.submitLogs().subscribe({ result ->
submitLogs(result)
submitLogs(result, purpose)
progressDialog.dismiss()
dismiss()
dismissAllowingStateLoss()
}, { _ ->
Toast.makeText(requireContext(), getString(R.string.HelpFragment__could_not_upload_logs), Toast.LENGTH_LONG).show()
progressDialog.dismiss()
dismiss()
dismissAllowingStateLoss()
})
}
binding.decline.setOnClickListener {
SignalStore.uiHints().markDeclinedShareNotificationLogs()
dismiss()
if (purpose == Purpose.NOTIFICATIONS) {
SignalStore.uiHints().markDeclinedShareNotificationLogs()
}
dismissAllowingStateLoss()
}
}
private fun submitLogs(debugLog: String) {
private fun submitLogs(debugLog: String, purpose: Purpose) {
CommunicationActions.openEmail(
requireContext(),
SupportEmailUtil.getSupportEmailAddress(requireContext()),
getString(R.string.DebugLogsPromptDialogFragment__signal_android_support_request),
getEmailBody(debugLog)
getEmailBody(debugLog, purpose)
)
}
private fun getEmailBody(debugLog: String?): String {
private fun getEmailBody(debugLog: String?, purpose: Purpose): String {
val suffix = StringBuilder()
if (debugLog != null) {
suffix.append("\n")
suffix.append(getString(R.string.HelpFragment__debug_log))
suffix.append(" ")
suffix.append(debugLog)
}
val category = ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__slow_notifications_category)
val category = when (purpose) {
Purpose.NOTIFICATIONS -> ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__slow_notifications_category)
Purpose.CRASH -> ResourceUtil.getEnglishResources(requireContext()).getString(R.string.DebugLogsPromptDialogFragment__crash_category)
}
return SupportEmailUtil.generateSupportEmailBody(
requireContext(),
R.string.DebugLogsPromptDialogFragment__signal_android_support_request,
@@ -100,4 +137,21 @@ class DebugLogsPromptDialogFragment : FixedRoundedCornerBottomSheetDialogFragmen
suffix.toString()
)
}
enum class Purpose(val serialized: Int) {
NOTIFICATIONS(1), CRASH(2);
companion object {
fun deserialize(serialized: Int): Purpose {
for (value in values()) {
if (value.serialized == serialized) {
return value
}
}
throw IllegalArgumentException("Invalid value: $serialized")
}
}
}
}

View File

@@ -5,13 +5,11 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder;
import android.text.style.CharacterStyle;
import android.util.AttributeSet;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import org.signal.core.util.BreakIteratorCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.emoji.SimpleEmojiTextView;
@@ -20,7 +18,6 @@ import org.thoughtcrime.securesms.util.ContextUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Iterator;
import java.util.Objects;
public class FromTextView extends SimpleEmojiTextView {

View File

@@ -19,7 +19,6 @@ import android.view.animation.Interpolator;
import android.view.animation.TranslateAnimation;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -141,7 +140,7 @@ public class InputPanel extends ConstraintLayout
public void onFinishInflate() {
super.onFinishInflate();
View quoteDismiss = findViewById(R.id.quote_dismiss);
View quoteDismiss = findViewById(R.id.quote_dismiss_stub);
this.composeContainer = findViewById(R.id.compose_bubble);
this.stickerSuggestion = findViewById(R.id.input_panel_sticker_suggestion);

View File

@@ -37,7 +37,6 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* LinearLayout that, when a view container, will report back when it thinks a soft keyboard

View File

@@ -163,10 +163,10 @@ public class LinkPreviewView extends FrameLayout {
}
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) {
setLinkPreview(glideRequests, linkPreview, showThumbnail, true);
setLinkPreview(glideRequests, linkPreview, showThumbnail, true, false);
}
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showDescription) {
public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showDescription, boolean scheduleMessageMode) {
spinner.setVisibility(GONE);
noPreview.setVisibility(GONE);
@@ -216,8 +216,8 @@ public class LinkPreviewView extends FrameLayout {
if (showThumbnail && linkPreview.getThumbnail().isPresent()) {
thumbnail.setVisibility(VISIBLE);
thumbnailState.applyState(thumbnail);
thumbnail.get().setImageResource(glideRequests, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false);
thumbnail.get().showDownloadText(false);
thumbnail.get().setImageResource(glideRequests, new ImageSlide(linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION && !scheduleMessageMode, false);
thumbnail.get().showSecondaryText(false);
} else if (callLinkRootKey != null) {
thumbnail.setVisibility(VISIBLE);
thumbnailState.applyState(thumbnail);
@@ -228,7 +228,7 @@ public class LinkPreviewView extends FrameLayout {
.asDrawable(getContext(),
AvatarColorHash.forCallLink(callLinkRootKey.getKeyBytes()))
);
thumbnail.get().showDownloadText(false);
thumbnail.get().showSecondaryText(false);
} else {
thumbnail.setVisibility(GONE);
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.components
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import org.signal.core.util.logging.Log
class LoggingAdapterDataObserver(
private val tag: String
) : AdapterDataObserver() {
override fun onChanged() {
Log.d(tag, "onChanged() called")
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
Log.d(tag, "onItemRangeChanged() called with: positionStart = $positionStart, itemCount = $itemCount")
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
Log.d(tag, "onItemRangeChanged() called with: positionStart = $positionStart, itemCount = $itemCount, payload = $payload")
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
Log.d(tag, "onItemRangeInserted() called with: positionStart = $positionStart, itemCount = $itemCount")
}
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
Log.d(tag, "onItemRangeRemoved() called with: positionStart = $positionStart, itemCount = $itemCount")
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
Log.d(tag, "onItemRangeMoved() called with: fromPosition = $fromPosition, toPosition = $toPosition, itemCount = $itemCount")
}
override fun onStateRestorationPolicyChanged() {
Log.d(tag, "onStateRestorationPolicyChanged() called")
}
}

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