Compare commits

..

508 Commits

Author SHA1 Message Date
Greyson Parrelli
e14fcf8577 Bump version to 6.27.9 2023-07-31 18:30:04 -04:00
Greyson Parrelli
0219c5253b Updated language translations. 2023-07-31 18:29:37 -04:00
Cody Henthorne
adf3d74d91 Fix attachment keyboard not showing. 2023-07-31 15:44:51 -04:00
Cody Henthorne
3acd68e0b3 Fix quoted reply being dropped from voice notes. 2023-07-31 15:44:51 -04:00
Cody Henthorne
e5eccd732d Fix gifs rendering behind compose bar. 2023-07-31 15:44:51 -04:00
Cody Henthorne
eb0df5791a Fix scroll date header going blank at top of conversation. 2023-07-31 15:44:51 -04:00
Cody Henthorne
f5371123da Fix send location not including description location. 2023-07-31 15:44:51 -04:00
Cody Henthorne
6aa723bc22 Fix lifecycle crashes when fragment is destroy before async callbacks. 2023-07-31 11:38:06 -04:00
Cody Henthorne
9ba34df4ae Fix memory leaks and potentially gif playback issues with conversation items. 2023-07-31 11:38:06 -04:00
Cody Henthorne
6194515f8e Fix invalid message request state being used. 2023-07-31 11:16:51 -04:00
Cody Henthorne
39289715bc Fix incorrect text on remote delete dialog in Note to Self. 2023-07-31 11:12:49 -04:00
Cody Henthorne
5fdd2430ca Bump version to 6.27.8 2023-07-28 20:14:40 -04:00
Cody Henthorne
a7f3f485ad Updated baseline profile. 2023-07-28 20:07:37 -04:00
Cody Henthorne
69a76fa1b7 Updated language translations. 2023-07-28 20:04:55 -04:00
Cody Henthorne
2e5e64b05d Fix crash when opening non-gv2 group. 2023-07-28 20:00:09 -04:00
Cody Henthorne
933e3233a7 Show correct note to self delete dialog options in CFv2. 2023-07-28 19:55:43 -04:00
Cody Henthorne
a54df29542 Protected against crash with unread counter that exceeds thread size. 2023-07-28 19:40:24 -04:00
Greyson Parrelli
cdce802b32 Do not retry auth failures in Svr2MirrorJob. 2023-07-28 17:25:14 -04:00
Greyson Parrelli
2abf30e94b Limit RefreshSvrCredentialsJob to registered users. 2023-07-28 17:14:27 -04:00
Cody Henthorne
148cff1b48 Fix missed menu invalidation after opening search. 2023-07-28 15:00:33 -04:00
Cody Henthorne
ce2a21c438 Fix disabled input state for Release Notes Channel. 2023-07-28 13:27:11 -04:00
Clark
d77c0198d1 Make CFv2 date header visible again. 2023-07-28 12:18:11 -04:00
Cody Henthorne
f58a1acff5 Bump version to 6.27.7 2023-07-27 17:54:06 -04:00
Cody Henthorne
aadbddd7e9 Updated baseline profile. 2023-07-27 17:11:48 -04:00
Cody Henthorne
689fe3947b Updated language translations. 2023-07-27 17:09:06 -04:00
Cody Henthorne
fff0b8b187 Fix crash when tapping re-register banner view. 2023-07-27 17:05:29 -04:00
Cody Henthorne
74562432cf Disable compose input when opening conversation with unregistered recipient. 2023-07-27 17:05:29 -04:00
Clark
8e3027642b Remove self from mention picker. 2023-07-27 16:28:39 -04:00
Cody Henthorne
39f96bb12c Revamp group name color generation. 2023-07-27 16:07:38 -04:00
Clark
938309d125 Remove SMS popup from CFv2. 2023-07-27 16:04:07 -04:00
Cody Henthorne
f740b69ffe Fix lifecycle crashes with keyboard fragment and media sending. 2023-07-27 15:36:38 -04:00
Nicholas Tinsley
0a4147aa0e Add margin to the Note to Self bottom sheet. 2023-07-27 14:58:12 -04:00
Greyson Parrelli
dcffc13843 Fix a RRP recovery path. 2023-07-26 20:15:57 -04:00
Cody Henthorne
1e9a0cdc16 Attempt to swallow erroneous cancel alarm security exceptions. 2023-07-26 14:07:10 -04:00
Cody Henthorne
82e7050864 Fix various lifecycle crashes. 2023-07-26 13:51:19 -04:00
Nicholas
72d1e55373 Re-enable bubble menu shortcut on CFv2. 2023-07-26 13:21:00 -04:00
Cody Henthorne
fe5d5df2d7 Bump version to 6.27.6 2023-07-26 12:39:20 -04:00
Cody Henthorne
9bc337373e Updated baseline profile. 2023-07-26 12:32:52 -04:00
Cody Henthorne
5aad879a95 Updated language translations. 2023-07-26 12:27:54 -04:00
Cody Henthorne
a3798dba68 Properly support group calls in CFv2. 2023-07-26 11:59:03 -04:00
Cody Henthorne
b9f7ef5cbd Fix bug with search UI not showing when searching from settings. 2023-07-26 11:20:06 -04:00
Cody Henthorne
0c3b541031 Fix odd keyboard open state when viewing media. 2023-07-26 11:05:18 -04:00
Cody Henthorne
a09bc53b99 Show mention picker after only typing @. 2023-07-26 10:53:21 -04:00
Cody Henthorne
3731723472 Improve group name coloring performance. 2023-07-25 19:12:04 -04:00
Cody Henthorne
ded29619cd Add payload support to CFv2. 2023-07-25 16:53:45 -04:00
Cody Henthorne
a5b39a8f17 Fix conversation banner animation bugs.
When in doubt, put it in a FrameLayout.
2023-07-25 16:53:11 -04:00
Cody Henthorne
26866a7b2c Reduce conversation transition animations to 200ms. 2023-07-25 16:36:33 -04:00
Cody Henthorne
7837f3999f Bump version to 6.27.5 2023-07-25 12:12:38 -04:00
Cody Henthorne
b25f658647 Updated baseline profile. 2023-07-25 12:06:36 -04:00
Cody Henthorne
49625619fe Updated language translations. 2023-07-25 12:04:15 -04:00
Cody Henthorne
912299bcfd Fix invalid type crash when attempting to recover keyboard landscape height.
A bug with setter using long means it easier to just use long going
forward.
2023-07-25 12:01:14 -04:00
Clark
5648fd2e91 Fix avatar provider buffer underflow. 2023-07-25 12:01:14 -04:00
Clark
557ef5820e Fix bad notification for start foreground when hanging up. 2023-07-25 12:01:14 -04:00
Cody Henthorne
4ce512d259 Fix crash when setting starting scroll position. 2023-07-25 12:01:14 -04:00
Cody Henthorne
a68319dae4 Bump version to 6.27.4 2023-07-24 19:36:29 -04:00
Cody Henthorne
080ecf51d3 Updated baseline profile. 2023-07-24 19:31:55 -04:00
Cody Henthorne
657109dae1 Updated language translations. 2023-07-24 19:27:13 -04:00
Greyson Parrelli
019ef02be8 Ensure we use SVR2 endpoint for checking RRP. 2023-07-24 19:22:07 -04:00
Nicholas Tinsley
d4774c963d Clear click listener after view is recycled.
Fixes #13070
2023-07-24 16:48:57 -04:00
Clark
34cb4c579c Fix for intermittent date header in CFv2. 2023-07-24 14:50:19 -04:00
Clark
74c261f913 Do not regenerate url preview if url has not changed. 2023-07-24 14:43:01 -04:00
Cody Henthorne
7420123519 Fix input panel moving behind navigation bar. 2023-07-24 14:29:31 -04:00
Cody Henthorne
374910736e Fix crash when data observer called after fragment destroy. 2023-07-24 13:43:35 -04:00
Clark
3a71696a49 Fix CFv2 Voice Note Drafts. 2023-07-24 13:34:58 -04:00
Cody Henthorne
73792905a2 Clear compose input immediately on send to match behavior of v1. 2023-07-24 13:32:16 -04:00
Cody Henthorne
05fc30e6e8 Fix CFv2 initial scrolling bugs. 2023-07-24 12:46:17 -04:00
Nicholas
9c308588b5 Bump version to 6.27.3 2023-07-21 22:30:59 -04:00
Nicholas
2f53200096 Updated language translations. 2023-07-21 22:30:36 -04:00
Cody Henthorne
8c1f2c6064 Fix attachment pointer crash when missing incremental digest. 2023-07-21 19:54:57 -04:00
Cody Henthorne
f5fc2acf50 Prevent attachment send of duplicate data with different transforms from failing. 2023-07-21 19:43:31 -04:00
Alex Hart
306fa24d6b Fix crash when text draft save debouncer fires after fragment is destroyed. 2023-07-21 16:32:28 -03:00
Fynn Godau
f5ee9d4a3b Call all lifecycle methods on snapshot mapView 2023-07-21 15:22:02 -04:00
Alex Hart
e7a5f64fe5 Fix scroll to bottom behavior during fast fling. 2023-07-21 16:10:37 -03:00
Alex Hart
6191e003fc Ensure edit history always starts scrolled to top. 2023-07-21 16:03:41 -03:00
Clark
fad401941e Hide old edit revisions from media preview gallery. 2023-07-21 15:00:27 -04:00
Alex Hart
1e0733bd46 Add log-line to see how often setTypists is called. 2023-07-21 15:43:16 -03:00
Clark
d0a44c3f14 Small screen fixes for ACI safety number screen. 2023-07-21 14:41:07 -04:00
Alex Hart
3cee0c1bd5 Fix possible data race in ThumbnailView after image send. 2023-07-21 13:38:49 -03:00
Clark Chen
f5d403e97d Fix tap to scan not wrapping to next line. 2023-07-21 11:11:43 -04:00
Alex Hart
4f1d021aa8 Fix crash when accessing message edit history in details fragment. 2023-07-21 10:11:27 -03:00
Nicholas
dca8c042ab Bump version to 6.27.2 2023-07-20 17:09:05 -04:00
Nicholas
ae1ccadcc8 Updated language translations. 2023-07-20 17:08:55 -04:00
Clark
9ac12c2532 Update safety number screen to be in line with design. 2023-07-20 16:53:46 -04:00
Clark
18337c97e2 Remove underline from safety number learn more. 2023-07-20 16:50:34 -04:00
Clark
1e652d497e Dont allow editing failed messages. 2023-07-20 16:50:21 -04:00
Cody Henthorne
b53cad2808 Fix various CFv2 scrolling issues. 2023-07-20 16:50:10 -04:00
Alex Hart
4520ff78ff Fix issue with icon pop in attachment keyboard. 2023-07-20 16:44:54 -03:00
Cody Henthorne
b887129cd7 Fix crash when leaving conversation. 2023-07-20 13:52:12 -04:00
Cody Henthorne
ec25831a37 Fixes for CFv2.
- Status bar color being incorrect when entering a screen that changes it and then returning (e.g., Message Details)
- Fix crash in enter sends mode
- Fix warning about non-closed cursor
- Prevent message abandonment (via trim thread) when it's the first in an inactive thread
- Fix payment attachment button flashing on attachment keyboard open if payments disabled
- Fix reactionDelegate crash
- Fix attachment preview (file, mp3, location, etc) not getting cleared on send
2023-07-20 13:50:32 -04:00
Clark
744f74b498 Address UI issues on safety number verification screen. 2023-07-20 13:09:37 -04:00
Clark Chen
52aaf93f37 Fix copy of the safety number fragment. 2023-07-20 12:53:22 -04:00
Cody Henthorne
2d92d4ad87 Fix jumbo emoji having bubbles bugs. 2023-07-19 19:54:12 -04:00
Cody Henthorne
7617cc0a80 Remove Phase 2 in preparation for CFv2. 2023-07-19 19:46:45 -04:00
Cody Henthorne
dc69bcf6f2 Fix view once media send in CFv2. 2023-07-19 19:41:17 -04:00
Nicholas
0775fc7ead Bump version to 6.27.1 2023-07-19 17:48:36 -04:00
Nicholas
034aef483b Updated language translations. 2023-07-19 17:48:06 -04:00
Nicholas Tinsley
ee5b99fed4 Rotate edit message feature flag. 2023-07-19 17:40:41 -04:00
Nicholas
e031da1337 Bump version to 6.27.0 2023-07-19 17:23:29 -04:00
Nicholas
8f1514642c Updated language translations. 2023-07-19 17:22:58 -04:00
Clark
5aa304ea9a Always show verify safety numbers option. 2023-07-19 17:12:19 -04:00
Alex Hart
c5a27b2cc7 Fix remote story deletion syncing. 2023-07-19 17:12:19 -04:00
Clark
0fde404da8 Add you may have messages notification. 2023-07-19 17:12:19 -04:00
Cody Henthorne
5242b9af39 Rotate CFv2 feature flag. 2023-07-19 17:12:19 -04:00
Cody Henthorne
5e2d6fc05f Fix incorrect unread divider behavior when receiving new messages. 2023-07-19 17:12:18 -04:00
Clark Chen
7e08a1f321 Only show one safety number education dialog at a time. 2023-07-19 17:12:18 -04:00
Cody Henthorne
076295eae8 Fix rendering bug when scrolling a chat with a background in CFv2.
"When in doubt, put it in a FrameLayout. - Wayne Gretzky" - MiCHAELSCOTT
2023-07-19 17:12:18 -04:00
Cody Henthorne
c13339ca52 Fix scroll to bottom on send bug in CFv2. 2023-07-19 17:12:18 -04:00
Nicholas
627657e1de Update to the final ExoPlayer release. 2023-07-19 17:12:18 -04:00
Alex Hart
a8349671d0 Add Receive support for the new CallLogEvent proto messages. 2023-07-19 17:12:18 -04:00
Clark
461875b0e4 Add support for displaying both ACI and e164 safety numbers. 2023-07-19 17:12:18 -04:00
Cody Henthorne
00bbb6bc6e Fix spoiler display bug in long message view. 2023-07-19 17:12:18 -04:00
Greyson Parrelli
e1f1181a07 Specifiy SHA256 for docker base image. 2023-07-19 17:12:18 -04:00
Nicholas Tinsley
0e1de39192 Remove Bluetooth mic voice message recording. 2023-07-19 17:12:18 -04:00
Cody Henthorne
05bbeb10da Revert "Attempt to fix crash on call hangup."
This reverts commit 025411c9fb.
2023-07-19 17:12:18 -04:00
Cody Henthorne
7375a9e06b Do not jumbo styled emojis. 2023-07-19 17:12:18 -04:00
Cody Henthorne
4910050891 Fix bubbles jumping around when entering selection mode. 2023-07-19 17:12:18 -04:00
Cody Henthorne
67d4f666ce Add share highwater timestamp protection to CFv2. 2023-07-19 17:12:18 -04:00
Cody Henthorne
e6c9449e3c Fix voice note playback and wave form generation in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart
b8effba497 Fix crash in hasHeader via range check. 2023-07-19 17:12:18 -04:00
Cody Henthorne
8fcdd7cb8a Update attachment keyboard based on payment availability in CFv2. 2023-07-19 17:12:18 -04:00
Alex Hart
f3fb5ccc3b CFV2 handle keyboard images and gifs. 2023-07-19 17:12:18 -04:00
Alex Hart
b8f55f982f Fix toggle in AdvancedPrivacySettingsFragment. 2023-07-19 17:12:18 -04:00
Cody Henthorne
6db59cb896 Prevent menu creation slowing data load performance in CFv2. 2023-07-18 10:19:17 -04:00
Cody Henthorne
3db83c1602 Fix multiple issues in CFv2. 2023-07-18 10:01:48 -04:00
Nicholas Tinsley
6be9225fbd Include incremental digest when sending attachments. 2023-07-18 09:55:02 -04:00
Nicholas Tinsley
653eff403c Prevent overlap of backup icon on small screens.
Fixes #13064.
2023-07-18 09:55:02 -04:00
Alex Hart
ab410ec0cf CFV2 Message Request state adapter update. 2023-07-18 09:55:02 -04:00
Cody Henthorne
7b75a32394 Clean up remaining CFv2 todos. 2023-07-18 09:55:02 -04:00
Cody Henthorne
daf077b3c9 Fix overlapping date and unread decorations. 2023-07-18 09:55:02 -04:00
Alex Hart
f6bbb59400 Fix crash when accessing binding via delayed runnable. 2023-07-18 09:55:02 -04:00
Cody Henthorne
09813d5dbd Fix crash when dismissing mention picker late. 2023-07-18 09:55:02 -04:00
Cody Henthorne
fe509838f4 Add CFv2 feature flag. 2023-07-18 09:55:02 -04:00
Alex Hart
6a443d0074 Fix clipping around incoming V2 conversation items. 2023-07-18 09:55:02 -04:00
Greyson Parrelli
8fc1065dd6 Rename some protos. 2023-07-18 09:55:02 -04:00
Cody Henthorne
1af50ba0f5 Perform safety number check pre-send in CFv2. 2023-07-18 09:55:02 -04:00
Alex Hart
82b3036b77 Add handling for text slide deck in sendMessage. 2023-07-18 09:55:02 -04:00
Alex Hart
676412019c CFV2 Add search bottom bar to bottom panel barrier ids. 2023-07-18 09:55:02 -04:00
Alex Hart
980f4e00e2 Scroll date header in CFV2. 2023-07-18 09:55:01 -04:00
Cody Henthorne
5731bf023a Add unread divider decoration to CFv2. 2023-07-18 09:55:01 -04:00
Alex Hart
2511ca17aa Add onRequestPermissionsResult to CFV2. 2023-07-18 09:55:01 -04:00
Nicholas Tinsley
fae653540b Make Safety Number Changed dialog scrollable.
This helps with smaller screens.
2023-07-18 09:55:01 -04:00
Alex Hart
b0ca66cc1a Add new active column to ThreadTable. 2023-07-18 09:55:01 -04:00
Nicholas
a65e9c76bc Bump version to 6.26.3 2023-07-17 23:31:59 -04:00
Nicholas
adbac4c557 Updated language translations. 2023-07-17 23:31:48 -04:00
Nicholas
fc7b024e96 Fix benchmark test user generation. 2023-07-17 22:06:00 -04:00
Clark
acee65ba25 Defer TooltipPopup show till anchor has been laid out. 2023-07-17 16:40:08 -04:00
Clark Chen
244902ecfc Bump version to 6.26.2 2023-07-14 18:15:18 -04:00
Clark Chen
4b1a678af2 Updated language translations. 2023-07-14 17:57:49 -04:00
Greyson Parrelli
59dd72b5c0 Fix issue with syncing remote deletes in note to self. 2023-07-14 17:46:40 -04:00
Greyson Parrelli
44c393f11a Fix possible ISE in registration. 2023-07-14 17:46:40 -04:00
Nicholas Tinsley
fddfbd8d2d Fix changing number flow in scenarios where service requires additional verification.
Fixes #12985, #13059.
2023-07-14 17:46:40 -04:00
Clark
e7e00bd428 Disable sticker suggestions when editing message. 2023-07-14 17:46:40 -04:00
Cody Henthorne
07702e69ad Improve spoiler render performance. 2023-07-14 14:41:36 -04:00
Cody Henthorne
e5c3757629 Remove Phase 1 in preparation for CFv2. 2023-07-14 13:51:30 -04:00
Greyson Parrelli
7031bbae43 Close the SVR2 socket when we're done. 2023-07-14 13:11:08 -04:00
Nicholas Tinsley
4a6dfed676 Even more Change Number logging. 2023-07-14 09:56:49 -04:00
Clark Chen
bb0b414d71 Bump version to 6.26.1 2023-07-13 18:11:30 -04:00
Nicholas Tinsley
95e25652c1 Add change number screen logging. 2023-07-13 17:59:53 -04:00
Nicholas Tinsley
58155b0859 Restore previous Registration session handler.
Fixes #12839, #13059.
2023-07-13 15:20:15 -04:00
Clark
f579b79d2e Change websocket keepalive response time to 20s. 2023-07-13 14:22:10 -04:00
Greyson Parrelli
47673be4e0 Bump version to 6.26.0 2023-07-12 16:09:59 -04:00
Clark Chen
f67e6a9e9f Updated language translations. 2023-07-12 15:56:38 -04:00
Greyson Parrelli
110d8259fa Explicity declare exported status in manifest entries.
This shouldn't change any behavior. We're just explicitly declaring the
exported field to be what it would otherwise get set to by default.
2023-07-12 15:48:52 -04:00
Alex Hart
8f253ffc43 Add lazy thread creation throughout in preparation for CFV2. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
6ca9cb6da1 Add migration to cleanup some inconsistent DB state. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
1b63bdec12 Control CDS compat mode with it's own remote config. 2023-07-12 15:48:52 -04:00
Greyson Parrelli
bb52172516 Fix validation of NullMessage types. 2023-07-12 15:48:52 -04:00
Alex Hart
1640495f34 Fix CFV2 selection. 2023-07-12 15:48:52 -04:00
Cody Henthorne
64415a980f Fix text cutoff in device transfer lock dialog.
Fixes #13040
2023-07-12 15:48:52 -04:00
Cody Henthorne
e06d141823 Tweak SpoilerPaint settings to reduce wave effect.
Closes #13041
2023-07-12 15:48:52 -04:00
Clark
ac4d8679a1 Add local metrics for message processing. 2023-07-12 15:48:52 -04:00
Clark
8fc03a67b9 Fix failing AttachmentCipherTest for incremental mac. 2023-07-12 15:48:52 -04:00
Cody Henthorne
648506fe04 Fix empty emoji search index. 2023-07-12 15:48:52 -04:00
Cody Henthorne
2c74ac8bfa Fix spoilers not working in story replies. 2023-07-12 15:48:52 -04:00
Cody Henthorne
963709c552 Fix draft message lost during media send flow. 2023-07-12 15:48:52 -04:00
Clark
9af888a595 Refactor FcmFetchManager to make foreground service clearer. 2023-07-12 15:48:51 -04:00
Cody Henthorne
ec373b5b4d Fix attachment dedupe race where original may be deleted before new usage is recorded. 2023-07-12 15:48:51 -04:00
Cody Henthorne
bfcd57881e Add test to verify sync behavior during VERIFIED to DEFAULT change. 2023-07-12 15:48:51 -04:00
Alex Hart
b277a8c5e0 CFV2 fix possible race when initializing menu. 2023-07-12 15:48:51 -04:00
Cody Henthorne
979a50716e Fix most android tests.
Co-authored-by: Greyson Parrelli <greyson@signal.org>
2023-07-12 15:48:51 -04:00
Jim Gustafson
c5f0da8151 Update to RingRTC v2.29.0 2023-07-12 15:48:51 -04:00
Cody Henthorne
aee0b5268f Improve conversation open benchmark test. 2023-07-12 15:48:51 -04:00
Alex Hart
7e909f2bee Add InternalSettings option for ConversationItem V2. 2023-07-12 15:48:51 -04:00
Cody Henthorne
584c90521a Polish voice notes in CFv2. 2023-07-12 15:48:51 -04:00
Yuval Razieli
23ef8c78bd Fix an issue where the charset in the link preview of some pages was not identified correctly. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
5ca025544e Improve logging around memory usage. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
500ae0c72e Add Spinner support for kyber keys. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
c359207f1f Fix potential NPE in MediaOverviewPageFragment. 2023-07-12 15:48:51 -04:00
Alex Hart
159f2ebec0 Don't crash on invalid window token in tooltip popup. 2023-07-12 15:48:51 -04:00
Alex Hart
a0db812606 Fix action bar background for multiselect in CFV2. 2023-07-12 15:48:51 -04:00
Alex Hart
d4c6a433d7 Add documentation to DeliveryStatusView. 2023-07-12 15:48:51 -04:00
Alex Hart
3a7cde9239 Fix pending rotation pivot point. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
4b6c308ae9 Stop HEAD requests for possibly-unlisted contacts during CDS refresh. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
002279f6a7 Ignore AccountRecord.e164 if PNP-capable. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
c807e52ad9 Support CDSI ignore the useCompat flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
9557e3b910 Remove unused feature flag constant. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
ddc77884bd Inline the credit card payments feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
9d6337d5a8 Inline the chat filters feature flag. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
117dd17215 Rotate edit message feature flag. 2023-07-12 15:48:51 -04:00
Alex Hart
f9eed0f6d0 Fix slide in animation for new messages in CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
4429145cdf Remove deprecated ignoreResults parameter. 2023-07-12 15:48:51 -04:00
Alex Hart
5ea4cbf9ca CFV2 Add proper body presentation code. 2023-07-12 15:48:51 -04:00
Ehren Kret
c6473ca9e6 Minor improvement to Android Backup file format. 2023-07-12 15:48:51 -04:00
Alex Hart
38b2a2f5b7 Add multi-select support to CFV2. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
ebaa445bee Save last-known server time offset. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
8372c699f7 Update username link QR code styling. 2023-07-12 15:48:51 -04:00
Greyson Parrelli
e1570e9512 Start mirroring to SVR2. 2023-07-12 15:48:51 -04:00
Alex Hart
dfb7304626 Fix external shares in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
919531a82b Enable quick toggle camera in CFV2 input panel. 2023-07-11 17:58:09 -04:00
Alex Hart
81b2e9ccd2 Hook up reaction callback. 2023-07-11 17:58:09 -04:00
Alex Hart
4ef2aba4e2 Ensure text-only entries are cached. 2023-07-11 17:58:09 -04:00
Alex Hart
4590655dc5 Fix CI V2 layout bounds when item has a reaction. 2023-07-11 17:58:09 -04:00
Alex Hart
3040b70100 Add initial instrumentation testing for V2 ConversationItem shapes. 2023-07-11 17:58:09 -04:00
Alex Hart
47b97aafc6 Add TypingIndicatorDecoration to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
27e7383db6 Add compose divider to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
42fe827cb3 Add proper navigation bottom bar color. 2023-07-11 17:58:09 -04:00
Alex Hart
3fa3e8357c Handle video calls for 1:1 conversations in CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
6260607e1b Launch settings on toolbar press in CFV2. 2023-07-11 17:58:09 -04:00
Rashad Sookram
3c1666e874 Update verification metadata for aapt2. 2023-07-11 17:58:09 -04:00
Clark
f4a082584c Add upload/download size restrictions for attachments based on remote config. 2023-07-11 17:58:09 -04:00
Ehren Kret
87d4dba32b remove whispersystems.org reference 2023-07-11 17:58:09 -04:00
Alex Hart
329f68d167 Upgrade to Gradle 8.0.2 and AGP 8.0.2 2023-07-11 17:58:09 -04:00
Alex Hart
2053cf085a Add retries to CallEventSyncJob. 2023-07-11 17:58:09 -04:00
Iñaqui
16d48984c5 Remove calling stun fallback. 2023-07-11 17:58:09 -04:00
Cody Henthorne
a17800283a Plumb schedule message and edit message send flows for CFv2. 2023-07-11 17:58:09 -04:00
Alex Hart
9b1917cbdc Add story ring to CFV2. 2023-07-11 17:58:09 -04:00
Alex Hart
e1e3d7a85b Small tweaks for footer positioning. 2023-07-11 17:58:09 -04:00
Alex Hart
dc37d1f029 Apply proper background to input panel when wallpaper is enabled. 2023-07-11 17:58:09 -04:00
Alex Hart
53e62f2be0 Add new text-only conversation item. 2023-07-11 17:58:09 -04:00
Alex Hart
e6cc789c6f Add conversation test springboard fragment. 2023-07-11 17:58:09 -04:00
Cody Henthorne
cfaef77b21 Properly plumb attachment keyboard in CFv2. 2023-07-11 17:58:09 -04:00
Clark Chen
36fc9aa82a Add 10s timeout to user facing CDSI requests. 2023-07-11 17:58:09 -04:00
Alex Hart
8d20669e46 Rewrite AlertView to be a single ImageView. 2023-07-11 17:58:09 -04:00
Greyson Parrelli
9d8501cd64 Bump version to 6.25.5 2023-07-11 14:21:30 -04:00
Greyson Parrelli
88827e94f5 Updated language translations. 2023-07-11 14:21:30 -04:00
Alex Hart
99d2a0c0b6 Wrap dialog content in a scroll view. 2023-07-11 14:21:30 -04:00
Alex Hart
1d0582867b Fix crashing when deleting a custom story. 2023-07-11 14:21:30 -04:00
Alex Hart
0ea6d9205d Fix rotation metrics for DeliveryStatusView. 2023-07-11 14:21:30 -04:00
Greyson Parrelli
f438ef543b Fix prekey generation during registration. 2023-07-10 23:05:36 -04:00
Alex Hart
61cd9767c8 Ensure bitmaps are not recycled when getting them from the cache. 2023-07-10 17:08:27 -03:00
Alex Hart
5fbf0a98b9 Bump version to 6.25.4 2023-07-07 15:08:04 -03:00
Alex Hart
1671518ded Updated language translations. 2023-07-07 15:04:13 -03:00
Alex Hart
f628ffca06 Fix avatar blurring during calls. 2023-07-07 14:53:23 -03:00
Alex Hart
1d6f4fd4e7 Bump version to 6.25.3 2023-07-06 16:27:55 -03:00
Alex Hart
9eedc0a36b Updated language translations. 2023-07-06 16:19:42 -03:00
Alex Hart
a870fe9e1a Do not throw an ISE when we cannot start a foreground service from calling. 2023-07-06 16:12:09 -03:00
Alex Hart
d3f779cea9 Fix crash when toggling stories. 2023-07-05 12:16:46 -03:00
Cody Henthorne
fb4f41b996 Extend style to inserted text via paste or auto-correct regardless of content. 2023-06-30 14:05:35 -04:00
Cody Henthorne
11aac76fb6 Fix spoiler rendering in quote views. 2023-06-30 13:46:17 -04:00
Nicholas
98424f6cbb Bump version to 6.25.2 2023-06-30 13:36:03 -04:00
Nicholas
1cf7c59af9 Updated language translations. 2023-06-30 13:33:51 -04:00
Clark
13470fb0c3 Increase FCM push websocket timeout. 2023-06-30 12:41:00 -04:00
Nicholas Tinsley
3aa0fd1937 Reset continue button state when dismissing registration number confirmation dialog. 2023-06-30 12:23:47 -04:00
Clark
9e6f2336d1 Add push websocket fetch stats. 2023-06-30 11:07:05 -04:00
Nicholas Tinsley
8b8d62f598 Only close AttachmentCipher streams if using incremental MAC. 2023-06-30 11:06:51 -04:00
Nicholas
4572ae5886 Bump version to 6.25.1 2023-06-29 18:42:29 -04:00
Nicholas
0802d4beb4 Updated language translations. 2023-06-29 18:40:18 -04:00
Nicholas
9361aa700a Transcode video files in a streamable format. 2023-06-29 18:30:29 -04:00
Greyson Parrelli
be5cad1cec Add support for Emoji v15.0 2023-06-29 15:55:49 -04:00
Greyson Parrelli
fe20de2995 Improve logging around edit and sync messages. 2023-06-29 15:55:12 -04:00
Clark
8714e4298e Specify scale type for glide thumbnails. 2023-06-29 15:32:04 -04:00
Alex Hart
1baebe7475 Remove read log line from AvatarProvider. 2023-06-29 13:17:05 -03:00
Nicholas
6686ae43f3 Bump version to 6.25.0 2023-06-28 17:24:23 -04:00
Nicholas
cc2c0e9561 Updated language translations. 2023-06-28 17:21:07 -04:00
Nicholas
34d252a4bd Add incremental digests to attachment sending. 2023-06-28 17:13:15 -04:00
Cody Henthorne
025411c9fb Attempt to fix crash on call hangup. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
4edb66d2b9 Fix SimpleProgressDialog. 2023-06-28 17:13:15 -04:00
Alex Hart
a17033dff4 Add ContentProvider for user avatars. 2023-06-28 17:13:15 -04:00
Cody Henthorne
04a5e56da7 Add mentions support to CFv2. 2023-06-28 17:13:15 -04:00
Bernie Dolan
0e6a3dd408 Fix MobileCoin test net config. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
47f48a6a8c Put backup enable dialog into ScrollView for smaller screens.
Addresses #13033.
2023-06-28 17:13:15 -04:00
Cody Henthorne
2ef7fabade Add inline emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart
5c2b475c01 Add randomized testing for ConversationItem. 2023-06-28 17:13:15 -04:00
Clark
559f4bc0d3 Rotate edit message feature flag. 2023-06-28 17:13:15 -04:00
Clark
1357a4816b Update edit history dialog to new style. 2023-06-28 17:13:15 -04:00
Nicholas Tinsley
d1b8a56c0f Revert "Add more logging around Bubble eligibility."
This purely logs the state without affecting the return logic.
2023-06-28 17:13:15 -04:00
Cody Henthorne
7ea38298ea Fix gif playback in CFv2. 2023-06-28 17:13:15 -04:00
Clark
c08f1355db Refresh isConnectionNecessary on network block changes. 2023-06-28 17:13:15 -04:00
Cody Henthorne
b6589637fa Add emoji search to CFv2. 2023-06-28 17:13:15 -04:00
Alex Hart
2ee2d2883a Disallow reacting to pending or failed messages. 2023-06-28 17:13:15 -04:00
Alex Hart
029c8ba917 Fix text story keyboard in text stories. 2023-06-28 17:13:15 -04:00
Alex Hart
60e1ee21ed Prevent ANR from large call logs. 2023-06-28 17:13:15 -04:00
Alex Hart
a96e9158c4 Change call notification flag to UPDATE_CURRENT and change request code. 2023-06-28 17:13:15 -04:00
Alex Hart
e47765d7d5 Add logging to WebRtcActivity Intent. 2023-06-28 17:13:15 -04:00
Clark Chen
e807435c8b Capitalize edited string. 2023-06-28 17:13:15 -04:00
Sgn-32
14b41a93e2 Fix Do not animate spoilers if system animations are disabled.
Closes #13016
2023-06-28 17:13:15 -04:00
Yuichi Araki
f51fb9da29 Report shortcut usage when a message is sent to a group.
Fixes #13029
Close #13030
2023-06-28 17:13:15 -04:00
Sebastian Scheibner
df3ca3d3cc Fix duplicate kyber pre key id in registration
The `PreKeyUtil.generateKyberPreKey` method doesn't update the `nextKyberPreKeyId` in the metadataStore,
so the two `metadataStore.getNextKyberPreKeyId()` calls in this method return the same id.
The first oneTimeKyberPreKey will have the same id as the lastResortKyberPreKey and overwrite it in the database.

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

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

View File

@@ -4,7 +4,6 @@ plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.protobuf'
id 'com.google.android.gms.oss-licenses-plugin'
id 'androidx.navigation.safeargs'
id 'org.jlleitschuh.gradle.ktlint'
id 'org.jetbrains.kotlin.android'
@@ -43,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 = 1268
def canonicalVersionName = "6.22.1"
def canonicalVersionCode = 1303
def canonicalVersionName = "6.27.9"
def postFixSize = 100
def abiPostFix = ['universal' : 0,
@@ -160,7 +158,7 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = '1.3.2'
kotlinCompilerExtensionVersion = '1.4.4'
}
defaultConfig {
@@ -186,7 +184,7 @@ android {
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.signal.org\""
buildConfigField "String", "SIGNAL_SFU_URL", "\"https://sfu.voip.signal.org\""
buildConfigField "String", "SIGNAL_STAGING_SFU_URL", "\"https://sfu.staging.voip.signal.org\""
buildConfigField "String[]", "SIGNAL_SFU_INTERNAL_NAMES", "new String[]{\"Test\", \"Staging\", \"Development\"}"
@@ -200,9 +198,11 @@ 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", "\"dc9fd472a5a9c871a3c7f76f1af60aa9c1f314abf2e8d1e0c4ba25c8aaa2848c\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"e18376436159cda3ad7a45d9320e382e4a497f26b0dca34d8eab0bd0139483b5\", " +
"\"3a485adb56e2058ef7737764c738c4069dd62bc457637eafb6bbce1ce29ddb89\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
@@ -229,8 +229,8 @@ android {
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resourceConfigurations += []
resConfigs autoResConfig()
splits {
abi {
@@ -379,6 +379,8 @@ android {
buildConfigField "String", "SIGNAL_CDN2_URL", "\"https://cdn2-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\""
buildConfigField "String", "SVR2_MRENCLAVE", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\""
buildConfigField "org.thoughtcrime.securesms.KbsEnclave", "KBS_ENCLAVE", "new org.thoughtcrime.securesms.KbsEnclave(\"39963b736823d5780be96ab174869a9499d56d66497aa8f9b2244f777ebc366b\", " +
"\"9dbc6855c198e04f21b5cc35df839fdcd51b53658454dfa3f817afefaffc95ef\", " +
"\"45627094b2ea4a66f4cf0b182858a8dcf4b8479122c3820fe7fd0551a6d4cf5c\")"
@@ -446,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 {
@@ -469,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
@@ -530,13 +541,11 @@ dependencies {
implementation libs.leolin.shortcutbadger
implementation libs.emilsjolander.stickylistheaders
implementation libs.jpardogo.materialtabstrip
implementation libs.apache.httpclient.android
implementation libs.glide.glide
implementation libs.roundedimageview
implementation libs.materialish.progress
implementation libs.greenrobot.eventbus
implementation libs.waitingdots
implementation libs.google.zxing.android.integration
implementation libs.google.zxing.core
implementation libs.google.flexbox
@@ -579,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,12 +6,16 @@ import androidx.test.filters.FlakyTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.UriAttachment
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.providers.BlobProvider
import org.thoughtcrime.securesms.testing.assertIs
import org.thoughtcrime.securesms.testing.assertIsNot
import org.thoughtcrime.securesms.util.MediaUtil
import java.util.Optional
@@ -64,6 +68,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()
@@ -90,6 +95,45 @@ class AttachmentTableTest {
assertNotEquals(attachment1Info, attachment2Info)
}
/**
* Given: A previous attachment and two pre-upload attachments with the same data but different transform properties (standard and high).
*
* When changing content of standard pre-upload attachment to match pre-existing attachment
*
* Then update standard pre-upload attachment to match previous attachment, do not update high pre-upload attachment, and do
* not delete shared pre-upload uri from disk as it is still being used by the high pre-upload attachment.
*/
@Test
fun doNotDeleteDedupedFileIfUsedByAnotherAttachmentWithADifferentTransformProperties() {
// GIVEN
val uncompressData = byteArrayOf(1, 2, 3, 4, 5)
val compressedData = byteArrayOf(1, 2, 3)
val blobUncompressed = BlobProvider.getInstance().forData(uncompressData).createForSingleSessionInMemory()
val previousAttachment = createAttachment(1, BlobProvider.getInstance().forData(compressedData).createForSingleSessionInMemory(), AttachmentTable.TransformProperties.empty())
val previousDatabaseAttachmentId: AttachmentId = SignalDatabase.attachments.insertAttachmentsForMessage(1, listOf(previousAttachment), emptyList()).values.first()
val standardQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.empty())
val standardDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(standardQualityPreUpload)
val highQualityPreUpload = createAttachment(1, blobUncompressed, AttachmentTable.TransformProperties.forSentMediaQuality(Optional.empty(), SentMediaQuality.HIGH))
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
// WHEN
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
// THEN
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA)!!
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA)!!
assertNotEquals(standardInfo, highInfo)
standardInfo.file assertIs previousInfo.file
highInfo.file assertIsNot standardInfo.file
highInfo.file.exists() assertIs true
}
private fun createAttachment(id: Long, uri: Uri, transformProperties: AttachmentTable.TransformProperties): UriAttachment {
return UriAttachmentBuilder.build(
id,

View File

@@ -0,0 +1,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

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

View File

@@ -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,76 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package androidx.recyclerview.widget
import android.content.Context
import org.signal.core.util.logging.Log
/**
* Variation of a vertical, reversed [LinearLayoutManager] that makes specific assumptions in how it will
* be used by Conversation view to support easier scrolling to the initial start position.
*
* Primarily, it assumes that an initial scroll to position call will always happen and that the implementation
* of [LinearLayoutManager] remains unchanged with respect to how it assigns [mPendingScrollPosition] and
* [mPendingScrollPositionOffset] in [LinearLayoutManager.scrollToPositionWithOffset] and how it always clears
* the pending state variables in every call to [LinearLayoutManager.onLayoutCompleted].
*
* The assumptions are necessary to force the requested scroll position/layout to occur even if the request
* happens prior to the data source populating the recycler view/adapter.
*/
class ConversationLayoutManager(context: Context) : LinearLayoutManager(context, RecyclerView.VERTICAL, true) {
private var afterScroll: (() -> Unit)? = null
override fun supportsPredictiveItemAnimations(): Boolean {
return false
}
/**
* Scroll to the desired position and be notified when the layout manager has completed the request
* via [afterScroll] callback.
*/
fun scrollToPositionWithOffset(position: Int, offset: Int, afterScroll: () -> Unit) {
this.afterScroll = afterScroll
super.scrollToPositionWithOffset(position, offset)
}
/**
* If a scroll to position request is made and a layout pass occurs prior to the list being populated with via the data source,
* the base implementation clears the request as if it was never made.
*
* This override will capture the pending scroll position and offset, determine if the scroll request was satisfied, and
* re-request the scroll to position to force another attempt if not satisfied.
*
* A pending scroll request will be re-requested if the pending scroll position is outside the bounds of the current known size of
* items in the list.
*/
override fun onLayoutCompleted(state: RecyclerView.State?) {
val pendingScrollPosition = mPendingScrollPosition
val pendingScrollOffset = mPendingScrollPositionOffset
val reRequestPendingPosition = pendingScrollPosition >= (state?.mItemCount ?: 0)
// Base implementation always clears mPendingScrollPosition+mPendingScrollPositionOffset
super.onLayoutCompleted(state)
// Re-request scroll to position request if necessary thus forcing mPendingScrollPosition+mPendingScrollPositionOffset to be re-assigned
if (reRequestPendingPosition) {
Log.d(TAG, "Re-requesting pending scroll position: $pendingScrollPosition offset: $pendingScrollOffset")
if (pendingScrollOffset != INVALID_OFFSET) {
scrollToPositionWithOffset(pendingScrollPosition, pendingScrollOffset)
} else {
scrollToPosition(pendingScrollPosition)
}
} else {
afterScroll?.invoke()
afterScroll = null
}
}
companion object {
private val TAG = Log.tag(ConversationLayoutManager::class.java)
}
}

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

@@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.Observer;
import org.signal.ringrtc.CallLinkRootKey;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationItem;
@@ -116,5 +117,6 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args);
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
}
}

View File

@@ -6,6 +6,8 @@ import android.view.Window;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@@ -20,7 +22,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
@Override
protected void onCreate(Bundle bundle, boolean ready) {
AlertDialog dialog = new AlertDialog.Builder(this)
AlertDialog dialog = new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
.setPositiveButton(R.string.DeviceProvisioningActivity_continue, (dialog1, which) -> {

View File

@@ -5,6 +5,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
@@ -26,7 +28,7 @@ public final class GroupMembersDialog {
}
public void display() {
AlertDialog dialog = new AlertDialog.Builder(fragmentActivity)
AlertDialog dialog = new MaterialAlertDialogBuilder(fragmentActivity)
.setTitle(R.string.ConversationActivity_group_members)
.setIcon(R.drawable.ic_group_24)
.setCancelable(true)

View File

@@ -21,6 +21,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.components.ContactFilterView;
import org.thoughtcrime.securesms.components.ContactFilterView.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode;
@@ -217,7 +219,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new AlertDialog.Builder(InviteActivity.this)
new MaterialAlertDialogBuilder(InviteActivity.this)
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()))

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

@@ -28,6 +28,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class PlayServicesProblemFragment extends DialogFragment {
@@ -37,7 +38,7 @@ public class PlayServicesProblemFragment extends DialogFragment {
Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(getActivity(), code, 9111);
if (dialog == null) {
return new AlertDialog.Builder(requireActivity())
return new MaterialAlertDialogBuilder(requireActivity())
.setNegativeButton(android.R.string.ok, null)
.setMessage(R.string.PlayServicesProblemFragment_the_version_of_google_play_services_you_have_installed_is_not_functioning)
.create();

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();
}
};
@@ -89,7 +89,7 @@ public class AudioRecorder {
}
recorder.start(fds[1]);
this.recordingSubject = recordingSingle;
} catch (IOException e) {
} catch (IOException | RuntimeException e) {
recordingSingle.onError(e);
recorder = null;
Log.w(TAG, e);

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}.")
}
}
}
@@ -81,46 +91,31 @@ private class BluetoothVoiceNoteUtilLegacy(val context: Context, val listener: (
private var hasWarnedAboutBluetooth = false
init {
if (Build.VERSION.SDK_INT < 31) {
audioHandler.post {
signalBluetoothManager.start()
Log.d(TAG, "Bluetooth manager started.")
}
audioHandler.post {
signalBluetoothManager.start()
Log.d(TAG, "Bluetooth manager started.")
}
}
override fun connectBluetoothScoConnection() {
if (Build.VERSION.SDK_INT >= 31) {
val audioManager = ApplicationDependencies.getAndroidCallAudioManager()
val device: AudioDeviceInfo? = audioManager.connectedBluetoothDevice
if (device != null) {
val result: Boolean = audioManager.setCommunicationDevice(device)
if (result) {
Log.d(TAG, "Successfully set Bluetooth device as active communication device.")
} else {
Log.d(TAG, "Found Bluetooth device but failed to set it as active communication device.")
}
} else {
Log.d(TAG, "Could not find Bluetooth device in list of communications devices, falling back to current input.")
audioHandler.post {
if (signalBluetoothManager.state.shouldUpdate()) {
Log.d(TAG, "Bluetooth manager updating devices.")
signalBluetoothManager.updateDevice()
}
listener()
} else {
audioHandler.post {
if (signalBluetoothManager.state.shouldUpdate()) {
signalBluetoothManager.updateDevice()
}
val currentState = signalBluetoothManager.state
if (currentState == SignalBluetoothManager.State.AVAILABLE) {
signalBluetoothManager.startScoAudio()
} else {
Log.d(TAG, "Recording from phone mic because bluetooth state was " + currentState + ", not " + SignalBluetoothManager.State.AVAILABLE)
uiThreadHandler.post {
if (currentState == SignalBluetoothManager.State.PERMISSION_DENIED && !hasWarnedAboutBluetooth) {
bluetoothPermissionDeniedHandler()
hasWarnedAboutBluetooth = true
}
listener()
val currentState = signalBluetoothManager.state
if (currentState == SignalBluetoothManager.State.AVAILABLE) {
Log.d(TAG, "Bluetooth manager state is AVAILABLE. Starting SCO connection.")
signalBluetoothManager.startScoAudio()
} else {
Log.d(TAG, "Recording from phone mic because bluetooth state was " + currentState + ", not " + SignalBluetoothManager.State.AVAILABLE)
uiThreadHandler.post {
if (currentState == SignalBluetoothManager.State.PERMISSION_DENIED && !hasWarnedAboutBluetooth) {
Log.d(TAG, "Warning about Bluetooth permissions.")
bluetoothPermissionDeniedHandler()
hasWarnedAboutBluetooth = true
}
listener(false)
}
}
}

View File

@@ -140,7 +140,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
viewModel.onAvatarEditCompleted(vector)
}
setFragmentResultListener(PhotoEditorFragment.REQUEST_KEY_EDIT) { _, bundle ->
setFragmentResultListener(PhotoEditorFragment.REQUEST_KEY_EDIT) { _, _ ->
}
photoEditorLauncher = registerForActivityResult(PhotoEditorActivity.Contract()) { photo ->
@@ -155,6 +155,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
ViewUtil.hideKeyboard(requireContext(), requireView())
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_SELECT_IMAGE && resultCode == Activity.RESULT_OK && data != null) {
val media: Media = requireNotNull(data.getParcelableExtraCompat(AvatarSelectionActivity.EXTRA_MEDIA, Media::class.java))
@@ -194,7 +195,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
return true
}
fun openEditor(avatar: Avatar) {
private fun openEditor(avatar: Avatar) {
when (avatar) {
is Avatar.Photo -> openPhotoEditor(avatar)
is Avatar.Resource -> throw UnsupportedOperationException()
@@ -250,6 +251,7 @@ class AvatarPickerFragment : Fragment(R.layout.avatar_picker_fragment) {
.execute()
}
@Deprecated("Deprecated in Java")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
}

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

@@ -45,7 +45,7 @@ public class BackupDialog {
@NonNull Runnable onBackupsEnabled)
{
String[] password = BackupUtil.generateBackupPassphrase();
AlertDialog dialog = new AlertDialog.Builder(context)
AlertDialog dialog = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.BackupDialog_enable_local_backups)
.setView(backupDirectorySelectionIntent != null ? R.layout.backup_enable_dialog_v29 : R.layout.backup_enable_dialog)
.setPositiveButton(R.string.BackupDialog_enable_backups, null)

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

@@ -42,12 +42,20 @@ object BackupVerifier {
frame = inputStream.readFrame()
}
if (frame.end == true) {
count++
}
}
if (cancellationSignal.isCanceled) {
throw FullBackupExporter.BackupCanceledException()
}
if (count != expectedCount) {
Log.e(TAG, "Incorrect number of frames expected $expectedCount but only $count")
return false
}
return true
}

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, ++count, estimatedCountOutside);
return new BackupEvent(BackupEvent.Type.FINISHED, outputStream.getFrames(), estimatedCountOutside);
}
private static long calculateCount(@NonNull Context context, @NonNull SQLiteDatabase input, List<String> tables) {
@@ -627,187 +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 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);
} 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;
}

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