Compare commits

..

302 Commits

Author SHA1 Message Date
Nicholas
0775fc7ead Bump version to 6.27.1 2023-07-19 17:48:36 -04:00
Nicholas
034aef483b Updated language translations. 2023-07-19 17:48:06 -04:00
Nicholas Tinsley
ee5b99fed4 Rotate edit message feature flag. 2023-07-19 17:40:41 -04:00
Nicholas
e031da1337 Bump version to 6.27.0 2023-07-19 17:23:29 -04:00
Nicholas
8f1514642c Updated language translations. 2023-07-19 17:22:58 -04:00
Clark
5aa304ea9a Always show verify safety numbers option. 2023-07-19 17:12:19 -04:00
Alex Hart
c5a27b2cc7 Fix remote story deletion syncing. 2023-07-19 17:12:19 -04:00
Clark
0fde404da8 Add you may have messages notification. 2023-07-19 17:12:19 -04:00
Cody Henthorne
5242b9af39 Rotate CFv2 feature flag. 2023-07-19 17:12:19 -04:00
Cody Henthorne
5e2d6fc05f Fix incorrect unread divider behavior when receiving new messages. 2023-07-19 17:12:18 -04:00
Clark Chen
7e08a1f321 Only show one safety number education dialog at a time. 2023-07-19 17:12:18 -04:00
Cody Henthorne
076295eae8 Fix rendering bug when scrolling a chat with a background in CFv2.
"When in doubt, put it in a FrameLayout. - Wayne Gretzky" - MiCHAELSCOTT
2023-07-19 17:12:18 -04:00
Cody Henthorne
c13339ca52 Fix scroll to bottom on send bug in CFv2. 2023-07-19 17:12:18 -04:00
Nicholas
627657e1de Update to the final ExoPlayer release. 2023-07-19 17:12:18 -04:00
Alex Hart
a8349671d0 Add Receive support for the new CallLogEvent proto messages. 2023-07-19 17:12:18 -04:00
Clark
461875b0e4 Add support for displaying both ACI and e164 safety numbers. 2023-07-19 17:12:18 -04:00
Cody Henthorne
00bbb6bc6e Fix spoiler display bug in long message view. 2023-07-19 17:12:18 -04:00
Greyson Parrelli
e1f1181a07 Specifiy SHA256 for docker base image. 2023-07-19 17:12:18 -04:00
Nicholas Tinsley
0e1de39192 Remove Bluetooth mic voice message recording. 2023-07-19 17:12:18 -04:00
Cody Henthorne
05bbeb10da Revert "Attempt to fix crash on call hangup."
This reverts commit 025411c9fb.
2023-07-19 17:12:18 -04:00
Cody Henthorne
7375a9e06b Do not jumbo styled emojis. 2023-07-19 17:12:18 -04:00
Cody Henthorne
4910050891 Fix bubbles jumping around when entering selection mode. 2023-07-19 17:12:18 -04:00
Cody Henthorne
67d4f666ce Add share highwater timestamp protection to CFv2. 2023-07-19 17:12:18 -04:00
Cody Henthorne
e6c9449e3c Fix voice note playback and wave form generation in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart
b8effba497 Fix crash in hasHeader via range check. 2023-07-19 17:12:18 -04:00
Cody Henthorne
8fcdd7cb8a Update attachment keyboard based on payment availability in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart
f3fb5ccc3b CFV2 handle keyboard images and gifs. 2023-07-19 17:12:18 -04:00
Alex Hart
b8f55f982f Fix toggle in AdvancedPrivacySettingsFragment. 2023-07-19 17:12:18 -04:00
Cody Henthorne
6db59cb896 Prevent menu creation slowing data load performance in CFv2. 2023-07-18 10:19:17 -04:00
Cody Henthorne
3db83c1602 Fix multiple issues in CFv2. 2023-07-18 10:01:48 -04:00
Nicholas Tinsley
6be9225fbd Include incremental digest when sending attachments. 2023-07-18 09:55:02 -04:00
Nicholas Tinsley
653eff403c Prevent overlap of backup icon on small screens.
Fixes #13064.
2023-07-18 09:55:02 -04:00
Alex Hart
ab410ec0cf CFV2 Message Request state adapter update. 2023-07-18 09:55:02 -04:00
Cody Henthorne
7b75a32394 Clean up remaining CFv2 todos. 2023-07-18 09:55:02 -04:00
Cody Henthorne
daf077b3c9 Fix overlapping date and unread decorations. 2023-07-18 09:55:02 -04:00
Alex Hart
f6bbb59400 Fix crash when accessing binding via delayed runnable. 2023-07-18 09:55:02 -04:00
Cody Henthorne
09813d5dbd Fix crash when dismissing mention picker late. 2023-07-18 09:55:02 -04:00
Cody Henthorne
fe509838f4 Add CFv2 feature flag. 2023-07-18 09:55:02 -04:00
Alex Hart
6a443d0074 Fix clipping around incoming V2 conversation items. 2023-07-18 09:55:02 -04:00
Greyson Parrelli
8fc1065dd6 Rename some protos. 2023-07-18 09:55:02 -04:00
Cody Henthorne
1af50ba0f5 Perform safety number check pre-send in CFv2. 2023-07-18 09:55:02 -04:00
Alex Hart
82b3036b77 Add handling for text slide deck in sendMessage. 2023-07-18 09:55:02 -04:00
Alex Hart
676412019c CFV2 Add search bottom bar to bottom panel barrier ids. 2023-07-18 09:55:02 -04:00
Alex Hart
980f4e00e2 Scroll date header in CFV2. 2023-07-18 09:55:01 -04:00
Cody Henthorne
5731bf023a Add unread divider decoration to CFv2. 2023-07-18 09:55:01 -04:00
Alex Hart
2511ca17aa Add onRequestPermissionsResult to CFV2. 2023-07-18 09:55:01 -04:00
Nicholas Tinsley
fae653540b Make Safety Number Changed dialog scrollable.
This helps with smaller screens.
2023-07-18 09:55:01 -04:00
Alex Hart
b0ca66cc1a Add new active column to ThreadTable. 2023-07-18 09:55:01 -04:00
Nicholas
a65e9c76bc Bump version to 6.26.3 2023-07-17 23:31:59 -04:00
Nicholas
adbac4c557 Updated language translations. 2023-07-17 23:31:48 -04:00
Nicholas
fc7b024e96 Fix benchmark test user generation. 2023-07-17 22:06:00 -04:00
Clark
acee65ba25 Defer TooltipPopup show till anchor has been laid out. 2023-07-17 16:40:08 -04:00
Clark Chen
244902ecfc Bump version to 6.26.2 2023-07-14 18:15:18 -04:00
Clark Chen
4b1a678af2 Updated language translations. 2023-07-14 17:57:49 -04:00
Greyson Parrelli
59dd72b5c0 Fix issue with syncing remote deletes in note to self. 2023-07-14 17:46:40 -04:00
Greyson Parrelli
44c393f11a Fix possible ISE in registration. 2023-07-14 17:46:40 -04:00
Nicholas Tinsley
fddfbd8d2d Fix changing number flow in scenarios where service requires additional verification.
Fixes #12985, #13059.
2023-07-14 17:46:40 -04:00
Clark
e7e00bd428 Disable sticker suggestions when editing message. 2023-07-14 17:46:40 -04:00
Cody Henthorne
07702e69ad Improve spoiler render performance. 2023-07-14 14:41:36 -04:00
Cody Henthorne
e5c3757629 Remove Phase 1 in preparation for CFv2. 2023-07-14 13:51:30 -04:00
Greyson Parrelli
7031bbae43 Close the SVR2 socket when we're done. 2023-07-14 13:11:08 -04:00
Nicholas Tinsley
4a6dfed676 Even more Change Number logging. 2023-07-14 09:56:49 -04:00
Clark Chen
bb0b414d71 Bump version to 6.26.1 2023-07-13 18:11:30 -04:00
Nicholas Tinsley
95e25652c1 Add change number screen logging. 2023-07-13 17:59:53 -04:00
Nicholas Tinsley
58155b0859 Restore previous Registration session handler.
Fixes #12839, #13059.
2023-07-13 15:20:15 -04:00
Clark
f579b79d2e Change websocket keepalive response time to 20s. 2023-07-13 14:22:10 -04:00
Greyson Parrelli
47673be4e0 Bump version to 6.26.0 2023-07-12 16:09:59 -04:00
Clark Chen
f67e6a9e9f Updated language translations. 2023-07-12 15:56:38 -04:00
Greyson Parrelli
110d8259fa Explicity declare exported status in manifest entries.
This shouldn't change any behavior. We're just explicitly declaring the
exported field to be what it would otherwise get set to by default.
2023-07-12 15:48:52 -04:00
Alex Hart
8f253ffc43 Add lazy thread creation throughout in preparation for CFV2. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
6ca9cb6da1 Add migration to cleanup some inconsistent DB state. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
1b63bdec12 Control CDS compat mode with it's own remote config. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
bb52172516 Fix validation of NullMessage types. 2023-07-12 15:48:52 -04:00
Alex Hart
1640495f34 Fix CFV2 selection. 2023-07-12 15:48:52 -04:00
Cody Henthorne
64415a980f Fix text cutoff in device transfer lock dialog.
Fixes #13040
2023-07-12 15:48:52 -04:00
Cody Henthorne
e06d141823 Tweak SpoilerPaint settings to reduce wave effect.
Closes #13041
2023-07-12 15:48:52 -04:00
Clark
ac4d8679a1 Add local metrics for message processing. 2023-07-12 15:48:52 -04:00
Clark
8fc03a67b9 Fix failing AttachmentCipherTest for incremental mac. 2023-07-12 15:48:52 -04:00
Cody Henthorne
648506fe04 Fix empty emoji search index. 2023-07-12 15:48:52 -04:00
Cody Henthorne
2c74ac8bfa Fix spoilers not working in story replies. 2023-07-12 15:48:52 -04:00
Cody Henthorne
963709c552 Fix draft message lost during media send flow. 2023-07-12 15:48:52 -04:00
Clark
9af888a595 Refactor FcmFetchManager to make foreground service clearer. 2023-07-12 15:48:51 -04:00
Cody Henthorne
ec373b5b4d Fix attachment dedupe race where original may be deleted before new usage is recorded. 2023-07-12 15:48:51 -04:00
Cody Henthorne
bfcd57881e Add test to verify sync behavior during VERIFIED to DEFAULT change. 2023-07-12 15:48:51 -04:00
Alex Hart
b277a8c5e0 CFV2 fix possible race when initializing menu. 2023-07-12 15:48:51 -04:00
Cody Henthorne
979a50716e Fix most android tests.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-07-12 15:48:51 -04:00
Jim Gustafson
c5f0da8151 Update to RingRTC v2.29.0 2023-07-12 15:48:51 -04:00
Cody Henthorne
aee0b5268f Improve conversation open benchmark test. 2023-07-12 15:48:51 -04:00
Alex Hart
7e909f2bee Add InternalSettings option for ConversationItem V2. 2023-07-12 15:48:51 -04:00
Cody Henthorne
584c90521a Polish voice notes in CFv2. 2023-07-12 15:48:51 -04:00
Yuval Razieli
23ef8c78bd Fix an issue where the charset in the link preview of some pages was not identified correctly. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
5ca025544e Improve logging around memory usage. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
500ae0c72e Add Spinner support for kyber keys. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
c359207f1f Fix potential NPE in MediaOverviewPageFragment. 2023-07-12 15:48:51 -04:00
Alex Hart
159f2ebec0 Don't crash on invalid window token in tooltip popup. 2023-07-12 15:48:51 -04:00
Alex Hart
a0db812606 Fix action bar background for multiselect in CFV2. 2023-07-12 15:48:51 -04:00
Alex Hart
d4c6a433d7 Add documentation to DeliveryStatusView. 2023-07-12 15:48:51 -04:00
Alex Hart
3a7cde9239 Fix pending rotation pivot point. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
4b6c308ae9 Stop HEAD requests for possibly-unlisted contacts during CDS refresh. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
002279f6a7 Ignore AccountRecord.e164 if PNP-capable. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
c807e52ad9 Support CDSI ignore the useCompat flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
9557e3b910 Remove unused feature flag constant. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
ddc77884bd Inline the credit card payments feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
9d6337d5a8 Inline the chat filters feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
117dd17215 Rotate edit message feature flag. 2023-07-12 15:48:51 -04:00
Alex Hart
f9eed0f6d0 Fix slide in animation for new messages in CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
4429145cdf Remove deprecated ignoreResults parameter. 2023-07-12 15:48:51 -04:00
Alex Hart
5ea4cbf9ca CFV2 Add proper body presentation code. 2023-07-12 15:48:51 -04:00
Ehren Kret
c6473ca9e6 Minor improvement to Android Backup file format. 2023-07-12 15:48:51 -04:00
Alex Hart
38b2a2f5b7 Add multi-select support to CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
ebaa445bee Save last-known server time offset. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
8372c699f7 Update username link QR code styling. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
e1570e9512 Start mirroring to SVR2. 2023-07-12 15:48:51 -04:00
Alex Hart
dfb7304626 Fix external shares in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
919531a82b Enable quick toggle camera in CFV2 input panel. 2023-07-11 17:58:09 -04:00
Alex Hart
81b2e9ccd2 Hook up reaction callback. 2023-07-11 17:58:09 -04:00
Alex Hart
4ef2aba4e2 Ensure text-only entries are cached. 2023-07-11 17:58:09 -04:00
Alex Hart
4590655dc5 Fix CI V2 layout bounds when item has a reaction. 2023-07-11 17:58:09 -04:00
Alex Hart
3040b70100 Add initial instrumentation testing for V2 ConversationItem shapes. 2023-07-11 17:58:09 -04:00
Alex Hart
47b97aafc6 Add TypingIndicatorDecoration to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
27e7383db6 Add compose divider to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
42fe827cb3 Add proper navigation bottom bar color. 2023-07-11 17:58:09 -04:00
Alex Hart
3fa3e8357c Handle video calls for 1:1 conversations in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
6260607e1b Launch settings on toolbar press in CFV2. 2023-07-11 17:58:09 -04:00
Rashad Sookram
3c1666e874 Update verification metadata for aapt2. 2023-07-11 17:58:09 -04:00
Clark
f4a082584c Add upload/download size restrictions for attachments based on remote config. 2023-07-11 17:58:09 -04:00
Ehren Kret
87d4dba32b remove whispersystems.org reference 2023-07-11 17:58:09 -04:00
Alex Hart
329f68d167 Upgrade to Gradle 8.0.2 and AGP 8.0.2 2023-07-11 17:58:09 -04:00
Alex Hart
2053cf085a Add retries to CallEventSyncJob. 2023-07-11 17:58:09 -04:00
Iñaqui
16d48984c5 Remove calling stun fallback. 2023-07-11 17:58:09 -04:00
Cody Henthorne
a17800283a Plumb schedule message and edit message send flows for CFv2. 2023-07-11 17:58:09 -04:00
Alex Hart
9b1917cbdc Add story ring to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
e1e3d7a85b Small tweaks for footer positioning. 2023-07-11 17:58:09 -04:00
Alex Hart
dc37d1f029 Apply proper background to input panel when wallpaper is enabled. 2023-07-11 17:58:09 -04:00
Alex Hart
53e62f2be0 Add new text-only conversation item. 2023-07-11 17:58:09 -04:00
Alex Hart
e6cc789c6f Add conversation test springboard fragment. 2023-07-11 17:58:09 -04:00
Cody Henthorne
cfaef77b21 Properly plumb attachment keyboard in CFv2. 2023-07-11 17:58:09 -04:00
Clark Chen
36fc9aa82a Add 10s timeout to user facing CDSI requests. 2023-07-11 17:58:09 -04:00
Alex Hart
8d20669e46 Rewrite AlertView to be a single ImageView. 2023-07-11 17:58:09 -04:00
Greyson Parrelli
9d8501cd64 Bump version to 6.25.5 2023-07-11 14:21:30 -04:00
Greyson Parrelli
88827e94f5 Updated language translations. 2023-07-11 14:21:30 -04:00
Alex Hart
99d2a0c0b6 Wrap dialog content in a scroll view. 2023-07-11 14:21:30 -04:00
Alex Hart
1d0582867b Fix crashing when deleting a custom story. 2023-07-11 14:21:30 -04:00
Alex Hart
0ea6d9205d Fix rotation metrics for DeliveryStatusView. 2023-07-11 14:21:30 -04:00
Greyson Parrelli
f438ef543b Fix prekey generation during registration. 2023-07-10 23:05:36 -04:00
Alex Hart
61cd9767c8 Ensure bitmaps are not recycled when getting them from the cache. 2023-07-10 17:08:27 -03:00
Alex Hart
5fbf0a98b9 Bump version to 6.25.4 2023-07-07 15:08:04 -03:00
Alex Hart
1671518ded Updated language translations. 2023-07-07 15:04:13 -03:00
Alex Hart
f628ffca06 Fix avatar blurring during calls. 2023-07-07 14:53:23 -03:00
Alex Hart
1d6f4fd4e7 Bump version to 6.25.3 2023-07-06 16:27:55 -03:00
Alex Hart
9eedc0a36b Updated language translations. 2023-07-06 16:19:42 -03:00
Alex Hart
a870fe9e1a Do not throw an ISE when we cannot start a foreground service from calling. 2023-07-06 16:12:09 -03:00
Alex Hart
d3f779cea9 Fix crash when toggling stories. 2023-07-05 12:16:46 -03:00
Cody Henthorne
fb4f41b996 Extend style to inserted text via paste or auto-correct regardless of content. 2023-06-30 14:05:35 -04:00
Cody Henthorne
11aac76fb6 Fix spoiler rendering in quote views. 2023-06-30 13:46:17 -04:00
Nicholas
98424f6cbb Bump version to 6.25.2 2023-06-30 13:36:03 -04:00
Nicholas
1cf7c59af9 Updated language translations. 2023-06-30 13:33:51 -04:00
Clark
13470fb0c3 Increase FCM push websocket timeout. 2023-06-30 12:41:00 -04:00
Nicholas Tinsley
3aa0fd1937 Reset continue button state when dismissing registration number confirmation dialog. 2023-06-30 12:23:47 -04:00
Clark
9e6f2336d1 Add push websocket fetch stats. 2023-06-30 11:07:05 -04:00
Nicholas Tinsley
8b8d62f598 Only close AttachmentCipher streams if using incremental MAC. 2023-06-30 11:06:51 -04:00
Nicholas
4572ae5886 Bump version to 6.25.1 2023-06-29 18:42:29 -04:00
Nicholas
0802d4beb4 Updated language translations. 2023-06-29 18:40:18 -04:00
Nicholas
9361aa700a Transcode video files in a streamable format. 2023-06-29 18:30:29 -04:00
Greyson Parrelli
be5cad1cec Add support for Emoji v15.0 2023-06-29 15:55:49 -04:00
Greyson Parrelli
fe20de2995 Improve logging around edit and sync messages. 2023-06-29 15:55:12 -04:00
Clark
8714e4298e Specify scale type for glide thumbnails. 2023-06-29 15:32:04 -04:00
Alex Hart
1baebe7475 Remove read log line from AvatarProvider. 2023-06-29 13:17:05 -03:00
Nicholas
6686ae43f3 Bump version to 6.25.0 2023-06-28 17:24:23 -04:00
Nicholas
cc2c0e9561 Updated language translations. 2023-06-28 17:21:07 -04:00
Nicholas
34d252a4bd Add incremental digests to attachment sending. 2023-06-28 17:13:15 -04:00
Cody Henthorne
025411c9fb Attempt to fix crash on call hangup. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
4edb66d2b9 Fix SimpleProgressDialog. 2023-06-28 17:13:15 -04:00
Alex Hart
a17033dff4 Add ContentProvider for user avatars. 2023-06-28 17:13:15 -04:00
Cody Henthorne
04a5e56da7 Add mentions support to CFv2. 2023-06-28 17:13:15 -04:00
Bernie Dolan
0e6a3dd408 Fix MobileCoin test net config. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
47f48a6a8c Put backup enable dialog into ScrollView for smaller screens.
Addresses #13033.
2023-06-28 17:13:15 -04:00
Cody Henthorne
2ef7fabade Add inline emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart
5c2b475c01 Add randomized testing for ConversationItem. 2023-06-28 17:13:15 -04:00
Clark
559f4bc0d3 Rotate edit message feature flag. 2023-06-28 17:13:15 -04:00
Clark
1357a4816b Update edit history dialog to new style. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
d1b8a56c0f Revert "Add more logging around Bubble eligibility."
This purely logs the state without affecting the return logic.
2023-06-28 17:13:15 -04:00
Cody Henthorne
7ea38298ea Fix gif playback in CFv2. 2023-06-28 17:13:15 -04:00
Clark
c08f1355db Refresh isConnectionNecessary on network block changes. 2023-06-28 17:13:15 -04:00
Cody Henthorne
b6589637fa Add emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart
2ee2d2883a Disallow reacting to pending or failed messages. 2023-06-28 17:13:15 -04:00
Alex Hart
029c8ba917 Fix text story keyboard in text stories. 2023-06-28 17:13:15 -04:00
Alex Hart
60e1ee21ed Prevent ANR from large call logs. 2023-06-28 17:13:15 -04:00
Alex Hart
a96e9158c4 Change call notification flag to UPDATE_CURRENT and change request code. 2023-06-28 17:13:15 -04:00
Alex Hart
e47765d7d5 Add logging to WebRtcActivity Intent. 2023-06-28 17:13:15 -04:00
Clark Chen
e807435c8b Capitalize edited string. 2023-06-28 17:13:15 -04:00
Sgn-32
14b41a93e2 Fix Do not animate spoilers if system animations are disabled.
Closes #13016
2023-06-28 17:13:15 -04:00
Yuichi Araki
f51fb9da29 Report shortcut usage when a message is sent to a group.
Fixes #13029
Close #13030
2023-06-28 17:13:15 -04:00
Sebastian Scheibner
df3ca3d3cc Fix duplicate kyber pre key id in registration
The `PreKeyUtil.generateKyberPreKey` method doesn't update the `nextKyberPreKeyId` in the metadataStore,
so the two `metadataStore.getNextKyberPreKeyId()` calls in this method return the same id.
The first oneTimeKyberPreKey will have the same id as the lastResortKyberPreKey and overwrite it in the database.

Closes #13021
2023-06-28 17:13:15 -04:00
Clark
7786956b11 Fix conversation date header weirdness with edited messages. 2023-06-28 17:13:15 -04:00
Cody Henthorne
c17d62aeab Update ktlint and gradle plugin. 2023-06-28 17:13:15 -04:00
Cody Henthorne
65255121de Add media keyboard support in CFv2. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
b042945fef Add check for internal users around group lock ordering. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
9d979217fa Include 'dont keep activities' setting in debuglog. 2023-06-28 17:13:15 -04:00
Greyson Parrelli
c177de2ec3 Add CDSI and SVR2 to static IP list. 2023-06-26 15:09:39 -04:00
Greyson Parrelli
b4fd57d900 Fix possible NPEs with megaphone container. 2023-06-26 15:09:36 -04:00
Nicholas Tinsley
92339dfdcf Add one more logging statement to the change number flow. 2023-06-26 15:09:36 -04:00
Nicholas
8ae115028e Update PIN switch keyboard button to be more straightforward.
Addresses #12866.
2023-06-26 15:09:36 -04:00
Greyson Parrelli
2dd0221680 Fix issue where FTS recovery path didn't work during migrations.
There was a recursive getDatabase() call because it was happening during
a migration. Solution was to re-use the DB instance we already had.
2023-06-26 15:09:36 -04:00
Greyson Parrelli
1a1923c6c0 Fix docker build by using our own mirror.
snapshot.debian.org is super flaky and unreliable. At this point it's
easier to host our own mirror snapshot.
2023-06-26 15:09:36 -04:00
Cody Henthorne
5801ad4bdb Update FCM to 23.1.2 2023-06-26 15:09:36 -04:00
Cody Henthorne
388f2971e9 Allow libsignal-service to build with JDK17. 2023-06-26 15:09:36 -04:00
Greyson Parrelli
14c3a36ec0 Improve logging for GV2 validation error. 2023-06-26 15:09:36 -04:00
Alex Hart
1ad338ce31 Turn DelveryStatusView into a custom AppCompatImageView. 2023-06-26 15:09:36 -04:00
Greyson Parrelli
71981e8a27 Fix dockerfile for reproducible builds. 2023-06-26 15:09:36 -04:00
Nicholas
5cb10cd054 Improve voice note Bluetooth state handling. 2023-06-26 15:09:36 -04:00
Rashad Sookram
ecf576e9b9 Don't localize audio output in logs. 2023-06-26 15:09:36 -04:00
Alex Hart
09d17659b9 Add media send support to CFV2. 2023-06-26 15:09:36 -04:00
Cody Henthorne
53673be5cb Update AGP to 8.0
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-06-26 15:09:36 -04:00
Alex Hart
ed4a1d6ddd Use delete instead of delete for me in call log dialog. 2023-06-26 15:09:36 -04:00
Alex Hart
e3044b8b85 Add preview treatment for call links. 2023-06-26 15:09:36 -04:00
Alex Hart
4ce05a064c Add CFV2 Sticker Suggestions. 2023-06-26 15:09:36 -04:00
Alex Hart
2fbcc23451 Add LinkPreview support to CFV2. 2023-06-26 15:09:36 -04:00
Alex Hart
3bdffed8c9 Add call link scrubbing for logs. 2023-06-26 15:09:36 -04:00
Nicholas
6cc8e87d46 Bump version to 6.24.4 2023-06-26 14:33:40 -04:00
Nicholas
7d8f549d97 Updated baseline profile. 2023-06-26 14:31:28 -04:00
Nicholas
fb2ef265bd Updated language translations. 2023-06-26 14:16:45 -04:00
Nicholas
96e8256781 Fix TestUser constructor. 2023-06-26 14:10:13 -04:00
Cody Henthorne
dd40517f12 Fix crash after hanging up call. 2023-06-26 14:10:13 -04:00
Cody Henthorne
6c95b766d6 Fix answer audio call and video starting bug. 2023-06-26 14:10:13 -04:00
Greyson Parrelli
feffdcb71e Bump version to 6.24.3 2023-06-22 18:53:58 -04:00
Greyson Parrelli
f33e2c49ca Updated language translations. 2023-06-22 18:53:29 -04:00
Clark
1037acd4a2 Load original message and use it for timestamps in Conversation. 2023-06-22 18:42:56 -04:00
Clark
68042fc755 No longer call startForeground in onCreate. 2023-06-22 14:09:23 -04:00
Alex Hart
1bdc77affe Resolve issue with incoming video call state. 2023-06-22 14:52:10 -03:00
Nicholas Tinsley
88f50da4fb Discard voice note recording on error. 2023-06-22 13:32:27 -04:00
Cody Henthorne
bc1fbd9b6c Fix spoilers not animating after leaving and returning to conversation.
Fixes #13015
2023-06-22 11:57:59 -04:00
Cody Henthorne
c1b7b7c95e Fix styling lost in draft state bug. 2023-06-22 11:16:27 -04:00
Greyson Parrelli
b0688eed5c Bump version to 6.24.2 2023-06-21 20:34:12 -04:00
Greyson Parrelli
e06ed03d33 Updated language translations. 2023-06-21 20:34:12 -04:00
Clark
e2a79394ab Always background legacy FCM fallback. 2023-06-21 20:34:12 -04:00
Clark
5db770ca44 Use original message instead of edit message when checking if we can edit. 2023-06-21 17:52:33 -04:00
Cody Henthorne
df8aaa2005 Provide better 'why' when an attachment is not auto-downloaded. 2023-06-21 16:18:16 -04:00
Cody Henthorne
882748f080 Allow formatting text from overflow menu. 2023-06-21 16:16:16 -04:00
Cody Henthorne
15035f4eb3 Fix rendering when editing a message with spoilers. 2023-06-21 13:08:00 -04:00
Cody Henthorne
1d0a87f52a Add ability to clear or toggle formatting. 2023-06-21 13:05:46 -04:00
Clark
59b2cc5f79 Fix edit message date header showing wrong date. 2023-06-21 12:44:22 -04:00
Nicholas Tinsley
d6758fc264 Store PIN keyboard type in file backup. 2023-06-21 11:53:01 -04:00
Greyson Parrelli
36418bec59 Ensure SqlCipherDeletingErrorHandler runs delete. 2023-06-21 10:24:12 -04:00
Cody Henthorne
07bd8b2fa3 Fix NPE in spoiler renderer. 2023-06-21 10:10:01 -04:00
Greyson Parrelli
d989d02af9 Bump version to 6.24.1 2023-06-20 14:26:35 -04:00
Greyson Parrelli
503ce13122 Updated language translations. 2023-06-20 14:24:16 -04:00
Alex Hart
8f77321adb Dispose notification disposable when stopping service. 2023-06-20 12:07:11 -03:00
Clark
60cdcea791 Revert "Call startForeground onCreate for generic foreground service. " 2023-06-20 10:53:28 -04:00
Clark
86cd4c5c30 Fix remote delete for edit messages. 2023-06-20 10:29:47 -04:00
Nicholas Tinsley
62d5f61a0b Allow verification with PIN rather than SMS when restoring from backup. 2023-06-16 16:31:46 -04:00
Clark
25860867bb Use correct receive timestamp for edit message handling. 2023-06-16 16:30:49 -04:00
Greyson Parrelli
272860f071 Do not animate spoilers if system animations are disabled. 2023-06-16 15:51:24 -04:00
Nicholas
767cfbc717 Fix atomic registrations when not using session ID. 2023-06-16 15:38:16 -04:00
Cody Henthorne
55af6ca84e Bump version to 6.24.0 2023-06-15 15:44:35 -04:00
Cody Henthorne
88933ae051 Updated baseline profile. 2023-06-15 15:36:09 -04:00
Cody Henthorne
4781beebee Updated language translations. 2023-06-15 15:36:09 -04:00
Clark Chen
0049c74323 Fix baseline profile generation benchmark. 2023-06-15 15:36:09 -04:00
Alex Hart
361727cec6 Add resend handler to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
1b95177e0e Add handleViewPaymentDetails to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
8d34c54de2 Add multiselect action support to CFV2. 2023-06-15 15:36:09 -04:00
Alex Hart
2fa0eba3db Add search call-through in CFV2 override. 2023-06-15 15:36:09 -04:00
Cody Henthorne
6cc41e95c6 Remove edit message receive feature flag. 2023-06-15 15:36:09 -04:00
Cody Henthorne
b1523f5b91 Update text formatting feature flag. 2023-06-15 15:36:09 -04:00
Nicholas
d16002546d Create account in single network request. 2023-06-15 15:36:09 -04:00
Clark
186a93f5d1 Use separate PNI key distribution endpoint instead of change number. 2023-06-15 15:36:09 -04:00
Alex Hart
3d4875bcfe Add sticker suggestion send support to CFV2. 2023-06-15 15:36:09 -04:00
Nicholas
441e30971a Add more logging around Bubble eligibility.
To help diagnose #12036.
2023-06-15 15:36:08 -04:00
Alex Hart
ff115c2349 Add voice recording to CFV2. 2023-06-15 15:36:08 -04:00
Clark
f23e5bdb44 Prepare edit message for beta run. 2023-06-15 15:36:08 -04:00
Jim Gustafson
d0a232d86a Update to RingRTC v2.28.1 2023-06-15 15:36:08 -04:00
Alex Hart
9fef8386e6 Fix initial call state when starting from action. 2023-06-15 15:36:08 -04:00
Alex Hart
b1680ba5c6 Add new call notification strings. 2023-06-15 15:36:08 -04:00
Alex Hart
6cd59daf0a Fix several issues with call notifications. 2023-06-15 15:36:08 -04:00
Greyson Parrelli
38f2b39ac4 Add common interface over SVR implementations. 2023-06-15 15:36:08 -04:00
Alex Hart
51222738df Remove avatar color from CallLink table. 2023-06-15 15:36:08 -04:00
Clark
b9835584d8 Add sharing for PNP usernames badge. 2023-06-15 13:32:00 -04:00
Alex Hart
cff01021c2 Ensure call links only ever have one call event associated with them. 2023-06-15 13:32:00 -04:00
Alex Hart
d19b8a125c Do not enable ringing for call links. 2023-06-15 13:32:00 -04:00
Alex Hart
4caaa0033b Re-enable call delete sync events. 2023-06-15 13:32:00 -04:00
Alex Hart
f3a0a059ea Add search and arbitrary jump support to CFV2. 2023-06-15 13:32:00 -04:00
Alex Hart
290b0fe46f Ensure owned call links are revoked on delete. 2023-06-15 13:32:00 -04:00
g1a55er
03a212eee4 Reschedule job if background web socket message retrieval fails.
Closes #12971
2023-06-15 13:31:59 -04:00
Alex Hart
f3b629bc06 Add reaction support to CFV2. 2023-06-15 13:31:59 -04:00
Alex Hart
b9e002f7b1 Update call links parsing. 2023-06-15 13:31:59 -04:00
Alex Hart
c90779beea Fix jump position for quotes. 2023-06-15 13:31:59 -04:00
Alex Hart
bc8c8a049f Add onViewsRevealed implementation. 2023-06-15 13:31:59 -04:00
Alex Hart
1d9dc66265 Update several androidx dependencies.
Navigation to 1.6.0
Fragment to 1.6.0
Compose BOM to 2023.05.01
Lifecycle to 2.6.1
Activity to 1.7.2
2023-06-15 13:31:59 -04:00
Alex Hart
886c149c3f Add in-call info sheet for call links. 2023-06-15 13:31:59 -04:00
Clark
369ca189d3 Disable stickers and gifs when editing message. 2023-06-15 13:31:59 -04:00
Cody Henthorne
02c4bbe816 Add date headers to CFv2. 2023-06-15 13:31:59 -04:00
Clark
523c9f6576 Add workflow for monitoring APK size changes. 2023-06-15 13:31:59 -04:00
Cody Henthorne
c259430b09 Bump version to 6.23.5 2023-06-15 13:04:42 -04:00
Cody Henthorne
fc94b90a03 Revert "Adopt new APIs for network connectivity check."
This reverts commit de4c6ab7b7.
2023-06-15 12:57:35 -04:00
Cody Henthorne
6af521130d Revert "Fix connectivity over VPN on older API versions."
This reverts commit 7e24252447.
2023-06-15 12:57:14 -04:00
Cody Henthorne
9c001e4f35 Bump version to 6.23.4 2023-06-15 11:30:05 -04:00
Cody Henthorne
9e8dee36a6 Updated baseline profile. 2023-06-15 11:25:25 -04:00
Cody Henthorne
26b17d8a3c Updated language translations. 2023-06-15 11:23:11 -04:00
Cody Henthorne
1c5e2e3359 Fix linkify for valid URLs with ... in the path. 2023-06-15 11:20:22 -04:00
Clark
0437d37f23 Fix pending retry receipt cache deadlock. 2023-06-15 11:20:22 -04:00
Cody Henthorne
ec6b1a44de Add text formatting support to release notes channel. 2023-06-15 11:20:22 -04:00
Clark
08c661bb14 Fix foreground service start/stop race. 2023-06-14 14:42:08 -04:00
797 changed files with 40497 additions and 29784 deletions

View File

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

View File

@@ -19,11 +19,11 @@ jobs:
steps:
- uses: actions/checkout@v3
- name: set up JDK 11
- name: set up JDK 17
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
java-version: 17
cache: gradle
- name: Validate Gradle Wrapper

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

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

View File

@@ -42,12 +42,11 @@ wire {
}
ktlint {
// Use a newer version to resolve https://github.com/JLLeitschuh/ktlint-gradle/issues/507
version = "0.47.1"
version = "0.49.1"
}
def canonicalVersionCode = 1276
def canonicalVersionName = "6.23.3"
def canonicalVersionCode = 1295
def canonicalVersionName = "6.27.1"
def postFixSize = 100
def abiPostFix = ['universal' : 0,
@@ -159,7 +158,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.2'
kotlinCompilerExtensionVersion = '1.4.4'
}
defaultConfig {
@@ -199,6 +198,8 @@ android {
buildConfigField "String[]", "SIGNAL_KBS_IPS", kbs_ips
buildConfigField "String[]", "SIGNAL_SFU_IPS", sfu_ips
buildConfigField "String[]", "SIGNAL_CONTENT_PROXY_IPS", content_proxy_ips
buildConfigField "String[]", "SIGNAL_CDSI_IPS", cdsi_ips
buildConfigField "String[]", "SIGNAL_SVR2_IPS", svr2_ips
buildConfigField "String", "SIGNAL_AGENT", "\"OWA\""
buildConfigField "String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
@@ -228,8 +229,8 @@ android {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resourceConfigurations += []
resConfigs autoResConfig()
splits {
abi {
@@ -447,6 +448,14 @@ android {
variant.setIgnore(true)
}
}
android.buildTypes.each {
if (it.name != 'release') {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/debug/java"
} else {
sourceSets.findByName(it.name).java.srcDirs += "$projectDir/src/release/java"
}
}
}
dependencies {
@@ -470,6 +479,7 @@ dependencies {
implementation libs.androidx.gridlayout
implementation libs.androidx.exifinterface
implementation libs.androidx.compose.rxjava3
implementation libs.androidx.compose.runtime.livedata
implementation libs.androidx.constraintlayout
implementation libs.androidx.multidex
implementation libs.androidx.navigation.fragment.ktx
@@ -578,9 +588,8 @@ dependencies {
exclude group: 'com.google.protobuf', module: 'protobuf-java'
}
testImplementation testLibs.robolectric.shadows.multidex
testImplementation (testLibs.bouncycastle.bcprov.jdk15on) {
force = true
}
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.hamcrest.hamcrest
testImplementation testLibs.mockk

View File

@@ -11,4 +11,11 @@
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
-keep class androidx.window.** { *; }
-keep class androidx.window.** { *; }
# AGP generated dont warns
-dontwarn com.android.org.conscrypt.SSLParametersImpl
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn sun.net.spi.nameservice.NameService
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor

View File

@@ -10,13 +10,11 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.signal.core.util.ThreadUtil
import org.signal.libsignal.protocol.state.SignedPreKeyRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.dependencies.InstrumentationApplicationDependencyProvider
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor
@@ -48,20 +46,17 @@ class ChangeNumberViewModelTest {
val harness = SignalActivityRule()
private lateinit var viewModel: ChangeNumberViewModel
private lateinit var kbsRepository: KbsRepository
@Before
fun setUp() {
ApplicationDependencies.getSignalServiceAccountManager().setSoTimeoutMillis(1000)
kbsRepository = mock()
ThreadUtil.runOnMainSync {
viewModel = ChangeNumberViewModel(
localNumber = harness.self.requireE164(),
changeNumberRepository = ChangeNumberRepository(),
savedState = SavedStateHandle(),
password = SignalStore.account().servicePassword!!,
verifyAccountRepository = VerifyAccountRepository(harness.application),
kbsRepository = kbsRepository
verifyAccountRepository = VerifyAccountRepository(harness.application)
)
viewModel.setNewCountry(1)
@@ -235,7 +230,7 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
MockProvider.mockGetRegistrationLockStringFlow()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },
@@ -323,7 +318,7 @@ class ChangeNumberViewModelTest {
lateinit var changeNumberRequest: ChangePhoneNumberRequest
lateinit var setPreKeysRequest: PreKeyState
MockProvider.mockGetRegistrationLockStringFlow(kbsRepository)
MockProvider.mockGetRegistrationLockStringFlow()
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Post("/v1/verification/session") { MockResponse().success(MockProvider.sessionMetadataJson.copy(verified = false)) },

View File

@@ -142,6 +142,7 @@ class ConversationItemPreviewer {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.jpg"),
false,
false,

View File

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

View File

@@ -6,6 +6,7 @@ import androidx.test.filters.FlakyTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.UriAttachment
@@ -64,6 +65,7 @@ class AttachmentTableTest {
}
@FlakyTest
@Ignore("test is flaky")
@Test
fun givenIdenticalAttachmentsInsertedForPreUpload_whenIUpdateAttachmentDataAndSpecifyOnlyModifyThisAttachment_thenIExpectDifferentFileInfos() {
val blob = BlobProvider.getInstance().forData(byteArrayOf(1, 2, 3, 4, 5)).createForSingleSessionInMemory()

View File

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

View File

@@ -10,13 +10,18 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.signal.ringrtc.CallId
import org.signal.ringrtc.CallManager
import org.thoughtcrime.securesms.calls.log.CallLogFilter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
@RunWith(AndroidJUnit4::class)
class CallTableTest {
@get:Rule
val harness = SignalActivityRule()
val harness = SignalActivityRule(createGroup = true)
private val groupRecipientId: RecipientId
get() = harness.group!!.recipientId
@Test
fun givenACall_whenISetTimestamp_thenIExpectUpdatedTimestamp() {
@@ -24,13 +29,13 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.setTimestamp(callId, harness.others[0], -1L)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
SignalDatabase.calls.setTimestamp(callId, groupRecipientId, -1L)
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(-1L, call?.timestamp)
@@ -45,15 +50,15 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
SignalDatabase.calls.deleteGroupCall(call!!)
val deletedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val deletedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
assertEquals(CallTable.Event.DELETE, deletedCall?.event)
@@ -66,12 +71,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
val oldestDeletionTimestamp = SignalDatabase.calls.getOldestDeletionTimestamp()
@@ -86,12 +91,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
assertEquals(harness.self.id, call?.ringerRecipient)
@@ -103,12 +108,12 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
System.currentTimeMillis()
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.JOINED, call?.event)
assertNull(call?.ringerRecipient)
@@ -120,13 +125,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
@@ -134,7 +139,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -143,13 +148,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
@@ -157,7 +162,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -166,13 +171,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
@@ -180,7 +185,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.ACCEPTED, acceptedCall?.event)
}
@@ -189,7 +194,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -197,7 +202,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
@@ -205,7 +210,7 @@ class CallTableTest {
call!!
)
val acceptedCall = SignalDatabase.calls.getCallById(callId, harness.others[0])
val acceptedCall = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertEquals(CallTable.Event.JOINED, acceptedCall?.event)
}
@@ -214,7 +219,7 @@ class CallTableTest {
val era = "aaa"
val callId = CallId.fromEra(era).longValue()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = System.currentTimeMillis(),
peekGroupCallEraId = "aaa",
@@ -222,7 +227,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
}
@@ -233,7 +238,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -241,13 +246,13 @@ class CallTableTest {
isCallFull = false
)
SignalDatabase.calls.getCallById(callId, harness.others[0]).let {
SignalDatabase.calls.getCallById(callId, groupRecipientId).let {
assertNotNull(it)
assertEquals(now, it?.timestamp)
}
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = 1L,
peekGroupCallEraId = "aaa",
@@ -255,7 +260,7 @@ class CallTableTest {
isCallFull = false
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.GENERIC_GROUP_CALL, call?.event)
assertEquals(1L, call?.timestamp)
@@ -266,20 +271,20 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertDeletedGroupCallFromSyncEvent(
callId = callId,
recipientId = harness.others[0],
recipientId = groupRecipientId,
direction = CallTable.Direction.INCOMING,
timestamp = System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
ringId = callId,
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
ringerRecipient = harness.others[1],
dateReceived = System.currentTimeMillis(),
ringState = CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DELETE, call?.event)
}
@@ -290,7 +295,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -300,13 +305,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -319,20 +324,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -344,7 +349,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -354,13 +359,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -372,7 +377,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -382,7 +387,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -390,13 +395,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -409,20 +414,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -434,20 +439,20 @@ class CallTableTest {
val now = System.currentTimeMillis()
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.INCOMING,
now
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -458,7 +463,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -468,7 +473,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -476,13 +481,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -493,7 +498,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -503,7 +508,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -511,13 +516,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
}
@@ -528,7 +533,7 @@ class CallTableTest {
val callId = CallId.fromEra(era).longValue()
val now = System.currentTimeMillis()
SignalDatabase.calls.insertOrUpdateGroupCallFromLocalEvent(
groupRecipientId = harness.others[0],
groupRecipientId = groupRecipientId,
sender = harness.others[1],
timestamp = now,
peekGroupCallEraId = "aaa",
@@ -538,13 +543,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
}
@@ -556,7 +561,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
@@ -564,13 +569,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -582,7 +587,7 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
@@ -590,13 +595,13 @@ class CallTableTest {
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
}
@@ -608,20 +613,20 @@ class CallTableTest {
SignalDatabase.calls.insertAcceptedGroupCall(
callId,
harness.others[0],
groupRecipientId,
CallTable.Direction.OUTGOING,
System.currentTimeMillis()
)
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.OUTGOING_RING, call?.event)
}
@@ -631,13 +636,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.REQUESTED
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.RINGING, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -649,13 +654,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.EXPIRED_REQUEST
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -667,13 +672,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.CANCELLED_BY_RINGER
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertEquals(harness.others[1], call?.ringerRecipient)
@@ -685,13 +690,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_LOCALLY
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -702,13 +707,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.BUSY_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.MISSED, call?.event)
assertNotNull(call?.messageId)
@@ -719,13 +724,13 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.ACCEPTED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.ACCEPTED, call?.event)
assertNotNull(call?.messageId)
@@ -736,15 +741,85 @@ class CallTableTest {
val callId = 1L
SignalDatabase.calls.insertOrUpdateGroupCallFromRingState(
callId,
harness.others[0],
groupRecipientId,
harness.others[1],
System.currentTimeMillis(),
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE
)
val call = SignalDatabase.calls.getCallById(callId, harness.others[0])
val call = SignalDatabase.calls.getCallById(callId, groupRecipientId)
assertNotNull(call)
assertEquals(CallTable.Event.DECLINED, call?.event)
assertNotNull(call?.messageId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallB_thenOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteBeforeCallA_thenIDoNotDeleteAnyCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(2, allCallEvents.size)
assertEquals(2, allCallEvents[0].record.callId)
assertEquals(1, allCallEvents[1].record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallA_thenIOnlyDeleteCallA() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(1000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(1, allCallEvents.size)
assertEquals(2, allCallEvents.first().record.callId)
}
@Test
fun givenTwoCalls_whenIDeleteOnCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2000)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
@Test
fun givenTwoCalls_whenIDeleteAfterCallB_thenIDeleteBothCalls() {
insertTwoCallEvents()
SignalDatabase.calls.deleteNonAdHocCallEventsOnOrBefore(2500)
val allCallEvents = SignalDatabase.calls.getCalls(0, 2, null, CallLogFilter.ALL)
assertEquals(0, allCallEvents.size)
}
private fun insertTwoCallEvents() {
SignalDatabase.calls.insertAcceptedGroupCall(
1,
groupRecipientId,
CallTable.Direction.INCOMING,
1000
)
SignalDatabase.calls.insertAcceptedGroupCall(
2,
groupRecipientId,
CallTable.Direction.OUTGOING,
2000
)
}
}

View File

@@ -82,7 +82,7 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
)
@@ -102,7 +102,7 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
)
@@ -121,13 +121,13 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
)
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -146,13 +146,13 @@ class MessageTableTest_gifts {
val messageId2 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)
@@ -171,13 +171,13 @@ class MessageTableTest_gifts {
MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 2,
giftBadge = GiftBadge.getDefaultInstance()
)
val messageId3 = MmsHelper.insert(
recipient = Recipient.resolved(recipients[0]),
sentTimeMillis = 1,
sentTimeMillis = 3,
giftBadge = null
)

View File

@@ -253,8 +253,7 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
// WHEN
@@ -319,8 +318,7 @@ class MmsTableTest_stories {
val groupStoryId = MmsHelper.insert(
recipient = myStory,
sentTimeMillis = 200,
storyType = StoryType.STORY_WITH_REPLIES,
threadId = -1L
storyType = StoryType.STORY_WITH_REPLIES
)
MmsHelper.insert(
@@ -331,7 +329,7 @@ class MmsTableTest_stories {
receivedTimeMillis = 202,
parentStoryId = ParentStoryId.GroupReply(groupStoryId)
),
-1
SignalDatabase.threads.getOrCreateThreadIdFor(myStory, ThreadTable.DistributionTypes.DEFAULT)
)
// WHEN

View File

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

View File

@@ -47,6 +47,7 @@ import org.whispersystems.signalservice.api.push.ServiceId
import java.util.Optional
import java.util.UUID
@Suppress("ClassName")
@RunWith(AndroidJUnit4::class)
class RecipientTableTest_getAndPossiblyMerge {
@@ -857,6 +858,7 @@ class RecipientTableTest_getAndPossiblyMerge {
}
ApplicationDependencies.getRecipientCache().clear()
ApplicationDependencies.getRecipientCache().clearSelf()
RecipientId.clearCache()
}
@@ -872,7 +874,7 @@ class RecipientTableTest_getAndPossiblyMerge {
if (createThread) {
// Create a thread and throw a dummy message in it so it doesn't get automatically deleted
SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.resolved(id))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, 0, 0, 0, "", Optional.empty(), 0, false, ""), ""))
SignalDatabase.messages.insertMessageInbox(IncomingEncryptedMessage(IncomingTextMessage(id, 1, (Math.random() * Long.MAX_VALUE).toLong(), 0, 0, "", Optional.empty(), 0, false, ""), ""))
}
if (pniSession) {

View File

@@ -12,12 +12,14 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.database.model.DistributionListId
import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.testing.SignalActivityRule
import org.whispersystems.signalservice.api.push.DistributionId
import org.whispersystems.signalservice.api.push.ServiceId
import java.util.UUID
@@ -25,6 +27,9 @@ import java.util.UUID
@RunWith(AndroidJUnit4::class)
class StorySendTableTest {
@get:Rule
val harness = SignalActivityRule(othersCount = 0, createGroup = false)
private val distributionId1 = DistributionId.from(UUID.randomUUID())
private val distributionId2 = DistributionId.from(UUID.randomUUID())
private val distributionId3 = DistributionId.from(UUID.randomUUID())

View File

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

View File

@@ -33,6 +33,7 @@ import org.whispersystems.signalservice.internal.configuration.SignalKeyBackupSe
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
@@ -74,20 +75,20 @@ class InstrumentationApplicationDependencyProvider(application: Application, def
serviceTrustStore = SignalServiceTrustStore(application)
uncensoredConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
mapOf(
signalServiceUrls = arrayOf(SignalServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
signalCdnUrlMap = mapOf(
0 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
2 to arrayOf(SignalCdnUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT))
),
arrayOf(SignalKeyBackupServiceUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalStorageUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
arrayOf(SignalCdsiUrl(baseUrl, "localhost", serviceTrustStore, ConnectionSpec.CLEARTEXT)),
emptyArray(),
emptyList(),
Optional.of(SignalServiceNetworkAccess.DNS),
Optional.empty(),
Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
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)),
networkInterceptors = emptyList(),
dns = Optional.of(SignalServiceNetworkAccess.DNS),
signalProxy = Optional.empty(),
zkGroupServerPublicParams = Base64.decode(BuildConfig.ZKGROUP_SERVER_PUBLIC_PARAMS),
genericServerPublicParams = Base64.decode(BuildConfig.GENERIC_SERVER_PUBLIC_PARAMS)
)
serviceNetworkAccessMock = mock {

View File

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

View File

@@ -12,6 +12,7 @@ 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
@@ -36,6 +37,11 @@ class RefreshOwnProfileJob__checkUsernameIsInSyncTest {
@Test
fun givenNoLocalUsername_whenICheckUsernameIsInSync_thenIExpectNoFailures() {
// GIVEN
InstrumentationApplicationDependencyProvider.addMockWebRequestHandlers(
Delete("/v1/accounts/username_hash") { MockResponse().success() }
)
// WHEN
RefreshOwnProfileJob.checkUsernameIsInSync()
}

View File

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

View File

@@ -70,7 +70,7 @@ class EditMessageSyncProcessorTest {
val syncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setDestinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(originalTimestamp)
.setExpirationStartTimestamp(originalTimestamp)
.setMessage(content.dataMessage)
@@ -89,7 +89,7 @@ class EditMessageSyncProcessorTest {
val editSyncContent = SignalServiceProtos.Content.newBuilder().setSyncMessage(
SignalServiceProtos.SyncMessage.newBuilder().setSent(
SignalServiceProtos.SyncMessage.Sent.newBuilder()
.setDestinationUuid(metadata.destinationServiceId.toString())
.setDestinationServiceId(metadata.destinationServiceId.toString())
.setTimestamp(editTimestamp)
.setExpirationStartTimestamp(editTimestamp)
.setEditMessage(

View File

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

View File

@@ -9,6 +9,7 @@ import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,7 +38,7 @@ import android.util.Log as AndroidLog
/**
* Sends N messages from Bob to Alice to track performance of Alice's processing of messages.
*/
// @Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@Ignore("Ignore test in normal testing as it's a performance test with no assertions")
@RunWith(AndroidJUnit4::class)
class MessageProcessingPerformanceTest {

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messages
import android.content.Context
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.testing.LogPredicate
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
import org.whispersystems.signalservice.internal.push.SignalServiceProtos
@@ -18,9 +19,9 @@ class TimingMessageContentProcessorV2(context: Context) : MessageContentProcesso
fun endTag(timestamp: Long) = "$timestamp end"
}
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean) {
override fun process(envelope: SignalServiceProtos.Envelope, content: SignalServiceProtos.Content, metadata: EnvelopeMetadata, serverDeliveredTimestamp: Long, processingEarlyContent: Boolean, localMetric: SignalLocalMetrics.MessageReceive?) {
Log.d(TAG, startTag(envelope.timestamp))
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent)
super.process(envelope, content, metadata, serverDeliveredTimestamp, processingEarlyContent, localMetric)
Log.d(TAG, endTag(envelope.timestamp))
}
}

View File

@@ -32,6 +32,7 @@ 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 java.lang.UnsupportedOperationException
import java.util.Optional
import java.util.UUID
import java.util.concurrent.locks.ReentrantLock
@@ -168,6 +169,10 @@ class BobClient(val serviceId: ServiceId, val e164: String, val identityKeyPair:
override fun getSenderKeySharedWith(distributionId: DistributionId?): MutableSet<SignalProtocolAddress> = throw UnsupportedOperationException()
override fun markSenderKeySharedWith(distributionId: DistributionId?, addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun clearSenderKeySharedWith(addresses: MutableCollection<SignalProtocolAddress>?) = throw UnsupportedOperationException()
override fun storeLastResortKyberPreKey(kyberPreKeyId: Int, kyberPreKeyRecord: KyberPreKeyRecord) = throw UnsupportedOperationException()
override fun removeKyberPreKey(kyberPreKeyId: Int) = throw UnsupportedOperationException()
override fun loadLastResortKyberPreKeys(): List<KyberPreKeyRecord> = throw UnsupportedOperationException()
override fun isMultiDevice(): Boolean = throw UnsupportedOperationException()
}
}

View File

@@ -69,7 +69,7 @@ object FakeClientHelpers {
.setSourceDevice(1)
.setTimestamp(timestamp)
.setServerTimestamp(timestamp + 1)
.setDestinationUuid(destination.toString())
.setDestinationServiceId(destination.toString())
.setServerGuid(UUID.randomUUID().toString())
.setContent(Base64.decode(this.content).toProtoByteString())
.setUrgent(true)

View File

@@ -105,7 +105,7 @@ object MessageContentFuzzer {
addAllUnidentifiedStatus(
deliveredTo.map {
SyncMessage.Sent.UnidentifiedDeliveryStatus.newBuilder().buildWith {
destinationUuid = Recipient.resolved(it).requireServiceId().toString()
destinationServiceId = Recipient.resolved(it).requireServiceId().toString()
unidentified = true
}
}
@@ -135,7 +135,7 @@ object MessageContentFuzzer {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = quoted.envelope.timestamp
authorUuid = quoted.metadata.sourceServiceId.toString()
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
addAllAttachments(quoted.content.dataMessage.attachmentsList)
addAllBodyRanges(quoted.content.dataMessage.bodyRangesList)
@@ -147,7 +147,7 @@ object MessageContentFuzzer {
val quoted = quoteAble.random(random)
quote = DataMessage.Quote.newBuilder().buildWith {
id = random.nextLong(quoted.envelope.timestamp - 1000000, quoted.envelope.timestamp)
authorUuid = quoted.metadata.sourceServiceId.toString()
authorAci = quoted.metadata.sourceServiceId.toString()
text = quoted.content.dataMessage.body
}
}
@@ -174,7 +174,7 @@ object MessageContentFuzzer {
reaction = DataMessage.Reaction.newBuilder().buildWith {
emoji = emojis.random(random)
remove = false
targetAuthorUuid = reactTo.metadata.sourceServiceId.toString()
targetAuthorAci = reactTo.metadata.sourceServiceId.toString()
targetSentTimestamp = reactTo.envelope.timestamp
}
}

View File

@@ -1,9 +1,7 @@
package org.thoughtcrime.securesms.testing
import io.reactivex.rxjava3.core.Single
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.signal.core.util.Hex
import org.signal.libsignal.protocol.IdentityKeyPair
@@ -15,17 +13,13 @@ import org.signal.libsignal.svr2.PinHash
import org.thoughtcrime.securesms.crypto.PreKeyUtil
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.test.BuildConfig
import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupService
import org.whispersystems.signalservice.api.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
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse
import org.whispersystems.signalservice.internal.push.AuthCredentials
import org.whispersystems.signalservice.internal.push.DeviceInfoList
import org.whispersystems.signalservice.internal.push.PreKeyEntity
@@ -46,7 +40,8 @@ object MockProvider {
val senderCertificate = SenderCertificate().apply { certificate = ByteArray(0) }
val lockedFailure = PushServiceSocket.RegistrationLockFailure().apply {
backupCredentials = AuthCredentials.create("username", "password")
svr1Credentials = AuthCredentials.create("username", "password")
svr2Credentials = null
}
val primaryOnlyDeviceList = DeviceInfoList().apply {
@@ -83,26 +78,15 @@ object MockProvider {
}
}
fun mockGetRegistrationLockStringFlow(kbsRepository: KbsRepository) {
val tokenData: TokenData = mock {
on { enclave } doReturn BuildConfig.KBS_ENCLAVE
on { basicAuth } doReturn "basicAuth"
on { triesRemaining } doReturn 10
on { tokenResponse } doReturn TokenResponse()
}
kbsRepository.stub {
on { getToken(any() as? String) } doReturn Single.just(ServiceResponse.forResult(tokenData, 200, ""))
}
fun mockGetRegistrationLockStringFlow() {
val session: KeyBackupService.RestoreSession = object : KeyBackupService.RestoreSession {
override fun hashSalt(): ByteArray = Hex.fromStringCondensed("cba811749042b303a6a7efa5ccd160aea5e3ea243c8d2692bd13d515732f51a8")
override fun restorePin(hashedPin: PinHash?): KbsPinData = KbsPinData(MasterKey.createNew(SecureRandom()), null)
override fun restorePin(hashedPin: PinHash?): SvrPinData = SvrPinData(MasterKey.createNew(SecureRandom()), null)
}
val kbsService = ApplicationDependencies.getKeyBackupService(BuildConfig.KBS_ENCLAVE)
kbsService.stub {
on { newRegistrationSession(any(), any()) } doReturn session
on { newRegistrationSession(anyOrNull(), anyOrNull()) } doReturn session
}
}

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ class SignalDatabaseRule(
private fun deleteAllThreads() {
if (deleteAllThreadsOnEachRun) {
SignalDatabase.messages.deleteAllThreads()
SignalDatabase.threads.clearForTests()
}
}
}

View File

@@ -41,7 +41,7 @@ class TestProtos private constructor() {
authorUuid: String = UUID.randomUUID().toString()
): DataMessage.StoryContext.Builder {
return DataMessage.StoryContext.newBuilder()
.setAuthorUuid(authorUuid)
.setAuthorAci(authorUuid)
.setSentTimestamp(sentTimestamp)
}

View File

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

View File

@@ -148,6 +148,7 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.jpg"),
false,
false,
@@ -169,6 +170,7 @@ object TestMessages {
1024,
1024,
Optional.empty(),
Optional.empty(),
Optional.of("/not-there.aac"),
true,
false,

View File

@@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.signal.benchmark.DummyAccountManagerFactory
import org.signal.core.util.concurrent.safeBlockingGet
import org.signal.libsignal.protocol.SignalProtocolAddress
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MasterSecretUtil
@@ -43,6 +44,9 @@ object TestUsers {
val preferences: SharedPreferences = application.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0)
preferences.edit().putBoolean("passphrase_initialized", true).commit()
SignalStore.account().generateAciIdentityKeyIfNecessary()
SignalStore.account().generatePniIdentityKeyIfNecessary()
val registrationRepository = RegistrationRepository(application)
val registrationData = RegistrationData(
code = "123123",
@@ -54,16 +58,26 @@ object TestUsers {
pniRegistrationId = registrationRepository.pniRegistrationId,
recoveryPassword = "asdfasdfasdfasdf"
)
val verifyResponse = VerifyResponse(VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false), null, null)
val verifyResponse = VerifyResponse(
VerifyAccountResponse(UUID.randomUUID().toString(), UUID.randomUUID().toString(), false),
masterKey = null,
pin = null,
aciPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().aciPreKeys),
pniPreKeyCollection = RegistrationRepository.generateSignedAndLastResortPreKeys(SignalStore.account().aciIdentityKey, SignalStore.account().pniPreKeys)
)
AccountManagerFactory.setInstance(DummyAccountManagerFactory())
val response: ServiceResponse<VerifyResponse> = registrationRepository.registerAccount(
registrationData,
verifyResponse,
false
).blockingGet()
).safeBlockingGet()
ServiceResponseProcessor.DefaultProcessor(response).resultOrThrow
SignalStore.kbsValues().optOut()
SignalStore.svr().optOut()
RegistrationUtil.maybeMarkRegistrationComplete()
SignalDatabase.recipients.setProfileName(Recipient.self().id, ProfileName.fromParts("Tester", "McTesterson"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@
<uses-feature android:name="android.hardware.wifi" android:required="false"/>
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
@@ -133,18 +134,21 @@
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:taskAffinity=".calling"
android:resizeableActivity="true"
android:launchMode="singleTask"/>
android:launchMode="singleTask"
android:exported="false" />
<activity android:name=".messagerequests.CalleeMustAcceptMessageRequestActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:noHistory="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
<activity android:name=".InviteActivity"
android:theme="@style/Signal.Light.NoActionBar.Invite"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".MainActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
@@ -153,7 +157,8 @@
<activity android:name=".PromptMmsActivity"
android:label="Configure MMS Settings"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DeviceProvisioningActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
@@ -175,11 +180,13 @@
</activity>
<activity android:name=".preferences.MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false" />
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="adjustResize" />
android:windowSoftInputMode="adjustResize"
android:exported="false" />
<activity android:name=".sharing.v2.ShareActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
@@ -602,10 +609,13 @@
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="signal.link" />
<data android:scheme="sgnl" />
<data android:scheme="https" />
<data android:host="signal.link" />
</intent-filter>
</activity>
@@ -613,7 +623,8 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity">
android:parentActivityName=".MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
@@ -623,7 +634,8 @@
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".MainActivity">
android:parentActivityName=".MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
@@ -632,7 +644,8 @@
<activity android:name=".conversation.BubbleConversationActivity"
android:theme="@style/Signal.DayNight"
android:allowEmbedded="true"
android:resizeableActivity="true" />
android:resizeableActivity="true"
android:exported="false"/>
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
@@ -640,78 +653,93 @@
android:taskAffinity=""
android:excludeFromRecents="true"
android:theme="@style/TextSecure.LightTheme.Popup"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".groups.ui.invitesandrequests.ManagePendingAndRequestingMembersActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="adjustResize"/>
android:windowSoftInputMode="adjustResize"
android:exported="false"/>
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphraseCreateActivity"
<activity android:name=".PassphraseCreateActivity"
android:label="@string/AndroidManifest__create_passphrase"
android:windowSoftInputMode="stateUnchanged"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphrasePromptActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightIntroTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".NewConversationActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".calls.links.details.CallLinkDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".calls.new.NewCallActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PushContactSelectionActivity"
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:label="@string/AndroidManifest__select_contacts"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".giph.ui.GiphyActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"/>
<activity android:name=".mediasend.v2.MediaSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".conversation.mutiselect.forward.MultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<activity android:name=".mediasend.v2.stories.StoriesMultiselectForwardActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".verify.VerifyIdentityActivity"
android:exported="false"
@@ -733,13 +761,15 @@
android:name=".stories.my.MyStoriesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden" />
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity
android:name=".stories.settings.StorySettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" />
android:windowSoftInputMode="stateAlwaysHidden|adjustResize"
android:exported="false"/>
<activity
android:name=".stories.viewer.StoryViewerActivity"
@@ -748,82 +778,99 @@
android:theme="@style/TextSecure.DarkNoActionBar.StoryViewer"
android:launchMode="singleTask"
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
android:parentActivityName=".MainActivity">
android:parentActivityName=".MainActivity"
android:exported="false">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".components.settings.app.changenumber.ChangeNumberLockActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".components.settings.conversation.ConversationSettingsActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.ConversationSettings"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".components.settings.conversation.CallInfoActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".badges.gifts.flow.GiftFlowActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|screenLayout|screenSize"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden">
</activity>
<activity
android:name=".wallpaper.ChatWallpaperPreviewActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:windowSoftInputMode="stateAlwaysHidden"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
android:noHistory="true"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".revealable.ViewOnceMessageActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:excludeFromRecents="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DeviceActivity"
android:screenOrientation="portrait"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".logsubmit.SubmitDebugLogActivity"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".mediapreview.MediaPreviewV2Activity"
android:label="@string/AndroidManifest__media_preview"
@@ -836,19 +883,21 @@
<activity android:name=".AvatarPreviewActivity"
android:label="@string/AndroidManifest__media_preview"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity
android:name=".avatar.photo.PhotoEditorActivity"
<activity android:name=".avatar.photo.PhotoEditorActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:label="@string/AndroidManifest__media_preview"
android:theme="@style/TextSecure.DarkNoActionBar"
android:windowSoftInputMode="stateHidden" />
android:windowSoftInputMode="stateHidden"
android:exported="false"/>
<activity android:name=".mediaoverview.MediaOverviewActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".DummyActivity"
android:theme="@android:style/Theme.NoDisplay"
@@ -859,7 +908,8 @@
android:alwaysRetainTaskState="false"
android:stateNotNeeded="true"
android:clearTaskOnLaunch="true"
android:finishOnTaskLaunch="true" />
android:finishOnTaskLaunch="true"
android:exported="false"/>
<activity android:name=".PlayServicesProblemActivity"
android:exported="false"
@@ -902,136 +952,190 @@
<activity android:name=".mediasend.AvatarSelectionActivity"
android:theme="@style/TextSecure.DarkNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".blocked.BlockedUsersActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".profiles.edit.EditProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".profiles.username.AddAUsernameActivity"
android:theme="@style/Signal.DayNight.NoActionBar"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".profiles.manage.ManageProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateVisible|adjustResize" />
android:windowSoftInputMode="stateVisible|adjustResize"
android:exported="false"/>
<activity android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".payments.preferences.PaymentsActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".lock.v2.CreateKbsPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".lock.v2.CreateSvrPinActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".lock.v2.KbsMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".lock.v2.SvrMigrationActivity"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contacts.TurnOffContactJoinedNotificationsActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert" />
android:theme="@style/Theme.AppCompat.Dialog.Alert"
android:exported="false"/>
<activity android:name=".contactshare.ContactShareEditActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contactshare.ContactNameEditActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".contactshare.SharedContactDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".ShortcutLauncherActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".MainActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".pin.PinRestoreActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".groups.ui.creategroup.CreateGroupActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.addtogroup.AddToGroupsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.addmembers.AddMembersActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.creategroup.details.AddGroupDetailsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:exported="false"/>
<activity android:name=".groups.ui.chooseadmin.ChooseNewAdminActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".megaphone.ClientDeprecatedActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
android:launchMode="singleTask"
android:exported="false"/>
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:launchMode="singleTask" />
android:launchMode="singleTask"
android:exported="false"/>
<activity android:name=".ratelimit.RecaptchaProofActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode" />
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
android:exported="false"/>
<activity android:name=".wallpaper.crop.WallpaperImageSelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:theme="@style/TextSecure.DarkNoActionBar" />
android:theme="@style/TextSecure.DarkNoActionBar"
android:exported="false"/>
<activity android:name=".wallpaper.crop.WallpaperCropActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:screenOrientation="portrait"
android:theme="@style/Theme.Signal.WallpaperCropper" />
android:theme="@style/Theme.Signal.WallpaperCropper"
android:exported="false"/>
<activity android:name=".reactions.edit.EditReactionsActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".exporter.flow.SmsExportActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<activity android:name=".components.settings.app.subscription.donate.DonateToSignalActivity"
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="false"/>
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$ForegroundService"/>
<service android:enabled="true" android:name=".messages.IncomingMessageObserver$BackgroundService"/>
<service android:name=".service.webrtc.AndroidCallConnectionService"
<service
android:enabled="true"
android:name=".exporter.SignalSmsExportService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<service
android:enabled="true"
android:name=".service.webrtc.WebRtcCallService"
android:foregroundServiceType="camera|microphone"
android:exported="false"/>
<service
android:enabled="true"
android:exported="false"
android:name=".service.KeyCachingService" />
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$ForegroundService"
android:exported="false"/>
<service
android:enabled="true"
android:name=".messages.IncomingMessageObserver$BackgroundService"
android:exported="false"/>
<service
android:name=".service.webrtc.AndroidCallConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
<intent-filter>
@@ -1039,7 +1143,9 @@
</intent-filter>
</service>
<service android:name=".components.voice.VoiceNotePlaybackService" android:exported="true">
<service
android:name=".components.voice.VoiceNotePlaybackService"
android:exported="true">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
@@ -1079,11 +1185,17 @@
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service android:name=".service.GenericForegroundService"/>
<service
android:name=".service.GenericForegroundService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchBackgroundService" />
<service
android:name=".gcm.FcmFetchBackgroundService"
android:exported="false"/>
<service android:name=".gcm.FcmFetchForegroundService" />
<service
android:name=".gcm.FcmFetchForegroundService"
android:exported="false"/>
<service android:name=".gcm.FcmReceiveService" android:exported="true">
<intent-filter>
@@ -1140,19 +1252,38 @@
</intent-filter>
</receiver>
<receiver android:name=".service.ExpirationListener" />
<receiver
android:name=".service.ExpirationListener"
android:exported="false"/>
<receiver android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm" />
<receiver
android:name=".service.ExpiringStoriesManager$ExpireStoriesAlarm"
android:exported="false"/>
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<receiver
android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm"
android:exported="false"/>
<receiver android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm" />
<receiver
android:name=".service.ScheduledMessageManager$ScheduledMessagesAlarm"
android:exported="false"/>
<receiver android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm" />
<receiver
android:name=".service.PendingRetryReceiptManager$PendingRetryReceiptAlarm"
android:exported="false"/>
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
<receiver
android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm"
android:exported="false"/>
<receiver android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver" />
<receiver
android:name=".payments.backup.phrase.ClearClipboardAlarmReceiver"
android:exported="false"/>
<provider android:name=".providers.AvatarProvider"
android:authorities="${applicationId}.avatar"
android:exported="false"
android:grantUriPermissions="true" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
@@ -1250,12 +1381,14 @@
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
android:exported="false"
tools:targetApi="26" />
<service

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 92 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.signal.glide.transforms
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import kotlin.math.max
import kotlin.math.min
object SignalDownsampleStrategy {
/**
* Center outside, but don't up-scale, only downscale. You should be setting centerOutside
* on the target image view to still maintain center outside behavior.
*/
@JvmField
val CENTER_OUTSIDE_NO_UPSCALE: DownsampleStrategy = CenterOutsideNoUpscale()
private class CenterOutsideNoUpscale : DownsampleStrategy() {
override fun getScaleFactor(
sourceWidth: Int,
sourceHeight: Int,
requestedWidth: Int,
requestedHeight: Int
): Float {
val widthPercentage = requestedWidth / sourceWidth.toFloat()
val heightPercentage = requestedHeight / sourceHeight.toFloat()
return min(MAX_SCALE_FACTOR, max(widthPercentage, heightPercentage))
}
override fun getSampleSizeRounding(
sourceWidth: Int,
sourceHeight: Int,
requestedWidth: Int,
requestedHeight: Int
): SampleSizeRounding {
return SampleSizeRounding.QUALITY
}
companion object {
private const val MAX_SCALE_FACTOR = 1f
}
}
}

View File

@@ -30,6 +30,7 @@ import com.google.android.gms.security.ProviderInstaller;
import org.conscrypt.Conscrypt;
import org.greenrobot.eventbus.EventBus;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.signal.core.util.MemoryTracker;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.AndroidLogger;
import org.signal.core.util.logging.Log;
@@ -47,6 +48,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.gcm.FcmFetchManager;
import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobs.AccountConsistencyWorkerJob;
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
@@ -60,7 +62,7 @@ import org.thoughtcrime.securesms.jobs.PnpInitializeDevicesJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
import org.thoughtcrime.securesms.jobs.RefreshSvrCredentialsJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
@@ -198,7 +200,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(this::initializeExpiringMessageManager)
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
.addPostRender(this::initializeTrimThreadsByDateManager)
.addPostRender(RefreshKbsCredentialsJob::enqueueIfNecessary)
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), FeatureFlags.retryRespondMaxAge()))
@@ -229,6 +231,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
ApplicationDependencies.getDeadlockDetector().start();
SubscriptionKeepAliveJob.enqueueAndTrackTimeIfNecessary();
FcmFetchManager.onForeground(this);
SignalExecutors.BOUNDED.execute(() -> {
FeatureFlags.refreshIfNecessary();
@@ -237,6 +240,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getShakeToReport().enable();
checkBuildExpiration();
MemoryTracker.start();
long lastForegroundTime = SignalStore.misc().getLastForegroundTime();
long currentTime = System.currentTimeMillis();
@@ -260,6 +264,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
ApplicationDependencies.getFrameRateTracker().stop();
ApplicationDependencies.getShakeToReport().disable();
ApplicationDependencies.getDeadlockDetector().stop();
MemoryTracker.stop();
}
public PersistentLogger getPersistentLogger() {

View File

@@ -13,11 +13,14 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.conversationlist.RelinkDevicesReminderBottomSheetFragment;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferLockedDialog;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceExitActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabRepository;
import org.thoughtcrime.securesms.stories.tabs.ConversationListTabsViewModel;
import org.thoughtcrime.securesms.util.AppStartup;
@@ -101,6 +104,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
handleGroupLinkInIntent(intent);
handleProxyInIntent(intent);
handleSignalMeIntent(intent);
handleCallLinkInIntent(intent);
}
@Override
@@ -114,7 +118,16 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
super.onResume();
dynamicTheme.onResume(this);
if (SignalStore.misc().isOldDeviceTransferLocked()) {
OldDeviceTransferLockedDialog.show(getSupportFragmentManager());
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.OldDeviceTransferLockedDialog__complete_registration_on_your_new_device)
.setMessage(R.string.OldDeviceTransferLockedDialog__your_signal_account_has_been_transferred_to_your_new_device)
.setPositiveButton(R.string.OldDeviceTransferLockedDialog__done, (d, w) -> OldDeviceExitActivity.exit(this))
.setNegativeButton(R.string.OldDeviceTransferLockedDialog__cancel_and_activate_this_device, (d, w) -> {
SignalStore.misc().clearOldDeviceTransferLocked();
DeviceTransferBlockingInterceptor.getInstance().unblockNetwork();
})
.setCancelable(false)
.show();
}
if (SignalStore.misc().getShouldShowLinkedDevicesReminder()) {

View File

@@ -7,20 +7,27 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId;
import io.reactivex.rxjava3.disposables.Disposable;
public class MainNavigator {
public static final int REQUEST_CONFIG_CHANGES = 901;
private final MainActivity activity;
private final MainActivity activity;
private final LifecycleDisposable lifecycleDisposable;
public MainNavigator(@NonNull MainActivity activity) {
this.activity = activity;
this.activity = activity;
this.lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(activity);
}
public static MainNavigator get(@NonNull Activity activity) {
@@ -33,7 +40,7 @@ public class MainNavigator {
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
* to the system to do the default behavior.
*/
public boolean onBackPressed() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
@@ -46,13 +53,16 @@ public class MainNavigator {
}
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
Intent intent = ConversationIntents.createBuilder(activity, recipientId, threadId)
.withDistributionType(distributionType)
.withStartingPosition(startingPosition)
.build();
Disposable disposable = ConversationIntents.createBuilder(activity, recipientId, threadId)
.map(builder -> builder.withDistributionType(distributionType)
.withStartingPosition(startingPosition)
.build())
.subscribe(intent -> {
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
});
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
lifecycleDisposable.add(disposable);
}
public void goToAppSettings() {
@@ -79,7 +89,7 @@ public class MainNavigator {
public interface BackHandler {
/**
* @return True if the back pressed was handled in our own custom way, false if it should be given
* to the system to do the default behavior.
* to the system to do the default behavior.
*/
boolean onBackPressed();
}

View File

@@ -36,6 +36,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.menu.ActionItem;
@@ -45,24 +46,25 @@ import org.thoughtcrime.securesms.contacts.management.ContactsManagementViewMode
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.conversation.ConversationIntents;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.reactivex.rxjava3.disposables.Disposable;
/**
* Activity container for starting a new conversation.
*
@@ -121,7 +123,7 @@ public class NewConversationActivity extends ContactSelectionActivity
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
try {
ContactDiscovery.refresh(this, resolved, false);
ContactDiscovery.refresh(this, resolved, false, TimeUnit.SECONDS.toMillis(10));
resolved = Recipient.resolved(resolved.getId());
} catch (IOException e) {
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
@@ -162,15 +164,18 @@ public class NewConversationActivity extends ContactSelectionActivity
}
private void launch(Recipient recipient) {
long existingThread = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
Intent intent = ConversationIntents.createBuilder(this, recipient.getId(), existingThread)
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
.withDataUri(getIntent().getData())
.withDataType(getIntent().getType())
.build();
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
.map(builder -> builder
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
.withDataUri(getIntent().getData())
.withDataType(getIntent().getType())
.build())
.subscribe(intent -> {
startActivity(intent);
finish();
});
startActivity(intent);
finish();
disposables.add(disposable);
}
@Override
@@ -232,8 +237,8 @@ public class NewConversationActivity extends ContactSelectionActivity
@Override
public boolean onLongClick(View anchorView, ContactSearchKey contactSearchKey, RecyclerView recyclerView) {
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
RecipientId recipientId = contactSearchKey.requireRecipientSearchKey().getRecipientId();
List<ActionItem> actions = generateContextualActionsForRecipient(recipientId);
if (actions.isEmpty()) {
return false;
}
@@ -268,7 +273,12 @@ public class NewConversationActivity extends ContactSelectionActivity
R.drawable.ic_chat_message_24,
getString(R.string.NewConversationActivity__message),
R.color.signal_colorOnSurface,
() -> startActivity(ConversationIntents.createBuilder(this, recipient.getId(), -1L).build())
() -> {
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
.subscribe(builder -> startActivity(builder.build()));
disposables.add(disposable);
}
);
}
@@ -369,7 +379,7 @@ public class NewConversationActivity extends ContactSelectionActivity
.show();
}
private void displaySnackbar(@StringRes int message, Object ... formatArgs) {
private void displaySnackbar(@StringRes int message, Object... formatArgs) {
Snackbar.make(findViewById(android.R.id.content), getString(message, formatArgs), Snackbar.LENGTH_SHORT).show();
}
}

View File

@@ -21,7 +21,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
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;
@@ -189,11 +189,11 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
}
private boolean userMustCreateSignalPin() {
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().lastPinCreateFailed() && !SignalStore.kbsValues().hasOptedOut();
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().lastPinCreateFailed() && !SignalStore.svr().hasOptedOut();
}
private boolean userHasSkippedOrForgottenPin() {
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut() && SignalStore.kbsValues().isPinForgottenOrSkipped();
return !SignalStore.registrationValues().isRegistrationComplete() && !SignalStore.svr().hasPin() && !SignalStore.svr().hasOptedOut() && SignalStore.svr().isPinForgottenOrSkipped();
}
private boolean userMustSetProfileName() {
@@ -234,7 +234,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
intent = getIntent();
}
return getRoutedIntent(CreateKbsPinActivity.class, intent);
return getRoutedIntent(CreateSvrPinActivity.class, intent);
}
private Intent getCreateProfileNameIntent() {

View File

@@ -48,9 +48,9 @@ public class SmsSendtoActivity extends Activity {
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
} else {
Recipient recipient = Recipient.external(this, destination.getDestination());
long threadId = SignalDatabase.threads().getThreadIdIfExistsFor(recipient.getId());
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
nextIntent = ConversationIntents.createBuilder(this, recipient.getId(), threadId)
nextIntent = ConversationIntents.createBuilderSync(this, recipient.getId(), threadId)
.withDraftText(destination.getBody())
.build();
}

View File

@@ -55,6 +55,7 @@ import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
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.CallParticipantsListUpdatePopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow;
@@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet;
@@ -104,9 +106,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private static final int STANDARD_DELAY_FINISH = 1000;
private static final int VIBRATE_DURATION = 50;
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
/**
* ANSWER the call via voice-only.
*/
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
/**
* ANSWER the call via video.
*/
public static final String ANSWER_VIDEO_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_VIDEO_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
@@ -159,10 +169,18 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
initializeViewModel(isLandscapeEnabled);
initializePictureInPictureParams();
processIntent(getIntent());
logIntent(getIntent());
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
if (ANSWER_VIDEO_ACTION.equals(getIntent().getAction())) {
enableVideoIfAvailable = true;
} else if (ANSWER_ACTION.equals(getIntent().getAction()) || getIntent().getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false)) {
enableVideoIfAvailable = false;
} else {
enableVideoIfAvailable = getIntent().getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false);
getIntent().removeExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE);
}
processIntent(getIntent());
windowLayoutInfoConsumer = new WindowLayoutInfoConsumer();
@@ -211,6 +229,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
public void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent(" + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false) + ")");
super.onNewIntent(intent);
logIntent(intent);
processIntent(intent);
}
@@ -297,9 +316,17 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
return isSystemPipEnabledAndAvailable() && isInPictureInPictureMode();
}
private void logIntent(@NonNull Intent intent) {
Log.d(TAG, "Intent: Action: " + intent.getAction());
Log.d(TAG, "Intent: EXTRA_STARTED_FROM_FULLSCREEN: " + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false));
Log.d(TAG, "Intent: EXTRA_ENABLE_VIDEO_IF_AVAILABLE: " + intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false));
}
private void processIntent(@NonNull Intent intent) {
if (ANSWER_ACTION.equals(intent.getAction())) {
handleAnswerWithAudio();
} else if (ANSWER_VIDEO_ACTION.equals(intent.getAction())) {
handleAnswerWithVideo();
} else if (DENY_ACTION.equals(intent.getAction())) {
handleDenyCall();
} else if (END_CALL_ACTION.equals(intent.getAction())) {
@@ -498,26 +525,20 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
}
private void handleAnswerWithVideo() {
Recipient recipient = viewModel.getRecipient().get();
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_give_signal_access_to_your_microphone_and_camera), R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setStatus(getString(R.string.RedPhone_answering));
if (!recipient.equals(Recipient.UNKNOWN)) {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, recipient.getDisplayName(this)),
R.drawable.ic_mic_solid_24, R.drawable.ic_video_solid_24_tinted)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setRecipient(recipient);
callScreen.setStatus(getString(R.string.RedPhone_answering));
ApplicationDependencies.getSignalCallManager().acceptCall(true);
ApplicationDependencies.getSignalCallManager().acceptCall(true);
handleSetMuteVideo(false);
})
.onAnyDenied(this::handleDenyCall)
.execute();
}
handleSetMuteVideo(false);
})
.onAnyDenied(this::handleDenyCall)
.execute();
}
private void handleDenyCall() {
@@ -901,7 +922,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
@Override
public void onCallInfoClicked() {
CallParticipantsListDialog.show(getSupportFragmentManager());
LiveRecipient liveRecipient = viewModel.getRecipient();
if (liveRecipient.get().isCallLink()) {
CallLinkInfoSheet.show(getSupportFragmentManager(), liveRecipient.get().requireCallLinkRoomId());
} else {
CallParticipantsListDialog.show(getSupportFragmentManager());
}
}
@Override

View File

@@ -5,7 +5,7 @@ import android.app.backup.BackupDataInput
import android.app.backup.BackupDataOutput
import android.os.ParcelFileDescriptor
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.absbackup.backupables.KbsAuthTokens
import org.thoughtcrime.securesms.absbackup.backupables.SvrAuthTokens
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.FileInputStream
@@ -17,7 +17,7 @@ import java.io.IOException
*/
class SignalBackupAgent : BackupAgent() {
private val items: List<AndroidBackupItem> = listOf(
KbsAuthTokens
SvrAuthTokens
)
override fun onBackup(oldState: ParcelFileDescriptor?, data: BackupDataOutput, newState: ParcelFileDescriptor) {

View File

@@ -3,13 +3,13 @@ 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.KbsAuthToken
import org.thoughtcrime.securesms.absbackup.protos.SvrAuthToken
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* 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.
*/
object KbsAuthTokens : AndroidBackupItem {
object SvrAuthTokens : AndroidBackupItem {
private const val TAG = "KbsAuthTokens"
override fun getKey(): String {
@@ -17,19 +17,19 @@ object KbsAuthTokens : AndroidBackupItem {
}
override fun getDataForBackup(): ByteArray {
val proto = KbsAuthToken(tokens = SignalStore.kbsValues().kbsAuthTokenList)
val proto = SvrAuthToken(tokens = SignalStore.svr().authTokenList)
return proto.encode()
}
override fun restoreData(data: ByteArray) {
if (SignalStore.kbsValues().kbsAuthTokenList.isNotEmpty()) {
if (SignalStore.svr().authTokenList.isNotEmpty()) {
return
}
try {
val proto = KbsAuthToken.ADAPTER.decode(data)
val proto = SvrAuthToken.ADAPTER.decode(data)
SignalStore.kbsValues().putAuthTokenList(proto.tokens)
SignalStore.svr().putAuthTokenList(proto.tokens)
} catch (e: InvalidProtocolBufferException) {
Log.w(TAG, "Cannot restore KbsAuthToken from backup service.")
}

View File

@@ -35,6 +35,9 @@ public abstract class Attachment {
@Nullable
private final byte[] digest;
@Nullable
private final byte[] incrementalDigest;
@Nullable
private final String fastPreflightId;
@@ -70,6 +73,7 @@ public abstract class Attachment {
@Nullable String key,
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -93,6 +97,7 @@ public abstract class Attachment {
this.key = key;
this.relay = relay;
this.digest = digest;
this.incrementalDigest = incrementalDigest;
this.fastPreflightId = fastPreflightId;
this.voiceNote = voiceNote;
this.borderless = borderless;
@@ -165,6 +170,11 @@ public abstract class Attachment {
return digest;
}
@Nullable
public byte[] getIncrementalDigest() {
return incrementalDigest;
}
@Nullable
public String getFastPreflightId() {
return fastPreflightId;

View File

@@ -33,6 +33,7 @@ public class DatabaseAttachment extends Attachment {
String key,
String relay,
byte[] digest,
byte[] incrementalDigest,
String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -48,7 +49,7 @@ public class DatabaseAttachment extends Attachment {
int displayOrder,
long uploadTimestamp)
{
super(contentType, transferProgress, size, fileName, cdnNumber, location, key, relay, digest, 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, quote, uploadTimestamp, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.attachmentId = attachmentId;
this.hasData = hasData;
this.hasThumbnail = hasThumbnail;

View File

@@ -11,7 +11,7 @@ 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, 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, false, 0, null, null, null, null, null);
}
@Nullable

View File

@@ -30,6 +30,7 @@ public class PointerAttachment extends Attachment {
@Nullable String key,
@Nullable String relay,
@Nullable byte[] digest,
@Nullable byte[] incrementalDigest,
@Nullable String fastPreflightId,
boolean voiceNote,
boolean borderless,
@@ -41,7 +42,7 @@ public class PointerAttachment extends Attachment {
@Nullable StickerLocator stickerLocator,
@Nullable BlurHash blurHash)
{
super(contentType, transferState, size, fileName, cdnNumber, location, key, relay, digest, 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, false, uploadTimestamp, caption, stickerLocator, blurHash, null, null);
}
@Nullable
@@ -112,6 +113,7 @@ public class PointerAttachment extends Attachment {
pointer.get().asPointer().getRemoteId().toString(),
encodedKey, null,
pointer.get().asPointer().getDigest().orElse(null),
pointer.get().asPointer().getincrementalDigest().orElse(null),
fastPreflightId,
pointer.get().asPointer().getVoiceNote(),
pointer.get().asPointer().isBorderless(),
@@ -137,6 +139,7 @@ public class PointerAttachment extends Attachment {
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getincrementalDigest().orElse(null) : null,
null,
false,
false,
@@ -166,6 +169,7 @@ public class PointerAttachment extends Attachment {
thumbnail != null && thumbnail.asPointer().getKey() != null ? Base64.encodeBytes(thumbnail.asPointer().getKey()) : null,
null,
thumbnail != null ? thumbnail.asPointer().getDigest().orElse(null) : null,
thumbnail != null ? thumbnail.asPointer().getincrementalDigest().orElse(null) : null,
null,
false,
false,

View File

@@ -16,7 +16,7 @@ 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, 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, quote, 0, null, null, null, null, null);
}
@Override

View File

@@ -52,7 +52,7 @@ public class UriAttachment extends Attachment {
@Nullable AudioHash audioHash,
@Nullable TransformProperties transformProperties)
{
super(contentType, transferState, size, fileName, 0, 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, quote, 0, caption, stickerLocator, blurHash, audioHash, transformProperties);
this.dataUri = Objects.requireNonNull(dataUri);
}

View File

@@ -48,14 +48,14 @@ public class AudioRecorder {
if (this.uiHandler != null) {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording via UI handler.");
this.uiHandler.onRecordCanceled(false);
}
};
} else {
onAudioFocusChangeListener = focusChange -> {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording");
Log.i(TAG, "Audio focus change " + focusChange + " stopping recording.");
stopRecording();
}
};

View File

@@ -7,6 +7,7 @@ import androidx.annotation.AnyThread
import androidx.annotation.RequiresApi
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import io.reactivex.rxjava3.subjects.SingleSubject
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.attachments.AttachmentId
@@ -29,6 +30,7 @@ object AudioWaveForms {
private val TAG = Log.tag(AudioWaveForms::class.java)
private val cache = ThreadSafeLruCache(200)
private val pending = hashMapOf<String, SingleSubject<AudioFileInfo>>()
@AnyThread
@JvmStatic
@@ -43,39 +45,47 @@ object AudioWaveForms {
val cachedInfo = cache.get(cacheKey)
if (cachedInfo != null) {
Log.i(TAG, "Loaded wave form from cache $cacheKey")
synchronized(pending) {
pending.remove(cacheKey)
}
return Single.just(cachedInfo)
}
val databaseCache = Single.fromCallable {
val audioHash = attachment.audioHash
return@fromCallable if (audioHash != null) {
checkDatabaseCache(cacheKey, audioHash.audioWaveForm)
val pendingSubject = synchronized(pending) {
if (pending.containsKey(cacheKey)) {
Log.i(TAG, "Wave currently generating, returning existing subject")
return pending[cacheKey]!!
} else {
Miss
pending[cacheKey] = SingleSubject.create()
}
}.subscribeOn(Schedulers.io())
val generateWaveForm: Single<CacheCheckResult> = if (attachment is DatabaseAttachment) {
Single.fromCallable { generateWaveForm(context, uri, cacheKey, attachment.attachmentId) }
} else {
Single.fromCallable { generateWaveForm(context, uri, cacheKey) }
}.subscribeOn(Schedulers.io())
pending[cacheKey]!!
}
return databaseCache
.flatMap { r ->
if (r is Miss) {
generateWaveForm
Single.fromCallable { attachment.audioHash?.let { checkDatabaseCache(cacheKey, it.audioWaveForm) } ?: Miss }
.flatMap { result ->
if (result !is Success) {
if (attachment is DatabaseAttachment) {
Single.fromCallable { generateWaveForm(context, uri, cacheKey, attachment.attachmentId) }
} else {
Single.fromCallable { generateWaveForm(context, uri, cacheKey) }
}
} else {
Single.just(r)
Single.just(result)
}
}
.map { r ->
if (r is Success) {
r.audioFileInfo
.map { result ->
if (result is Success) {
result.audioFileInfo
} else {
throw IOException("Unable to generate wave form")
}
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(pendingSubject)
return pendingSubject
}
private fun checkDatabaseCache(cacheKey: String, audioWaveForm: AudioWaveFormData): CacheCheckResult {

View File

@@ -25,14 +25,14 @@ sealed interface BluetoothVoiceNoteUtil {
fun destroy()
companion object {
fun create(context: Context, listener: () -> Unit, bluetoothPermissionDeniedHandler: () -> Unit): BluetoothVoiceNoteUtil {
fun create(context: Context, listener: (Boolean) -> Unit, bluetoothPermissionDeniedHandler: () -> Unit): BluetoothVoiceNoteUtil {
return if (Build.VERSION.SDK_INT >= 31) BluetoothVoiceNoteUtil31(listener) else BluetoothVoiceNoteUtilLegacy(context, listener, bluetoothPermissionDeniedHandler)
}
}
}
@RequiresApi(31)
private class BluetoothVoiceNoteUtil31(val listener: () -> Unit) : BluetoothVoiceNoteUtil {
private class BluetoothVoiceNoteUtil31(val listener: (Boolean) -> Unit) : BluetoothVoiceNoteUtil {
override fun connectBluetoothScoConnection() {
val audioManager = ApplicationDependencies.getAndroidCallAudioManager()
val device: AudioDeviceInfo? = audioManager.connectedBluetoothDevice
@@ -40,13 +40,15 @@ private class BluetoothVoiceNoteUtil31(val listener: () -> Unit) : BluetoothVoic
val result: Boolean = audioManager.setCommunicationDevice(device)
if (result) {
Log.d(TAG, "Successfully set Bluetooth device as active communication device.")
listener(true)
} else {
Log.d(TAG, "Found Bluetooth device but failed to set it as active communication device.")
listener(false)
}
} else {
Log.d(TAG, "Could not find Bluetooth device in list of communications devices, falling back to current input.")
listener(false)
}
listener()
}
override fun disconnectBluetoothScoConnection() {
@@ -64,15 +66,23 @@ private class BluetoothVoiceNoteUtil31(val listener: () -> Unit) : BluetoothVoic
* @param listener This will be executed on the main thread after the Bluetooth connection connects, or if it doesn't.
* @param bluetoothPermissionDeniedHandler called when we detect the Bluetooth permission has been denied to our app.
*/
private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: () -> Unit, val bluetoothPermissionDeniedHandler: () -> Unit) : BluetoothVoiceNoteUtil {
private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: (Boolean) -> Unit, val bluetoothPermissionDeniedHandler: () -> Unit) : BluetoothVoiceNoteUtil {
private val commandAndControlThread: HandlerThread = SignalExecutors.getAndStartHandlerThread("voice-note-audio", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD)
private val uiThreadHandler = Handler(context.mainLooper)
private val audioHandler: SignalAudioHandler = SignalAudioHandler(commandAndControlThread.looper)
private val deviceUpdatedListener: AudioDeviceUpdatedListener = object : AudioDeviceUpdatedListener {
override fun onAudioDeviceUpdated() {
if (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED) {
Log.d(TAG, "Bluetooth SCO connected. Starting voice note recording on UI thread.")
uiThreadHandler.post { listener() }
when (signalBluetoothManager.state) {
SignalBluetoothManager.State.CONNECTED -> {
Log.d(TAG, "Bluetooth SCO connected. Starting voice note recording on UI thread.")
uiThreadHandler.post { listener(true) }
}
SignalBluetoothManager.State.ERROR,
SignalBluetoothManager.State.PERMISSION_DENIED -> {
Log.w(TAG, "Unable to complete Bluetooth connection due to ${signalBluetoothManager.state}. Starting voice note recording anyway on UI thread.")
uiThreadHandler.post { listener(false) }
}
else -> Log.d(TAG, "Current Bluetooth connection state: ${signalBluetoothManager.state}.")
}
}
}
@@ -105,7 +115,7 @@ private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: (
bluetoothPermissionDeniedHandler()
hasWarnedAboutBluetooth = true
}
listener()
listener(false)
}
}
}

View File

@@ -1,9 +1,9 @@
package org.thoughtcrime.securesms.avatar.text
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.distinctUntilChanged
import org.thoughtcrime.securesms.avatar.Avatar
import org.thoughtcrime.securesms.avatar.Avatars
import org.thoughtcrime.securesms.util.livedata.Store
@@ -12,7 +12,7 @@ class TextAvatarCreationViewModel(initialText: Avatar.Text) : ViewModel() {
private val store = Store(TextAvatarCreationState(initialText))
val state: LiveData<TextAvatarCreationState> = Transformations.distinctUntilChanged(store.stateLiveData)
val state: LiveData<TextAvatarCreationState> = store.stateLiveData.distinctUntilChanged()
fun setColor(colorPair: Avatars.ColorPair) {
store.update { it.copy(currentAvatar = it.currentAvatar.copy(color = colorPair)) }

View File

@@ -41,7 +41,7 @@ class AvatarView @JvmOverloads constructor(
}
storyRing.visible = true
storyRing.isActivated = hasUnreadStory
storyRing.setBackgroundResource(if (hasUnreadStory) R.drawable.avatar_story_ring_active else R.drawable.avatar_story_ring_inactive)
avatar.scaleX = storyRingScale
avatar.scaleY = storyRingScale

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup;
import androidx.annotation.NonNull;
import org.signal.core.util.Conversions;
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.attachments.AttachmentId;
import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.proto.Header;
import org.thoughtcrime.securesms.backup.proto.KeyValue;
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
import org.thoughtcrime.securesms.backup.proto.Sticker;
import org.thoughtcrime.securesms.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
class BackupFrameOutputStream extends FullBackupBase.BackupStream {
private static final String TAG = Log.tag(BackupFrameOutputStream.class);
private final OutputStream outputStream;
private final Cipher cipher;
private final Mac mac;
private final byte[] cipherKey;
private final byte[] iv;
private int counter;
private int frames;
BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String passphrase) throws IOException {
try {
byte[] salt = Util.getSecretBytes(32);
byte[] key = getBackupKey(passphrase, salt);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
this.cipherKey = split[0];
byte[] macKey = split[1];
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
this.mac = Mac.getInstance("HmacSHA256");
this.outputStream = output;
this.iv = Util.getSecretBytes(16);
this.counter = Conversions.byteArrayToInt(iv);
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
byte[] header = new BackupFrame.Builder().header_(new Header.Builder()
.iv(new okio.ByteString(iv))
.salt(new okio.ByteString(salt))
.version(BackupVersions.CURRENT_VERSION)
.build())
.build()
.encode();
outputStream.write(Conversions.intToByteArray(header.length));
outputStream.write(header);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void write(SharedPreference preference) throws IOException {
write(outputStream, new BackupFrame.Builder().preference(preference).build());
}
public void write(KeyValue keyValue) throws IOException {
write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build());
}
public void write(SqlStatement statement) throws IOException {
write(outputStream, new BackupFrame.Builder().statement(statement).build());
}
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.avatar(new Avatar.Builder()
.recipientId(avatarName)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write avatar to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.attachment(new Attachment.Builder()
.rowId(attachmentId.getRowId())
.attachmentId(attachmentId.getUniqueId())
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write " + attachmentId + " to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.sticker(new Sticker.Builder()
.rowId(rowId)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write sticker to backup", e);
throw new FullBackupExporter.InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
void writeDatabaseVersion(int version) throws IOException {
write(outputStream, new BackupFrame.Builder()
.version(new DatabaseVersion.Builder().version(version).build())
.build());
}
void writeEnd() throws IOException {
write(outputStream, new BackupFrame.Builder().end(true).build());
}
/**
* @return The amount of data written from the provided InputStream.
*/
private long writeStream(@NonNull InputStream inputStream) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
mac.update(iv);
byte[] buffer = new byte[8192];
long total = 0;
int read;
while ((read = inputStream.read(buffer)) != -1) {
byte[] ciphertext = cipher.update(buffer, 0, read);
if (ciphertext != null) {
outputStream.write(ciphertext);
mac.update(ciphertext);
}
total += read;
}
byte[] remainder = cipher.doFinal();
outputStream.write(remainder);
mac.update(remainder);
byte[] attachmentDigest = mac.doFinal();
outputStream.write(attachmentDigest, 0, 10);
return total;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] encodedFrame = frame.encode();
// this assumes a stream cipher
byte[] length = Conversions.intToByteArray(encodedFrame.length + 10);
if (BackupVersions.isFrameLengthEncrypted(BackupVersions.CURRENT_VERSION)) {
byte[] encryptedLength = cipher.update(length);
if (encryptedLength.length != length.length) {
throw new IOException("Stream cipher assumption has been violated!");
}
mac.update(encryptedLength);
length = encryptedLength;
}
byte[] frameCiphertext = cipher.doFinal(frame.encode());
if (frameCiphertext.length != encodedFrame.length) {
throw new IOException("Stream cipher assumption has been violated!");
}
byte[] frameMac = mac.doFinal(frameCiphertext);
out.write(length);
out.write(frameCiphertext);
out.write(frameMac, 0, 10);
frames++;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
public void close() throws IOException {
outputStream.flush();
outputStream.close();
}
public int getFrames() {
return frames;
}
}

View File

@@ -27,6 +27,7 @@ import javax.crypto.spec.SecretKeySpec;
class BackupRecordInputStream extends FullBackupBase.BackupStream {
private final int version;
private final InputStream in;
private final Cipher cipher;
private final Mac mac;
@@ -55,12 +56,21 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
Header header = frame.header_;
if (header.iv == null) {
throw new IOException("Missing IV!");
}
this.iv = header.iv.toByteArray();
if (iv.length != 16) {
throw new IOException("Invalid IV length!");
}
this.version = header.version != null ? header.version : 0;
if (!BackupVersions.isCompatible(version)) {
throw new IOException("Invalid backup version: " + version);
}
byte[] key = getBackupKey(passphrase, header.salt != null ? header.salt.toByteArray() : null);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
@@ -135,7 +145,23 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
byte[] length = new byte[4];
StreamUtil.readFully(in, length);
byte[] frame = new byte[Conversions.byteArrayToInt(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);
}
byte[] frame = new byte[frameLength];
StreamUtil.readFully(in, frame);
byte[] theirMac = new byte[10];
@@ -148,9 +174,6 @@ class BackupRecordInputStream extends FullBackupBase.BackupStream {
throw new IOException("Bad MAC");
}
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(frame, 0, frame.length - 10);
return BackupFrame.ADAPTER.decode(plaintext);

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.backup
object BackupVersions {
const val CURRENT_VERSION = 1
const val MINIMUM_VERSION = 0
@JvmStatic
fun isCompatible(version: Int): Boolean {
return version in MINIMUM_VERSION..CURRENT_VERSION
}
@JvmStatic
fun isFrameLengthEncrypted(version: Int): Boolean {
return version >= 1
}
}

View File

@@ -16,24 +16,15 @@ import com.annimon.stream.function.Predicate;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.Conversions;
import org.signal.core.util.CursorUtil;
import org.signal.core.util.SetUtil;
import org.signal.core.util.SqlUtil;
import org.signal.core.util.Stopwatch;
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.attachments.AttachmentId;
import org.thoughtcrime.securesms.backup.proto.Attachment;
import org.thoughtcrime.securesms.backup.proto.Avatar;
import org.thoughtcrime.securesms.backup.proto.BackupFrame;
import org.thoughtcrime.securesms.backup.proto.DatabaseVersion;
import org.thoughtcrime.securesms.backup.proto.Header;
import org.thoughtcrime.securesms.backup.proto.KeyValue;
import org.thoughtcrime.securesms.backup.proto.SharedPreference;
import org.thoughtcrime.securesms.backup.proto.SqlStatement;
import org.thoughtcrime.securesms.backup.proto.Sticker;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream;
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
@@ -59,7 +50,6 @@ import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.io.File;
import java.io.FileNotFoundException;
@@ -67,9 +57,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -80,14 +67,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import okio.ByteString;
public class FullBackupExporter extends FullBackupBase {
@@ -228,7 +207,7 @@ public class FullBackupExporter extends FullBackupBase {
outputStream.close();
}
}
return new BackupEvent(BackupEvent.Type.FINISHED, outputStream.frames, estimatedCountOutside);
return new BackupEvent(BackupEvent.Type.FINISHED, outputStream.getFrames(), estimatedCountOutside);
}
private static long calculateCount(@NonNull Context context, @NonNull SQLiteDatabase input, List<String> tables) {
@@ -627,190 +606,6 @@ public class FullBackupExporter extends FullBackupBase {
return false;
}
private static class BackupFrameOutputStream extends BackupStream {
private final OutputStream outputStream;
private final Cipher cipher;
private final Mac mac;
private final byte[] cipherKey;
private final byte[] iv;
private int counter;
private int frames;
private BackupFrameOutputStream(@NonNull OutputStream output, @NonNull String passphrase) throws IOException {
try {
byte[] salt = Util.getSecretBytes(32);
byte[] key = getBackupKey(passphrase, salt);
byte[] derived = HKDF.deriveSecrets(key, "Backup Export".getBytes(), 64);
byte[][] split = ByteUtil.split(derived, 32, 32);
this.cipherKey = split[0];
byte[] macKey = split[1];
this.cipher = Cipher.getInstance("AES/CTR/NoPadding");
this.mac = Mac.getInstance("HmacSHA256");
this.outputStream = output;
this.iv = Util.getSecretBytes(16);
this.counter = Conversions.byteArrayToInt(iv);
mac.init(new SecretKeySpec(macKey, "HmacSHA256"));
byte[] header = new BackupFrame.Builder().header_(new Header.Builder()
.iv(new okio.ByteString(iv))
.salt(new okio.ByteString(salt))
.build())
.build()
.encode();
outputStream.write(Conversions.intToByteArray(header.length));
outputStream.write(header);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new AssertionError(e);
}
}
public void write(SharedPreference preference) throws IOException {
write(outputStream, new BackupFrame.Builder().preference(preference).build());
}
public void write(KeyValue keyValue) throws IOException {
write(outputStream, new BackupFrame.Builder().keyValue(keyValue).build());
}
public void write(SqlStatement statement) throws IOException {
write(outputStream, new BackupFrame.Builder().statement(statement).build());
}
public void write(@NonNull String avatarName, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.avatar(new Avatar.Builder()
.recipientId(avatarName)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write avatar to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void write(@NonNull AttachmentId attachmentId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.attachment(new Attachment.Builder()
.rowId(attachmentId.getRowId())
.attachmentId(attachmentId.getUniqueId())
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write " + attachmentId + " to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
public void writeSticker(long rowId, @NonNull InputStream in, long size) throws IOException {
try {
write(outputStream, new BackupFrame.Builder()
.sticker(new Sticker.Builder()
.rowId(rowId)
.length(Util.toIntExact(size))
.build())
.build());
} catch (ArithmeticException e) {
Log.w(TAG, "Unable to write sticker to backup", e);
throw new InvalidBackupStreamException();
}
if (writeStream(in) != size) {
throw new IOException("Size mismatch!");
}
}
void writeDatabaseVersion(int version) throws IOException {
write(outputStream, new BackupFrame.Builder()
.version(new DatabaseVersion.Builder().version(version).build())
.build());
}
void writeEnd() throws IOException {
write(outputStream, new BackupFrame.Builder().end(true).build());
}
/**
* @return The amount of data written from the provided InputStream.
*/
private long writeStream(@NonNull InputStream inputStream) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
mac.update(iv);
byte[] buffer = new byte[8192];
long total = 0;
int read;
while ((read = inputStream.read(buffer)) != -1) {
byte[] ciphertext = cipher.update(buffer, 0, read);
if (ciphertext != null) {
outputStream.write(ciphertext);
mac.update(ciphertext);
}
total += read;
}
byte[] remainder = cipher.doFinal();
outputStream.write(remainder);
mac.update(remainder);
byte[] attachmentDigest = mac.doFinal();
outputStream.write(attachmentDigest, 0, 10);
return total;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private void write(@NonNull OutputStream out, @NonNull BackupFrame frame) throws IOException {
try {
Conversions.intToByteArray(iv, 0, counter++);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
byte[] frameCiphertext = cipher.doFinal(frame.encode());
byte[] frameMac = mac.doFinal(frameCiphertext);
byte[] length = Conversions.intToByteArray(frameCiphertext.length + 10);
out.write(length);
out.write(frameCiphertext);
out.write(frameMac, 0, 10);
frames++;
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
public void close() throws IOException {
outputStream.flush();
outputStream.close();
}
}
public interface PostProcessor {
int postProcess(@NonNull Cursor cursor, int count) throws IOException;
}

View File

@@ -264,12 +264,14 @@ class GiftFlowConfirmationFragment :
override fun onPaymentComplete(gatewayRequest: GatewayRequest) {
val mainActivityIntent = MainActivity.clearTop(requireContext())
val conversationIntent = ConversationIntents
.createBuilder(requireContext(), viewModel.snapshot.recipient!!.id, -1L)
.withGiftBadge(viewModel.snapshot.giftBadge!!)
.build()
requireActivity().startActivities(arrayOf(mainActivityIntent, conversationIntent))
lifecycleDisposable += ConversationIntents
.createBuilder(requireContext(), viewModel.snapshot.recipient!!.id, -1L)
.subscribe { conversationIntent ->
requireActivity().startActivities(
arrayOf(mainActivityIntent, conversationIntent.withGiftBadge(viewModel.snapshot.giftBadge!!).build())
)
}
}
override fun onProcessorActionProcessed() = Unit

View File

@@ -23,6 +23,6 @@ data class GiftFlowState(
RECIPIENT_VERIFICATION,
TOKEN_REQUEST,
PAYMENT_PIPELINE,
FAILURE;
FAILURE
}
}

View File

@@ -7,7 +7,9 @@ package org.thoughtcrime.securesms.calls.links
import android.content.Context
import android.util.AttributeSet
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
@@ -22,6 +24,10 @@ class CallLinkJoinButton @JvmOverloads constructor(
private val joinButton: MaterialButton = findViewById(R.id.join_button)
fun setTextColor(@ColorRes textColorResId: Int) {
joinButton.setTextColor(ContextCompat.getColor(context, textColorResId))
}
fun setJoinClickListener(onClickListener: OnClickListener) {
joinButton.setOnClickListener(onClickListener)
}

View File

@@ -6,8 +6,8 @@
package org.thoughtcrime.securesms.calls.links
import io.reactivex.rxjava3.core.Observable
import org.signal.core.util.Hex
import org.signal.core.util.logging.Log
import org.signal.ringrtc.CallException
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.DatabaseObserver
@@ -21,11 +21,12 @@ import java.net.URLDecoder
*/
object CallLinks {
private const val ROOT_KEY = "key"
private const val LINK_PREFIX = "https://signal.link/call/#key="
private const val HTTPS_LINK_PREFIX = "https://signal.link/call/#key="
private const val SNGL_LINK_PREFIX = "sgnl://signal.link/#key="
private val TAG = Log.tag(CallLinks::class.java)
fun url(linkKeyBytes: ByteArray) = "$LINK_PREFIX${Hex.dump(linkKeyBytes)}"
fun url(linkKeyBytes: ByteArray) = "$HTTPS_LINK_PREFIX${CallLinkRootKey(linkKeyBytes)}"
fun watchCallLink(roomId: CallLinkRoomId): Observable<CallLinkTable.CallLink> {
return Observable.create { emitter ->
@@ -50,9 +51,20 @@ object CallLinks {
}
}
@JvmStatic
fun isCallLink(url: String): Boolean {
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
Log.w(TAG, "Invalid url prefix.")
return false
}
return url.split("#").last().startsWith("key=")
}
@JvmStatic
fun parseUrl(url: String): CallLinkRootKey? {
if (!url.startsWith(LINK_PREFIX)) {
if (!url.startsWith(HTTPS_LINK_PREFIX) && !url.startsWith(SNGL_LINK_PREFIX)) {
Log.w(TAG, "Invalid url prefix.")
return null
}
@@ -82,7 +94,11 @@ object CallLinks {
return null
}
// TODO Parse the key into a byte array
return null
return try {
CallLinkRootKey(key)
} catch (e: CallException) {
Log.w(TAG, "Invalid root key found in fragment query string.")
null
}
}
}

View File

@@ -37,7 +37,6 @@ import org.signal.core.ui.Buttons
import org.signal.core.ui.theme.SignalTheme
import org.signal.ringrtc.CallLinkRootKey
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.conversation.colors.AvatarColorPair
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -49,7 +48,6 @@ import java.time.Instant
@Preview
@Composable
private fun SignalCallRowPreview() {
val avatarColor = remember { AvatarColor.random() }
val callLink = remember {
val credentials = CallLinkCredentials.generate()
CallLinkTable.CallLink(
@@ -61,8 +59,7 @@ private fun SignalCallRowPreview() {
restrictions = org.signal.ringrtc.CallLinkState.Restrictions.NONE,
expiration = Instant.MAX,
revoked = false
),
avatarColor = avatarColor
)
)
}
SignalTheme(false) {
@@ -76,7 +73,7 @@ private fun SignalCallRowPreview() {
@Composable
fun SignalCallRow(
callLink: CallLinkTable.CallLink,
onJoinClicked: () -> Unit,
onJoinClicked: (() -> Unit)?,
modifier: Modifier = Modifier
) {
Row(
@@ -122,13 +119,15 @@ fun SignalCallRow(
)
}
Spacer(modifier = Modifier.width(10.dp))
if (onJoinClicked != null) {
Spacer(modifier = Modifier.width(10.dp))
Buttons.Small(
onClick = onJoinClicked,
modifier = Modifier.align(CenterVertically)
) {
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))
Buttons.Small(
onClick = onJoinClicked,
modifier = Modifier.align(CenterVertically)
) {
Text(text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__join))
}
}
}
}

View File

@@ -34,6 +34,7 @@ import androidx.core.app.ShareCompat
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.core.ui.BottomSheets
import org.signal.core.ui.Buttons
import org.signal.core.ui.Dividers
import org.signal.core.ui.Rows
@@ -86,7 +87,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
) {
val callLink: CallLinkTable.CallLink by viewModel.callLink
Handle(modifier = Modifier.align(Alignment.CenterHorizontally))
BottomSheets.Handle(modifier = Modifier.align(Alignment.CenterHorizontally))
Spacer(modifier = Modifier.height(20.dp))
@@ -108,7 +109,7 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__add_call_name),
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked)
onClick = this@CreateCallLinkBottomSheetDialogFragment::onAddACallNameClicked
)
Rows.ToggleRow(
@@ -123,19 +124,19 @@ class CreateCallLinkBottomSheetDialogFragment : ComposeBottomSheetDialogFragment
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link_via_signal),
icon = ImageVector.vectorResource(id = R.drawable.symbol_forward_24),
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareViaSignalClicked)
onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareViaSignalClicked
)
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__copy_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_copy_android_24),
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onCopyLinkClicked)
onClick = this@CreateCallLinkBottomSheetDialogFragment::onCopyLinkClicked
)
Rows.TextRow(
text = stringResource(id = R.string.CreateCallLinkBottomSheetDialogFragment__share_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_share_android_24),
modifier = Modifier.clickable(onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareLinkClicked)
onClick = this@CreateCallLinkBottomSheetDialogFragment::onShareLinkClicked
)
Buttons.MediumTonal(

View File

@@ -7,7 +7,6 @@ package org.thoughtcrime.securesms.calls.links.create
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
@@ -24,7 +23,7 @@ import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
class CreateCallLinkRepository(
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
) {
fun ensureCallLinkCreated(credentials: CallLinkCredentials, avatarColor: AvatarColor): Single<EnsureCallLinkCreatedResult> {
fun ensureCallLinkCreated(credentials: CallLinkCredentials): Single<EnsureCallLinkCreatedResult> {
val callLinkRecipientId = Single.fromCallable {
SignalDatabase.recipients.getByCallLinkRoomId(credentials.roomId)
}
@@ -41,8 +40,7 @@ class CreateCallLinkRepository(
recipientId = RecipientId.UNKNOWN,
roomId = credentials.roomId,
credentials = credentials,
state = it.state,
avatarColor = avatarColor
state = it.state
)
)

View File

@@ -16,7 +16,6 @@ import io.reactivex.rxjava3.kotlin.subscribeBy
import org.signal.ringrtc.CallLinkState.Restrictions
import org.thoughtcrime.securesms.calls.links.CallLinks
import org.thoughtcrime.securesms.calls.links.UpdateCallLinkRepository
import org.thoughtcrime.securesms.conversation.colors.AvatarColorHash
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
@@ -29,7 +28,6 @@ class CreateCallLinkViewModel(
private val mutationRepository: UpdateCallLinkRepository = UpdateCallLinkRepository()
) : ViewModel() {
private val credentials = CallLinkCredentials.generate()
private val avatarColor = AvatarColorHash.forCallLink(credentials.linkKeyBytes)
private val _callLink: MutableState<CallLinkTable.CallLink> = mutableStateOf(
CallLinkTable.CallLink(
recipientId = RecipientId.UNKNOWN,
@@ -40,8 +38,7 @@ class CreateCallLinkViewModel(
restrictions = Restrictions.NONE,
revoked = false,
expiration = Instant.MAX
),
avatarColor = avatarColor
)
)
)
@@ -63,7 +60,7 @@ class CreateCallLinkViewModel(
}
fun commitCallLink(): Single<EnsureCallLinkCreatedResult> {
return repository.ensureCallLinkCreated(credentials, avatarColor)
return repository.ensureCallLinkCreated(credentials)
}
fun setApproveAllMembers(approveAllMembers: Boolean): Single<UpdateCallLinkResult> {

View File

@@ -10,7 +10,6 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
@@ -200,8 +199,7 @@ private fun CallLinkDetailsPreview() {
revoked = false,
restrictions = Restrictions.NONE,
expiration = Instant.MAX
),
avatarColor = avatarColor
)
)
}
@@ -248,7 +246,7 @@ private fun CallLinkDetails(
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__add_call_name),
modifier = Modifier.clickable(onClick = callback::onEditNameClicked)
onClick = callback::onEditNameClicked
)
Rows.ToggleRow(
@@ -262,14 +260,14 @@ private fun CallLinkDetails(
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__share_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_link_24),
modifier = Modifier.clickable(onClick = callback::onShareClicked)
onClick = callback::onShareClicked
)
Rows.TextRow(
text = stringResource(id = R.string.CallLinkDetailsFragment__delete_call_link),
icon = ImageVector.vectorResource(id = R.drawable.symbol_trash_24),
foregroundTint = MaterialTheme.colorScheme.error,
modifier = Modifier.clickable(onClick = callback::onDeleteClicked)
onClick = callback::onDeleteClicked
)
}

View File

@@ -5,17 +5,15 @@
package org.thoughtcrime.securesms.calls.links.details
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.subscribeBy
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.concurrent.MaybeCompat
import org.signal.core.util.orNull
import org.thoughtcrime.securesms.database.CallLinkTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.ReadCallLinkResult
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkManager
@@ -24,7 +22,7 @@ class CallLinkDetailsRepository(
private val callLinkManager: SignalCallLinkManager = ApplicationDependencies.getSignalCallManager().callLinkManager
) {
fun refreshCallLinkState(callLinkRoomId: CallLinkRoomId): Disposable {
return Maybe.fromCallable<CallLinkTable.CallLink> { SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) }
return MaybeCompat.fromCallable { SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) }
.flatMapSingle { callLinkManager.readCallLink(it.credentials!!) }
.subscribeOn(Schedulers.io())
.subscribeBy { result ->
@@ -36,7 +34,7 @@ class CallLinkDetailsRepository(
}
fun watchCallLinkRecipient(callLinkRoomId: CallLinkRoomId): Observable<Recipient> {
return Maybe.fromCallable<RecipientId> { SignalDatabase.recipients.getByCallLinkRoomId(callLinkRoomId).orNull() }
return MaybeCompat.fromCallable { SignalDatabase.recipients.getByCallLinkRoomId(callLinkRoomId).orNull() }
.flatMapObservable { Recipient.observable(it) }
.distinctUntilChanged { a, b -> a.hasSameContent(b) }
.subscribeOn(Schedulers.io())

View File

@@ -75,12 +75,10 @@ class CallLogAdapter(
fun submitCallRows(
rows: List<CallLogRow?>,
selectionState: CallLogSelectionState,
stagedDeletion: CallLogStagedDeletion?,
onCommit: () -> Unit
): Int {
val filteredRows = rows
.filterNotNull()
.filterNot { stagedDeletion?.isStagedForDeletion(it.id) == true }
.map {
when (it) {
is CallLogRow.Call -> CallModel(it, selectionState, itemCount)
@@ -355,6 +353,7 @@ class CallLogAdapter(
MessageTypes.INCOMING_AUDIO_CALL_TYPE, MessageTypes.INCOMING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_downleft_compact_16
MessageTypes.OUTGOING_AUDIO_CALL_TYPE, MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.drawable.symbol_arrow_upright_compact_16
MessageTypes.GROUP_CALL_TYPE -> when {
call.type == CallTable.Type.AD_HOC_CALL -> R.drawable.symbol_link_compact_16
call.event == CallTable.Event.MISSED -> R.drawable.symbol_missed_incoming_24
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.drawable.symbol_group_compact_16
call.direction == CallTable.Direction.INCOMING -> R.drawable.symbol_arrow_downleft_compact_16
@@ -376,6 +375,7 @@ class CallLogAdapter(
MessageTypes.OUTGOING_AUDIO_CALL_TYPE -> R.string.CallLogAdapter__outgoing
MessageTypes.OUTGOING_VIDEO_CALL_TYPE -> R.string.CallLogAdapter__outgoing
MessageTypes.GROUP_CALL_TYPE -> when {
call.type == CallTable.Type.AD_HOC_CALL -> R.string.CallLogAdapter__call_link
call.event == CallTable.Event.MISSED -> R.string.CallLogAdapter__missed
call.event == CallTable.Event.GENERIC_GROUP_CALL || call.event == CallTable.Event.JOINED -> R.string.CallPreference__group_call
call.direction == CallTable.Direction.INCOMING -> R.string.CallLogAdapter__incoming

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