Compare commits

...

926 Commits

Author SHA1 Message Date
Alan Evans
9c8a99c79c Bump version to 4.53.2 2020-01-07 15:02:43 -05:00
Greyson Parrelli
c6b9855198 Always show view-once video remaining time. 2020-01-07 15:02:43 -05:00
Alan Evans
c142928fad Updated language translations. 2020-01-07 13:45:45 -05:00
Greyson Parrelli
fc0cfd5188 Disable multiselect actions for inapplicable message types. 2020-01-07 13:09:25 -05:00
Greyson Parrelli
d9c78e5c3e Fix toolbar overlap with cutouts in recipient preferences.
Fixes #9323
2020-01-07 12:17:41 -05:00
Greyson Parrelli
b449fceca0 Dismiss conversation search after swipe-to-reply.
Fixes #9325
2020-01-07 11:13:16 -05:00
Greyson Parrelli
f0d15c0bce Fix crash when clicking group avatars. 2020-01-07 10:51:40 -05:00
Greyson Parrelli
8f031f61ea Fix group update string when re-added to group. 2020-01-06 18:36:56 -05:00
Alan Evans
502e8559f0 Bump version to 4.53.1 2020-01-06 16:53:31 -05:00
Alan Evans
8cf6f7e936 Relocate jni libs. 2020-01-06 16:52:41 -05:00
Alan Evans
0ef01cc620 Bump version to 4.53.0 2020-01-06 11:08:30 -05:00
Alan Evans
930828ef86 Updated language translations. 2020-01-06 11:08:30 -05:00
Alan Evans
9ebe920195 Move all files to natural position. 2020-01-06 11:08:30 -05:00
Greyson Parrelli
0df36047e7 Ensure transport type gets reset in onNewIntent()
Fixes #9319
2020-01-06 11:08:27 -05:00
Greyson Parrelli
94604921f9 Fix issue with swipe-to-reply triggering incorrectly.
Fixes #9227
2020-01-06 11:08:27 -05:00
Curt Brune
284fe294ac Skip monochrome cameras when switching cameras on video calls. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
ffa01d491a Do not list yourself in group membership updates. 2020-01-06 11:08:27 -05:00
Kevin T. Berstene
1d9513e743 Respect local camera aspect ratio in calls.
Fixes #8615
2020-01-06 11:08:27 -05:00
Greyson Parrelli
277c9e22f1 Show long-press-magnify in sticker preview screen. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
4e7b4da941 Implement resumable downloads. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
7e72c9c33b Add view-once tooltip. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
e0e2c3a3f5 Fix some UI bugs in view-once sending. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
3b5e444e76 Enable view-once send support. 2020-01-06 11:08:27 -05:00
Alan Evans
02006e3ff5 Update copyright year. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
8083596f19 Use FitCenter sizing for stickers. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
6551689a0c Increase the size of stickers in the conversation. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
3fbf21a34e Don't crash on packs missing metadata. 2020-01-06 11:08:27 -05:00
Alan Evans
b598431237 Separate message decryption from message processing. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
3b5d9a2cae Consider groups 'unknown' if they have no title, avatar, or members. 2020-01-06 11:08:27 -05:00
Alex Hart
3bd8aa8a86 Apply MessageStyle and fix chronology issues. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
fe5fca8eaf Sync thread order and archive status with linked devices. 2020-01-06 11:08:27 -05:00
Greyson Parrelli
848101a783 Refactor AvatarImageView#setAvatarClickHandler.
This triggered a call to Recipient#isGroup when rendering a view, which
itself could trigger a DB read. This delays the call to #isGroup to
lower the possibility of doing DB reads on the main thread.
2019-12-19 18:06:29 -05:00
Alex Hart
d7c350f987 Fix in-call proximity lock for wireless, speaker, and bluetooth. 2019-12-19 18:06:29 -05:00
Curt Brune
4c526f0b3c Update ringrtc to 0.3.0 2019-12-19 18:06:29 -05:00
Greyson Parrelli
876ffb5b13 Bump version to 4.52.4 2019-12-19 18:00:56 -05:00
Greyson Parrelli
0b14cf3d6a Show sticker install tooltips less often. 2019-12-19 17:59:42 -05:00
Greyson Parrelli
c2044b36b1 Bump version to 4.52.3 2019-12-18 20:58:07 -05:00
Greyson Parrelli
f970b4acfa Revert "Remove the Pixel 4 from the CameraX blacklist."
This reverts commit 1f5a597d50.
2019-12-18 20:55:03 -05:00
Greyson Parrelli
e69c0af613 Bump version to 4.52.2 2019-12-18 19:56:16 -05:00
Greyson Parrelli
42e2dd8b30 Updated language translations. 2019-12-18 18:04:42 -05:00
Greyson Parrelli
4fd6e7b033 Fix issue with stickers flickering on send. 2019-12-18 16:45:07 -05:00
Greyson Parrelli
3580816eac Bump version to 4.52.1 2019-12-18 00:50:02 -05:00
Greyson Parrelli
f1c2e5f194 Updated language translations. 2019-12-18 00:49:36 -05:00
Greyson Parrelli
b6d59f1d46 Fix sticker manager text wrapping issues. 2019-12-17 22:26:09 -05:00
Greyson Parrelli
a3521681e7 Bump version to 4.52.0 2019-12-17 15:38:12 -05:00
Alan Evans
ca10e1136c Updated language translations. 2019-12-17 15:37:11 -05:00
Greyson Parrelli
282fb2af0e Fix tinting issues on API 21. 2019-12-17 14:36:54 -05:00
Greyson Parrelli
1f5a597d50 Remove the Pixel 4 from the CameraX blacklist. 2019-12-17 14:36:54 -05:00
Greyson Parrelli
a32666817c Remove old image editor stickers. 2019-12-17 14:36:54 -05:00
Greyson Parrelli
b9bd3f2b4c Launch stickers. 2019-12-17 14:36:54 -05:00
Alex Hart
4173efbe5a Fix NPE on call initalization 2019-12-16 11:01:32 -04:00
yate
8121c8bd41 Remove blank MMS config overrides.
Some SIM cards include blank values for `uaProfUrl` and `userAgent`.

Passing in these blank values along with the overrides causes a 412 precondition error when downloading MMS from ATT&T.

If these values happen to be blank, then they should be removed completely from the overrides, allowing the request to use the default `uaProfUrl` and `userAgent` for the system.

Fixes #9173
2019-12-14 11:29:46 -05:00
Greyson Parrelli
6ca8218d55 Bump version to 4.51.6 2019-12-06 11:58:12 -05:00
Greyson Parrelli
653afbdd46 Updated language translations. 2019-12-06 11:57:33 -05:00
Alex Hart
4453d1752f Fix reactions scrubber positioning on vertically split multiscreen. 2019-12-06 11:43:51 -05:00
Alan Evans
5782c8a58b Make media overview view pager scrollable for long translations. 2019-12-06 11:43:51 -05:00
Greyson Parrelli
3e041befc8 Bump version to 4.51.5 2019-12-06 01:14:24 -05:00
Greyson Parrelli
6d3847c26f Updated language translations. 2019-12-06 01:10:47 -05:00
Alex Hart
4bdfc37bc1 Fix swipe to reply clobbering long press events. 2019-12-06 01:00:39 -05:00
Alan Evans
28afe0d829 Fix first media overview item selected text. 2019-12-06 01:00:39 -05:00
Greyson Parrelli
04dddd3378 Bump version to 4.51.4 2019-12-05 19:15:10 -05:00
Greyson Parrelli
23a14583fe Updated language translations. 2019-12-05 19:14:48 -05:00
Greyson Parrelli
19c83de510 Fix possible performance issues with reactions query. 2019-12-05 19:10:37 -05:00
Greyson Parrelli
e94d4b64cf Bump version to 4.51.3 2019-12-05 12:21:12 -05:00
Greyson Parrelli
7380c99f3a Updated language translations. 2019-12-05 12:20:28 -05:00
Greyson Parrelli
2961a372c3 Avoid unneccessary recipient refreshes. 2019-12-05 12:20:28 -05:00
Greyson Parrelli
5e2a4fb058 Put safeguards around Recipient creation in the IdentityStore. 2019-12-05 12:20:28 -05:00
Greyson Parrelli
a16242b9f8 Fix issues with RecipientDatabase#update(). 2019-12-05 12:20:28 -05:00
Alan Evans
acfba9ac96 Show total file size of selected items when appropriate. 2019-12-05 12:20:28 -05:00
Alan Evans
006343460e Fix long press on image thumbnail in detail view.
Allow long press to select inside select mode.
2019-12-05 12:20:28 -05:00
Alex Hart
1cc8634cc7 Display reactions in ImageView instead of TextView on message bubbles. 2019-12-05 12:20:27 -05:00
Greyson Parrelli
9f8a110428 Bump version to 4.51.2 2019-12-05 02:59:42 -05:00
Greyson Parrelli
07b4279d0b Updated language translations. 2019-12-05 02:59:42 -05:00
Greyson Parrelli
6a33b231e3 Add a FrameRateTracker to log frame drops. 2019-12-05 02:59:42 -05:00
Greyson Parrelli
b38d02061d Improve logging in RetrieveProfileJob. 2019-12-05 02:59:42 -05:00
Greyson Parrelli
f832a36a5e Prevent possible UUID-only recipient creations. 2019-12-05 02:59:42 -05:00
Alan Evans
911ca7c29d Improve storage management detail view descriptions. 2019-12-05 02:59:42 -05:00
Greyson Parrelli
544b75a2a7 Bump version to 4.51.2 2019-12-04 15:30:30 -05:00
Greyson Parrelli
56e8d4fb06 Updated language translations. 2019-12-04 15:28:45 -05:00
Greyson Parrelli
36a2278aef Add sanity checks for phone number during link process. 2019-12-04 15:28:45 -05:00
Alex Hart
0c785b85b8 Fix video icon issue. 2019-12-04 15:28:45 -05:00
Alan Evans
a0ecba147e Use decimal digit groups in file size pretty printing. 2019-12-04 15:28:45 -05:00
Alan Evans
977591ac82 Remove assertion error in recipient preferences. 2019-12-04 15:28:28 -05:00
Greyson Parrelli
fe1838d3fe Fix issue where unlocking would dismiss MainActivity. 2019-12-04 15:25:26 -05:00
Greyson Parrelli
ede06cf97d Remove unnecessary directory refreshes. 2019-12-04 15:25:26 -05:00
Greyson Parrelli
93deee6824 Attempt to fix crash in ClassicOpenHelper.
These users must have > 1.5-year-old installs that haven't been updated,
but it's still a top crash for whatever reason, so gotta try to fix it.
2019-12-04 15:25:26 -05:00
Greyson Parrelli
db19077834 Fix crash when receiving a PreKey message in a new session.
We don't allow creating recipients with only a UUID at the moment
(for good reason), but the way the decrypt method was written, it
was possible to do so. Until we have CDS, we should prefer the phone
number in scenarios like these.
2019-12-04 15:25:26 -05:00
Alex Hart
6f91f62db2 Fix InviteActivity sendSmsMessage type error. 2019-12-04 15:25:26 -05:00
Greyson Parrelli
1fc63b7597 Fix crash in conversation search. 2019-12-04 15:25:26 -05:00
Alex Hart
a079e479ec Skip call dialog if signal call is already active. 2019-12-04 15:25:26 -05:00
Alex Hart
e90705b459 Add correct archive icon to light theme. 2019-12-04 15:25:26 -05:00
Alex Hart
244db437cb Fix reaction display issues. 2019-12-04 15:25:25 -05:00
Alan Evans
6558eae032 Fix audio color on light theme. 2019-12-04 09:10:48 -05:00
Greyson Parrelli
88b54a262b Bump version to 4.51.0 2019-12-04 00:17:03 -05:00
Alan Evans
4fa8b8a4bd Updated language translations. 2019-12-04 00:08:01 -05:00
Greyson Parrelli
cc0ced9a81 Add internal pre-alpha support for storage service. 2019-12-04 00:08:01 -05:00
Alan Evans
52447f5e97 Add storage management features. 2019-12-04 00:07:49 -05:00
Alex Hart
bceb69b284 Add internal pre-alpha support for receiving reactions. 2019-12-04 00:07:49 -05:00
Alex Hart
a8d826020d Implement new video call experience. 2019-12-04 00:07:49 -05:00
Alan Evans
2ada7f87f2 Update lib phone number. 2019-12-04 00:07:49 -05:00
Alan Evans
7f8ca58762 Add internal pre-alpha support for Registration Lock v2. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
058c25808b Clean up website auto-update APKs.
I think previously we thought that because we always used the same
filename, that we were just overwring the same file repeatedly. However,
DownloadManager will actually append numbers to a filename instead of
overwriting, so we were generating a ton of files.

Now we just clear out any previous files before downloading a new one.

Related to #9033
2019-12-04 00:07:49 -05:00
Greyson Parrelli
c1e6b6b086 Add better Loader performance logs. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
b0a6bb79f6 Fix issue with setting Note to Self color.
Fixes #9235
2019-12-04 00:07:49 -05:00
Greyson Parrelli
de52bf50a2 Improve image and sticker notifications. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
207dd23c86 Fix performance issue with large number of notifications.
Constructing the notification would call KeyCachingService#isLocked()
many times in a loop. This call is slow, because when the device isn't
locked, it would construct the master secret each time, which can take
50+ ms. Do that twice in a loop a hundred times, and it adds up.

This simplified #isLocked to avoid the unnecessary master secret
generation.
2019-12-04 00:07:49 -05:00
Alex Hart
7e0de29dd7 Add notification policy permission and runtime check.
Fixes #9217
2019-12-04 00:07:49 -05:00
Greyson Parrelli
eaa8f1ee8f Ignore expiration updates from groups you've left. 2019-12-04 00:07:49 -05:00
Alex Hart
fb494c1151 Implement new invite screen. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
49ecd9ef5d Added additional logging in RetrieveProfileJob. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
8772214fd4 Increase number of log files to 7. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
5b74d0cbeb Improve activity lifecycle logging. 2019-12-04 00:07:49 -05:00
Greyson Parrelli
1e7c93007d Convert the conversation list into a real fragment.
It was a fragment before, but it's functionality was inappropriately
split between the various layers.

This also sets us up better to do tablet stuff in the future, if we
choose to do that.
2019-12-04 00:07:49 -05:00
Greyson Parrelli
608815a69b Add internal pre-alpha support for usernames. 2019-12-04 00:07:42 -05:00
Greyson Parrelli
fb49efa34d Add internal pre-alpha libsignal support for reactions. 2019-12-04 00:07:13 -05:00
Android Team
b20e8616ec Move libsignal-service-java into this repo.
libsignal-service-java repo commit: 1a01c22636
2019-12-04 00:07:13 -05:00
Alan Evans
acf78b6b63 Make new top level gradle file, make app dir and move build.gradle. 2019-12-04 00:07:13 -05:00
Alex Hart
d68fe928b8 Fix nav bar contrast on sticker preview. 2019-12-04 00:07:13 -05:00
Alex Hart
db4d561d9e Fix nav bar color on shared contacts edit screen. 2019-12-04 00:07:07 -05:00
Alex Hart
6933ca50a7 Implement new Message Request UI. 2019-12-04 00:07:07 -05:00
Greyson Parrelli
88c0e6f8ab Fail a job when you can't instantiate it.
This will still crash, but prevent apps from getting into crash loops
when we have bad data in a job.
2019-11-25 11:32:16 -05:00
Greyson Parrelli
6cd4728e3c Update job serialized data after retry. 2019-11-25 11:32:16 -05:00
Greyson Parrelli
97cc82837c Don't backup the JobsDatabase. 2019-11-25 11:32:16 -05:00
Greyson Parrelli
5f0b0e8495 Bump version to 4.50.6 2019-11-25 11:32:07 -05:00
Greyson Parrelli
c837d590ab Removed RefreshUnidentifiedDeliveryAvailabilityJob.
It's been long enough -- it's no longer necessary to check.

Also, the service is going to start returning certs no matter what, so
at this point it's just an unnecessary network call.
2019-11-25 11:32:06 -05:00
Greyson Parrelli
6108a32631 Prevent reading UUIDs from external contacts. 2019-11-25 11:29:58 -05:00
Greyson Parrelli
6d78e1760d Fix possible crash in very old database migration.
The NumberMigrator constructor is crashing. One possibility is that this
migration is running before the user registered, in which case they
wouldn't have a number. Wrapped the parts that don't need to run in this
situation in an `if` block.
2019-11-25 11:29:58 -05:00
Greyson Parrelli
c7b7242eff Fix issues with blocking and MMS groups.
Fixes #9218.

Note that this removes MMS group blocking for now, just because it never
really worked, and I don't want to hotfix in a feature.
2019-11-25 11:29:19 -05:00
Greyson Parrelli
77c687efcf Bump version to 4.50.5 2019-11-21 13:28:23 -05:00
Greyson Parrelli
274af2f010 Ensure group message is being sent to a group. 2019-11-21 13:28:00 -05:00
Greyson Parrelli
6cd5100530 Ensure Recipient.self() is available. 2019-11-21 13:24:54 -05:00
Alan Evans
115a408b0b Bump version to 4.50.4 2019-11-18 09:36:14 -05:00
Alan Evans
d983016137 Updated language translations. 2019-11-18 09:33:53 -05:00
Greyson Parrelli
a8309b6b2b Fix attachment sizes during backup exports. 2019-11-18 09:19:11 -05:00
Alan Evans
e241589c92 Bump version to 4.50.3 2019-11-15 15:43:47 -05:00
Alan Evans
dcb6240a6a Updated language translations. 2019-11-15 15:39:39 -05:00
Alex Hart
599bb5ab0f Update insights copy and queries. 2019-11-15 16:33:54 -04:00
Alan Evans
739fe584c6 Bump version to 4.50.2 2019-11-14 14:49:48 -05:00
Alan Evans
920d1c6207 Updated language translations. 2019-11-14 14:49:48 -05:00
Greyson Parrelli
c1d3d26a15 Removed unnecessary subtext in Insights. 2019-11-14 14:49:48 -05:00
theBoatman
e3b66dc7e8 Fixing RealtimeSleepTimer to work in concurrent threads. 2019-11-14 13:56:12 -05:00
Alan Evans
dfa08d1356 In dark mode, use a dark theme base for RegistrationLockDialog and RationaleDialog.
Fixes #8995
Closes #9077
2019-11-14 13:51:01 -05:00
Curt Brune
6da734c2f9 Update ringrtc to 0.2.0 2019-11-13 14:05:22 -08:00
Alan Evans
a9f3141a77 Create android.yml running qa task. 2019-11-13 15:49:22 -05:00
Alan Evans
ee267d4702 Bump version to 4.50.1 2019-11-13 14:40:04 -05:00
Alan Evans
c39174ed8a Updated language translations. 2019-11-13 14:40:04 -05:00
Greyson Parrelli
9c872b6da6 Fix issue where SMS groups showing up as 'Unnamed Group'. 2019-11-13 11:37:49 -05:00
Greyson Parrelli
2924a09936 Don't run UuidMigration if not registered. 2019-11-13 11:37:49 -05:00
Greyson Parrelli
b9da012cc4 Fix media send issues on Android 4.4.
Before lollipop, Android would ignore activity results for activities
launched with singleTask. So I switched to singleTop, since that should
still solve the problem of double-activity-opens without running into
this issue.

Fixes #9149
2019-11-13 11:37:49 -05:00
Alan Evans
8626d41787 I18N for "Unlock Signal". 2019-11-13 11:37:49 -05:00
Alex Hart
c58705722a Fix Insights Adapter item text clashing. 2019-11-13 11:37:49 -05:00
Greyson Parrelli
c702ff676a Support additional sync behavior for linked devices. 2019-11-13 11:37:49 -05:00
Alan Evans
995569dd50 Bump version to 4.50.0 2019-11-12 10:55:41 -05:00
Greyson Parrelli
a0dfd42527 Fix possible context crash. 2019-11-12 10:55:41 -05:00
Alex Hart
a7dd78cce6 Implement in-app insights. 2019-11-12 10:55:41 -05:00
Greyson Parrelli
e2e9cd40b3 Blacklist additional phone models from the new camera. 2019-11-11 18:13:34 -05:00
Alan Evans
e4fc6f41b8 Schedule first backup after restore for 24hrs time. 2019-11-11 13:00:01 -05:00
Alan Evans
778b2a0d27 Remove old strings file. 2019-11-10 09:23:44 -05:00
Alan Evans
64cd15ca0b Make AlbumThumbnailView_plus non-translatable. 2019-11-10 09:23:43 -05:00
Alan Evans
1e46c3c0ba Make app_name non-translatable. 2019-11-10 09:23:43 -05:00
Alan Evans
91772b4e11 Exclude non-translatable strings.
Lint exclude ExtraTranslation.
2019-11-10 09:23:42 -05:00
Alan Evans
af5c7cb7ca Added dotted lines to time icons. 2019-11-09 07:45:53 -05:00
Alan Evans
e760474fa9 Clear unauthorized flag on connect. 2019-11-09 07:01:08 -05:00
Greyson Parrelli
28bb88e347 Lower max number of JobScheduler IDs.
Some devices can only handle 25 job IDs, so I lowered it to 20.
2019-11-09 07:01:08 -05:00
Curt Brune
17a1fe97ca Send hangup message when terminating on call errors.
When errors occur during a call, attempt to send the remote peer a
hangup message before terminating the call.  This allows the remote
peer to shutdown their side of the call in a timely manner.
2019-11-09 07:01:08 -05:00
Alan Evans
7348237862 Lint.
- baseline update.
- eradicate ButtonOrder.
- eradicate VectorRaster.
- eradicate HardcodedText.
2019-11-09 07:01:08 -05:00
Alex Hart
4e66db5dc1 Fix timer disabled icon. 2019-11-09 07:01:08 -05:00
Alex Hart
ffa6c9acd1 Utilize icon_tint color filter for UpdateItem icons. 2019-11-09 07:01:08 -05:00
Greyson Parrelli
b3e66a9259 Prevent creation of UUID-only recipients. 2019-11-09 07:01:08 -05:00
Alan Evans
5c0cf8c1f0 English staging app name. 2019-11-09 07:01:08 -05:00
Alex Hart
89c716fca4 Fix layout gravity on recv long message footer. 2019-11-09 07:01:08 -05:00
Alan Evans
aae9830dfa Use lib-signal-servivce 2.15.1 2019-11-09 07:01:08 -05:00
Alan Evans
48c72697c1 Add protobuf lite exception for proguard. 2019-11-09 07:01:08 -05:00
Greyson Parrelli
aac7e9ea53 Fix issue with ShareActivity contact display. 2019-11-09 07:01:08 -05:00
Alan Evans
a1b10b3222 Update to protobuf 3.10 lite.
Update fingerprint logic.
Update for additional validation around SignalServiceAddresses.

Co-authored-by: Greyson Parrelli <greyson@signal.org>
2019-11-09 07:01:08 -05:00
Curt Brune
2ab8db33e3 Update ringrtc to 0.1.9 2019-11-09 07:01:08 -05:00
Curt Brune
2b1386232f Implement logging interface for ringrtc-0.1.8
Implement the org.signal.ringrtc.Log.Logger interface, using
org.thoughtcrime.securesms.logging.Log as the underlying logger.

This allows ringrtc to log in the same way as the rest of the
application.
2019-11-09 07:01:07 -05:00
Alex Hart
7bb1caa22e Remove unused pictures that were captured outside of the in-app camera. 2019-11-09 07:01:07 -05:00
Alex Hart
72c14b8651 Apply new Keyboard and Dial Pad icons. 2019-11-09 07:01:07 -05:00
Alex Hart
f85c3bb1e6 Add feature flag to new Profile display. 2019-11-09 07:01:07 -05:00
Alan Evans
d8714fe3b4 Delegate to library Base64. 2019-11-09 07:01:07 -05:00
Alex Hart
c54c1907b6 Fix nav bar colors on reg and call screen. 2019-11-09 07:01:07 -05:00
Greyson Parrelli
55257155dd Handle ProtocolInvalidMessageExceptions with no sender. 2019-11-09 07:01:07 -05:00
alex-signal
55a8bd86de Consolidate profile display labels. 2019-11-09 07:01:07 -05:00
Greyson Parrelli
c60909272b UUID migration. 2019-11-09 07:01:07 -05:00
Greyson Parrelli
3dcc2d8171 Moved DirectoryHelper. 2019-11-09 07:01:07 -05:00
Greyson Parrelli
0ccbb22e4c Unsubscribe from old recipient observers in ConversationActivity. 2019-11-09 07:01:07 -05:00
Greyson Parrelli
0ffa10eaea Bump version to 4.49.18 2019-11-08 20:27:14 -05:00
Alan Evans
9824073023 Keep line numbers within R8. 2019-11-08 16:17:32 -05:00
Alan Evans
d2adc11a2d New languages.
bn,bs,ms,mr,ta,ur
2019-11-08 16:17:32 -05:00
Alan Evans
4d0b870c88 Updated language translations. 2019-11-08 16:17:32 -05:00
Alan Evans
840997cb7d Translate tasks. 2019-11-08 11:41:38 -05:00
Alex Hart
e4406621ed Fix lint issues. 2019-11-08 11:41:38 -05:00
Greyson Parrelli
7f96ee0b62 Clear DATA_HASH column. 2019-11-08 11:30:48 -05:00
Alan Evans
17f850b31a Ensure that we can still navigate when we get a response from server. 2019-11-08 11:19:57 -05:00
Alan Evans
5d87ad0301 Ignore period in phone number entry.
Fixes #9168
2019-11-08 11:19:32 -05:00
Greyson Parrelli
a33eeed6d3 Bump version to 4.49.17 2019-11-07 16:18:27 -05:00
Alex Hart
554ce29337 Adjust Typing Indicator margin in groups. 2019-11-07 16:03:13 -05:00
Alex Hart
6054d0b36f Nullify Data Hash when Data is Nullified 2019-11-07 16:02:56 -05:00
Greyson Parrelli
8ba15cf3b4 Bump version to 4.49.16 2019-11-06 16:42:16 -05:00
Greyson Parrelli
b15e5b4867 Fix potential invalid context crash. 2019-11-06 16:38:28 -05:00
Greyson Parrelli
544511905a Fix issue where deduped media might not be kept in sync. 2019-11-06 16:37:25 -05:00
Greyson Parrelli
02e3f511bd Bump version to 4.49.15 2019-11-04 11:30:08 -05:00
Alex Hart
0539b071e7 Fix message info icon inflation error on API 19 devices.
Fixes #9148
2019-11-04 11:29:31 -05:00
Greyson Parrelli
36e400650c Fix backup restore issue.
Fixes #9133
2019-11-04 11:21:11 -05:00
Greyson Parrelli
d8930daf4b Bump version to 4.49.14 2019-11-01 17:23:52 -04:00
Greyson Parrelli
17b5816d5b Skip corrupt attachments during backup restore.
Instead of aborting the entire backup restore.

Related to #8355
2019-11-01 17:11:10 -04:00
Curt Brune
e574c81ea2 Update ringrtc to 0.1.7.2 2019-11-01 12:26:00 -07:00
Greyson Parrelli
6799a2f841 Bump version to 4.49.13 2019-10-29 16:10:09 -04:00
Curt Brune
61fb20f412 Update ringrtc to v0.1.7 2019-10-29 12:46:01 -07:00
Alan Evans
47f648e7bc Correct text on registration lock fragment. 2019-10-29 13:47:14 -04:00
Alan Evans
064c0ddb82 Manually restrict to 30 digits to allow pasting containing any number of spaces. 2019-10-29 09:59:53 -04:00
Greyson Parrelli
b42c42007d Bump version to 4.49.12 2019-10-28 19:48:07 -04:00
Greyson Parrelli
807cdfce2e Increase backup passphrase input length to 35.
The way pasting works, it could prevent you from pasting in text if you
included the spaces in between number chunks.
2019-10-28 19:47:05 -04:00
Greyson Parrelli
43dc3aeebd Fix 'direct share' icon rendering issue.
Fixes #9138
2019-10-28 16:51:06 -04:00
Greyson Parrelli
0369c5ee16 Make avatars larger in conversations. 2019-10-28 16:46:21 -04:00
Greyson Parrelli
3df2390fe4 Bump version to 4.49.11 2019-10-28 12:28:58 -04:00
Greyson Parrelli
9ec4a7af0f Update max video duration for MMS. 2019-10-28 12:09:54 -04:00
Alex Hart
70636fb4a7 Blacklist Pixel4 from CameraX (#319)
* Blacklist Pixel4 from CameraX

* Create isSupported method
2019-10-28 13:07:28 -03:00
Alex Hart
3c4efdd8f9 Apply proper color when action mode destroyed 2019-10-28 12:00:59 -04:00
Greyson Parrelli
80deb301e5 Bump version to 4.49.10 2019-10-25 14:00:33 -07:00
Greyson Parrelli
5eabfdc34c Fix crash if you leave during log generation. 2019-10-25 14:00:33 -07:00
Greyson Parrelli
5e96832666 Disable video capture for legacy camera devices. 2019-10-25 13:54:38 -07:00
Alan Evans
1ccce24cf8 Fix drawable crash on API 19. 2019-10-25 15:33:34 -04:00
Greyson Parrelli
d59e4f2da7 Bump version to 4.49.9 2019-10-24 14:20:09 -07:00
Greyson Parrelli
a254c1a7b2 Updated language translations. 2019-10-24 14:17:19 -07:00
Alex Hart
82cc610938 Apply themed context to SingleRecipientNotificationBuilder 2019-10-24 17:59:34 -03:00
Greyson Parrelli
becf7c40e8 Bump version to 4.49.8 2019-10-23 23:45:13 -07:00
Greyson Parrelli
ed21c3ca03 Improve send button in media send flow.
Fixes #8988
2019-10-23 23:45:13 -07:00
Greyson Parrelli
631005e565 CameraX cleanup. 2019-10-23 23:45:13 -07:00
Greyson Parrelli
b57fab8c75 Make MediaSendActivity singleTask. 2019-10-23 23:45:13 -07:00
Greyson Parrelli
4260a8436b Fix possible de-duping issues.
- Clean bad hashes from earlier release.
- Fix file equality comparison.
- Given our new de-duping, we don't want to run into a situation where two
simultaneous compressions could be happening on the same image.
2019-10-23 23:45:13 -07:00
Greyson Parrelli
23d478191f Don't render 'null' contact labels. 2019-10-23 23:45:13 -07:00
Greyson Parrelli
d075a33d4e Fix issue where video controls may be missing.
Fixes #9121
2019-10-23 23:45:13 -07:00
Greyson Parrelli
7507dadbe7 Bump version to 4.49.7 2019-10-22 14:17:24 -04:00
Greyson Parrelli
bfd0363390 Fix potential tooltip crash. 2019-10-22 14:17:01 -04:00
Greyson Parrelli
cfb22825f4 Fix recent conversation query. 2019-10-22 13:49:31 -04:00
Greyson Parrelli
d37fafbfe7 Bump version to 4.49.6 2019-10-22 13:02:26 -04:00
Greyson Parrelli
2dc2eb5835 Updated language translations. 2019-10-22 13:02:26 -04:00
Greyson Parrelli
019d036f69 Fix soft nav color in conversation list.
Going in and out of multi-select mode would screw it up.

This fixes it by setting the flags correctly instead of blasting them
all away.
2019-10-22 13:02:26 -04:00
Greyson Parrelli
f1ca5fc8e2 Exclude inactive groups from search results where appropriate.
Fixes #9091
Fixes #9080
2019-10-22 12:41:20 -04:00
Greyson Parrelli
9089fc4001 Fix potential display issues in Message Details screen. 2019-10-22 11:00:03 -04:00
Greyson Parrelli
b281b817ba Add additional logging around attachment upload/deletion. 2019-10-22 10:52:55 -04:00
Greyson Parrelli
cee6736656 Fix flickering 'about' section in group recipient preferences.
Gotta disable those pesky layout animations.

Fixes #9092
2019-10-22 10:30:11 -04:00
Greyson Parrelli
350ca059b9 Pick camera resolution based on screen resolution. 2019-10-22 10:16:14 -04:00
Greyson Parrelli
a7cc5bdc5e Ensure keyboard is hidden when going to the gallery.
Fixes #9120
2019-10-22 09:50:01 -04:00
Greyson Parrelli
2893dfff60 Bump version to 4.49.5 2019-10-21 20:05:31 -04:00
Greyson Parrelli
1c14f06734 Updated language translations. 2019-10-21 20:04:20 -04:00
Greyson Parrelli
ff053437e3 Fix issue where album rail wasn't immediately shown.
Fixes #9111
2019-10-21 20:04:20 -04:00
Alex Hart
03dc220cea Fix crash related to unsupported camera mode.
Fixes #9106
2019-10-21 19:32:19 -04:00
Greyson Parrelli
097f97b5e4 Add system to allow skipping attachment compression. 2019-10-21 19:32:19 -04:00
Alex Hart
95d3db3260 Add ic_message webp files 2019-10-21 15:30:20 -03:00
Greyson Parrelli
0485ae603b Set targetResolution to something reasonable for camera capture. 2019-10-21 10:58:07 -04:00
Alex Hart
2f93da6f1a Fix wrong icon color in expiry footer 2019-10-21 11:50:32 -03:00
Alex Hart
78f3e28974 Fix progress wheel color in share screen 2019-10-21 10:53:18 -03:00
Alex Hart
fecd8300c3 Swap ValueAnimator with version from NineOldAndroids 2019-10-21 10:51:04 -03:00
Alex Hart
262f90dbe7 Add new info icon to message details view 2019-10-21 10:09:54 -03:00
Alex Hart
ed271c6f3e Fix longmessage and shared contact footer alignment 2019-10-21 10:00:28 -03:00
Greyson Parrelli
3fa5843c93 Bump version to 4.49.4 2019-10-21 01:13:19 -04:00
Greyson Parrelli
7f544cb3e5 Updated language translations. 2019-10-21 01:12:51 -04:00
Greyson Parrelli
9f4d40a364 Revert "Prevent compression of images without EXIF data."
This reverts commit 8449d75684.
2019-10-21 01:00:31 -04:00
Greyson Parrelli
09f0c5b63f Fix bug in manual conversation search query submission.
Fixes #9115
2019-10-21 00:49:06 -04:00
Greyson Parrelli
53e0dc5dee Potential MMS download fix for some users.
Special thanks to @mdaniel

Fixes #8571
2019-10-21 00:36:54 -04:00
Greyson Parrelli
8b06051e7c Fix blurhash being visible on transparent images. 2019-10-21 00:21:53 -04:00
Greyson Parrelli
660ec88202 Fix data deduping issues.
Fixes #9109
2019-10-21 00:21:48 -04:00
Greyson Parrelli
b9057a1c11 Use fallback images for Giphy results. 2019-10-20 01:13:02 -04:00
Greyson Parrelli
1f85b1f3d2 Bump version to 4.49.3 2019-10-19 12:37:00 -04:00
Greyson Parrelli
71f78f03f4 Updated language translations. 2019-10-19 12:34:30 -04:00
Greyson Parrelli
4b3d129097 Cleanup some possible bad RecipientIds in the MMS table. 2019-10-19 12:31:06 -04:00
Greyson Parrelli
daeb823399 Fix CameraX crash. 2019-10-19 12:25:40 -04:00
Greyson Parrelli
c7f76c5d1c Add a util to safely set a MediaMetadataRetriever data source. 2019-10-19 12:23:23 -04:00
Greyson Parrelli
9ba1391a1e Fix issue with non-contact avatar colors not updating. 2019-10-19 12:15:14 -04:00
Greyson Parrelli
5d137465e8 Bump version to 4.49.2 2019-10-18 18:46:07 -04:00
Greyson Parrelli
78cbb3c073 Updated language translations. 2019-10-18 18:45:43 -04:00
Greyson Parrelli
097d95428a Fix conversation list toolbar styling. 2019-10-18 17:34:01 -04:00
Greyson Parrelli
2cfb6ede7f Increase record button size. 2019-10-18 17:34:01 -04:00
Greyson Parrelli
695663a5b1 Log more data about video capture support. 2019-10-18 17:34:01 -04:00
Greyson Parrelli
a2204c8370 Update video recording tooltip text. 2019-10-18 17:34:01 -04:00
Alex Hart
8c4757ea02 Fix safety number theme 2019-10-18 17:34:01 -04:00
Greyson Parrelli
85821274fa Don't render null phone number labels. 2019-10-18 17:34:01 -04:00
Greyson Parrelli
a8b1ec8e52 Increase compose maxLines to 5. 2019-10-18 17:34:01 -04:00
Alex Hart
adbdaebd69 Fix invite screen theme. 2019-10-18 17:33:45 -04:00
Alex Hart
c2da4fcd7d Update CameraX to Alpha06 and bring in new View / Module code. 2019-10-18 17:36:00 -03:00
Greyson Parrelli
46ebff3659 Bump version to 4.49.1 2019-10-18 13:58:59 -04:00
Greyson Parrelli
452ccd0350 Updated language translations. 2019-10-18 13:58:35 -04:00
Alan Evans
6e6c809690 Use lighter-weight shadow view. 2019-10-18 13:51:56 -04:00
Greyson Parrelli
7b5c1904cf Update contact list divider styling. 2019-10-18 13:09:20 -04:00
Alex Hart
c0aa9d7587 Update preference divider colors 2019-10-18 13:09:20 -04:00
Alex Hart
5d03e3d516 Apply video recording permissions checks and error handling. 2019-10-18 13:09:20 -04:00
Greyson Parrelli
2bc3a4417f Fix possible crash when retrieving a video thumbnail. 2019-10-18 11:10:26 -04:00
Greyson Parrelli
59d03cbeb2 Address possible issues with AvatarMigrationJob. 2019-10-18 11:08:06 -04:00
Alex Hart
f9f9ae68f5 Fix MediaSendActivity send button metrics and positioning. 2019-10-18 10:49:54 -03:00
Alex Hart
88f2b67984 Fix crash on conversation color picker open. 2019-10-18 10:33:10 -03:00
Greyson Parrelli
44dba4bd98 Only show video tooltip if capable. 2019-10-18 09:31:01 -04:00
Alan Evans
ef585eba42 Revert "Fix invite drawable on API 21."
This reverts commit 78fbfe40736dd8c9ccafb402aa7fc6921c1d4496.

We have turned on AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) now for API19.
2019-10-18 09:15:20 -04:00
Alex Hart
42967dab13 Apply proper tinting to conversation app bar. 2019-10-18 09:55:27 -03:00
Greyson Parrelli
4f75d1a5db Bump version to 4.49.0 2019-10-17 22:45:56 -04:00
Greyson Parrelli
debacbdf58 Updated language translations. 2019-10-17 22:44:32 -04:00
Greyson Parrelli
12c4283c30 Conditionally show a toolbar shadow in the conversation list. 2019-10-17 22:23:27 -04:00
Greyson Parrelli
096c9e0ff7 Ensure videos are paused properly in MediaSendActivity. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
2f23a13a6f Fix issue where video playback controls sometimes don't appear. 2019-10-17 21:33:52 -04:00
Alan Evans
bdadbaaac4 Remove larger images for link preview intro.
(I bet you thought they would be together forever).
2019-10-17 21:33:52 -04:00
Alan Evans
16f41555ba Local SMS rate limiting. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
8c037456e7 Warm up LiveRecipientCache at launch. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
feccee557e Include better defaults for ConversationActivity. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
67f5605bc4 Block in RestStrategy until PushDecryptJobs finish. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
ccb18cd46c Added JobTracker. 2019-10-17 21:33:52 -04:00
Alan Evans
5b1d91016c Fix backup sorting logic and do not delete non-backups in path. 2019-10-17 21:33:52 -04:00
Alan Evans
27ab04ab41 Add version and spread out witness information. 2019-10-17 21:33:52 -04:00
alex-signal
9432a45b39 Implement blur-hash based low resolution thumbnail previews. 2019-10-17 21:33:52 -04:00
alex-signal
9e3475ed94 Upgrade Gradle to 5.6.2 and AGP to 3.5.1 2019-10-17 21:33:52 -04:00
Alan Evans
86d088bce2 Improved registration flow. 2019-10-17 21:33:52 -04:00
alex-signal
a135e7efa2 Check DND settings before show activity or play ring or vibrate. 2019-10-17 21:33:52 -04:00
tzm
c247935f1a Fix NPE when has no contact permissions.
Fixes #9048

Co-authored-by: Alan Evans <alan@signal.org>
2019-10-17 21:33:52 -04:00
Alan Evans
31657f5c15 Gradle witness sort dependencies and move to own auto generated file. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
bcecc30d33 Disallow RecipientId's of zero. 2019-10-17 21:33:52 -04:00
Greyson Parrelli
7193252d77 Fix TooltipPopup positioning. 2019-10-17 21:33:51 -04:00
Greyson Parrelli
5b682a3a3d Use our own location retriever. 2019-10-17 21:33:51 -04:00
Greyson Parrelli
6b8659a393 Move JobManager to ApplicationDependencies. 2019-10-17 21:33:51 -04:00
Alan Evans
14557d3dc1 Change default GZIP behavior on Base64.decode(String).
Remove only reference to Base64.decode(byte[]).
2019-10-17 21:33:51 -04:00
Curt Brune
03cbee0277 Add ringrtc support.
RingRTC provides Signal Messenger applications with a common interface
for video and voice calling services built on top of WebRTC.
2019-10-17 21:33:51 -04:00
alex-signal
adc0907906 Copy image to encrypted blob and delete from device immediately.
Fixes #9097
2019-10-17 21:33:51 -04:00
Alan Evans
f14a71a076 Update Lint suppressions. 2019-10-17 21:33:51 -04:00
alex-signal
12c2b53f7c Set icon to themed version via attribute.
Fixes #9054
2019-10-17 21:33:51 -04:00
alex-signal
ff60b5b731 Add in-app video recording for supported devices. 2019-10-17 21:33:51 -04:00
alex-signal
43954a176a Apply new Signal icons and color palette. 2019-10-17 21:33:51 -04:00
Alex Hart
d698d3bd6f Added support for view-once video. 2019-10-17 16:01:34 -04:00
Greyson Parrelli
50a81c0e60 Added a simple staging build config. 2019-10-17 16:01:34 -04:00
Greyson Parrelli
b0b8377a8e Migrate notification channels to recipientId's. 2019-10-17 16:01:34 -04:00
alex-signal
7d02bb8487 Store file hash to avoid data duplication. 2019-10-17 16:01:34 -04:00
alex-signal
8449d75684 Prevent compression of images without EXIF data. 2019-10-17 16:01:34 -04:00
Greyson Parrelli
b89163bb14 Migrate avatars to use recipientId filenames. 2019-10-17 16:01:34 -04:00
Greyson Parrelli
b7ce220600 Updated libsignal version to 2.13.8 2019-10-15 10:35:59 -04:00
Greyson Parrelli
948079a42e Bump version to 4.48.17 2019-10-15 10:35:15 -04:00
Greyson Parrelli
de1c6cdd0c Fix crash when MMS messages have no 'from' address. 2019-10-15 10:35:15 -04:00
Greyson Parrelli
37147fb0a5 Bump version to 4.48.16 2019-10-08 21:28:54 -07:00
Greyson Parrelli
33334f80c3 Add back proper support for unknown recipients.
Fixes #9085
2019-10-08 21:28:35 -07:00
Greyson Parrelli
36286da9bd Bump version to 4.48.15 2019-10-08 14:23:29 -07:00
Greyson Parrelli
2248acb9f2 Remove unnecessary resolves. 2019-10-08 14:23:29 -07:00
Greyson Parrelli
3632a2cc95 Hide no-name contacts from system search results. 2019-10-08 13:38:20 -07:00
Greyson Parrelli
9447ea12cb Remove concept of 'unknown' recipient. 2019-10-08 13:33:13 -07:00
Greyson Parrelli
89c2329fdf Bump version to 4.48.14 2019-10-07 16:52:36 -07:00
Greyson Parrelli
84012c7adb Fix crash in SmsMigrator. 2019-10-07 16:41:47 -07:00
Greyson Parrelli
d7395af774 Simplify recipient inserts. 2019-10-07 16:31:04 -07:00
Greyson Parrelli
be4daff0f3 Ensure recipient models are up-to-date. 2019-10-07 16:31:04 -07:00
Greyson Parrelli
c2459d0a31 Bump version to 4.48.13 2019-10-04 12:13:16 -04:00
Greyson Parrelli
5f993ed0f7 Fix threading issue in RecipientDatabase. 2019-10-04 12:07:46 -04:00
Greyson Parrelli
acea24c19c Reduce Recipient refreshes. 2019-10-04 10:52:43 -04:00
Greyson Parrelli
82b5d58d04 Fix threading issues in LiveRecipient. 2019-10-04 10:52:43 -04:00
Greyson Parrelli
af2990fa08 Bump version to 4.48.12 2019-10-03 16:08:18 -04:00
Greyson Parrelli
0fa48540e1 Fix migration of old pending SMS sends. 2019-10-03 16:07:53 -04:00
Greyson Parrelli
0f06b96832 Bump version to 4.48.11 2019-10-03 11:18:51 -04:00
Greyson Parrelli
4f423be6f9 Updated language translations. 2019-10-03 11:18:07 -04:00
Greyson Parrelli
bea21ed5ff Further recipient insertion improvements. 2019-10-03 11:17:48 -04:00
Greyson Parrelli
a9cff032f5 Bump version to 4.48.10 2019-10-02 20:44:22 -04:00
Greyson Parrelli
b2d02d4ace Add extra protections to recipient insertions. 2019-10-02 20:13:49 -04:00
Greyson Parrelli
066df77abf Revert "Use recipientId's in ContactSelectionListAdapter."
This reverts commit 89e075c56e.
2019-10-02 19:26:24 -04:00
Greyson Parrelli
b38a3e6259 Remove unnecessary recipient resolves. 2019-10-02 19:16:59 -04:00
Greyson Parrelli
d78919acf8 Bump version to 4.48.9 2019-10-02 12:36:24 -04:00
Greyson Parrelli
af96d11188 Pretty-print missing recipientId crashes. 2019-10-02 12:22:26 -04:00
Greyson Parrelli
5e1bef26ed Fix display of profile names in FTS results. 2019-10-02 12:22:26 -04:00
Greyson Parrelli
95333eccd4 Cleanup bad recipients. 2019-10-02 12:22:26 -04:00
Greyson Parrelli
89e075c56e Use recipientId's in ContactSelectionListAdapter. 2019-10-02 11:24:55 -04:00
Greyson Parrelli
d026498a8c Use recipientId's in SearchRepository. 2019-10-02 11:24:55 -04:00
Greyson Parrelli
bae381e6f8 Bump version to 4.48.8 2019-10-01 08:20:16 -04:00
Greyson Parrelli
050f7a24c9 Updated language translations. 2019-10-01 08:19:46 -04:00
Greyson Parrelli
77ad1ea729 Prevent ConversationActivity recipientId crash. 2019-10-01 08:17:25 -04:00
Greyson Parrelli
bf667c0cfc Revert "Add logging to track down ConversationActivity crash."
This reverts commit 447236ee38.
2019-10-01 08:16:57 -04:00
Greyson Parrelli
ccb8ef98b4 Bump version to 4.48.7 2019-09-30 15:19:41 -04:00
Greyson Parrelli
a1f0660339 Updated language translations. 2019-09-30 15:19:24 -04:00
Greyson Parrelli
447236ee38 Add logging to track down ConversationActivity crash. 2019-09-30 15:19:24 -04:00
Alan Evans
ebf3b0dfe1 Change versionCode for universal builds. 2019-09-30 14:16:49 -04:00
Greyson Parrelli
ee9216df8a Ensure group status is available in ConversationAdapter constructor. 2019-09-30 14:15:19 -04:00
Greyson Parrelli
3e34668232 Bump version to 4.48.6 2019-09-28 00:16:41 -04:00
Curt Brune
8f8f41f184 Localize call audio and video activation code 2019-09-28 00:16:09 -04:00
Greyson Parrelli
4f7cba8d7c Bump version to 4.48.5 2019-09-27 14:26:08 -04:00
Greyson Parrelli
00b719c783 Updated language translations. 2019-09-27 14:22:13 -04:00
Greyson Parrelli
b03eccec33 Persist the JobFactory key after job migrations. 2019-09-27 14:18:15 -04:00
Greyson Parrelli
5805539deb Revert "Potential MMS download fix for some users."
This reverts commit 1e375ec494.
2019-09-27 14:02:01 -04:00
Greyson Parrelli
1b48fd07a3 Bump version to 4.48.4 2019-09-26 18:32:28 -04:00
Greyson Parrelli
1e375ec494 Potential MMS download fix for some users.
Special thanks to @mdaniel

Fixes #8571
2019-09-26 18:32:28 -04:00
Greyson Parrelli
ee9acf2687 Show tooltip for swipe-to-reply. 2019-09-26 16:37:09 -04:00
Greyson Parrelli
1d8e85fcad Updated the feel of swipe-to-reply. 2019-09-26 16:36:52 -04:00
Greyson Parrelli
4e2afa7362 Fix some more broken JobMigrations. 2019-09-26 11:18:04 -04:00
Greyson Parrelli
d06564c7b9 Bump version to 4.48.3 2019-09-25 19:12:24 -04:00
Greyson Parrelli
ced48d0788 Fix custom notification creation. 2019-09-25 18:58:21 -04:00
Greyson Parrelli
e273593343 Fix more possible JobMigration crashes. 2019-09-25 15:19:08 -04:00
Greyson Parrelli
c5767b07a7 Initialize CameraX on a background thread.
We saw a situation where CameraX initialization could lock up when
trying to obtain system resource. We already fallback to the
Camera1Fragment when CameraX isn't initialized, so it should be safe to
do this initialization on its own thread.
2019-09-25 15:17:35 -04:00
Greyson Parrelli
709ce7e9de Bump version to 4.48.2 2019-09-25 11:37:55 -04:00
Greyson Parrelli
a8f7908ed4 Updated language translations. 2019-09-25 11:37:55 -04:00
Greyson Parrelli
8230786638 Added permissions to the debug log. 2019-09-25 11:23:23 -04:00
Greyson Parrelli
df4ecc4e32 Add blocked threads to the debug log. 2019-09-25 11:23:23 -04:00
Greyson Parrelli
5b755b9501 Crash early when using a null RecipientId. 2019-09-25 09:51:50 -04:00
Greyson Parrelli
b3c7c8db5c Fix possible crash in DirectoryRefreshJob. 2019-09-25 09:51:50 -04:00
Greyson Parrelli
a21c537428 Fix crash during JobMigration. 2019-09-25 09:51:50 -04:00
Greyson Parrelli
6d339cd023 Fix a crash when starting a call from the system contacts. 2019-09-25 09:51:50 -04:00
Alan Evans
840c493265 Fix pretty print phone numbers.
Fixes #9047
2019-09-25 09:49:26 -04:00
alex-signal
14999800e2 Fix Swipe to Reply progress reset when AnimatorDurationScale is 0.
Fixes #9051
2019-09-25 10:08:10 -03:00
Greyson Parrelli
8e332d0798 Bump version to 4.48.1 2019-09-24 12:13:27 -04:00
Greyson Parrelli
cd007a20d7 Fix possible thread visibility issue in CellConstraintObserver.
Related to #9030.
2019-09-24 12:13:27 -04:00
Greyson Parrelli
8b99af3eef Fix threading issues with LiveRecipient. 2019-09-24 12:03:33 -04:00
Greyson Parrelli
d354de806e Bump version to 4.48.0 2019-09-24 10:24:37 -04:00
alex-signal
c7ef0c06f8 Fix exif dimension edge case and handle invalid dimens better in ThumbnailView. 2019-09-24 10:11:17 -04:00
Greyson Parrelli
9d14bcb808 Enable sticker sharing. 2019-09-24 10:11:17 -04:00
alex-signal
de03706d8d Fix video thumbnail not displaying after sharing to conversation thread. 2019-09-24 10:11:17 -04:00
alex-signal
a3caabcafd Fix video forwarding thumbnail display. 2019-09-24 10:11:17 -04:00
alex-signal
faafa40122 Fix DecryptableStreamLocalUriFetcher bug for external video thumbnails.
Fix bug where external video thumbnails do not appear.
2019-09-24 10:11:17 -04:00
Greyson Parrelli
d1a6582ad7 Support independent application migration versions. 2019-09-24 10:11:17 -04:00
alex-signal
f81c0b448e Add CameraXFlashToggleView and selfie flash. 2019-09-24 10:11:17 -04:00
Greyson Parrelli
70347e754c Add a feature flag system. 2019-09-24 10:11:17 -04:00
Greyson Parrelli
a1245baf61 Don't show MMS groups in recent Signal contacts list. 2019-09-24 10:11:17 -04:00
Alex Hart
007ea43dc8 Add Swipe-To-Reply in conversations.
Swipe-To-Reply is a progress based animation that is controlled
by the ConversationItemSwipeCallback.  We use the TouchListener to
keep track of the latest down coordinates, so that we can check whether
we are over a seek bar. The SwipeAnimationHelper is responsible for
actually performing the transitions as the swipe progresses.
2019-09-24 10:11:17 -04:00
Greyson Parrelli
582028f2c2 Search contacts via the RecipientDatabase. 2019-09-24 10:11:17 -04:00
Greyson Parrelli
0e2d52026e Migrated to locally-assigned RecipientId's.
Oh boy.
2019-09-24 10:11:17 -04:00
Greyson Parrelli
233cc7ecce Bump version to 4.47.6 2019-09-16 17:00:06 -04:00
Greyson Parrelli
1baf03f51a Fix message fetching bug when app is in the foreground.
Fixes #9036
2019-09-16 16:58:51 -04:00
Greyson Parrelli
f066a9cab2 Bump version to 4.47.5 2019-09-11 18:01:50 -04:00
Curt Brune
825d1a02ab Update to WebRTC M77 2019-09-11 13:55:26 -07:00
Curt Brune
97cc8b7777 Revert "Add ringrtc support."
Revert the following commits:

"Handle busy call while in PSTN call."
Commit 23a0bb3ce0.

"Add ringrtc support."
Commit 3ac540c687.
2019-09-11 11:17:23 -07:00
Greyson Parrelli
72662b5b52 Bump version to 4.47.4 2019-09-06 13:56:17 -04:00
Greyson Parrelli
ea4e0b6d6f Updated language translations. 2019-09-06 13:56:04 -04:00
Alan Evans
6df93c0bb5 Fix potential contact search crash. 2019-09-06 13:53:26 -04:00
Curt Brune
23a0bb3ce0 Handle busy call while in PSTN call. 2019-09-06 10:20:32 -07:00
Greyson Parrelli
4cd5256267 Bump version to 4.47.3 2019-09-04 11:10:47 -04:00
Greyson Parrelli
5362e07a5c Fix crash that may happen upon first sticker install. 2019-09-04 11:09:30 -04:00
Greyson Parrelli
936bd327bd Bump version to 4.47.2 2019-09-03 15:38:34 -04:00
Greyson Parrelli
db89619a4a Updated language translations. 2019-09-03 15:38:20 -04:00
Alan Evans
b5ce7325fc Fix invite drawable on API 19. 2019-09-03 15:20:09 -04:00
Greyson Parrelli
ccaeb089ab Bump version to 4.47.1 2019-09-02 18:57:14 -04:00
Greyson Parrelli
3240ba3a55 Updated language translations. 2019-09-02 18:53:57 -04:00
Alan Evans
bb7be66efe Fix stickers in image editor.
Fixes #9012
2019-09-02 18:16:30 -04:00
Alan Evans
8814a0d949 Update invite icon. 2019-09-02 18:15:27 -04:00
Greyson Parrelli
9aa488223f Bump version to 4.47.0 2019-08-31 11:28:34 -04:00
Greyson Parrelli
30d9233365 Updated language translations. 2019-08-31 11:28:34 -04:00
Curt Brune
3ac540c687 Add ringrtc support.
RingRTC provides Signal Messenger applications with a common interface
for video and voice calling services built on top of WebRTC.
2019-08-31 07:54:47 -07:00
Alan Evans
8d561ead21 Stickers in image editor. 2019-08-30 17:45:35 -04:00
Alan Evans
15b650382e Fullscreen media preview on tap. 2019-08-30 17:31:59 -04:00
Oscar Mira
110a40592b Make E164 log scrubber redact numbers of length >= 7. 2019-08-30 08:20:21 -04:00
Alan Evans
d0ce4ff032 Lottie play pause animation. 2019-08-29 11:57:41 -04:00
Alan Evans
85c9a9050a Add an invite button in the new conversation screen. 2019-08-29 11:07:33 -04:00
Greyson Parrelli
af42d5b671 Create system for job migrations. 2019-08-29 11:07:33 -04:00
Greyson Parrelli
9580bb0a38 Renamed WorkManager migration package. 2019-08-29 11:07:33 -04:00
Greyson Parrelli
9abb167874 Bump version to 4.46.2 2019-08-28 10:27:40 -04:00
Greyson Parrelli
cd13676a21 Updated language translations. 2019-08-28 10:26:29 -04:00
Greyson Parrelli
1dd59bee36 Use raw versionCodes in web apk json.
We missed a spot when transitioning to split apk versions. It ended up
breaking the update prompt for web apks.

This will put the raw universal apk version in the json file, which
should put us back on the right track.

Fixes #8936
2019-08-28 10:26:29 -04:00
Greyson Parrelli
59bcbe592b Revert "Add ringrtc support"
This reverts commit 7f0a7b0c13.
2019-08-28 09:16:51 -04:00
Greyson Parrelli
0917e17c69 Fix bug where you couldn't reply with media.
Fixes ##8999
2019-08-27 09:40:32 -04:00
Greyson Parrelli
eb249ca69a Bump version to 4.46.1 2019-08-24 15:18:22 -04:00
Greyson Parrelli
97d1175915 Updated language translations. 2019-08-24 14:35:35 -04:00
Alan Evans
2141f1073e Ensure memory file descriptor is available. 2019-08-24 14:30:02 -04:00
Greyson Parrelli
c6287547a3 Improve video transcoding exception handling.
Previously, we'd crash if we couldn't find the attachment at runtime,
but that's not uncommon if someone deletes before sending. Now we just
fail.

Also, previously we'd fail if we couldn't create a memory file. Now we
will only fail if the attachment is >100mb (same as the other video
failures).
2019-08-24 09:27:08 -04:00
Greyson Parrelli
9257c6ddf3 Fix failure propogation in longer job chains. 2019-08-24 09:09:20 -04:00
Greyson Parrelli
480748e1aa Bump version to 4.46.0 2019-08-23 10:01:29 -04:00
Greyson Parrelli
c015687951 Updated language translations. 2019-08-23 10:01:06 -04:00
Alan Evans
1bd1e9cc65 Read optional video display dimensions.
Add RequiresApi annotations.

Fixes #8982
2019-08-22 10:15:56 -04:00
Alan Evans
c4a4374465 RTL screen animations.
RTL fix Profile name in Settings.
2019-08-22 10:15:56 -04:00
Alan Evans
90681d47f8 Fix first item sticky header. 2019-08-22 10:15:56 -04:00
Alan Evans
936be693ce Enlarge some home touch targets and add search content description. 2019-08-22 10:15:56 -04:00
Alan Evans
a32b875587 Support disabled animations for accessibility. 2019-08-22 10:15:56 -04:00
Alan Evans
4e6798e38e Fix occasional double scroll bar on fast scroller. 2019-08-22 10:15:56 -04:00
Alan Evans
6352f7baf4 Update item design in contact selection. 2019-08-22 10:15:56 -04:00
Alan Evans
aac9725adc Move shared attachment job generation code to parent class. 2019-08-22 10:15:56 -04:00
Alan Evans
900371bb30 Call answer button listens to accessibility changes. 2019-08-22 10:15:56 -04:00
Alan Evans
a58f564d1e Escape string within Full Text Search.
Fixes #8975
2019-08-22 10:15:56 -04:00
Alan Evans
942154a61f Separate compression job. 2019-08-22 10:04:23 -04:00
Curt Brune
7f0a7b0c13 Add ringrtc support
Initial commit of the RingRTC Java interface implementation.

The implementation lives in an external .aar with the package
org.signal.ringrtc.

The package provides two high level objects of interest
=======================================================

org.signal.ringrtc.CallConnection -- represents the session of a call,
very similar to WebRTC's PeerConnection.

org.signal.ringrtc.CallConnectionFactory -- creates CallConnection
objects, very similar to WebRTC's PeerConnectionFactory.

The implementation interfaces with the Android application in a few
places:
==================================================================

src/org/thoughtcrime/securesms/ApplicationContext.java -- RingRTC
library initialization at application startup.

src/org/thoughtcrime/securesms/service/WebRtcCallService.java -- Call
creation and state machine.

src/org/thoughtcrime/securesms/ringrtc -- this package implements
interface classes needed by ringrtc and a CallConnectionWrapper helper
class.

The two interfaces needed so far are:

  ringrtc/Logger.java
  ringrtc/SignalMessageRecipient.java

The logger is self-explanatory, but SignalMessageRecipient is a little
more involved.  SignalMessageRecipient encapsulates the Signal-Android
notion of "Recipient" and the mechanism for sending Signal Messages
related to audio/video calling.

The CallConnectionWrapper class is clone of the original
org.thoughtcrime.securesms.webrtc.PeerConnectionWrapper, suitably
modified to match the CallConnection interface.

This class continues to handle the Camera switching APIs, with that
portion of the code remaining unmodified from the original.

CallConnectionFactory Details
=============================

The primary public methods:

initialize() -- initialize the WebRTC library and RingRTC library.
The WebRTC initialization is lifted from the original Signal-Android
code.

createCallConnectionFactory() -- creates a CallConnectionFactory
object.  Internally it creates a WebRTC PeerConnectionFactory object
and a RingRTC CallConnectionFactory object.

dispose() -- tears down the CallConnectionFactory object, including
the internal PeerConnectionFactory and RingRTC CallConnectionFactory.

createCallConnection() -- creates a CallConnection object, connecting
that with an application controlled CallConnection.Observer object.

This function takes a CallConnection.Configuration object to link the
CallConnection object with some application provided services, like
sending Signal protocol messages.

CallConnection Details
======================

This object is a subclass of WebRTC's PeerConnection class.

The primary public methods and objects:

CallConnection.Configuration
----------------------------

Configuration object used to parameterize a call.  Notable members:

- SignalServiceMessageSender messageSender
- long callId
- org.signal.SignalMessageRecipient recipient

The 'accountManager' is used to fetch public information from the Signal
service, specifically used here to obtain the public Signal TURN
server details.

The 'callId' is a 64-bit pseudo-random number generated when the call
is initiated, used to identify the call through out its lifetime.

The "recipient' is an implementation of the
org.signal.SignalMessageRecipient interface, which encapsulates the
sending of Signal service messages to a recipient (remote peer) using
existing Signal protocol data structures.

The native library needs to be able to send Signal messages via the
service, but it does not have a native implementation to do so.
Instead the native code calls out to the client for sending Signal
messages.  To accomplish this, the client implements the
org.signal.SignalMessageRecipient interface and passes an instance of
that in a CallConnection.Configuration object.

CallConnection
--------------

dispose() -- tears down the CallConnection object, including the
internal PeerConnection and RingRTC CallConnection.

sendOffer() -- initiates a call to a remote recipient.  This is the
beginning of an outbound call.

validateResponse() -- checks an offer response recipient against the
originating call details.

handleOfferAnswer() -- handles the receipt of answer, which was a
response from an originating offer.

acceptOffer() -- accept an offer from a remote participant.  This is
the begin of an incoming call.

answerCall() -- invoked when the call is completely established and
online.

hangUp() -- hang up the connection and shut things done.  This is the
end of the call.

sendBusy() -- send the remote side an indication that the local side
is already in a call and the line is busy.

sendVideoStatus() -- send the current state of the local camera video
stream to the remote side.

CallConnection.Observer
-----------------------

Observer object, used by the RingRTC library to notify the client
application of important events and status changes.  Similar in spirit
to WebRTC's PeerConnection.Observer.

Observer callbacks come in three flavors:

- state change notifications,
- on stream notifications
- errors conditions

For state notifications, the callback contains the callId, the
recipient and a CallConnection.CallEvent type.

For streams, the callback contains the callId, the
recipient and a org.webrtc.MediaStream.

For errors, the callback contains the callId, the recipient and an
exception type.  The currently thrown exceptions include:

- UntrustedIdentityException
- UnregisteredUserException
- IOException

Signed-off-by: Curt Brune <curt@signal.org>

Updates to support ringrtc-android version 0.1.0.

* simplify logging interface

It is no longer necessary for the application to specify a Log object
as the library can log via the NDK directly.

* improve error handling and notification

In a number of places where ringrtc errors could occur, no
notification was ever sent to the user, nor was the UI cleaned up.  It
would look like the app was in hung state.

This patch updates these situations to send the WebRtcViewModel a
NETWORK_FAILURE message.

* update handleIncomingCall() for lockManager and notification

During the conversion to RingRTC, the implementation of
handleIncomingCall() missed a couple of things:

-- updating the Phone state with the lockManager
-- sending a message to the viewModel

* log the callId in various handler methods

For debugging purposes it is very handy to have the callId present in
the log during the various call handler methods.

Signed-off-by: Curt Brune <curt@signal.org>
2019-08-22 10:04:23 -04:00
Alan Evans
37bcac40bb URL encoded scrubber.
* Replace scrubber and tests.

* Improves email regex performance.
2019-08-22 10:04:23 -04:00
Greyson Parrelli
02ea99254a Reset message receiver upon REST failure.
We've been seeing a lot of socket timeouts on REST requests under
certain conditions. The issue seems to be a problem with the OkHttp
client. Through testing, I've seen that resetting the receiver and
retrying again seems to resolve most issues.
2019-08-22 10:04:23 -04:00
Greyson Parrelli
3849b46f0a Refactor ApplicationDependencies. 2019-08-22 10:04:23 -04:00
Greyson Parrelli
116bd41c63 Use EmojiTextView in the sticker preview. 2019-08-22 10:04:23 -04:00
Greyson Parrelli
457ad4c607 Added a central system for message retrieval. 2019-08-22 10:04:23 -04:00
Greyson Parrelli
d0a9bd4c6d Create a new system for application-level migrations. 2019-08-22 10:04:23 -04:00
Greyson Parrelli
d3bed549f2 Switch back to storing incoming messages in PushDatabase.
On a Pixel 3, this adds ~30-40ms of delay, but it's going to be a
requirement for upcoming migrations.
2019-08-22 10:04:23 -04:00
Alan Evans
fe1aa016b9 Refactor group operations. 2019-08-22 10:04:17 -04:00
Greyson Parrelli
af55cb0c03 Bump version to 4.45.2 2019-08-05 15:48:22 -04:00
Greyson Parrelli
714eaa62a8 Updated language translations. 2019-08-05 15:48:22 -04:00
Greyson Parrelli
5038f49487 Fix issue with sticker preview sizing. 2019-08-05 15:48:22 -04:00
Greyson Parrelli
57835dc8f1 Update view-once message behavior. 2019-08-05 15:46:43 -04:00
Alan Evans
3439eb4536 Replace disk icon with download icon. 2019-08-05 12:13:15 -04:00
Alan Evans
929ee04814 Handle missing video duration.
On EncodingException, or no duration metadata found, when video < 100MB, continue with send.
2019-08-05 10:48:18 -04:00
Greyson Parrelli
9d98a779a8 Bump version to 4.45.1 2019-08-02 17:01:11 -04:00
Greyson Parrelli
5a23ddeaf4 Updated language translations. 2019-08-02 17:01:11 -04:00
Greyson Parrelli
7ae5159194 Downgrade some job exceptions.
Runtime exceptions were being thrown where it'd be safer to either
ignore it or throw a checked, failing exception.
2019-08-02 17:00:48 -04:00
Alan Evans
bdf93af3db Clear outstanding glide requests.
Fixes #8967
2019-08-02 16:00:22 -04:00
Alan Evans
dcc147d994 Reduce frame timeout crashes. 2019-08-02 15:57:20 -04:00
Greyson Parrelli
19b2658414 Fix Job.Result logging. 2019-08-01 16:42:59 -04:00
Greyson Parrelli
1c4833f3b4 Bump version to 4.45.0 2019-08-01 10:40:52 -04:00
Greyson Parrelli
e8ca673bf8 Updated language translations. 2019-08-01 10:40:52 -04:00
Alan Evans
5a614faee1 Camera content descriptions and allow camera capture in talk back. 2019-08-01 10:40:52 -04:00
Greyson Parrelli
af8042c5f4 Use proper icon and theme for camera button. 2019-08-01 10:40:52 -04:00
Greyson Parrelli
a46e7541d0 Switch from contentUris to fileUris in the media gallery.
Android Q encounters massive slowdown when using contentUris for media.
Switching to fileUris (which we're already doing for the folder
thumbnail) gets us back to the expected performance level.
2019-08-01 10:40:37 -04:00
Alan Evans
a6890fc8dd Video playback on TextureView and DepthPageTransformer. 2019-08-01 08:45:29 -04:00
Alan Evans
0c0d7aeead Remove old pre-v9, pre-v11, and pre-v12 assets. 2019-08-01 08:45:29 -04:00
Ellen Poe
874697f6e5 Added content descriptions for some important controls.
Fixes #8728

Co-authored-by: Alan Evans <alan@signal.org>
2019-08-01 08:45:29 -04:00
Alan Evans
e0d1987445 Accessible call answering/rejecting and content descriptions. 2019-08-01 08:45:29 -04:00
Greyson Parrelli
17400020b7 Allow RuntimeExceptions thrown by Jobs to crash. 2019-07-31 09:54:46 -04:00
Alan Evans
e8e80e5d05 Transcode video during attachment upload. 2019-07-31 09:54:46 -04:00
Alan Evans
f9946083dd Video transcoding. 2019-07-30 14:09:33 -04:00
Greyson Parrelli
453f93a84f Bump version to 4.44.7 2019-07-28 10:20:38 -04:00
Greyson Parrelli
a8c47b5091 Guard media reads with a permissions check. 2019-07-28 10:20:04 -04:00
Greyson Parrelli
78a818eba6 Bump version to 4.44.6 2019-07-28 10:05:58 -04:00
Greyson Parrelli
4ca90374b9 Fix bug displaying an empty camera contacts search result. 2019-07-28 10:05:22 -04:00
Greyson Parrelli
a5fbcffa14 Bump version to 4.44.5 2019-07-26 15:38:10 -04:00
Greyson Parrelli
a21ec2f166 Updated language translations. 2019-07-26 15:37:39 -04:00
Greyson Parrelli
cdfb88ea18 Some tweaks to the camera FAB. 2019-07-26 15:29:44 -04:00
Greyson Parrelli
1ec45fe364 Fix bug where search wasn't focused in contact select.
Introduced in c0a44c7fc3.
Apparently targetSdk 28 changed the view focus model.
We have to manually focus this particular view now.
2019-07-26 15:06:09 -04:00
Greyson Parrelli
60b5c82da8 Bump version to 4.44.4 2019-07-25 11:29:03 -04:00
Greyson Parrelli
f0af5743c4 Updated language translations. 2019-07-25 11:25:57 -04:00
Alan Evans
81930a6833 Fix push challenge, Event bus needs public. 2019-07-25 11:19:21 -04:00
Greyson Parrelli
278ee79df0 Update camera-first contact display. 2019-07-25 10:10:32 -04:00
Greyson Parrelli
7f2a758400 Require contacts permission for camera-first flow.
Fixes #8950
2019-07-25 10:04:03 -04:00
Greyson Parrelli
fc1c092cf0 Bump version to 4.44.3 2019-07-24 19:31:52 -04:00
Greyson Parrelli
f42a8cf962 Updated language translations. 2019-07-24 19:31:52 -04:00
Greyson Parrelli
27db9d06e4 Ensure media folder title is not null.
Fixes #8949
2019-07-24 19:31:52 -04:00
Greyson Parrelli
3d5cfb3c74 Remove some dead code. 2019-07-24 19:24:21 -04:00
Greyson Parrelli
df9186827c Fix some UI issues with view-once photo receive. 2019-07-24 19:24:16 -04:00
Greyson Parrelli
e0137706b2 Bump version to 4.44.2 2019-07-24 11:25:01 -04:00
Greyson Parrelli
99bcda8709 Updated language translations. 2019-07-24 11:25:01 -04:00
Greyson Parrelli
eddff07eb8 Fix TimedEventManager crash in a sane way.
Follow up on acb48752c.

Just have the subclass call that method to guarantee that everything is
initialized.
2019-07-24 11:24:52 -04:00
Greyson Parrelli
4e859a84ce UI tweaks in the media send flow. 2019-07-24 11:04:21 -04:00
Greyson Parrelli
8665dad867 Prevent clicks from passing through media rail. 2019-07-23 10:48:23 -04:00
Greyson Parrelli
c0996ed116 Bump version to 4.44.1 2019-07-23 09:48:16 -04:00
Greyson Parrelli
830e651fef Updated language translations. 2019-07-23 09:39:15 -04:00
Greyson Parrelli
acb48752ce Fix possible crash in RevealableMessageManager.
The crash happened because #getNextClosestEvent was called when
mmsDatabase was null, which would normally be impossible. However, it
seems implied that somehow #getNextClosestEvent was being called in the
parent constructor before the child class was fully initialized. That
would imply that the looper was called synchronously in some freak
scenario, but it's the only explanation. So I added a delay to the call
in the parent constructor.

```java
java.lang.NullPointerException:
  at org.thoughtcrime.securesms.revealable.RevealableMessageManager.getNextClosestEvent (RevealableMessageManager.java:40)
  at org.thoughtcrime.securesms.revealable.RevealableMessageManager.getNextClosestEvent (RevealableMessageManager.java:23)
  at org.thoughtcrime.securesms.service.TimedEventManager.lambda$scheduleIfNecessary$1 (TimedEventManager.java:44)
  at org.thoughtcrime.securesms.service.-$$Lambda$TimedEventManager$kZDO3F2WBQVtGx-SkAgEDt8jCeU.run (Unknown Source:2)
  at android.os.Handler.handleCallback (Handler.java:873)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loop (Looper.java:193)
  at android.os.HandlerThread.run (HandlerThread.java:65)
```
2019-07-23 09:39:15 -04:00
Greyson Parrelli
ba8597900a Fix NPE in MediaSendActivity. 2019-07-23 09:39:15 -04:00
Alan Evans
c1f0253aa3 Resolve MissingSuperCall lint. 2019-07-23 09:35:23 -04:00
Alan Evans
d70d82c5ea Make fragments public.
Fixes #8940
2019-07-23 08:56:07 -04:00
Greyson Parrelli
29b9d3f902 Fix crash when viewing "inbox zero" state. 2019-07-23 08:50:27 -04:00
Greyson Parrelli
1a85a9cb31 Fix media send HUD consistency issue.
Only affects people who have force-enabled revealable messages and then
gone back to a build that doesn't have it enabled.
2019-07-23 08:50:20 -04:00
Greyson Parrelli
ead6e6b2f3 Bump version to 4.44.0 2019-07-22 23:33:54 -04:00
Alan Evans
03b1eb4bd5 Prevent attempting to send push media messages to non-phone addresses.
Prevents crash loop in #8910
2019-07-22 23:16:01 -04:00
Greyson Parrelli
5c870ca8ea Prevent sending revealable messages in Note to Self.
Send support isn't enabled yet, but didn't want to forget about it.
2019-07-22 23:16:01 -04:00
Greyson Parrelli
965de16de1 Fix possible hangup with CellServiceConstraint.
On phones with no SIM card, if you manage to enqueue a job with a
CellServiceConstraint, the previous check we were using to check if
there was cell service could hang indefinitely on some devices.

This changes it to a fast check, which all constraints should be.
2019-07-22 23:16:01 -04:00
Greyson Parrelli
a210ef3136 Added ability to save image captures to external storage. 2019-07-22 23:16:01 -04:00
Greyson Parrelli
beaa86389d Implement camera-first capture flow.
This allows you to take a photo, then choose the recipients after. This
also makes it so we only upload the attachment once.
2019-07-22 23:15:50 -04:00
Greyson Parrelli
4fbb87b5b7 Created a new SectionedRecyclerViewAdapter. 2019-07-22 20:55:31 -04:00
Sebastian Kürten
76d1382d9a Fix some Javadoc typos 2019-07-22 20:55:31 -04:00
Alan Evans
79a142c1be Refactor media preview to use fragments. 2019-07-22 20:55:26 -04:00
Greyson Parrelli
dd66e22443 Ensure that camera captures have correct dimensions. 2019-07-18 16:10:59 -04:00
Greyson Parrelli
c77809fa90 Add support for view-once messages. 2019-07-18 16:10:10 -04:00
Greyson Parrelli
9f7bb69341 Update the media send flow with a persistent rail. 2019-07-18 11:04:14 -04:00
Alan Evans
b58faf4fd1 Change settings transitions. 2019-07-18 10:29:27 -04:00
Alan Evans
dbeb2b5330 Fix play button in RTL layouts. 2019-07-18 10:08:08 -04:00
Alan Evans
9574a19ec2 Signal service update 2.13.6.
Fixes #8901
2019-07-18 10:06:44 -04:00
Greyson Parrelli
73bb7873e1 Log FCM notification delay. 2019-07-17 17:07:32 -04:00
Greyson Parrelli
475c54213d Move from dagger to a service locator pattern. 2019-07-17 16:12:53 -04:00
Alan Evans
8d6f1341f1 Reduce resolution of image editor preview and make memory efficiencies.
Fixes #8929
2019-07-17 15:05:19 -04:00
Alan Evans
80d0ba31ca Specify locale in some String.formats. 2019-07-17 14:07:39 -04:00
Alan Evans
c7bfede74c Do not use Canvas size. 2019-07-17 14:07:20 -04:00
Greyson Parrelli
c902d17f98 Remove additional log information that is not necessary for debugging purposes. 2019-07-16 19:24:51 -04:00
Greyson Parrelli
6c31d656dd Fix queueKey for SmsSendJob. 2019-07-16 19:19:56 -04:00
Greyson Parrelli
34e8d5ac57 Ignore whitespace when determining if we show jumbomoji. 2019-07-15 16:02:51 -04:00
Greyson Parrelli
d636f37132 Updated Emoji icons. 2019-07-12 13:25:36 -04:00
Curt Brune
150a21bfa3 Update to WebRTC M75 2019-07-11 10:25:07 -04:00
Alan Evans
5b61c8ac18 Request a push challenge and supply to SMS and voice verification. 2019-07-10 16:59:08 -04:00
Alan Evans
d72d4c4c41 Update libsignal to 2.13.5 2019-07-10 15:19:23 -04:00
Greyson Parrelli
5a1464c069 Remove pending messages notification. 2019-07-10 14:38:28 -04:00
Greyson Parrelli
c2ec09f079 Remove AttachmentServer. 2019-07-03 19:04:17 -04:00
Greyson Parrelli
8a8817f8d3 Remove video player usage of AttachmentServer. 2019-07-03 19:04:17 -04:00
Greyson Parrelli
a5368b7ea9 Remove audio player usage of AttachmentServer.
Now that we use ExoPlayer, it's no longer needed.
2019-07-03 19:04:17 -04:00
Greyson Parrelli
d8a75d599d Update emoji. 2019-07-03 19:04:17 -04:00
Greyson Parrelli
f92e2bae4a Bump version to 4.43.8 2019-07-03 12:14:28 -04:00
Greyson Parrelli
19d15cb3e5 Updated language translations. 2019-07-03 12:14:28 -04:00
Greyson Parrelli
b66e0e7e32 Fix incorrect EditorModel being restored. 2019-07-03 11:51:22 -04:00
Alan Evans
4a1f39f4be Drop shadow below conversation title bar. 2019-07-03 11:41:13 -04:00
Greyson Parrelli
a17d3e1b47 Bump version to 4.43.7 2019-07-02 18:08:03 -04:00
Greyson Parrelli
f580128366 Fix camera crash on API 21. 2019-07-02 18:07:13 -04:00
Greyson Parrelli
18252712a5 Bump version to 4.43.6 2019-07-02 10:18:44 -04:00
Greyson Parrelli
ed5f5adc9b Prevented avatar read failures from crashing.
There are a handful of devices that refuse to use our AesGcmProvider,
and as a result they would crash with AssertionErrors when downloading
avatars. Still haven't found why, but for now, probably best to stop
these devices from crashing, since it puts them in a crash loop, and the
app is still usable without avatars.
2019-07-02 10:06:20 -04:00
Greyson Parrelli
4c30aa9f13 Fixed crash when setting your profile picture.
The version of the cropping library we were using had a targetSdk 28 bug
in it. Updating to the newest version fixes the problem.
2019-07-02 10:05:23 -04:00
Greyson Parrelli
3cdf17ccaa Bump version to 4.43.5 2019-07-01 17:15:46 -04:00
Greyson Parrelli
a8dbfd812d Fix issue with bad camera quality on some devices.
There appears to be a weird bucketing thing that happens on some
devices, where if you give them a resolution that's too small, it'll
default you to some potato resolution. So we just bumped it up to
1920x1920, and that seems to be working on my swath of devices.
2019-07-01 17:15:14 -04:00
Greyson Parrelli
ff3890cc12 Name threads more consistently. 2019-07-01 17:15:04 -04:00
Greyson Parrelli
9c196bd2d5 Fix issue where an empty file could be read forever.
If you gave the class an empty file, it would get back -1 for read(),
causing it to fall forever into negative values, never escaping the
existing conditions.

The side effect of this was weird corner cases where these infinite
reads could clog up threads, causing audio recordings to get blocked
and such.

Fixes #8898
2019-07-01 17:15:01 -04:00
Greyson Parrelli
4508aa7c35 Clean up logging. 2019-07-01 15:12:34 -04:00
Greyson Parrelli
f15a629731 Bump version to 4.43.4 2019-06-30 09:43:32 -04:00
Greyson Parrelli
857fda42c8 Fix audio playback on API 28 devices. 2019-06-30 09:41:58 -04:00
Greyson Parrelli
a5eb823a17 Avoid crash with Address parcelable.
There seems to be a bad implementation of Address parcelization that
pops up in certain scenarios. We can avoid it by just excluding it
from the parcel altogether.
2019-06-30 09:26:08 -04:00
Greyson Parrelli
31435128f4 Bump version to 4.43.3 2019-06-30 01:10:50 -04:00
Greyson Parrelli
6656077edc Updated language translations. 2019-06-30 01:10:34 -04:00
Greyson Parrelli
9974f6edf1 Correctly retrict BucketInfo API level. 2019-06-30 00:54:51 -04:00
Greyson Parrelli
6715a89a25 Disable default CameraX initializer.
CameraX was initializing Camera2 API stuff on API < 21, causing
crashes at boot. To handle this, we disable the default
ContentProvider initializer and initialize things ourselves for
appropriate API levels.
2019-06-30 00:54:42 -04:00
Greyson Parrelli
23b4a9b191 Bump version to 4.43.2 2019-06-28 19:17:35 -04:00
Greyson Parrelli
666cdeae6b Change compileSdk back to 28. 2019-06-28 19:16:27 -04:00
Greyson Parrelli
5d7ac81c4b Bump version to 4.43.1 2019-06-28 18:57:34 -04:00
Greyson Parrelli
93856ed8cf Fix some bookkeeping in CameraXFragment. 2019-06-28 18:57:04 -04:00
Greyson Parrelli
84fd1a9140 Bump version to 4.43.0 2019-06-28 18:39:32 -04:00
Greyson Parrelli
9949e5e3a5 Updated language translations. 2019-06-28 18:39:20 -04:00
Alan Evans
c0a44c7fc3 Target API 28. 2019-06-27 16:18:53 -04:00
Alan Evans
d9df1ec39e Preparing to target API 28. 2019-06-27 16:18:53 -04:00
Greyson Parrelli
abcd599ad8 Support Android Q call notifications. 2019-06-27 15:46:13 -04:00
Greyson Parrelli
8a3d0dde91 Bump compileSdk to 29. 2019-06-27 15:46:13 -04:00
Alan Evans
0f3de6c979 Check action mode is still valid.
Due to support library bug, onActionItemClicked can be called after finish/onDestroyActionMode so we can check for that by checking if our reference to the action mode is still valid.

Fixes #8891
2019-06-27 15:44:05 -04:00
Alan Evans
c089d6cd43 Allow multiple messages on the Generic Foreground Service. Show the oldest still active. 2019-06-27 12:18:52 -04:00
Alan Evans
cfcb9a8cdb Custom place picker to replace places SDK. 2019-06-27 12:14:54 -04:00
Matthias Riedl
83479d11b7 White text on dark theme pin input.
Fixed #7509
2019-06-27 11:28:59 -04:00
Greyson Parrelli
73b8f11b5a Improve camera capture with CameraX. 2019-06-26 18:11:49 -04:00
Greyson Parrelli
4593014d00 Bump version to 4.42.3 2019-06-24 12:55:02 -04:00
Greyson Parrelli
2cf5d57454 Updated language translations. 2019-06-24 12:54:37 -04:00
Greyson Parrelli
048d859881 Handle missing profile photos better.
There were a couple funny behaviors you could run into if you had a
contact that previously had a profile photo, but then removed it.
2019-06-24 12:36:05 -04:00
Greyson Parrelli
1df28c6564 Bump version to 4.42.2 2019-06-20 09:35:20 -04:00
Greyson Parrelli
f59e937006 Updated language translations. 2019-06-20 09:34:38 -04:00
Greyson Parrelli
5948b46ac7 Fix crash in AvatarImageView. 2019-06-20 09:27:58 -04:00
Greyson Parrelli
75b232bfdc Bump version to 4.42.1 2019-06-19 13:01:58 -04:00
Greyson Parrelli
9e6594cc0b Updated language translations. 2019-06-19 12:56:56 -04:00
Greyson Parrelli
7f85b61e89 Serialize RotateSignedPreKeyJob. 2019-06-19 12:56:56 -04:00
Alan Evans
1a32bc8232 Fix flickering avatar. 2019-06-19 12:29:15 -04:00
Greyson Parrelli
e603162ee7 Bump version to 4.42.0 2019-06-17 13:51:16 -04:00
Greyson Parrelli
08eca5d844 Updated language translations. 2019-06-17 13:51:16 -04:00
Greyson Parrelli
1d1dbcf9cd Fix RecyclerView lint errors. 2019-06-17 13:51:16 -04:00
Alan Evans
e6b107fa78 Add a content description for the inline add attachment button.
Fixes #8730
2019-06-17 13:51:16 -04:00
Alan Evans
284cca3e25 Prevent reply and mark as read abilities while Signal is locked.
Fixes #8874
2019-06-17 13:51:16 -04:00
Greyson Parrelli
1e0b0d926a Update libsignal to 2.13.4 2019-06-17 13:51:16 -04:00
Alan Evans
cb86be578b Handle accented characters in country name sorting.
Closes #8419
2019-06-17 13:51:16 -04:00
Alan Evans
b8bb2b78bd Query total memory just once.
Closes #8315
2019-06-17 13:51:16 -04:00
Alan Evans
6fceb25121 Take typing indicator into account when moving to quoted message.
Fixes #8858

And fixes same bug in search.
2019-06-17 13:51:16 -04:00
Alan Evans
0484047b4e Fix avatar removal. 2019-06-17 13:51:16 -04:00
Alan Evans
b9a10653f1 Image Editor - Multi-line text.
* Two pass rendering for text on top while editing.
2019-06-17 13:51:16 -04:00
Alan Evans
42a5504f0d Do not attempt to retrieve a profile when it is not a number.
Fixes #8855
2019-06-17 13:51:16 -04:00
Alan Evans
ae784db80d Video is initially disabled.
Fixes #8867
2019-06-17 12:52:42 -04:00
Alan Evans
f5cbf64ccf Fix initial visibility of scroll to bottom button.
Fixes #8862
2019-06-17 12:52:42 -04:00
Alan Evans
cecf16c595 Do not show contact address in subtitle. 2019-06-17 12:52:42 -04:00
Greyson Parrelli
fb4c9d3bf1 Improve message download reliability. 2019-06-17 12:52:43 -04:00
Greyson Parrelli
b5aa46bb67 Convert to AndroidX. 2019-06-17 12:52:42 -04:00
Greyson Parrelli
2dc68ed053 Switch from compile to implementation. 2019-06-17 12:52:42 -04:00
Greyson Parrelli
58d818923d Convert /res folder to webp.
Used lossless.
2019-06-17 12:52:42 -04:00
Greyson Parrelli
f4a6cd9c68 Convert emoji to webp.
Used lossy at 99% quality. No perceivable degredation at max font size,
but a pretty big improvement in size over lossless.
2019-06-17 12:52:42 -04:00
Greyson Parrelli
ed04535537 Convert image editor stickers to webp.
Used lossless.
2019-06-17 12:52:42 -04:00
Greyson Parrelli
7d4de3d4cb Removed some unused resources. 2019-06-17 12:52:42 -04:00
Greyson Parrelli
bc7cc306cb Bump version code to 4.41.6 2019-06-11 01:52:52 -04:00
Greyson Parrelli
5fa07e7094 Improve network reliability. 2019-06-11 01:51:46 -04:00
Greyson Parrelli
2b069fa63f Bump version to 4.41.5 2019-06-07 16:53:48 -04:00
Greyson Parrelli
99848f98d3 Sanitize sticker URL inputs. 2019-06-07 16:08:33 -04:00
Greyson Parrelli
967e9dd9a7 Disable sticker pack sharing. 2019-06-07 16:08:29 -04:00
Greyson Parrelli
4a720289e2 Bump version to 4.41.4 2019-06-06 10:56:52 -04:00
Greyson Parrelli
86d58e97cf Updated language translations. 2019-06-06 10:56:04 -04:00
Greyson Parrelli
bc6b7d15f1 Fix number formatting issue. 2019-06-06 10:41:37 -04:00
Greyson Parrelli
37f3b1f1ba Bump version to 4.41.3 2019-06-04 03:35:14 -04:00
Greyson Parrelli
262012887c Revert "Do not show contact address in subtitle."
This reverts commit dae0d30367.
2019-06-04 03:30:24 -04:00
Greyson Parrelli
d2cb6098fc Bump version to 4.41.2 2019-06-03 18:58:38 -04:00
Greyson Parrelli
1973fbf376 Fix programmatic VectorDrawable references.
Have to be careful with pre-21 devices. You have to use specific
compat loading methods with VectorDrawables or it'll crash.
2019-06-03 18:53:18 -04:00
Greyson Parrelli
bd63b9bec9 Updated language translations. 2019-06-03 14:50:15 -04:00
Greyson Parrelli
faa8c78f8d Clean up string for translators. 2019-06-03 14:46:51 -04:00
Greyson Parrelli
923016f12c Fix backup restore crash.
Fixes #8011
2019-06-03 14:06:55 -04:00
Greyson Parrelli
f469ce049d Bump version to 4.41.1 2019-05-30 20:22:54 -04:00
Greyson Parrelli
922f6d89e9 Render placeholders for unsupported messages. 2019-05-30 20:18:53 -04:00
Greyson Parrelli
4741a76f37 Add support for isRecipientUpdate flag. 2019-05-30 20:16:14 -04:00
Alan Evans
d197c57c55 Gradle witness - Do not hide resolution errors. 2019-05-30 20:22:03 -03:00
Greyson Parrelli
4ee60bf867 Fix bug where you couldn't forward albums. 2019-05-30 16:17:45 -04:00
Alan Evans
4351578838 Darker nav bar. 2019-05-30 17:11:56 -03:00
Greyson Parrelli
56c17e32f1 Bump version to 4.41.0 2019-05-30 01:17:07 -04:00
Alan Evans
48698381fc Dark theme navigation bar.
Fixes #8758
2019-05-30 01:10:10 -04:00
Alan Evans
5ad02f724c Enable 64-bit.
* Multiply version codes by 10 and add a code for each abi in order to generate different version codes for the play store.
2019-05-30 01:08:01 -04:00
Alan Evans
132c81b142 Bring Gradle Witness into repo.
- Api/Implementation compatible.
- Regex configuration name.
2019-05-30 01:08:01 -04:00
Greyson Parrelli
77e3cc40e0 Fix message bubble sizing with link previews and quotes.
Previously, quotes could extend beyond the width of the link preview
banner image. Now quotes will be constrained to the size of the link
preview banner image.
2019-05-30 01:08:01 -04:00
Greyson Parrelli
2a644437fb Add sticker support.
No sticker packs are available for use yet, but we now have the
latent ability to send and receive.
2019-05-30 01:08:01 -04:00
Alan Evans
d5fffb0132 Fix conversation menu colors. 2019-05-28 17:36:07 -03:00
Alan Evans
c8c152fe60 Lint - baseline of errors.
* qa task calls lint.
2019-05-28 17:36:07 -03:00
Alan Evans
350d1f47d3 Give conversation a standard navigate up button.
* Prevent a failing IDE preview.
2019-05-28 17:36:07 -03:00
Alan Evans
2ae42cb095 Hide local video when toggled on and off.
Fixes #8827
2019-05-28 17:36:07 -03:00
Alan Evans
dae0d30367 Do not show contact address in subtitle. 2019-05-23 16:57:50 -03:00
Alan Evans
e5f70bdbda End RTC call on incoming PSTN call.
* Hangs up when new device call is answered.
* Ensure not on a device call when starting a Signal call.
2019-05-23 16:56:05 -03:00
Alan Evans
156fe37a60 Get SubscriptionManager by name due to ContextCompat bug for API 22.
Fixes #8826
2019-05-23 11:38:24 -03:00
Alan Evans
56848fb83d Replace sgnl.link urls with a more readable url.
* Taken out random string, reverted to single "Let's switch".

#8767
2019-05-23 08:02:15 -03:00
Alan Evans
3cba8ab58a Keep system default SIM as a fallback for when no conversation default SIM.
Fixes #8452
2019-05-22 16:59:00 -03:00
Alan Evans
88dac70087 Lint - Custom Widgets extend Appcompat Widgets. 2019-05-22 14:02:21 -03:00
Alan Evans
9445555d66 Code analysis - address @NotNull/@Nullable issues. 2019-05-22 13:51:56 -03:00
Alan Evans
7db1588578 Do not assume phone number in conversation.
Fixes #8813
2019-05-22 13:30:27 -03:00
Alan Evans
0a7970ad0c Image Editor - Allow undoing back to the original state when exceeds the undo limit. 2019-05-22 13:28:02 -03:00
Alan Evans
10ad3fbf82 Lint - Use easily identifiable wake lock tags. 2019-05-20 13:24:34 -03:00
Alan Evans
95858898d7 Lint - avoid calling a restricted API. 2019-05-20 13:24:33 -03:00
Alan Evans
16c8cc88d7 Update visibility and icon of camera flip control in call.
Fixes #8221
2019-05-20 13:24:33 -03:00
Greyson Parrelli
c0c051bb66 Bump version to 4.40.4 2019-05-20 08:31:47 -07:00
Greyson Parrelli
bd0d1e842f Updated language translations. 2019-05-20 08:28:28 -07:00
Alan Evans
7f0c998b24 Image Editor - Further crop improvements.
* Thumb accuracy improved.
* When out of bounds from drag, try to fix by adjusting translation.
* Update undo state when listener changes.
2019-05-20 12:02:40 -03:00
Greyson Parrelli
5a4c2fc7b0 Bump version to 4.40.3 2019-05-17 15:56:50 -07:00
Alan Evans
456ba5fa02 Image Editor - Replace minimum scale, with minimum pixel count.
- Anti alias images.
- Minimum crop ratio of 15:1 or original image ratio.
2019-05-17 19:42:12 -03:00
Alan Evans
9de420fde6 Image Editor - On flip or rotate, ensure undo button visibility is updated. 2019-05-17 19:00:10 -03:00
Alan Evans
401e3687de Image Editor - when no sticker selected, go back to mode NONE. 2019-05-17 16:22:07 -03:00
Alan Evans
6777b3e0e6 Image Editor - Undo button visibility. 2019-05-17 16:15:27 -03:00
Greyson Parrelli
b5d37702f9 Switch back to the classic handling of landscape text entry.
Fixes #8814
2019-05-17 12:14:14 -07:00
Greyson Parrelli
320ea9eb4e Bump version to 4.40.2 2019-05-16 16:23:19 -07:00
Greyson Parrelli
86d8cde9b4 Updated language translations. 2019-05-16 16:22:32 -07:00
Alan Evans
bf759711ef Image Editor - Keep image within crop bounds.
* 4% of original pixels must be visible.
* The entire crop must be within the image.
* On release, try to scale crop area and image to fit if the crop is invalid.
* Undo to last valid position if that didn't work.
* Additionally, center thumbs now do not respect aspect ratio lock.
2019-05-16 15:52:15 -07:00
Alan Evans
068ffc2167 Image Editor - Allow undoing during croping. 2019-05-16 15:52:03 -07:00
Alan Evans
95304fe001 Image Editor - Remove initial text.
- Flashing cursor.
2019-05-16 15:51:56 -07:00
Alan Evans
2de64fca02 Image Editor - Fix double HUD animation on older devices. 2019-05-16 15:51:41 -07:00
Greyson Parrelli
3211dd2a8f Ignore resources.arsc in apkdiff.py
Due to a bug described in:

https://issuetracker.google.com/issues/110237303

Ordering of resources can be non-deterministic.

A comment on the issue indicates that this may be resolved in
Android Gradle Plugin 3.4. We should revisit when we update.
2019-05-11 10:46:25 -07:00
Peter Gerber
b6dc25a368 Reproducible build: Ensure apkdiff.py works properly again
The recent switch to Python3 (2ccdf0e396) introduced a regression
that led to file content no longer being compared:

   In compareEntries(), two generators/iterators are created:

     sourceInfoList      = filter(lambda sourceInfo: …, sourceZip.infolist())
     destinationInfoList = filter(lambda destinationInfo: …, destinationZip.infolist())

   Few lines later, those are exhausted:

     if len(sourceInfoList) != len(destinationInfoList):

   Yet another few lines later, the exhausted generator is used again:

     for sourceEntryInfo in sourceInfoList:
        …          # <-- unreachable

This is caused by behavioral differences between Python2 and Python3:

   user@z_signal:~$ python2
   Python 2.7.13 (default, Sep 26 2018, 18:42:22)
   [GCC 6.3.0 20170516] on linux2
   Type "help", "copyright", "credits" or "license" for more information.
   >>> f = filter(lambda i: i % 2 == 0, [0, 1, 2, 3, 4, 5, 6])
   >>> list(f)
   [0, 2, 4, 6]
   >>> list(f)
   [0, 2, 4, 6]
   >>>

   user@z_signal:~$ python3
   Python 3.5.3 (default, Sep 27 2018, 17:25:39)
   [GCC 6.3.0 20170516] on linux
   Type "help", "copyright", "credits" or "license" for more information.
   >>> f = filter(lambda i: i % 2 == 0, [0, 1, 2, 3, 4, 5, 6])
   >>> list(f)
   [0, 2, 4, 6]
   >>> list(f)
   []
   >>>
2019-05-11 10:43:51 -07:00
Greyson Parrelli
4e64242883 Bump version to 4.40.1 2019-05-10 13:08:49 -07:00
Greyson Parrelli
fcd3b501eb Revert "Enable 64-bit."
This reverts commit 67704612df.
2019-05-10 13:01:34 -07:00
Greyson Parrelli
62ed098687 Bump version to 4.40.0 2019-05-10 09:35:11 -07:00
Greyson Parrelli
2a93ddfb99 Updated language translations. 2019-05-10 09:19:39 -07:00
Alan Evans
387392f38b End align footer for long message bubble sent.
Fixes #8806
2019-05-10 12:41:15 -03:00
Alan Evans
5b298b4a04 Resize image in attempts to get it to fit into the maxImageSize bytes.
Fixes #8803
2019-05-10 12:16:19 -03:00
Alan Evans
cb78684282 Ensure push groups cannot have isForceSmsSelection set.
Fixes #8807
2019-05-10 12:13:59 -03:00
Alan Evans
67704612df Enable 64-bit. 2019-05-10 12:03:45 -03:00
Alan Evans
f3c8b51520 Web RTC M74 for 64-bit. 2019-05-10 12:03:16 -03:00
Alan Evans
b1057d63a1 Lint.
- Check for permissions.
- Fix Welsh positional format.
- Remove UIThread restriction.
- Asynchronous method does not need to be restricted to UIThread and there is no StaticFieldLeak to suppress.
- Fix or Ignore New API errors.
- Reduce severity of some errors from L10N.
2019-05-10 11:57:43 -03:00
Alan Evans
2ccdf0e396 Bring the Reproducible Builds instructions and script into repo. 2019-05-10 11:57:43 -03:00
Alan Evans
93e6ccb9e4 Replace image editor. 2019-05-10 11:57:43 -03:00
Alan Evans
196ef60a82 Update camera icons. 2019-05-09 14:38:28 -03:00
Alan Evans
478e5667b4 Update signal-service-android to 2.13.1 for 64-bit curve-25519. 2019-05-09 14:38:28 -03:00
Alan Evans
06ea000f42 Repeat count for format args of plural string.
Fixes #8724
2019-05-07 12:26:01 -03:00
Alan Evans
d1b8e77fdc Always show the SIM on the footer of a multi-SIM device, even if one SIM is disabled. 2019-05-07 12:25:11 -03:00
Alan Evans
8cf2654c5b Show reply method SMS/Signal and respect sticky.
Fixes #8792
2019-05-06 16:45:30 -07:00
Alan Evans
18531146f7 Update the sticky EventBus message to reflect changes in microphone enabled state.
Fixes #7827
2019-05-06 16:45:30 -07:00
Jeffrey Griffin
c274c1bb28 Eliminate noisy directory feedback
We observed IOExceptions loading the Contact Discovery IAS KeyStore. We will now throw an AssertionError upon any error
creating the IAS KeyStore, matching the behaviour for creation of the TrustStore used for the main Signal Service. NB: If
this assertion is hit, the user will not even be able to refresh their contacts with the old directory service.
2019-05-06 16:45:30 -07:00
Alan Evans
42a8522e98 Manually call the onPageSelected when entering page 0.
Fixes #7610
2019-05-06 16:45:30 -07:00
Greyson Parrelli
960e165c7d Bump version to 4.39.4 2019-05-06 15:51:10 -07:00
Greyson Parrelli
eab23a9e66 Fixed issue where giphy results weren't loading. 2019-05-06 15:48:43 -07:00
Greyson Parrelli
c7b626082c Bump version to 4.39.3 2019-05-06 12:31:59 -07:00
Greyson Parrelli
59f362495a Add additional checks around link preview domains.
We never make requests to non-whitelisted domains, but there were
situations where some links would redirect to non-whitelisted domains,
which would hit a final failsafe that resulted in a crash.

To prevent this, we detect bad redirects earlier and fail more
gracefully.

Fixes #8796
2019-05-06 12:25:53 -07:00
Greyson Parrelli
6c44437c6f Bump version to 4.39.2 2019-05-02 15:38:25 -07:00
Greyson Parrelli
fed8ae68e9 Updated language translations. 2019-05-02 15:32:44 -07:00
Greyson Parrelli
934a2a67bc Fix some keyboard issues in landscape. 2019-05-02 14:32:53 -07:00
Greyson Parrelli
05345b8582 Remove some unnecessary logging. 2019-05-02 14:32:53 -07:00
Greyson Parrelli
cef5de2be4 Removed unnecessary WorkManager relic. 2019-05-02 14:32:53 -07:00
Greyson Parrelli
7b4299d5da Bring back conscrypt, improve provider initialization ordering. 2019-05-02 14:32:48 -07:00
Alan Evans
ec20b0e0e3 Fallback to SIM index.
#8725
2019-05-02 18:16:59 -03:00
Greyson Parrelli
ff1531b836 Bump version to 4.39.1 2019-05-01 08:42:49 -07:00
Greyson Parrelli
1675c8a79a Updated language translations. 2019-05-01 08:42:44 -07:00
Greyson Parrelli
bb90987e7c Fix potential crash when retrieving SIM list. 2019-05-01 08:36:24 -07:00
Greyson Parrelli
ecea6abeb6 Temporary revert (again) to fix an avatar retrieval issue.
This reverts commit 77524ae1f2.
2019-05-01 08:25:14 -07:00
Greyson Parrelli
4a2f3136c6 Bump version to 4.39.0 2019-04-30 12:51:58 -07:00
Greyson Parrelli
d12b02fac5 Updated language translations. 2019-04-30 12:51:58 -07:00
Greyson Parrelli
1e564b6ad1 Fix exponential backoff retry limits. 2019-04-30 12:51:58 -07:00
Alan Evans
c77daa8226 Display carrier or number if any SIM descriptions clash.
Fixes #8725
2019-04-30 12:51:58 -07:00
Alan Evans
fa35814344 Disable transport disables all matching TransportOptions.
Fixes #8744
2019-04-30 12:51:58 -07:00
Alan Evans
48efcaa785 Do not list SIMs that are not ready.
Fixes #8426
2019-04-30 12:51:58 -07:00
Greyson Parrelli
f3f6cc87d9 Request a small chunk instead of HEAD for images of unknown size. 2019-04-30 12:51:58 -07:00
Greyson Parrelli
29cdb5290b Make headers for giphy requests random sizes. 2019-04-30 12:51:58 -07:00
Greyson Parrelli
77524ae1f2 Revert "Temporary revert to fix an avatar retrieval issue."
This reverts commit 267bc32e23.
2019-04-30 12:51:58 -07:00
Greyson Parrelli
30ba9d7e27 Improve CDN reliability. 2019-04-30 12:51:58 -07:00
Alan Evans
9652fd2844 Update MMS configs. 2019-04-30 12:51:58 -07:00
Francois Blackburn
d2ece1c1f2 Add MI 5 to hardware AEC blacklist 2019-04-30 12:51:57 -07:00
Arnt Gulbrandsen
8bdc257963 Avoid hardware echo cancellation for Fairphone FP2
The issue has as been confirmed by me with the stock ROM as of November
2018, and other users have complained for almost 18 months, see
https://forum.fairphone.com/t/fnord/28849 and
https://bugtracker.fairphone.com/project/fairphone-fairphone-os-android-6/issue/77
2019-04-30 12:51:57 -07:00
Kevin Mark
88f9ec313f Close SQL statement, preventing finalizer crashes
This will stop instances of the following from occuring in the logs
on SMS migration:

W/SQLiteCompiledSql: Releasing statement in a finalizer. Please ensure
that you explicitly call close() on your cursor: INSERT INTO sms
(address, person, date_sent, date, protocol, read, status, type,
reply_path_present,
    net.sqlcipher.database.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
        at net.sqlcipher.database.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:62)
        at net.sqlcipher.database.SQLiteProgram.<init>(SQLiteProgram.java:109)
        at net.sqlcipher.database.SQLiteStatement.<init>(SQLiteStatement.java:39)
        at net.sqlcipher.database.SQLiteDatabase.compileStatement(SQLiteDatabase.java:1647)
        at org.thoughtcrime.securesms.database.SmsDatabase.createInsertStatement(SmsDatabase.java:767)
        at org.thoughtcrime.securesms.database.SmsMigrator.migrateConversation(SmsMigrator.java:166)
        at org.thoughtcrime.securesms.database.SmsMigrator.migrateDatabase(SmsMigrator.java:210)
        at org.thoughtcrime.securesms.service.ApplicationMigrationService$ImportRunnable.run(ApplicationMigrationService.java:159)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
        at java.lang.Thread.run(Thread.java:764)

We aren't closing Statement objects before the finalizer on those
objects runs. When the GC runs, we'll get warnings like the above
which alert us to the fact that these objects are being automatically
closed for us in the finalizer, but that this is suboptimal behavior.

If we leave too many Statement (or Cursor) objects to be closed in
their finalizers, when the GC runs, it'll take longer than 10 seconds
to close them all and Android will kill the app. This 10 second limit
is hardcoded and we can only try to avoid it. A crash will look like:

java.util.concurrent.TimeoutException: net.sqlcipher.database.SQLiteCompiledSql.finalize() timed out after 10 seconds
    at java.lang.Object.wait(Native Method)
    at java.lang.Thread.parkFor$(Thread.java:1220)
    at sun.misc.Unsafe.park(Unsafe.java:299)
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:810)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:844)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1173)
    at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:196)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:257)
    at net.sqlcipher.database.SQLiteDatabase.lock(SQLiteDatabase.java:553)
    at net.sqlcipher.database.SQLiteCompiledSql.releaseSqlStatement(SQLiteCompiledSql.java:106)
    at net.sqlcipher.database.SQLiteCompiledSql.finalize(SQLiteCompiledSql.java:152)
    at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:202)
    at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:185)
    at java.lang.Thread.run(Thread.java:818)

I was able to replicate the above crash consistently on a
Samsung Galaxy S7 edge when importing well over 100k SMS messages.
But as soon as I attached a debugger the crash did not persist. I
assume this is because of some VM-level interactions between the two
and did not investigate further after fixing it.

I do not have access to the stack trace for issue #7953 but this
could potentially resolve it. The crash is identical to that in #7477
but this patch is for SMS migration not restoring from a backup. I
was not able to replicate the crash on restoring a >100k message
backup.
2019-04-30 12:51:57 -07:00
Michael Walker
8ad5126408 Add pinterest domain and asset domains for link preview support 2019-04-30 12:51:57 -07:00
Greyson Parrelli
1e27847015 Update link preview meta tag regex. 2019-04-30 12:51:57 -07:00
Greyson Parrelli
e67eca77ff Prevent landscape text editing from taking over the screen.
It used to be that we let Android do the default behavior of
full-screening the EditText when in landscape, but honestly I
don't know who prefers that. So I've turned it off.

Fixes #8769
2019-04-30 12:51:52 -07:00
Greyson Parrelli
ba46a9d81a Bump version to 4.38.3 2019-04-28 11:44:23 -07:00
Greyson Parrelli
49cccc6927 Update job logging. 2019-04-28 11:44:17 -07:00
Greyson Parrelli
5977e9141d Wrap transactions in try-finally. 2019-04-28 11:42:02 -07:00
Greyson Parrelli
c0982293bf Fix bug where sticky SMS setting wasn't respected.
Fixes #8783
2019-04-28 11:34:56 -07:00
Greyson Parrelli
ae6ef62160 Bump version to 4.38.2 2019-04-16 10:56:59 -04:00
Greyson Parrelli
d95b08d4fd Updated language translations. 2019-04-16 10:51:25 -04:00
Greyson Parrelli
128da6db04 Fix crash in backup restore related to sqlite_sequence.
The new JobManager stuff created a table that had an
auto-incrementing ID, which was incorrectly being backed
up and restored, causing a crash. Now we skip it on both
import and export.
2019-04-16 10:10:02 -04:00
Greyson Parrelli
2701607810 Reduce the possible number of unique jobs to avoid crash.
Some devices have a limit of 100 unique JobScheduler jobs.
Previously we allowed up to 1,000. Given that we just need
_some_ job running, I lowered the limit to 75 to give us
some head room.
2019-04-16 09:52:12 -04:00
Greyson Parrelli
4055fe183b Bump version to 4.38.1 2019-04-15 15:45:16 -04:00
Greyson Parrelli
1c47812877 Fix crash when migrating read receipt jobs.
Fixes #8764
2019-04-15 15:45:16 -04:00
Greyson Parrelli
060bed8559 Bump version to 4.38.0 2019-04-15 10:59:38 -04:00
Greyson Parrelli
4a3c173adb Migrated to new JobManager. 2019-04-15 10:56:26 -04:00
Greyson Parrelli
8cf3ba424a Trim long text before displaying if necessary.
Fixes #8759
2019-04-15 10:13:35 -04:00
Alan Evans
9c40de5bf1 Save the SMS setting on a per-conversation basis. 2019-04-15 10:13:35 -04:00
Greyson Parrelli
11a2ed0743 Increase reliability of locally logging crashes.
Exception logging tends to be race-y, so now we block and wait
for all logs to be written before continuing with the crash.
2019-04-10 12:53:55 -04:00
Alan Evans
01a9931d92 Do not use background threads for download UI events. 2019-04-10 13:13:10 -03:00
Greyson Parrelli
38bcc6c293 Long text detail view now respects text size preference.
Fixes #8747
2019-04-09 09:40:32 -04:00
Greyson Parrelli
bceb9b4972 Fixed quotes not being cleared when forwarding.
Fixes #8748
Fixes #7935
2019-04-09 09:35:47 -04:00
Alan Evans
ecdc285378 Fix unresponsive button on image send with text in landscape.
Fixes #8575
Closes #8638

Co-authored-by: Jakub Gregorek <jg@inthemeadow.net>
2019-04-08 07:45:16 -03:00
Greyson Parrelli
6d111e5f68 Bump version to 4.37.2 2019-04-04 23:18:30 -04:00
Greyson Parrelli
9aed2343c1 Attempt to resolve connectivity problems for some users. 2019-04-04 18:10:41 -04:00
Greyson Parrelli
733d54e339 Linkify links in long message view.
Fixes #8735
2019-04-04 10:09:32 -04:00
Greyson Parrelli
267bc32e23 Temporary revert to fix an avatar retrieval issue.
This reverts commit 8aa185070b.
2019-04-03 16:32:52 -04:00
Greyson Parrelli
7acb4973d8 Bump version to 4.37.1 2019-04-01 17:29:26 -04:00
Alan Evans
39ba8c2ad3 Remove armeabi from splits. 2019-04-01 17:43:10 -03:00
Alan Evans
621ac62c7e Add red flashing voice note microphone. 2019-04-01 17:42:57 -03:00
Greyson Parrelli
652306edd0 Bump version to 4.37.0 2019-03-29 09:55:09 -07:00
Alan Evans
b9b4dccff4 ABI splits. 2019-03-28 17:21:57 -03:00
Alan Evans
ce6d2d9c69 MMS image quality.
Fixes #8590
- Scale image larger within the dimensions.
- Apply a minimum dimension of 1024.
2019-03-28 15:14:06 -03:00
Alan Evans
e842f78457 Voice Note Locking.
Limit of 60 minutes, after which it's cancelled.
2019-03-28 15:04:38 -03:00
Alan Evans
cab3657ab0 ABI info for device. 2019-03-26 17:29:08 -03:00
Alan Evans
a7aa980e58 New dynamic locale system.
- Fixes #7619
2019-03-26 17:08:05 -03:00
Alan Evans
6a0a419f0c Add self to MMS group.
- Fixes #7683
- Closes #8296
2019-03-26 17:07:04 -03:00
Greyson Parrelli
94e8303022 Remove dead code from ScribbleHud/Fragment. 2019-03-22 12:47:16 -07:00
Greyson Parrelli
ccfcfa71df Enable the sending of long messages. 2019-03-22 12:28:41 -07:00
Alan Evans
cfdcd61e51 Local build quality assurance task. 2019-03-21 11:19:07 -07:00
Greyson Parrelli
b89c20ff40 Switch to 'start' and 'end' instead of 'left' and 'right'.
We can do this now that our minSdk is 19.
2019-03-21 11:19:06 -07:00
Greyson Parrelli
8caaf057e8 Remove unnecessary pre-19 code branches.
Now that our minSdk is 19, we can remove a lot of old code paths that
only ran pre-19.
2019-03-21 11:19:06 -07:00
Moxie Marlinspike
a52c295a38 Simplify access to SecureRandom
This shouldn't matter at all, but it's more "correct," and shows
my age less.
2019-03-21 11:19:06 -07:00
Moxie Marlinspike
8aa185070b Update libsignalservice to 2.13.0
- Eliminate the explicit spongycastle dependency. All access to
  primitives is done through the JCE interfaces now, which allows
  us to use a secure native-backed provider like conscrypt.

- Use conscrypt for our default security provider. This gives us
  fast TLS 1.2 and 1.3 support on all devices, even before they
  had platform support (like 4.4).

- Update minSdk to 18. Unfortunately the JCE interfaces for GCM
  primitives are JDK 7+ (!) only, which became supported by Android
  at 18.
2019-03-21 11:19:06 -07:00
Alan Evans
de60d4d37f 64-bit native utils.
* Update Application.mk.
* 64-bit libnative-utils
* New build of x86 and armeabi-v7a libnative-utils.
* Update to API19.
2019-03-21 11:19:06 -07:00
Alan Evans
6e5658431b Remove armeabi. 2019-03-21 11:19:06 -07:00
Greyson Parrelli
6df5457305 Bump minSdk to 19. 2019-03-21 11:19:06 -07:00
Greyson Parrelli
fd50b38630 Bump version to 4.36.2 2019-03-21 11:02:12 -07:00
Alan Evans
d41b24f9ae Fix "multiple substitutions specified in non-positional format" lint. 2019-03-21 11:02:12 -07:00
Greyson Parrelli
aa5e32f0ee Add back the highlight and sticker tools. 2019-03-21 11:02:07 -07:00
Greyson Parrelli
749d096931 Updated language translations. 2019-03-21 09:57:12 -07:00
Greyson Parrelli
8e86c7d81a Fix media button visibility issue on older Android versions.
Turns out setVisibility doesn't work unless you call clearAnimation()
first on older Android versions. Isn't that grand?
2019-03-21 00:11:01 -07:00
Greyson Parrelli
148cfd1b53 Fix crash when single-selecting large media. 2019-03-20 23:01:32 -07:00
Greyson Parrelli
93c1277fd0 Fix voice note button clipping. 2019-03-20 22:25:19 -07:00
Greyson Parrelli
23e069ffa8 Bump version to 4.36.1 2019-03-19 19:27:29 -07:00
Greyson Parrelli
6e7fab40ac Guard against OOB moves in media send flow. 2019-03-19 18:38:06 -07:00
Greyson Parrelli
8a7cac7c03 Switch gallery to permanent dark theme. 2019-03-19 18:38:06 -07:00
Greyson Parrelli
932e7b4af5 Fix button flicker when selecting single media item. 2019-03-19 18:38:02 -07:00
Greyson Parrelli
2f8a7fa296 Bounce the media select button when selecting media. 2019-03-19 15:42:08 -07:00
Greyson Parrelli
5e6f71cd32 Show selection order in multi-select. 2019-03-19 15:42:08 -07:00
Greyson Parrelli
ce0058864f Make BlobProvider write to disk on a background thread.
Otherwise we hit some weird blocking issues with voice note recording.
2019-03-19 15:34:04 -07:00
Greyson Parrelli
5a8753de85 Fix group string. 2019-03-19 10:34:56 -07:00
Greyson Parrelli
c646316a97 Block typing indicators from blocked contacts. 2019-03-19 09:39:19 -07:00
Greyson Parrelli
6df8988f54 Updated to WebRTC M73. 2019-03-19 09:03:02 -07:00
Greyson Parrelli
5b534c8b1a Update URL homograph rules. 2019-03-18 17:37:19 -07:00
Greyson Parrelli
ab2e85f6c7 Bump version to 4.36.0 2019-03-18 15:10:13 -07:00
Greyson Parrelli
975a121c55 Show a warning for users on API < 19.
We'll be updating minSdk to 19 in 4.37. This lets these users continue
to use the app, but they'll be warned with a persistent banner saying
that they can't receive updates.
2019-03-18 15:09:56 -07:00
Greyson Parrelli
64cf032181 Updated media send UI. 2019-03-18 15:09:56 -07:00
Alan Evans
d8a56be5e8 Gradle/Gradlew/AndroidGradlePlugin update. 2019-03-18 15:09:56 -07:00
Alan Evans
286b64274c Replace Avatar Cropper. 2019-03-18 15:09:50 -07:00
Alan Evans
0cb2404735 Lint error fixes.
* Old Gingerbread code removed.
* Add missing super call.
2019-03-18 11:14:18 -03:00
Greyson Parrelli
0a8bbf14a6 Merge camera into send flow. 2019-03-14 16:25:35 -07:00
Greyson Parrelli
eb1dd58a0b Persistent media in multi-send. 2019-03-14 16:20:36 -07:00
Alan Evans
a79df7d815 Automatic resConfig.
- Test to ensure language_entries list matches exactly the available resources.
2019-03-14 13:33:43 -03:00
Alan Evans
e0c11998c3 Make DisplayRecord take context as argument, so locale is more likely to be correct. 2019-03-13 18:28:16 -03:00
Alan Evans
de72eceecf Update help URL to avoid redirect. 2019-03-13 18:19:33 -03:00
Alan Evans
c46e53ab24 Add Esperanto and Swahili (Kiswahili) to language selection. 2019-03-13 17:45:53 -03:00
Alan Evans
2c28fa6a57 Make GIF search layout option sticky.
Fixes #7843
2019-03-12 11:41:54 -03:00
Alan Evans
f010a3ec0d Consistent Recipient to add contact Intent export. 2019-03-12 11:29:59 -03:00
Alan Evans
e390fb4fc5 Fix ShareMessage toolbar back button. 2019-03-12 11:29:59 -03:00
Greyson Parrelli
a4ce77cbcc Remove unused method. 2019-03-12 11:29:59 -03:00
Greyson Parrelli
18613e3b6f Remove generic foreground service condition from Job.
I don't think it actually helps at all, meaning it just adds
complexity.

Fixes #8677
2019-03-12 11:29:59 -03:00
Greyson Parrelli
278fdebf43 Update WorkManager to 1.0.0. 2019-03-12 11:29:59 -03:00
Greyson Parrelli
a122bb4899 Created new BlobProvider.
One unified place to create blobs for different lifespans.
2019-03-10 15:18:39 -07:00
Greyson Parrelli
22ed8caed3 Bump version to 4.35.3 2019-03-07 17:59:18 -08:00
Greyson Parrelli
8b6ecd1d2e Fixed profile screen layout on small screens.
Fixes #8670
2019-03-07 17:25:02 -08:00
Greyson Parrelli
f2703f0b7b Bump version to 4.35.2 2019-03-06 08:12:42 -08:00
Greyson Parrelli
1efb92b913 Fix issue where SMS were sometimes sent as MMS.
Fixes #8664
2019-03-06 08:12:00 -08:00
Greyson Parrelli
5ccf84f7a2 Bump version to 4.35.1 2019-03-02 21:01:15 -08:00
Greyson Parrelli
c3368f6de6 Updated language translations. 2019-03-02 20:46:40 -08:00
Greyson Parrelli
77e971cb9b Fix bug where system emoji setting was ignored in message bubbles.
EmojiTextView. It is our gift. It is our curse.
2019-03-02 19:31:28 -08:00
Greyson Parrelli
003fa1b059 Make long text attachments contain the entire message.
Instead of just containing the 'overflow', long text attachments now
contain the entire body in full.
2019-03-02 19:31:22 -08:00
Greyson Parrelli
03aa9e9712 Bump version to 4.35.0 2019-03-01 14:19:21 -08:00
Greyson Parrelli
55699e27bc Added ability to receive long messages.
Send support is in here too. We'll enable it in a future release after
enough people have updated.
2019-03-01 14:15:08 -08:00
Greyson Parrelli
bf28e109d3 Update registration UI. 2019-03-01 14:15:01 -08:00
Greyson Parrelli
6b476876d9 Lower volume of call connect/disconnect sound.
It's now closer in amplitude to the ringing sound.

Fixes #8165
2019-03-01 13:44:32 -08:00
Greyson Parrelli
fd862e575b Fix situation where search nav could get stuck.
If you navigated to the ConversationActivity again via some action (like
a shared contact invite) while searching, we don't get the toolbar close
event, and therefore the search nav would stay open. Now we just reset
it on newIntent() to be safe.
2019-03-01 13:44:32 -08:00
Greyson Parrelli
7fd6f5b3ff Fix search highlight in multi-whitespace bodies.
The way the highlight was done could get screwed up if you had multiple
whitespaces in a row. This particularly came up with messages with
multiple newlines.
2019-03-01 13:44:32 -08:00
Greyson Parrelli
42e94d8f92 Remove leftover half-height camera code. 2019-03-01 13:44:32 -08:00
Greyson Parrelli
b572fce658 Remove some unnecessary intermediate blob creation. 2019-03-01 13:44:32 -08:00
Greyson Parrelli
276e867f9a Improved address resolution for ContentProxySelector.
We can use an unresolved address to avoid all the threading stuff in
that class.
2019-03-01 13:42:10 -08:00
Greyson Parrelli
b2d4608cdb Improved handling for URLs that are composed of mixed character sets. 2019-02-24 10:00:44 -08:00
Greyson Parrelli
9d21c36ddf Bump verstion to 4.34.8
Again, no changes. Needed to resubmit to the Play Store.
2019-02-21 18:04:44 -08:00
Greyson Parrelli
983290aa5b Bump version to 4.34.7
No changes. Necessary to resubmit to Google Play.
2019-02-21 12:57:55 -08:00
Greyson Parrelli
88b9fc25d2 Bump version to 4.34.6 2019-02-20 17:23:54 -08:00
Greyson Parrelli
60c7fb0056 Fix possible NPE. 2019-02-20 17:20:12 -08:00
Greyson Parrelli
fa6da1902f Fix button spinning after failed CAPTCHA. 2019-02-19 13:13:32 -08:00
Greyson Parrelli
5cc3ac00c7 Bump version to 4.34.5 2019-02-19 09:37:30 -08:00
Greyson Parrelli
33daa21ad9 Guard against devices not supporting mandatory ContentProvider columns.
The docs specify that this column is supposed to be present, but a
crash says it wasn't, so alas, here in goes the check.
2019-02-19 09:34:24 -08:00
Greyson Parrelli
c4d1bdc44d Bump version to 4.34.4
No changes. Necessary to resubmit to the Play Store.
2019-02-18 16:03:15 -08:00
Greyson Parrelli
ca99c732f8 Bump version to 4.34.3 2019-02-18 11:47:40 -08:00
Greyson Parrelli
1f79808cf0 Remove unneccesary FCM manifest attribute.
`firebase_analytics_collection_enabled` is used for temporarily
enabling/disabling analytics.

We already use `firebase_analytics_collection_deactivated`, which is
used for permanently disabling analytics.
2019-02-18 11:46:53 -08:00
Greyson Parrelli
5c0e1100ed Fix possible NPE in conversation menu.
Would occur if someone had previously muted a conversation with
themselves.
2019-02-16 11:45:09 -08:00
Greyson Parrelli
d0b763c16e Bump version to 4.34.2 2019-02-15 19:34:19 -08:00
Greyson Parrelli
b962751c96 Fix possible IllegalArgumentException during a database migration. 2019-02-15 19:33:16 -08:00
Greyson Parrelli
94e8553b73 Fix possible NPE during conversation load. 2019-02-15 19:24:23 -08:00
Greyson Parrelli
351b625975 Bump version to 4.34.1 2019-02-15 14:21:27 -08:00
Greyson Parrelli
a2b6dbda14 Correctly sync Note to Self conversation color. 2019-02-15 14:21:27 -08:00
Greyson Parrelli
a6564f8f84 FCM improvements. 2019-02-15 14:21:23 -08:00
Greyson Parrelli
4dbe165c18 Bump version to 4.34.0 2019-02-14 21:04:01 -08:00
Greyson Parrelli
f29a42411e Update WorkManager to beta05. 2019-02-14 20:19:07 -08:00
Greyson Parrelli
02b0800b22 Support requesting a CAPTCHA during registration. 2019-02-14 20:19:07 -08:00
Greyson Parrelli
2cfa431cad Supply a reason for CDS error reporting. 2019-02-14 20:19:07 -08:00
Greyson Parrelli
fe4068afce Don't preview links if your cursor is touching them. 2019-02-14 20:19:07 -08:00
Greyson Parrelli
1c23603c25 Add the Redmi Note 5 to the hardware AEC blacklist. 2019-02-14 20:19:07 -08:00
Greyson Parrelli
c2a86fcc74 Sync self-sends to desktop.
Updated UI to show self-conversations as "Note to Self".
2019-02-14 20:19:07 -08:00
Greyson Parrelli
d42c9b5dbc Ensure the group shortstring in the action bar is up-to-date.
There were situations where adding/removing members from a group
would update the group member list, but the short string (the little
text listing the first couple members of the group) wouldn't be updated
until you left the screen and came back.
2019-02-14 20:19:06 -08:00
Greyson Parrelli
3b6429c163 Don't unnecessarily stop the ShareActivity in onPause.
1. Due to ShareActivity having noHistory=true, it will already be
ditched when you leave the activity.
2. We only need to truly finish() here if we've dropped the underlying
media.

Fixes #8591
2019-02-14 20:19:06 -08:00
Greyson Parrelli
6896f8ea15 Properly check attachment size during media send.
Prevent users from trying to send videos that exceed the size limit.

Also, this commit properly populates height/width on media shared into
the app.

Fixes #8573
2019-02-14 20:19:06 -08:00
Greyson Parrelli
a3768c7d74 Fix StickyHeader measuring.
It didn't re-measure when pulling an item from the cache, screwing stuff
up after a phone rotation. Had a workaround for it for specific screens,
but this fixes the problem at the source.

Fixes #8583
2019-02-14 20:19:06 -08:00
Greyson Parrelli
c9a0a66f18 Migrate backup passphrase to be keystore-encrypted when available. 2019-02-14 20:19:06 -08:00
Greyson Parrelli
db1ad39c6b Fix issues with bundled notifications. 2019-02-14 20:19:06 -08:00
Greyson Parrelli
9f04c28bfd Implemented conversation search.
You can now search for messages within a specific conversation.
2019-02-14 20:19:01 -08:00
Greyson Parrelli
10631d7e71 Add a gradle.properties with increased memory size. 2019-02-14 14:29:12 -08:00
Greyson Parrelli
cfff10622a Move conversation classes to their own package. 2019-02-14 14:29:12 -08:00
Greyson Parrelli
b769c7d9b6 Properly batch contact inserts.
Fixes #8580
2019-02-14 14:29:12 -08:00
Greyson Parrelli
1e0f691a56 Updated to WebRTC M72. 2019-02-14 14:28:57 -08:00
Greyson Parrelli
f0852d1d39 Bump version to 4.33.5 2019-02-07 16:54:59 -08:00
Greyson Parrelli
1ee422a012 Update link preview splash to say 'Got it'. 2019-02-07 16:43:40 -08:00
Greyson Parrelli
ca87820dd5 Updated language translations. 2019-02-07 16:20:39 -08:00
Greyson Parrelli
45ddb7e1ad Revert "Fix notification badge display."
This reverts commit a0c1446e9f.
2019-02-07 16:17:17 -08:00
Greyson Parrelli
fd46777f04 Bump version to 4.33.4
No changes. Need to create a new version to handle Play Store issues.
2019-02-06 10:27:59 -08:00
Greyson Parrelli
5bb36c15d5 Bump version to 4.33.3 2019-02-06 09:22:19 -08:00
Meteor0id
c5571e8a8d Add 'Got it' as string to be used on link preview splash screen.
"Understood" or "Got it" is a way of dismissing a splash screen of notification without any change that is applies agreeing to anything.
It replaces "OK" which was sometimes misinterpreted as "I am OK with what I just read".
2019-02-06 09:22:19 -08:00
Greyson Parrelli
b8ab1bc3b2 Updated language translations. 2019-02-06 08:39:00 -08:00
Greyson Parrelli
3683e6a9e2 Revert "Apply contact updates in batches of 50."
This reverts commit 5d9d6ac12b.
2019-02-06 00:54:03 -08:00
Greyson Parrelli
c364345e1d Add some additional FCM logging. 2019-02-06 00:53:52 -08:00
Greyson Parrelli
7da73bbc30 Updated link preview splash layout. 2019-02-06 00:34:48 -08:00
Greyson Parrelli
177322eca4 Bump version to 4.33.2 2019-02-04 13:57:49 -08:00
Greyson Parrelli
506491d13d Updated language translations. 2019-02-04 13:56:06 -08:00
Greyson Parrelli
e884911b60 Update the link preview domain whitelist. 2019-02-04 09:05:40 -08:00
Greyson Parrelli
7b20fca1ac Clearing the text field resets link preview cancellation. 2019-02-04 08:54:54 -08:00
Greyson Parrelli
e2c2e59442 Bump version to 4.33.1 2019-02-02 10:13:37 -08:00
Greyson Parrelli
be66db898c Fix possible double-cancel in CallRequestController. 2019-02-02 10:09:38 -08:00
Greyson Parrelli
5d9d6ac12b Apply contact updates in batches of 50.
If batch sizes are too large, we'll get a Binder exception.

Fixes #8580
2019-02-01 12:47:34 -08:00
Greyson Parrelli
c6d3bed8da Hide emoji button in media flows when system emoji are enabled.
Fixes #8581
2019-02-01 09:17:07 -08:00
Greyson Parrelli
e74c429695 Bump version to 4.33.0 2019-01-30 16:34:10 -08:00
Greyson Parrelli
90a37852cc Hide quoted attachments from All Media and media rails.
Fixes #8009
2019-01-30 16:34:10 -08:00
Greyson Parrelli
1763be2956 Hide call settings for groups. 2019-01-30 16:33:02 -08:00
Greyson Parrelli
9797c54a4d Removed sonar ping from calls.
People rarely understood what it meant, and the information is available
on the screen if-needed ('dialing' vs 'ringing').
2019-01-30 16:10:47 -08:00
Greyson Parrelli
c5114e2cb3 Updated to WebRTC M71. 2019-01-30 16:10:47 -08:00
Greyson Parrelli
e3b22dabce Harden notification channels.
There's odd corner cases where channels can be duplicated. This commit
adds some hard checks where we trim any dead channels, and unset any
notification channels from recipients whose notification channel isn't
present in the system settings.
2019-01-30 16:10:47 -08:00
Bas van Schaik
ba67796992 Fix invalid equals in NotificationChannels.channelExists
`DEFAULT_CHANNEL_ID` is a String, but `channel` is a NotificationChannel. Equals will therefore always return `false`. I think my fix (using `getId()`) is what was intended.
2019-01-30 16:10:47 -08:00
Greyson Parrelli
d482c60a98 Switch from GCM to FCM. 2019-01-30 16:10:47 -08:00
Greyson Parrelli
ebe8d38a91 Update WorkManager to beta03. 2019-01-30 16:10:47 -08:00
Greyson Parrelli
c76081d99c Added support for link previews. 2019-01-30 16:10:43 -08:00
Greyson Parrelli
bef9beff16 Updated Gradle to 5.1 2019-01-29 09:22:44 -08:00
Greyson Parrelli
25e82ff5e4 Fixed issue where we'd incorrectly show input shadow.
We might have also scrolled with typing indicators when we shouldn't of.

All came down to #isAtBottom() being calculated incorrectly.
2019-01-27 22:07:55 -08:00
Greyson Parrelli
13ffba1c99 Updated language translations. 2019-01-25 17:55:31 -08:00
Greyson Parrelli
a0c1446e9f Fix notification badge display.
This reverts commit 2489ea0d5b.
2019-01-21 16:43:59 -08:00
Greyson Parrelli
657b520908 Bump version to 4.32.8 2019-01-20 14:22:27 -08:00
Greyson Parrelli
51e8e8c2c8 Go back to WorkManager beta01.
Looks like there may be a rescheduling bug in beta02. Working through it
with the WorkManager devs.
2019-01-20 14:12:29 -08:00
Greyson Parrelli
1e534a2a10 Fix group avatars not sending.
Fixes #8547
2019-01-20 12:00:18 -08:00
Greyson Parrelli
9fe30524b2 Bump version to 4.32.7 2019-01-17 15:39:13 -08:00
Greyson Parrelli
e6b4249cf3 Deprecate usage of the signaling key. 2019-01-17 15:37:53 -08:00
Greyson Parrelli
57ef0e9024 Bump version to 4.32.6 2019-01-17 12:33:41 -08:00
Greyson Parrelli
9ed290b8f6 Updated language translations. 2019-01-17 12:33:41 -08:00
Greyson Parrelli
d2be554e1b Use orientation in media picker to determine width and height. 2019-01-17 12:33:41 -08:00
Greyson Parrelli
e7a807ab5b Ensure body text is carried through to the media send screen.
We were only doing it if you entered through the media rail. Now we also
do it if you enter through the gallery.
2019-01-17 11:42:01 -08:00
Greyson Parrelli
9ff8f8587b Bump version to 4.32.5 2019-01-16 14:34:36 -08:00
Greyson Parrelli
a6f31c60bd Show a toast when you've gone over the max media selection. 2019-01-16 14:31:59 -08:00
Greyson Parrelli
4643dea2ad Filter out non-media from media send screen. 2019-01-16 13:32:39 -08:00
Greyson Parrelli
f70bf9c5bd Default to 'All media' bucket for items from the rail.
This should give people a better multi-send experience when they want to
add more media.
2019-01-16 13:18:16 -08:00
Greyson Parrelli
557e6a800f Bump version to 4.32.4 2019-01-16 00:10:46 -08:00
Greyson Parrelli
17a391d3cf Updated language translations. 2019-01-16 00:06:53 -08:00
Greyson Parrelli
fc989f3820 Fix album sizing issues on skinnier devices. 2019-01-15 23:57:43 -08:00
Greyson Parrelli
2be382afab Bump version to 4.32.3 2019-01-15 15:10:18 -08:00
Greyson Parrelli
b08f81a8dc Fix bug where back stack can get stuck in the gallery. 2019-01-15 15:08:35 -08:00
Greyson Parrelli
535e00c6d0 Saving an album will now save every item.
Fixes a bug where saving would incorrectly only save the first image.
2019-01-15 14:56:27 -08:00
5307 changed files with 237356 additions and 144819 deletions

17
.github/workflows/android.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Android CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew qa

2
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.classpath
captures/
project.properties
.project
.settings
@@ -8,7 +9,6 @@ gen/
*.iml
out
tests
lint.xml
local.properties
ant.properties
.DS_Store

View File

@@ -1,10 +0,0 @@
[main]
host = https://www.transifex.com
lang_map = da_DK:da-rDK,he:iw,hi_IN:hi-rIN,id:in,kn_IN:kn-rIN,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
[signal-android.master]
file_filter = res/values-<lang>/strings.xml
source_file = res/values/strings.xml
source_lang = en
type = ANDROID

View File

@@ -12,7 +12,7 @@ RUN dpkg --add-architecture i386 && \
ENV ANDROID_SDK_FILENAME android-sdk_r24.4.1-linux.tgz
ENV ANDROID_SDK_URL https://dl.google.com/android/${ANDROID_SDK_FILENAME}
ENV ANDROID_API_LEVELS android-28
ENV ANDROID_BUILD_TOOLS_VERSION 27.0.3
ENV ANDROID_BUILD_TOOLS_VERSION 28.0.3
ENV ANDROID_HOME /usr/local/android-sdk-linux
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
RUN cd /usr/local/ && \

View File

@@ -62,7 +62,7 @@ The form and manner of this distribution makes it eligible for export under the
Copyright 2011 Whisper Systems
Copyright 2013-2017 Open Whisper Systems
Copyright 2013-2020 Open Whisper Systems
Licensed under the GPLv3: http://www.gnu.org/licenses/gpl-3.0.html

287
ReproducibleBuilds.md Normal file
View File

@@ -0,0 +1,287 @@
# Reproducible Builds
## TL;DR
You can just use these [instructions](https://signal.org/blog/reproducible-android/) from the official announcement at Open Whisper Systems's blog:
```
# Clone the Signal Android source repository
$ git clone https://github.com/signalapp/Signal-Android.git && cd Signal-Android
# Check out the release tag for the version you'd like to compare
$ git checkout v[the version number]
# Build using the Docker environment
$ docker run --rm -v $(pwd):/project -w /project whispersystems/signal-android:1.3 ./gradlew clean assembleRelease
# Verify the APKs
$ python3 apkdiff/apkdiff.py build/outputs/apks/project-release-unsigned.apk path/to/SignalFromPlay.apk
```
Note that the instructions above use a pre-built Signal Docker image from [Docker Hub](https://hub.docker.com/u/whispersystems/). If you wish to compile the image yourself, continue reading the longer version below.
***
## Introduction
Since version 3.15.0 Signal for Android has supported reproducible builds. This is achieved by replicating the build environment as a Docker image. You'll need to build the image, run a container instance of it, compile Signal inside the container and finally compare the resulted APK to the APK that is distributed in the Google Play Store.
The command line parts in this guide are written for Linux but with some little modifications you can adapt them to macOS (OS X) and Windows. In the following sections we will use `3.15.2` as an example Signal version. You'll just need to replace all occurrences of `3.15.2` with the version number you are about to verify.
## Setting up directories
First let's create a new directory for this whole reproducible builds project. In your home folder (`~`), create a new directory called `reproducible-signal`.
```
user@host:$ mkdir ~/reproducible-signal
```
Next create another directory inside `reproducible-signal` called `apk-from-google-play-store`.
```
user@host:$ mkdir ~/reproducible-signal/apk-from-google-play-store
```
We will use this directory to share APKs between the host OS and the Docker container.
Finally create one more directory inside `reproducible-signal` called `image-build-context`.
```
user@host:$ mkdir ~/reproducible-signal/image-build-context
```
This directory will be used later to build our Docker image.
## Getting the Google Play Store version of Signal APK
To compare the APKs we of course need a version of Signal from the Google Play Store.
First make sure that the Signal version you want to verify is installed on your Android device. You'll need `adb` for this part.
Plug your device to your computer and run this command to pull the APK from the device:
```
user@host:$ adb pull $(adb shell pm path org.thoughtcrime.securesms | grep /base.apk | awk -F':' '{print $2}') ~/reproducible-signal/apk-from-google-play-store/Signal-$(adb shell dumpsys package org.thoughtcrime.securesms | grep versionName | awk -F'=' '{print $2}').apk
```
This will pull a file into `~/reproducible-signal/apk-from-google-play-store/` with the name `Signal-<version>.apk`
Alternatively, you can do this step-by-step:
```
user@host:$ adb shell pm path org.thoughtcrime.securesms
```
This will output something like:
```
package:/data/app/org.thoughtcrime.securesms-aWRzcGlzcG9wZA==/base.apk
```
The output will tell you where the Signal APK is located in your device. (In this example the path is `/data/app/org.thoughtcrime.securesms-aWRzcGlzcG9wZA==/base.apk`)
Now using this information, pull the APK from your device to the `reproducible-signal/apk-from-google-play-store` directory you created before:
```
user@host:$ adb pull \
/data/app/org.thoughtcrime.securesms-aWRzcGlzcG9wZA==/base.apk \
~/reproducible-signal/apk-from-google-play-store/Signal-3.15.2.apk
```
We will use this APK in the final part when we compare it with the self-built APK from GitHub.
## Identifying the ABI
Since v4.37.0, the APKs have been split by ABI, the CPU architecture of the target device. Google play will serve the correct one to you for your device.
To identify which ABIs the google play APK supports, we can look inside the APK, which is just a zip file:
```
user@host:$ unzip -l ~/reproducible-signal/apk-from-google-play-store/Signal-*.apk | grep lib/
```
Example:
```
1214348 00-00-1980 00:00 lib/armeabi-v7a/libconscrypt_jni.so
151980 00-00-1980 00:00 lib/armeabi-v7a/libcurve25519.so
4164320 00-00-1980 00:00 lib/armeabi-v7a/libjingle_peerconnection_so.so
13948 00-00-1980 00:00 lib/armeabi-v7a/libnative-utils.so
2357812 00-00-1980 00:00 lib/armeabi-v7a/libsqlcipher.so
```
As there is just one sub directory of `lib/` called `armeabi-v7a`, that is your ABI. Make a note of that for later. If you see more than one subdirectory of `lib/`:
```
1214348 00-00-1980 00:00 lib/armeabi-v7a/libconscrypt_jni.so
151980 00-00-1980 00:00 lib/armeabi-v7a/libcurve25519.so
4164320 00-00-1980 00:00 lib/armeabi-v7a/libjingle_peerconnection_so.so
13948 00-00-1980 00:00 lib/armeabi-v7a/libnative-utils.so
2357812 00-00-1980 00:00 lib/armeabi-v7a/libsqlcipher.so
2111376 00-00-1980 00:00 lib/x86/libconscrypt_jni.so
201056 00-00-1980 00:00 lib/x86/libcurve25519.so
7303888 00-00-1980 00:00 lib/x86/libjingle_peerconnection_so.so
5596 00-00-1980 00:00 lib/x86/libnative-utils.so
3977636 00-00-1980 00:00 lib/x86/libsqlcipher.so
```
Then that means you have the `universal` APK.
## Installing Docker
Install Docker by following the instructions for your platform at https://docs.docker.com/engine/installation/
Your platform might also have its own preferred way of installing Docker. E.g. Ubuntu has its own Docker package (`docker.io`) if you do not want to follow Docker's instructions.
In the following sections we will assume that your Docker installation works without issues. So after installing, please make sure that everything is running smoothly before continuing.
## Building a Docker image for Signal
#### Grabbing the `Dockerfile`
First you will need the `Dockerfile` for Signal Android. It comes bundled with Signal's source code. The `Dockerfile` contains instructions on how to automatically build a Docker image for Signal. You just need to run it and it builds itself.
Download the `Dockerfile` to the `image-build-context` directory.
```
user@host:$ wget -O ~/reproducible-signal/image-build-context/Dockerfile_v3.15.2 \
https://raw.githubusercontent.com/signalapp/Signal-Android/v3.15.2/Dockerfile
```
Note that the `Dockerfile` is specific to the Signal version you want to compare to. Again you have to adjust the URL above to match the right version. (Though sometimes the file might not be up to date, see the [Troubleshooting section](#troubleshooting))
#### Building the image
Now we have everything we need to build the Docker image for Signal. Go to the `image-build-context` directory:
```
user@host:$ cd ~/reproducible-signal/image-build-context
```
And list the contents.
```
user@host:$ ls
```
The output should look like this:
```
Dockerfile_v3.15.2
```
Now in this directory build the image using `Dockerfile_v3.15.2`:
```
user@host:$ docker build --file Dockerfile_v3.15.2 --tag signal-android .
```
(Note that there is a dot at the end of that command!)
Wait a few years for the build to finish... :construction_worker:
(Depending on your computer and network connection, this may take several minutes.)
:calendar: :sleeping:
After the build has finished, you may wish to list all your Docker images to see that it's really there:
```
user@host:$ docker images
```
Output should look something like this:
```
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
signal-android latest c6b84450b896 46 seconds ago 2.94 GB
ubuntu 14.04.3 8693db7e8a00 9 weeks ago 187.9 MB
```
## Compiling Signal inside a container
Next we will run a container of the image we just built, grab Signal's source code and compile Signal.
First go to the `reproducible-signal` directory:
```
user@host:$ cd ~/reproducible-signal/
```
To run a new ephemeral container with an interactive terminal session execute the following long command:
```
user@host:$ docker run \
--name signal \
--rm \
--interactive \
--tty \
--volume $(pwd)/apk-from-google-play-store:/signal-build/apk-from-google-play-store \
--workdir /signal-build \
signal-android
```
Now you are inside the container.
Grab Signal's source code from GitHub and go to the repository directory:
```
root@container:# git clone https://github.com/signalapp/Signal-Android.git
root@container:# cd Signal-Android
```
Before you can compile, you **must** ensure that you are at the right commit. In other words you **must** checkout the version you wish to verify (here we are verifying 3.15.2):
```
root@container:# git checkout --quiet v3.15.2
```
Now you may compile the release APK by running:
```
root@container:# ./gradlew clean assemblePlayRelease --exclude-task signProductionPlayRelease
```
This will take a few minutes :sleeping:
#### Checking if the APKs match
After the build has completed successfully we can finally compare if the APKs match. For the comparison we need of course the Google Play Store version of Signal APK which you copied to the `apk-from-google-play-store` directory in the beginning of this guide. Because we used that directory as a `--volume` parameter for our container, we can see all the files in that directory within our container.
So now we can compare the APKs using the `apkdiff.py` tool.
The above build step produced several APKs, one for each supported ABI and one universal one. You will need to determine the correct APK to compare.
Currently, the most common ABI is `armeabi-v7a`. Other options at this time include `x86` and `universal`. In the future it will also include 64-bit options, such as `x86_64` and `arm64-v8a`.
See [Identifying the ABI](#identifying-the-abi) above if you don't know the ABI of your play store APK.
Once you have determined the ABI, add an `abi` environment variable. For example, suppose we determine that `armeabi-v7a` is the ABI google play has served:
```
root@container:# export abi=armeabi-v7a
```
And the diff script to compare:
```
root@container:# python3 apkdiff/apkdiff.py \
build/outputs/apk/play/release/*play-$abi-release-unsigned*.apk \
../apk-from-google-play-store/Signal-3.15.2.apk
```
Output:
```
APKs match!
```
If you get `APKs match!`, you have successfully verified that the Google Play release matches with your own self-built version of Signal. Congratulations! Your APKs are a match made in heaven! :sparkles:
If you get `APKs don't match!`, you did something wrong in the previous steps. See the [Troubleshooting section](#troubleshooting) for more info.
## Comparing next time
If the build environment (i.e. `Dockerfile`) has not changed, you don't need to build the image again to verify a newer APK. You can just [run the container again](#compiling-signal-inside-a-container).
## Troubleshooting
If you cannot get things to work, please do not open an issue or comment on an existing issue at GitHub. Instead, ask for help at https://community.signalusers.org/c/development
Some common issues why things may not work:
- some pinned packages in the `Dockerfile` are not available anymore and building of the Docker image fails
- the Android packages in the Docker image are outdated and compiling Signal fails
- you built the Docker image with a wrong version of the `Dockerfile`
- you didn't checkout the correct Signal version tag with Git before compiling
- the ABI you selected is not the correct ABI, particularly if you see an error along the lines of `Sorted manifests don't match, lib/x86/libcurve25519.so vs lib/armeabi-v7a/libcurve25519.so`.
- this guide is outdated
- you are in a dream
- if you run into this issue: https://issuetracker.google.com/issues/110237303 try to add `resources.arsc` to the list of ignored files and compare again

View File

@@ -1,20 +1,21 @@
#! /usr/bin/env python
#! /usr/bin/env python3
import sys
from zipfile import ZipFile
class ApkDiff:
IGNORE_FILES = ["META-INF/CERT.RSA", "META-INF/CERT.SF", "META-INF/MANIFEST.MF"]
# resources.arsc is ignored due to https://issuetracker.google.com/issues/110237303
# May be fixed in Android Gradle Plugin 3.4
IGNORE_FILES = ["META-INF/MANIFEST.MF", "META-INF/SIGNAL_S.RSA", "META-INF/SIGNAL_S.SF", "resources.arsc"]
def compare(self, sourceApk, destinationApk):
sourceZip = ZipFile(sourceApk, 'r')
destinationZip = ZipFile(destinationApk, 'r')
if self.compareManifests(sourceZip, destinationZip) and self.compareEntries(sourceZip, destinationZip) == True:
print "APKs match!"
print("APKs match!")
else:
print "APKs don't match!"
print("APKs don't match!")
def compareManifests(self, sourceZip, destinationZip):
sourceEntrySortedList = sorted(sourceZip.namelist())
@@ -23,23 +24,23 @@ class ApkDiff:
for ignoreFile in self.IGNORE_FILES:
while ignoreFile in sourceEntrySortedList: sourceEntrySortedList.remove(ignoreFile)
while ignoreFile in destinationEntrySortedList: destinationEntrySortedList.remove(ignoreFile)
if len(sourceEntrySortedList) != len(destinationEntrySortedList):
print "Manifest lengths differ!"
print("Manifest lengths differ!")
for (sourceEntryName, destinationEntryName) in zip(sourceEntrySortedList, destinationEntrySortedList):
if sourceEntryName != destinationEntryName:
print "Sorted manifests don't match, %s vs %s" % (sourceEntryName, destinationEntryName)
print("Sorted manifests don't match, %s vs %s" % (sourceEntryName, destinationEntryName))
return False
return True
def compareEntries(self, sourceZip, destinationZip):
sourceInfoList = filter(lambda sourceInfo: sourceInfo.filename not in self.IGNORE_FILES, sourceZip.infolist())
destinationInfoList = filter(lambda destinationInfo: destinationInfo.filename not in self.IGNORE_FILES, destinationZip.infolist())
sourceInfoList = list(filter(lambda sourceInfo: sourceInfo.filename not in self.IGNORE_FILES, sourceZip.infolist()))
destinationInfoList = list(filter(lambda destinationInfo: destinationInfo.filename not in self.IGNORE_FILES, destinationZip.infolist()))
if len(sourceInfoList) != len(destinationInfoList):
print "APK info lists of different length!"
print("APK info lists of different length!")
return False
for sourceEntryInfo in sourceInfoList:
@@ -49,19 +50,19 @@ class ApkDiff:
destinationEntry = destinationZip.open(destinationEntryInfo, 'r')
if self.compareFiles(sourceEntry, destinationEntry) != True:
print "APK entry %s does not match %s!" % (sourceEntryInfo.filename, destinationEntryInfo.filename)
print("APK entry %s does not match %s!" % (sourceEntryInfo.filename, destinationEntryInfo.filename))
return False
destinationInfoList.remove(destinationEntryInfo)
break
return True
def compareFiles(self, sourceFile, destinationFile):
sourceChunk = sourceFile.read(1024)
destinationChunk = destinationFile.read(1024)
while sourceChunk != "" or destinationChunk != "":
while sourceChunk != b"" or destinationChunk != b"":
if sourceChunk != destinationChunk:
return False
@@ -72,7 +73,7 @@ class ApkDiff:
if __name__ == '__main__':
if len(sys.argv) != 3:
print "Usage: apkdiff <pathToFirstApk> <pathToSecondApk>"
print("Usage: apkdiff <pathToFirstApk> <pathToSecondApk>")
sys.exit(1)
ApkDiff().compare(sys.argv[1], sys.argv[2])

10
app/.tx/config Normal file
View File

@@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
lang_map = da_DK:da-rDK,he:iw,id:in,kn_IN:kn-rIN,pt_BR:pt-rBR,pt_PT:pt,qu_EC:qu-rEC,sv_SE:sv-rSE,zh_CN:zh-rCN,zh_HK:zh-rHK,zh_TW:zh-rTW
[signal-android.master]
file_filter = src/main/res/values-<lang>/strings.xml
source_file = src/main/res/values/strings.xml
source_lang = en
type = ANDROID

436
app/build.gradle Normal file
View File

@@ -0,0 +1,436 @@
import org.signal.signing.ApkSignerUtil
import java.security.MessageDigest
buildscript {
repositories {
google()
maven {
url "https://repo1.maven.org/maven2"
}
jcenter {
content {
includeVersion 'org.jetbrains.trove4j', 'trove4j', '20160824'
}
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.1'
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.10'
}
}
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'
apply plugin: 'androidx.navigation.safeargs'
apply plugin: 'witness'
apply from: 'translations.gradle'
apply from: 'witness-verifications.gradle'
repositories {
maven {
url "https://raw.github.com/signalapp/maven/master/photoview/releases/"
content {
includeGroupByRegex "com\\.github\\.chrisbanes.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/shortcutbadger/releases/"
content {
includeGroupByRegex "me\\.leolin.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/circular-progress-button/releases/"
content {
includeGroupByRegex "com\\.github\\.dmytrodanylyk\\.circular-progress-button\\.*"
}
}
maven {
url "https://raw.github.com/signalapp/maven/master/sqlcipher/release/"
content {
includeGroupByRegex "org\\.signal.*"
}
}
maven { // textdrawable
url 'https://dl.bintray.com/amulyakhare/maven'
content {
includeGroupByRegex "com\\.amulyakhare.*"
}
}
google()
mavenCentral()
jcenter()
mavenLocal()
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.10.0'
}
generateProtoTasks {
all().each { task ->
task.builtins {
java {
option "lite"
}
}
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0-beta01'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.0.0'
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.exifinterface:exifinterface:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
implementation "androidx.camera:camera-core:1.0.0-alpha06"
implementation "androidx.camera:camera-camera2:1.0.0-alpha06"
implementation('com.google.firebase:firebase-messaging:17.3.4') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
implementation 'com.google.android.gms:play-services-maps:16.1.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.android.exoplayer:exoplayer-core:2.9.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.9.1'
implementation 'org.conscrypt:conscrypt-android:2.0.0'
implementation 'org.signal:aesgcmprovider:0.0.3'
implementation project(':libsignal-service')
implementation 'org.signal:ringrtc-android:0.3.1'
implementation "me.leolin:ShortcutBadger:1.1.16"
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
implementation 'com.jpardogo.materialtabstrip:library:1.0.9'
implementation 'org.apache.httpcomponents:httpclient-android:4.3.5'
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
implementation 'com.makeramen:roundedimageview:2.1.0'
implementation 'com.pnikosis:materialish-progress:1.5'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'pl.tajchert:waitingdots:0.1.0'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'com.melnykov:floatingactionbutton:1.3.0'
implementation 'com.google.zxing:android-integration:3.1.0'
implementation 'mobi.upod:time-duration-picker:1.1.3'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.google.zxing:core:3.2.1'
implementation ('com.davemorrissey.labs:subsampling-scale-image-view:3.6.0') {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation ('cn.carbswang.android:NumberPickerView:1.0.9') {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation ('com.tomergoldst.android:tooltips:1.0.6') {
exclude group: 'com.android.support', module: 'appcompat-v7'
}
implementation ('com.klinkerapps:android-smsmms:4.0.1') {
exclude group: 'com.squareup.okhttp', module: 'okhttp'
exclude group: 'com.squareup.okhttp', module: 'okhttp-urlconnection'
}
implementation 'com.annimon:stream:1.1.8'
implementation ('com.takisoft.fix:colorpicker:0.9.1') {
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
implementation 'com.airbnb.android:lottie:3.0.7'
implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4'
implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2'
implementation 'org.signal:android-database-sqlcipher:3.5.9-S3'
implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') {
exclude group: 'com.fasterxml.jackson.core'
exclude group: 'org.freemarker'
}
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.11.1'
testImplementation 'org.mockito:mockito-core:1.9.5'
testImplementation 'org.powermock:powermock-api-mockito:1.6.1'
testImplementation 'org.powermock:powermock-module-junit4:1.6.1'
testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1'
testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1'
testImplementation 'androidx.test:core:1.2.0'
androidTestImplementation 'androidx.multidex:multidex:2.0.1'
androidTestImplementation 'androidx.multidex:multidex-instrumentation:2.0.0'
androidTestImplementation 'com.google.dexmaker:dexmaker:1.2'
androidTestImplementation 'com.google.dexmaker:dexmaker-mockito:1.2'
androidTestImplementation ('org.assertj:assertj-core:1.7.1') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}
androidTestImplementation ('com.squareup.assertj:assertj-android:1.1.1') {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
exclude group: 'com.android.support', module: 'support-annotations'
}
testImplementation 'org.robolectric:robolectric:4.2'
testImplementation 'org.robolectric:shadows-multidex:4.2'
}
dependencyVerification {
configuration = '(play|website)(Debug|Release)RuntimeClasspath'
}
def canonicalVersionCode = 587
def canonicalVersionName = "4.53.2"
def postFixSize = 10
def abiPostFix = ['universal' : 0,
'armeabi-v7a' : 1,
'arm64-v8a' : 2,
'x86' : 3,
'x86_64' : 4]
android {
flavorDimensions "none"
compileSdkVersion 28
buildToolsVersion '28.0.3'
useLibrary 'org.apache.http.legacy'
dexOptions {
javaMaxHeapSize "4g"
}
defaultConfig {
versionCode canonicalVersionCode * postFixSize
versionName canonicalVersionName
minSdkVersion 19
targetSdkVersion 28
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
project.ext.set("archivesBaseName", "Signal");
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api.directory.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api.backup.signal.org\""
buildConfigField "String", "CONTENT_PROXY_HOST", "\"contentproxy.signal.org\""
buildConfigField "int", "CONTENT_PROXY_PORT", "443"
buildConfigField "String", "USER_AGENT", "\"OWA\""
buildConfigField "boolean", "DEV_BUILD", "false"
buildConfigField "String", "MRENCLAVE", "\"cd6cfc342937b23b1bdd3bbf9721aa5615ac9ff50a75c5527d441cd3276826c9\""
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"f2e2a5004794a6c1bac5c4949eadbc243dd02e02d1a93f10fe24584fb70815d8\""
buildConfigField "String", "KEY_BACKUP_MRENCLAVE", "\"f51f435802ada769e67aaf5744372bb7e7d519eecf996d335eb5b46b872b5789\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\""
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
ndk {
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
resConfigs autoResConfig()
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
exclude 'LICENSE.txt'
exclude 'LICENSE'
exclude 'NOTICE'
exclude 'asm-license.txt'
exclude 'META-INF/LICENSE'
exclude 'META-INF/NOTICE'
exclude 'META-INF/proguard/androidx-annotations.pro'
}
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard/proguard-firebase-messaging.pro',
'proguard/proguard-google-play-services.pro',
'proguard/proguard-jackson.pro',
'proguard/proguard-sqlite.pro',
'proguard/proguard-appcompat-v7.pro',
'proguard/proguard-square-okhttp.pro',
'proguard/proguard-square-okio.pro',
'proguard/proguard-spongycastle.pro',
'proguard/proguard-rounded-image-view.pro',
'proguard/proguard-glide.pro',
'proguard/proguard-shortcutbadger.pro',
'proguard/proguard-retrofit.pro',
'proguard/proguard-webrtc.pro',
'proguard/proguard-klinker.pro',
'proguard/proguard-retrolambda.pro',
'proguard/proguard-okhttp.pro',
'proguard/proguard-ez-vcard.pro',
'proguard/proguard.cfg'
testProguardFiles 'proguard/proguard-automation.pro',
'proguard/proguard.cfg'
}
staging {
initWith debug
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service-staging.whispersystems.org\""
buildConfigField "String", "STORAGE_URL", "\"https://storage-staging.signal.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn-staging.signal.org\""
buildConfigField "String", "SIGNAL_CONTACT_DISCOVERY_URL", "\"https://api-staging.directory.signal.org\""
buildConfigField "String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\""
buildConfigField "String", "MRENCLAVE", "\"ba4ebb438bc07713819ee6c98d94037747006d7df63fc9e44d2d6f1fec962a79\""
buildConfigField "String", "KEY_BACKUP_ENCLAVE_NAME", "\"b5a865941f95887018c86725cc92308d34a3084dc2b4e7bd2de5e5e1690b50c6\""
buildConfigField "String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\""
}
release {
minifyEnabled true
proguardFiles = buildTypes.debug.proguardFiles
}
}
productFlavors {
play {
dimension "none"
ext.websiteUpdateUrl = "null"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
}
website {
dimension "none"
ext.websiteUpdateUrl = "https://updates.signal.org/android"
buildConfigField "boolean", "PLAY_STORE_DISABLED", "true"
buildConfigField "String", "NOPLAY_UPDATE_URL", "\"$ext.websiteUpdateUrl\""
}
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
def abiName = output.getFilter("ABI") ?: 'universal'
def postFix = abiPostFix.get(abiName, 0)
if (postFix >= postFixSize) throw new AssertionError("postFix is too large")
output.versionCodeOverride = canonicalVersionCode * postFixSize + postFix
}
}
lintOptions {
abortOnError true
baseline file("lint-baseline.xml")
disable "LintError"
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
def assembleWebsiteDescriptor = { variant, file ->
if (file.exists()) {
MessageDigest md = MessageDigest.getInstance("SHA-256");
file.eachByte 4096, {bytes, size ->
md.update(bytes, 0, size);
}
String digest = md.digest().collect {String.format "%02x", it}.join();
String url = variant.productFlavors.get(0).ext.websiteUpdateUrl
String apkName = file.getName()
String descriptor = "{" +
"\"versionCode\" : ${canonicalVersionCode * postFixSize + abiPostFix['universal']}," +
"\"versionName\" : \"$canonicalVersionName\"," +
"\"sha256sum\" : \"$digest\"," +
"\"url\" : \"$url/$apkName\"" +
"}"
File descriptorFile = new File(file.getParent(), apkName.replace(".apk", ".json"))
descriptorFile.write(descriptor)
}
}
def signProductionRelease = { variant ->
variant.outputs.collect { output ->
String apkName = output.outputFile.name
File inputFile = new File(output.outputFile.path)
File outputFile = new File(output.outputFile.parent, apkName.replace('-unsigned', ''))
new ApkSignerUtil('sun.security.pkcs11.SunPKCS11',
'pkcs11.config',
'PKCS11',
'file:pkcs11.password').calculateSignature(inputFile.getAbsolutePath(),
outputFile.getAbsolutePath())
inputFile.delete()
outputFile
}
}
task signProductionPlayRelease {
doLast {
signProductionRelease(android.applicationVariants.find { (it.name == 'playRelease') })
}
}
task signProductionWebsiteRelease {
doLast {
def variant = android.applicationVariants.find { (it.name == 'websiteRelease') }
File signedRelease = signProductionRelease(variant).find { it.name.contains('universal') }
assembleWebsiteDescriptor(variant, signedRelease)
}
}
tasks.whenTaskAdded { task ->
if (task.name.equals("assemblePlayRelease")) {
task.finalizedBy signProductionPlayRelease
}
if (task.name.equals("assembleWebsiteRelease")) {
task.finalizedBy signProductionWebsiteRelease
}
}
def getLastCommitTimestamp() {
new ByteArrayOutputStream().withStream { os ->
def result = exec {
executable = 'git'
args = ['log', '-1', '--pretty=format:%ct']
standardOutput = os
}
return os.toString() + "000"
}
}

6
app/jni/Application.mk Normal file
View File

@@ -0,0 +1,6 @@
# Built with NDK 19.2.5345600
APP_ABI := armeabi-v7a x86 arm64-v8a x86_64
APP_PLATFORM := android-19
APP_STL := c++_static
APP_CPPFLAGS += -fexceptions
APP_OPTIM := debug

View File

@@ -0,0 +1,45 @@
#include "org_thoughtcrime_securesms_util_FileUtils.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/memfd.h>
#include <syscall.h>
jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
{
jclass fdClass = env->GetObjectClass(fileDescriptor);
if (fdClass == NULL) {
return -1;
}
jfieldID fdFieldId = env->GetFieldID(fdClass, "descriptor", "I");
if (fdFieldId == NULL) {
return -1;
}
int fd = env->GetIntField(fileDescriptor, fdFieldId);
struct stat stat_struct;
if (fstat(fd, &stat_struct) != 0) {
return -1;
}
return stat_struct.st_uid;
}
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *env, jclass clazz, jstring jname)
{
const char *name = env->GetStringUTFChars(jname, NULL);
int fd = syscall(SYS_memfd_create, name, MFD_CLOEXEC);
env->ReleaseStringUTFChars(jname, name);
return fd;
}

View File

@@ -0,0 +1,29 @@
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class org_thoughtcrime_securesms_util_FileUtils */
#ifndef _Included_org_thoughtcrime_securesms_util_FileUtils
#define _Included_org_thoughtcrime_securesms_util_FileUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: org_thoughtcrime_securesms_util_FileUtils
* Method: getFileDescriptorOwner
* Signature: (Ljava/io/FileDescriptor;)I
*/
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_getFileDescriptorOwner
(JNIEnv *, jclass, jobject);
/*
* Class: org_thoughtcrime_securesms_util_FileUtils
* Method: createMemoryFileDescriptor
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_org_thoughtcrime_securesms_util_FileUtils_createMemoryFileDescriptor
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif

48
app/lint-baseline.xml Normal file
View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="5" by="lint 3.3.2" client="gradle" variant="playRelease" version="3.3.2">
<issue
id="MissingPermission"
message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
errorLine1=" List&lt;SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
file="src/main/java/org/thoughtcrime/securesms/util/dualsim/SubscriptionManagerCompat.java"
line="101"
column="35"/>
</issue>
<issue
id="ResourceType"
message="Expected resource of type styleable"
errorLine1=" drawables.getColor(1, 0xff000000);"
errorLine2=" ~">
<location
file="src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListAdapter.java"
line="187"
column="36"/>
</issue>
<issue
id="AppLinkUrlError"
message="Missing URL"
errorLine1=" &lt;intent-filter>"
errorLine2=" ^">
<location
file="AndroidManifest.xml"
line="368"
column="9"/>
</issue>
<issue
id="AppLinkUrlError"
message="Missing URL"
errorLine1=" &lt;intent-filter>"
errorLine2=" ^">
<location
file="AndroidManifest.xml"
line="381"
column="9"/>
</issue>
</issues>

25
app/lint.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- L10N errors -->
<!-- This is a runtime crash so we don't want to ship with this. -->
<issue id="StringFormatMatches" severity="error" />
<!-- L10N warnings -->
<issue id="MissingTranslation" severity="warning" />
<issue id="MissingQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="ImpliedQuantity" severity="warning" />
<issue id="CanvasSize" severity="error" />
<issue id="HardcodedText" severity="error" />
<issue id="VectorRaster" severity="error" />
<issue id="ButtonOrder" severity="error" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="RestrictedApi" severity="error">
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/VideoCapture.java" />
<ignore path="*/org/thoughtcrime/securesms/mediasend/camerax/CameraXModule.java" />
</issue>
</lint>

View File

@@ -0,0 +1 @@
-dontwarn com.google.firebase.analytics.connector.AnalyticsConnector

11
app/proguard/proguard.cfg Normal file
View File

@@ -0,0 +1,11 @@
-dontoptimize
-dontobfuscate
-keepattributes SourceFile,LineNumberTable
-keep class org.whispersystems.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
-keepclassmembers class ** {
public void onEvent*(**);
}
# Protobuf lite
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }

View File

@@ -3,7 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms">
<uses-sdk tools:overrideLibrary="com.amulyakhare.textdrawable,com.astuetz.pagerslidingtabstrip,pl.tajchert.waitingdots,com.h6ah4i.android.multiselectlistpreferencecompat,android.support.v13,com.davemorrissey.labs.subscaleview,com.tomergoldst.tooltips,com.klinker.android.send_message,com.takisoft.colorpicker,android.support.v14.preference"/>
<uses-sdk tools:overrideLibrary="androidx.camera.core,androidx.camera.camera2"/>
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
@@ -27,6 +27,7 @@
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
@@ -58,6 +59,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
@@ -86,10 +88,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher"
@@ -110,23 +109,25 @@
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="true" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="false" />
<meta-data android:name="firebase_messaging_auto_init_enabled" android:value="false" />
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
android:theme="@style/TextSecure.LightTheme.WebRTCCall"
android:excludeFromRecents="true"
android:screenOrientation="portrait"
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
android:launchMode="singleTask"/>
<activity android:name=".CountrySelectionActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".InviteActivity"
android:theme="@style/TextSecure.HighlightTheme"
android:theme="@style/Signal.Light.NoActionBar.Invite"
android:windowSoftInputMode="stateHidden"
android:parentActivityName=".ConversationListActivity"
android:parentActivityName=".MainActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".PromptMmsActivity"
@@ -174,15 +175,31 @@
</activity>
<activity android:name=".ConversationListActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
<activity android:name=".stickers.StickerPackPreviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:exported="true" />
android:launchMode="singleTask"
android:noHistory="true"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sgnl"
android:host="addstickers" />
</intent-filter>
<intent-filter>
<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.art"
android:pathPrefix="/addstickers"/>
</intent-filter>
</activity>
<activity-alias android:name=".RoutingActivity"
android:targetActivity=".ConversationListActivity"
android:targetActivity=".MainActivity"
android:exported="true">
<intent-filter>
@@ -198,27 +215,19 @@
</activity-alias>
<activity android:name=".ConversationListArchiveActivity"
android:label="@string/AndroidManifest_archived_conversations"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".ConversationListActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
</activity>
<activity android:name=".ConversationActivity"
<activity android:name=".conversation.ConversationActivity"
android:windowSoftInputMode="stateUnchanged"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
android:parentActivityName=".ConversationListActivity">
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.thoughtcrime.securesms.ConversationListActivity" />
android:value="org.thoughtcrime.securesms.MainActivity" />
</activity>
<activity android:name=".ConversationPopupActivity"
<activity android:name=".longmessage.LongMessageActivity" />
<activity android:name=".conversation.ConversationPopupActivity"
android:windowSoftInputMode="stateVisible"
android:launchMode="singleTask"
android:taskAffinity=""
@@ -241,13 +250,13 @@
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DatabaseUpgradeActivity"
<activity android:name=".migrations.ApplicationMigrationActivity"
android:theme="@style/NoAnimation.Theme.AppCompat.Light.DarkActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ExperienceUpgradeActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -278,10 +287,11 @@
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.MediaSendActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediasend.MediaSendActivity"
android:theme="@style/TextSecure.FullScreenMedia"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTop"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".PassphraseChangeActivity"
android:label="@string/AndroidManifest__change_passphrase"
@@ -298,9 +308,22 @@
</intent-filter>
</activity>
<activity android:name=".RegistrationActivity"
<activity android:name=".registration.RegistrationNavigationActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightNoActionBar"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<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"/>
<activity android:name=".stickers.StickerManagementActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightTheme"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -319,10 +342,9 @@
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".MediaOverviewActivity"
<activity android:name=".mediaoverview.MediaOverviewActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateHidden"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DummyActivity"
@@ -380,19 +402,15 @@
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.ScribbleActivity"
android:theme="@style/TextSecure.ScribbleTheme"
android:windowSoftInputMode="stateHidden"
<activity android:name=".scribbles.ImageEditorStickerSelectActivity"
android:theme="@style/TextSecure.DarkTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".scribbles.StickerSelectActivity"
android:theme="@style/TextSecure.ScribbleTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/TextSecure.DarkTheme"/>
<activity android:name=".CreateProfileActivity"
android:theme="@style/TextSecure.LightTheme"
android:theme="@style/TextSecure.LightRegistrationTheme"
android:windowSoftInputMode="stateVisible"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -425,10 +443,21 @@
android:exported="true"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".camera.CameraActivity"
android:theme="@style/TextSecure.ScribbleTheme"
android:windowSoftInputMode="stateHidden"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".maps.PlacePickerActivity"
android:label="@string/PlacePickerActivity_title"
android:theme="@style/TextSecure.LightNoActionBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity
android:name=".usernames.ProfileEditActivityV2"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="adjustResize"/>
<activity android:name=".MainActivity"
android:theme="@style/TextSecure.LightNoActionBar"
android:launchMode="singleTask"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
@@ -472,12 +501,11 @@
<service android:name=".service.GenericForegroundService"/>
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
<service android:name=".gcm.FcmService">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="org.thoughtcrime.securesms" />
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</receiver>
</service>
<receiver android:name=".service.SmsListener"
android:permission="android.permission.BROADCAST_SMS"
@@ -544,6 +572,8 @@
<receiver android:name=".service.ExpirationListener" />
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
android:exported="false"
@@ -554,7 +584,7 @@
android:exported="false"
android:authorities="org.thoughtcrime.provider.securesms.mms" />
<provider android:name="android.support.v4.content.FileProvider"
<provider android:name="androidx.core.content.FileProvider"
android:authorities="org.thoughtcrime.securesms.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
@@ -575,11 +605,18 @@
android:authorities="org.thoughtcrime.securesms.database.attachment"
android:exported="false" />
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
<provider android:name=".database.DatabaseContentProviders$Sticker"
android:authorities="org.thoughtcrime.securesms.database.sticker"
android:exported="false" />
<provider android:name=".database.DatabaseContentProviders$StickerPack"
android:authorities="org.thoughtcrime.securesms.database.stickerpack"
android:exported="false" />
<provider android:name="androidx.camera.camera2.impl.Camera2Initializer"
android:authorities="${applicationId}.camerax-init"
android:exported="false"
tools:node="remove" />
android:enabled="false" />
<receiver android:name=".service.BootReceiver">
<intent-filter>
@@ -655,6 +692,37 @@
</intent-filter>
</receiver>
<service
android:name=".gcm.FcmJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.JobSchedulerScheduler$SystemService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@bool/enable_job_service"
tools:targetApi="26" />
<service
android:name=".jobmanager.KeepAliveService"
android:enabled="@bool/enable_alarm_manager" />
<receiver
android:name=".jobmanager.AlarmManagerScheduler$RetryReceiver"
android:enabled="@bool/enable_alarm_manager" />
<!-- Probably don't need this one -->
<receiver
android:name=".jobmanager.BootReceiver"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
<uses-library android:name="com.sec.android.app.multiwindow" android:required="false"/>
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
<meta-data android:name="com.sec.android.multiwindow.DEFAULT_SIZE_W" android:value="632.0dip" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@@ -0,0 +1,395 @@
/*
* Copyright (C) 2013 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.camera.camera2.Camera2AppConfig;
import androidx.camera.core.CameraX;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.multidex.MultiDexApplication;
import com.google.android.gms.security.ProviderInstaller;
import org.conscrypt.Conscrypt;
import org.signal.aesgcmprovider.AesGcmProvider;
import org.signal.ringrtc.CallConnectionFactory;
import org.thoughtcrime.securesms.components.TypingStatusRepository;
import org.thoughtcrime.securesms.components.TypingStatusSender;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.insights.InsightsOptOut;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.StickerPackDownloadJob;
import org.thoughtcrime.securesms.logging.AndroidLogger;
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.logging.UncaughtExceptionLogger;
import org.thoughtcrime.securesms.mediasend.camerax.CameraXUtil;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.ringrtc.RingRtcLogger;
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.service.IncomingMessageObserver;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.LocalBackupListener;
import org.thoughtcrime.securesms.revealable.ViewOnceMessageManager;
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
import org.thoughtcrime.securesms.stickers.BlessedPacks;
import org.thoughtcrime.securesms.util.FrameRateTracker;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import org.webrtc.voiceengine.WebRtcAudioManager;
import org.webrtc.voiceengine.WebRtcAudioUtils;
import org.whispersystems.libsignal.logging.SignalProtocolLoggerProvider;
import java.security.Security;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Will be called once when the TextSecure process is created.
*
* We're using this as an insertion point to patch up the Android PRNG disaster,
* to initialize the job manager, and to check for GCM registration freshness.
*
* @author Moxie Marlinspike
*/
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
private static final String TAG = ApplicationContext.class.getSimpleName();
private ExpiringMessageManager expiringMessageManager;
private ViewOnceMessageManager viewOnceMessageManager;
private TypingStatusRepository typingStatusRepository;
private TypingStatusSender typingStatusSender;
private IncomingMessageObserver incomingMessageObserver;
private PersistentLogger persistentLogger;
private volatile boolean isAppVisible;
public static ApplicationContext getInstance(Context context) {
return (ApplicationContext)context.getApplicationContext();
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate()");
initializeSecurityProvider();
initializeLogging();
initializeCrashHandling();
initializeAppDependencies();
initializeFirstEverAppLaunch();
initializeApplicationMigrations();
initializeMessageRetrieval();
initializeExpiringMessageManager();
initializeRevealableMessageManager();
initializeTypingStatusRepository();
initializeTypingStatusSender();
initializeGcmCheck();
initializeSignedPreKeyCheck();
initializePeriodicTasks();
initializeCircumvention();
initializeRingRtc();
initializePendingMessages();
initializeBlobProvider();
initializeCameraX();
NotificationChannels.create(this);
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
if (Build.VERSION.SDK_INT < 21) {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
ApplicationDependencies.getJobManager().beginJobLoop();
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
isAppVisible = true;
Log.i(TAG, "App is now visible.");
ApplicationDependencies.getRecipientCache().warmUp();
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);
ApplicationDependencies.getFrameRateTracker().begin();
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
isAppVisible = false;
Log.i(TAG, "App is no longer visible.");
KeyCachingService.onAppBackgrounded(this);
MessageNotifier.setVisibleThread(-1);
ApplicationDependencies.getFrameRateTracker().end();
}
public ExpiringMessageManager getExpiringMessageManager() {
return expiringMessageManager;
}
public ViewOnceMessageManager getViewOnceMessageManager() {
return viewOnceMessageManager;
}
public TypingStatusRepository getTypingStatusRepository() {
return typingStatusRepository;
}
public TypingStatusSender getTypingStatusSender() {
return typingStatusSender;
}
public boolean isAppVisible() {
return isAppVisible;
}
public PersistentLogger getPersistentLogger() {
return persistentLogger;
}
private void initializeSecurityProvider() {
try {
Class.forName("org.signal.aesgcmprovider.AesGcmCipher");
} catch (ClassNotFoundException e) {
Log.e(TAG, "Failed to find AesGcmCipher class");
throw new ProviderInitializationException();
}
int aesPosition = Security.insertProviderAt(new AesGcmProvider(), 1);
Log.i(TAG, "Installed AesGcmProvider: " + aesPosition);
if (aesPosition < 0) {
Log.e(TAG, "Failed to install AesGcmProvider()");
throw new ProviderInitializationException();
}
int conscryptPosition = Security.insertProviderAt(Conscrypt.newProvider(), 2);
Log.i(TAG, "Installed Conscrypt provider: " + conscryptPosition);
if (conscryptPosition < 0) {
Log.w(TAG, "Did not install Conscrypt provider. May already be present.");
}
}
private void initializeLogging() {
persistentLogger = new PersistentLogger(this);
org.thoughtcrime.securesms.logging.Log.initialize(new AndroidLogger(), persistentLogger);
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
}
private void initializeCrashHandling() {
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionLogger(originalHandler));
}
private void initializeApplicationMigrations() {
ApplicationMigrations.onApplicationCreate(this, ApplicationDependencies.getJobManager());
}
public void initializeMessageRetrieval() {
this.incomingMessageObserver = new IncomingMessageObserver(this);
}
private void initializeAppDependencies() {
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this, new SignalServiceNetworkAccess(this)));
}
private void initializeFirstEverAppLaunch() {
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
Log.i(TAG, "First ever app launch!");
InsightsOptOut.userRequestedOptOut(this);
TextSecurePreferences.setAppMigrationVersion(this, ApplicationMigrations.CURRENT_VERSION);
TextSecurePreferences.setJobManagerVersion(this, JobManager.CURRENT_VERSION);
TextSecurePreferences.setLastExperienceVersionCode(this, Util.getCanonicalVersionCode());
TextSecurePreferences.setHasSeenStickerIntroTooltip(this, true);
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
}
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
}
}
private void initializeGcmCheck() {
if (TextSecurePreferences.isPushRegistered(this)) {
long nextSetTime = TextSecurePreferences.getFcmTokenLastSetTime(this) + TimeUnit.HOURS.toMillis(6);
if (TextSecurePreferences.getFcmToken(this) == null || nextSetTime <= System.currentTimeMillis()) {
ApplicationDependencies.getJobManager().add(new FcmRefreshJob());
}
}
}
private void initializeSignedPreKeyCheck() {
if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) {
ApplicationDependencies.getJobManager().add(new CreateSignedPreKeyJob(this));
}
}
private void initializeExpiringMessageManager() {
this.expiringMessageManager = new ExpiringMessageManager(this);
}
private void initializeRevealableMessageManager() {
this.viewOnceMessageManager = new ViewOnceMessageManager(this);
}
private void initializeTypingStatusRepository() {
this.typingStatusRepository = new TypingStatusRepository();
}
private void initializeTypingStatusSender() {
this.typingStatusSender = new TypingStatusSender(this);
}
private void initializePeriodicTasks() {
RotateSignedPreKeyListener.schedule(this);
DirectoryRefreshListener.schedule(this);
LocalBackupListener.schedule(this);
RotateSenderCertificateListener.schedule(this);
if (BuildConfig.PLAY_STORE_DISABLED) {
UpdateApkRefreshListener.schedule(this);
}
}
private void initializeRingRtc() {
try {
Set<String> HARDWARE_AEC_BLACKLIST = new HashSet<String>() {{
add("Pixel");
add("Pixel XL");
add("Moto G5");
add("Moto G (5S) Plus");
add("Moto G4");
add("TA-1053");
add("Mi A1");
add("E5823"); // Sony z5 compact
add("Redmi Note 5");
add("FP2"); // Fairphone FP2
add("MI 5");
}};
Set<String> OPEN_SL_ES_WHITELIST = new HashSet<String>() {{
add("Pixel");
add("Pixel XL");
}};
if (HARDWARE_AEC_BLACKLIST.contains(Build.MODEL)) {
WebRtcAudioUtils.setWebRtcBasedAcousticEchoCanceler(true);
}
if (!OPEN_SL_ES_WHITELIST.contains(Build.MODEL)) {
WebRtcAudioManager.setBlacklistDeviceForOpenSLESUsage(true);
}
CallConnectionFactory.initialize(this, new RingRtcLogger());
} catch (UnsatisfiedLinkError e) {
Log.w(TAG, e);
}
}
@SuppressLint("StaticFieldLeak")
private void initializeCircumvention() {
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
try {
ProviderInstaller.installIfNeeded(ApplicationContext.this);
} catch (Throwable t) {
Log.w(TAG, t);
}
}
return null;
}
};
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void executePendingContactSync() {
if (TextSecurePreferences.needsFullContactSync(this)) {
ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob(true));
}
}
private void initializePendingMessages() {
if (TextSecurePreferences.getNeedsMessagePull(this)) {
Log.i(TAG, "Scheduling a message fetch.");
if (Build.VERSION.SDK_INT >= 26) {
FcmJobService.schedule(this);
} else {
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
}
TextSecurePreferences.setNeedsMessagePull(this, false);
}
}
private void initializeBlobProvider() {
AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
BlobProvider.getInstance().onSessionStart(this);
});
}
@SuppressLint("RestrictedApi")
private void initializeCameraX() {
if (CameraXUtil.isSupported()) {
new Thread(() -> {
try {
CameraX.init(this, Camera2AppConfig.create(this));
} catch (Throwable t) {
Log.w(TAG, "Failed to initialize CameraX.");
}
}, "signal-camerax-initialization").start();
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(base, TextSecurePreferences.getLanguage(base)));
}
private static class ProviderInitializationException extends RuntimeException {
}
}

View File

@@ -17,22 +17,17 @@
*/
package org.thoughtcrime.securesms;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.preference.Preference;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import org.thoughtcrime.securesms.preferences.AdvancedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
@@ -41,11 +36,15 @@ import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.usernames.ProfileEditActivityV2;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
/**
* The Activity for application preference display and management.
@@ -66,6 +65,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
private static final String PREFERENCE_CATEGORY_CHATS = "preference_category_chats";
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
@@ -111,7 +111,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else {
Intent intent = new Intent(this, ConversationListActivity.class);
// TODO [greyson] Navigation
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
@@ -150,14 +151,21 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_CHATS)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_CHATS));
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_STORAGE));
this.findPreference(PREFERENCE_CATEGORY_DEVICES)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_DEVICES));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(PREFERENCE_CATEGORY_ADVANCED));
if (VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
tintIcons(getActivity());
}
tintIcons();
}
private void tintIcons() {
if (Build.VERSION.SDK_INT >= 21) return;
Preference preference = this.findPreference(PREFERENCE_CATEGORY_SMS_MMS);
preference.getIcon().setColorFilter(ThemeUtil.getThemedColor(requireContext(), R.attr.icon_tint), PorterDuff.Mode.SRC_IN);
}
@Override
@@ -196,38 +204,6 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
}
}
@TargetApi(11)
private void tintIcons(Context context) {
Drawable sms = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_textsms_white_24dp));
Drawable notifications = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_notifications_white_24dp));
Drawable privacy = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_security_white_24dp));
Drawable appearance = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_brightness_6_white_24dp));
Drawable chats = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_forum_white_24dp));
Drawable devices = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_laptop_white_24dp));
Drawable advanced = DrawableCompat.wrap(ContextCompat.getDrawable(context, R.drawable.ic_advanced_white_24dp));
int[] tintAttr = new int[]{R.attr.pref_icon_tint};
TypedArray typedArray = context.obtainStyledAttributes(tintAttr);
int color = typedArray.getColor(0, 0x0);
typedArray.recycle();
DrawableCompat.setTint(sms, color);
DrawableCompat.setTint(notifications, color);
DrawableCompat.setTint(privacy, color);
DrawableCompat.setTint(appearance, color);
DrawableCompat.setTint(chats, color);
DrawableCompat.setTint(devices, color);
DrawableCompat.setTint(advanced, color);
this.findPreference(PREFERENCE_CATEGORY_SMS_MMS).setIcon(sms);
this.findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS).setIcon(notifications);
this.findPreference(PREFERENCE_CATEGORY_APP_PROTECTION).setIcon(privacy);
this.findPreference(PREFERENCE_CATEGORY_APPEARANCE).setIcon(appearance);
this.findPreference(PREFERENCE_CATEGORY_CHATS).setIcon(chats);
this.findPreference(PREFERENCE_CATEGORY_DEVICES).setIcon(devices);
this.findPreference(PREFERENCE_CATEGORY_ADVANCED).setIcon(advanced);
}
private class CategoryClickListener implements Preference.OnPreferenceClickListener {
private String category;
@@ -255,6 +231,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_CHATS:
fragment = new ChatsPreferenceFragment();
break;
case PREFERENCE_CATEGORY_STORAGE:
fragment = new StoragePreferenceFragment();
break;
case PREFERENCE_CATEGORY_DEVICES:
Intent intent = new Intent(getActivity(), DeviceActivity.class);
startActivity(intent);
@@ -272,6 +251,9 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end);
fragmentTransaction.replace(android.R.id.content, fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
@@ -284,11 +266,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private class ProfileClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(preference.getContext(), CreateProfileActivity.class);
intent.putExtra(CreateProfileActivity.EXCLUDE_SYSTEM, true);
if (FeatureFlags.USERNAMES) {
requireActivity().startActivity(ProfileEditActivityV2.getLaunchIntent(requireContext()));
} else {
Intent intent = new Intent(preference.getContext(), CreateProfileActivity.class);
intent.putExtra(CreateProfileActivity.EXCLUDE_SYSTEM, true);
getActivity().startActivity(intent);
// ((BaseActionBarActivity)getActivity()).startActivitySceneTransition(intent, getActivity().findViewById(R.id.avatar), "avatar");
requireActivity().startActivity(intent);
}
return true;
}
}

View File

@@ -1,14 +1,15 @@
package org.thoughtcrime.securesms;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.AppCompatActivity;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewConfiguration;
@@ -16,6 +17,8 @@ import android.view.WindowManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
import java.lang.reflect.Field;
@@ -35,6 +38,7 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
protected void onResume() {
super.onResume();
initializeScreenshotSecurity();
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
}
@Override
@@ -52,9 +56,7 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
}
private void initializeScreenshotSecurity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
TextSecurePreferences.isScreenSecurityEnabled(this))
{
if (TextSecurePreferences.isScreenSecurityEnabled(this)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
@@ -92,4 +94,8 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
}
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
}
}

View File

@@ -0,0 +1,46 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import android.view.KeyEvent;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageActivityHelper;
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
public abstract class BaseActivity extends FragmentActivity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && isMenuWorkaroundRequired()) {
openOptionsMenu();
return true;
}
return super.onKeyUp(keyCode, event);
}
public static boolean isMenuWorkaroundRequired() {
return VERSION.SDK_INT < VERSION_CODES.KITKAT &&
VERSION.SDK_INT > VERSION_CODES.GINGERBREAD_MR1 &&
("LGE".equalsIgnoreCase(Build.MANUFACTURER) || "E6710".equalsIgnoreCase(Build.DEVICE));
}
@Override
protected void onResume() {
super.onResume();
DynamicLanguageActivityHelper.recreateIfNotInCorrectLanguage(this, TextSecurePreferences.getLanguage(this));
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(DynamicLanguageContextWrapper.updateContext(newBase, TextSecurePreferences.getLanguage(newBase)));
}
}

View File

@@ -1,7 +1,8 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -41,7 +42,7 @@ public class BasicIntroFragment extends Fragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.color_fragment, container, false);

View File

@@ -0,0 +1,49 @@
package org.thoughtcrime.securesms;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.view.View;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public interface BindableConversationItem extends Unbindable {
void bind(@NonNull MessageRecord messageRecord,
@NonNull Optional<MessageRecord> previousMessageRecord,
@NonNull Optional<MessageRecord> nextMessageRecord,
@NonNull GlideRequests glideRequests,
@NonNull Locale locale,
@NonNull Set<MessageRecord> batchSelected,
@NonNull Recipient recipients,
@Nullable String searchQuery,
boolean pulseHighlight);
MessageRecord getMessageRecord();
void setEventListener(@Nullable EventListener listener);
interface EventListener {
void onQuoteClicked(MmsMessageRecord messageRecord);
void onLinkPreviewClicked(@NonNull LinkPreview linkPreview);
void onMoreTextClicked(@NonNull RecipientId conversationRecipientId, long messageId, boolean isMms);
void onStickerClicked(@NonNull StickerLocator stickerLocator);
void onViewOnceMessageClicked(@NonNull MmsMessageRecord messageRecord);
void onSharedContactDetailsClicked(@NonNull Contact contact, @NonNull View avatarTransitionView);
void onAddToContactsClicked(@NonNull Contact contact);
void onMessageSharedContactClicked(@NonNull List<Recipient> choices);
void onInviteSharedContactClicked(@NonNull List<Recipient> choices);
void onReactionClicked(long messageId, boolean isMms);
}
}

View File

@@ -1,8 +1,7 @@
package org.thoughtcrime.securesms;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;

View File

@@ -4,12 +4,12 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.cursoradapter.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@@ -17,12 +17,14 @@ import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.loaders.BlockedContactsLoader;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.preferences.BlockedContactListItem;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -78,6 +80,12 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onStart() {
super.onStart();
getLoaderManager().restartLoader(0, null, this);
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
@@ -85,19 +93,19 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new BlockedContactsLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
if (getListAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(data);
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
if (getListAdapter() != null) {
((CursorAdapter) getListAdapter()).changeCursor(null);
}
@@ -107,7 +115,7 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Recipient recipient = ((BlockedContactListItem)view).getRecipient();
Intent intent = new Intent(getActivity(), RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
startActivity(intent);
}
@@ -129,8 +137,8 @@ public class BlockedContactsActivity extends PassphraseRequiredActionBarActivity
@Override
public void bindView(View view, Context context, Cursor cursor) {
String address = cursor.getString(1);
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), true);
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RecipientDatabase.ID)));
LiveRecipient recipient = Recipient.live(recipientId);
((BlockedContactListItem) view).set(glideRequests, recipient);
}

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AlertDialog;
import androidx.appcompat.app.AlertDialog;
public class ClearProfileAvatarActivity extends Activity {

View File

@@ -5,14 +5,14 @@ import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
@@ -20,12 +20,16 @@ import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
@@ -46,9 +50,9 @@ public class ConfirmIdentityDialog extends AlertDialog {
{
super(context);
Recipient recipient = Recipient.from(context, mismatch.getAddress(), false);
String name = recipient.toShortString();
String introduction = String.format(context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed), name, name);
Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context));
String name = recipient.toShortString(context);
String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name);
SpannableString spannableString = new SpannableString(introduction + " " +
context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact));
@@ -59,7 +63,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
setTitle(name);
setMessage(spannableString);
setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getAddress()));
setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getId()));
setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener());
}
@@ -78,12 +82,12 @@ public class ConfirmIdentityDialog extends AlertDialog {
private final MessageRecord messageRecord;
private final IdentityKeyMismatch mismatch;
private final Address address;
private final RecipientId recipientId;
private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, Address address) {
private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, RecipientId recipientId) {
this.messageRecord = messageRecord;
this.mismatch = mismatch;
this.address = address;
this.recipientId = recipientId;
}
@SuppressLint("StaticFieldLeak")
@@ -94,7 +98,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
@Override
protected Void doInBackground(Void... params) {
synchronized (SESSION_LOCK) {
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(address.toPhoneString(), 1);
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1);
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true);
@@ -137,17 +141,17 @@ public class ConfirmIdentityDialog extends AlertDialog {
if (messageRecord.isMms()) {
mmsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getRecipientId(getContext()),
mismatch.getIdentityKey());
if (messageRecord.getRecipient().isPushGroupRecipient()) {
MessageSender.resendGroupMessage(getContext(), messageRecord, mismatch.getAddress());
if (messageRecord.getRecipient().isPushGroup()) {
MessageSender.resendGroupMessage(getContext(), messageRecord, Recipient.resolved(mismatch.getRecipientId(getContext())).getId());
} else {
MessageSender.resend(getContext(), messageRecord);
}
} else {
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getRecipientId(getContext()),
mismatch.getIdentityKey());
MessageSender.resend(getContext(), messageRecord);
@@ -160,13 +164,13 @@ public class ConfirmIdentityDialog extends AlertDialog {
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext());
smsDatabase.removeMismatchedIdentity(messageRecord.getId(),
mismatch.getAddress(),
mismatch.getRecipientId(getContext()),
mismatch.getIdentityKey());
boolean legacy = !messageRecord.isContentBundleKeyExchange();
SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE,
messageRecord.getIndividualRecipient().getAddress().toPhoneString(),
Optional.of(RecipientUtil.toSignalServiceAddress(getContext(), messageRecord.getIndividualRecipient())),
messageRecord.getRecipientDeviceId(),
messageRecord.getDateSent(),
legacy ? Base64.decode(messageRecord.getBody()) : null,
@@ -175,9 +179,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
long pushId = pushDatabase.insert(envelope);
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new PushDecryptJob(getContext(), pushId, messageRecord.getId()));
ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), pushId, messageRecord.getId()));
} catch (IOException e) {
throw new AssertionError(e);
}

View File

@@ -19,17 +19,21 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -63,7 +67,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int displayMode = TextSecurePreferences.isSmsEnabled(this) ? DisplayMode.FLAG_ALL
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_GROUPS;
: DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS | DisplayMode.FLAG_INACTIVE_GROUPS;
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
}
@@ -112,10 +116,10 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
}
@Override
public void onContactSelected(String number) {}
public void onContactSelected(Optional<RecipientId> recipientId, String number) {}
@Override
public void onContactDeselected(String number) {}
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {}
private static class RefreshDirectoryTask extends AsyncTask<Context, Void, Void> {
@@ -127,7 +131,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override
protected Void doInBackground(Context... params) {
try {
DirectoryHelper.refreshDirectory(params[0], true);
} catch (IOException e) {

View File

@@ -0,0 +1,398 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.pnikosis.materialishprogress.ProgressWheel;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.adapter.FixedViewsAdapter;
import org.thoughtcrime.securesms.util.adapter.RecyclerViewConcatenateAdapterStickyHeader;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Fragment for selecting a one or more contacts from a list.
*
* @author Moxie Marlinspike
*
*/
public final class ContactSelectionListFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor>
{
@SuppressWarnings("unused")
private static final String TAG = Log.tag(ContactSelectionListFragment.class);
public static final String DISPLAY_MODE = "display_mode";
public static final String MULTI_SELECT = "multi_select";
public static final String REFRESHABLE = "refreshable";
public static final String RECENTS = "recents";
private TextView emptyText;
private Set<SelectedContact> selectedContacts;
private OnContactSelectedListener onContactSelectedListener;
private SwipeRefreshLayout swipeRefresh;
private View showContactsLayout;
private Button showContactsButton;
private TextView showContactsDescription;
private ProgressWheel showContactsProgress;
private String cursorFilter;
private RecyclerView recyclerView;
private RecyclerViewFastScroller fastScroller;
private ContactSelectionListAdapter cursorRecyclerViewAdapter;
@Nullable private FixedViewsAdapter footerAdapter;
@Nullable private InviteCallback inviteCallback;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof InviteCallback) {
inviteCallback = (InviteCallback) context;
}
}
@Override
public void onActivityCreated(Bundle icicle) {
super.onActivityCreated(icicle);
initializeCursor();
}
@Override
public void onStart() {
super.onStart();
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.onAllGranted(() -> {
if (!TextSecurePreferences.hasSuccessfullyRetrievedDirectory(getActivity())) {
handleContactPermissionGranted();
} else {
this.getLoaderManager().initLoader(0, null, this);
}
})
.onAnyDenied(() -> {
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
if (getActivity().getIntent().getBooleanExtra(RECENTS, false)) {
getLoaderManager().initLoader(0, null, ContactSelectionListFragment.this);
} else {
initializeNoContactsPermission();
}
})
.execute();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
emptyText = ViewUtil.findById(view, android.R.id.empty);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
swipeRefresh = ViewUtil.findById(view, R.id.swipe_refresh);
fastScroller = ViewUtil.findById(view, R.id.fast_scroller);
showContactsLayout = view.findViewById(R.id.show_contacts_container);
showContactsButton = view.findViewById(R.id.show_contacts_button);
showContactsDescription = view.findViewById(R.id.show_contacts_description);
showContactsProgress = view.findViewById(R.id.progress);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
swipeRefresh.setEnabled(getActivity().getIntent().getBooleanExtra(REFRESHABLE, true));
return view;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
public @NonNull List<SelectedContact> getSelectedContacts() {
List<SelectedContact> selected = new LinkedList<>();
if (selectedContacts != null) {
selected.addAll(selectedContacts);
}
return selected;
}
private boolean isMulti() {
return getActivity().getIntent().getBooleanExtra(MULTI_SELECT, false);
}
private void initializeCursor() {
cursorRecyclerViewAdapter = new ContactSelectionListAdapter(requireContext(),
GlideApp.with(this),
null,
new ListClickListener(),
isMulti());
selectedContacts = cursorRecyclerViewAdapter.getSelectedContacts();
RecyclerViewConcatenateAdapterStickyHeader concatenateAdapter = new RecyclerViewConcatenateAdapterStickyHeader();
concatenateAdapter.addAdapter(cursorRecyclerViewAdapter);
if (inviteCallback != null) {
footerAdapter = new FixedViewsAdapter(createInviteActionView(inviteCallback));
footerAdapter.hide();
concatenateAdapter.addAdapter(footerAdapter);
}
recyclerView.setAdapter(concatenateAdapter);
recyclerView.addItemDecoration(new StickyHeaderDecoration(concatenateAdapter, true, true));
}
private View createInviteActionView(@NonNull InviteCallback inviteCallback) {
View view = LayoutInflater.from(requireContext())
.inflate(R.layout.contact_selection_invite_action_item, (ViewGroup) requireView(), false);
view.setOnClickListener(v -> inviteCallback.onInvite());
return view;
}
private void initializeNoContactsPermission() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsProgress.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.contact_selection_list_fragment__signal_needs_access_to_your_contacts_in_order_to_display_them);
showContactsButton.setVisibility(View.VISIBLE);
showContactsButton.setOnClickListener(v -> {
Permissions.with(this)
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.ContactSelectionListFragment_signal_requires_the_contacts_permission_in_order_to_display_your_contacts))
.onSomeGranted(permissions -> {
if (permissions.contains(Manifest.permission.WRITE_CONTACTS)) {
handleContactPermissionGranted();
}
})
.execute();
});
}
public void setQueryFilter(String filter) {
this.cursorFilter = filter;
LoaderManager.getInstance(this).restartLoader(0, null, this);
}
public void resetQueryFilter() {
setQueryFilter(null);
swipeRefresh.setRefreshing(false);
}
public void setRefreshing(boolean refreshing) {
swipeRefresh.setRefreshing(refreshing);
}
public void reset() {
selectedContacts.clear();
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) {
getLoaderManager().restartLoader(0, null, this);
}
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new ContactsCursorLoader(getActivity(),
getActivity().getIntent().getIntExtra(DISPLAY_MODE, DisplayMode.FLAG_ALL),
cursorFilter, getActivity().getIntent().getBooleanExtra(RECENTS, false));
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, @Nullable Cursor data) {
swipeRefresh.setVisibility(View.VISIBLE);
showContactsLayout.setVisibility(View.GONE);
cursorRecyclerViewAdapter.changeCursor(data);
if (footerAdapter != null) {
footerAdapter.show();
}
emptyText.setText(R.string.contact_selection_group_activity__no_contacts);
boolean useFastScroller = data != null && data.getCount() > 20;
recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
if (useFastScroller) {
fastScroller.setVisibility(View.VISIBLE);
fastScroller.setRecyclerView(recyclerView);
} else {
fastScroller.setRecyclerView(null);
fastScroller.setVisibility(View.GONE);
}
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
cursorRecyclerViewAdapter.changeCursor(null);
fastScroller.setVisibility(View.GONE);
}
@SuppressLint("StaticFieldLeak")
private void handleContactPermissionGranted() {
final Context context = requireContext();
new AsyncTask<Void, Void, Boolean>() {
@Override
protected void onPreExecute() {
swipeRefresh.setVisibility(View.GONE);
showContactsLayout.setVisibility(View.VISIBLE);
showContactsButton.setVisibility(View.INVISIBLE);
showContactsDescription.setText(R.string.ConversationListFragment_loading);
showContactsProgress.setVisibility(View.VISIBLE);
showContactsProgress.spin();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
DirectoryHelper.refreshDirectory(context, false);
return true;
} catch (IOException e) {
Log.w(TAG, e);
}
return false;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
showContactsLayout.setVisibility(View.GONE);
swipeRefresh.setVisibility(View.VISIBLE);
reset();
} else {
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
initializeNoContactsPermission();
}
}
}.execute();
}
private class ListClickListener implements ContactSelectionListAdapter.ItemClickListener {
@Override
public void onItemClick(ContactSelectionListItem contact) {
SelectedContact selectedContact = contact.isUsernameType() ? SelectedContact.forUsername(contact.getRecipientId().orNull(), contact.getNumber())
: SelectedContact.forPhone(contact.getRecipientId().orNull(), contact.getNumber());
if (!isMulti() || !selectedContacts.contains(selectedContact)) {
if (contact.isUsernameType()) {
AlertDialog loadingDialog = SimpleProgressDialog.show(requireContext());
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), () -> {
return UsernameUtil.fetchUuidForUsername(requireContext(), contact.getNumber());
}, uuid -> {
loadingDialog.dismiss();
if (uuid.isPresent()) {
Recipient recipient = Recipient.externalUsername(requireContext(), uuid.get(), contact.getNumber());
selectedContacts.add(SelectedContact.forUsername(recipient.getId(), contact.getNumber()));
contact.setChecked(true);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(Optional.of(recipient.getId()), null);
}
} else {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.ContactSelectionListFragment_username_not_found)
.setMessage(getString(R.string.ContactSelectionListFragment_s_is_not_a_signal_user, contact.getNumber()))
.setPositiveButton(R.string.ContactSelectionListFragment_okay, (dialog, which) -> dialog.dismiss())
.show();
}
});
} else {
selectedContacts.add(selectedContact);
contact.setChecked(true);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactSelected(contact.getRecipientId(), contact.getNumber());
}
}
} else {
selectedContacts.remove(selectedContact);
contact.setChecked(false);
if (onContactSelectedListener != null) {
onContactSelectedListener.onContactDeselected(contact.getRecipientId(), contact.getNumber());
}
}
}
}
public void setOnContactSelectedListener(OnContactSelectedListener onContactSelectedListener) {
this.onContactSelectedListener = onContactSelectedListener;
}
public void setOnRefreshListener(SwipeRefreshLayout.OnRefreshListener onRefreshListener) {
this.swipeRefresh.setOnRefreshListener(onRefreshListener);
}
public interface OnContactSelectedListener {
void onContactSelected(Optional<RecipientId> recipientId, String number);
void onContactDeselected(Optional<RecipientId> recipientId, String number);
}
public interface InviteCallback {
void onInvite();
}
}

View File

@@ -2,9 +2,10 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import androidx.annotation.NonNull;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
@@ -54,12 +55,12 @@ public class CountrySelectionFragment extends ListFragment implements LoaderMana
}
@Override
public Loader<ArrayList<Map<String, String>>> onCreateLoader(int arg0, Bundle arg1) {
public @NonNull Loader<ArrayList<Map<String, String>>> onCreateLoader(int arg0, Bundle arg1) {
return new CountryListLoader(getActivity());
}
@Override
public void onLoadFinished(Loader<ArrayList<Map<String, String>>> loader,
public void onLoadFinished(@NonNull Loader<ArrayList<Map<String, String>>> loader,
ArrayList<Map<String, String>> results)
{
String[] from = {"country_name", "country_code"};
@@ -72,7 +73,7 @@ public class CountrySelectionFragment extends ListFragment implements LoaderMana
}
@Override
public void onLoaderReset(Loader<ArrayList<Map<String, String>>> arg0) {
public void onLoaderReset(@NonNull Loader<ArrayList<Map<String, String>>> arg0) {
this.setListAdapter(null);
}

View File

@@ -0,0 +1,447 @@
package org.thoughtcrime.securesms;
import android.Manifest;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.components.emoji.EmojiKeyboardProvider;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.components.emoji.MediaKeyboard;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileContentUpdateJob;
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints;
import org.thoughtcrime.securesms.profiles.SystemProfileUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BitmapDecodingException;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.crypto.ProfileCipher;
import org.whispersystems.signalservice.api.util.StreamDetails;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
@SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity {
private static final String TAG = CreateProfileActivity.class.getSimpleName();
public static final String NEXT_INTENT = "next_intent";
public static final String EXCLUDE_SYSTEM = "exclude_system";
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private InputAwareLayout container;
private ImageView avatar;
private CircularProgressButton finishButton;
private LabeledEditText name;
private EmojiToggle emojiToggle;
private MediaKeyboard mediaKeyboard;
private View reveal;
private Intent nextIntent;
private byte[] avatarBytes;
private File captureFile;
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
setContentView(R.layout.profile_create_activity);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
initializeResources();
initializeEmojiInput();
initializeProfileName(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false));
initializeProfileAvatar(getIntent().getBooleanExtra(EXCLUDE_SYSTEM, false));
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public void onBackPressed() {
if (container.isInputOpen()) container.hideCurrentInput(name.getInput());
else super.onBackPressed();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (container.getCurrentInput() == mediaKeyboard) {
container.hideAttachedInput(true);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case AvatarSelection.REQUEST_CODE_AVATAR:
if (resultCode == Activity.RESULT_OK) {
Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped"));
Uri inputFile = (data != null ? data.getData() : null);
if (inputFile == null && captureFile != null) {
inputFile = Uri.fromFile(captureFile);
}
if (data != null && data.getBooleanExtra("delete", false)) {
avatarBytes = null;
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_solid_white_24).asDrawable(this, getResources().getColor(R.color.grey_400)));
} else {
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar);
}
}
break;
case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
if (resultCode == Activity.RESULT_OK) {
new AsyncTask<Void, Void, byte[]>() {
@Override
protected byte[] doInBackground(Void... params) {
try {
BitmapUtil.ScaleResult result = BitmapUtil.createScaledBytes(CreateProfileActivity.this, AvatarSelection.getResultUri(data), new ProfileMediaConstraints());
return result.getBitmap();
} catch (BitmapDecodingException e) {
Log.w(TAG, e);
return null;
}
}
@Override
protected void onPostExecute(byte[] result) {
if (result != null) {
avatarBytes = result;
GlideApp.with(CreateProfileActivity.this)
.load(avatarBytes)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.circleCrop()
.into(avatar);
} else {
Toast.makeText(CreateProfileActivity.this, R.string.CreateProfileActivity_error_setting_profile_photo, Toast.LENGTH_LONG).show();
}
}
}.execute();
}
break;
}
}
private void initializeResources() {
TextView skipButton = ViewUtil.findById(this, R.id.skip_button);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.name = ViewUtil.findById(this, R.id.name);
this.emojiToggle = ViewUtil.findById(this, R.id.emoji_toggle);
this.mediaKeyboard = ViewUtil.findById(this, R.id.emoji_drawer);
this.container = ViewUtil.findById(this, R.id.container);
this.finishButton = ViewUtil.findById(this, R.id.finish_button);
this.reveal = ViewUtil.findById(this, R.id.reveal);
this.nextIntent = getIntent().getParcelableExtra(NEXT_INTENT);
this.avatar.setOnClickListener(view -> Permissions.with(this)
.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
.onAnyResult(this::startAvatarSelection)
.execute());
this.name.getInput().addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (s.toString().getBytes().length > ProfileCipher.NAME_PADDED_LENGTH) {
name.getInput().setError(getString(R.string.CreateProfileActivity_too_long));
finishButton.setEnabled(false);
} else if (name.getInput().getError() != null || !finishButton.isEnabled()) {
name.getInput().setError(null);
finishButton.setEnabled(true);
}
}
});
this.finishButton.setOnClickListener(view -> {
this.finishButton.setIndeterminateProgressMode(true);
this.finishButton.setProgress(50);
handleUpload();
});
skipButton.setOnClickListener(view -> {
if (nextIntent != null) startActivity(nextIntent);
finish();
});
}
private void initializeProfileName(boolean excludeSystem) {
if (!TextUtils.isEmpty(TextSecurePreferences.getProfileName(this))) {
String profileName = TextSecurePreferences.getProfileName(this);
name.setText(profileName);
name.getInput().setSelection(profileName.length(), profileName.length());
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileName(this).addListener(new ListenableFuture.Listener<String>() {
@Override
public void onSuccess(String result) {
if (!TextUtils.isEmpty(result)) {
name.setText(result);
name.getInput().setSelection(result.length(), result.length());
}
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
}
});
}
}
private void initializeProfileAvatar(boolean excludeSystem) {
RecipientId selfId = Recipient.self().getId();
if (AvatarHelper.getAvatarFile(this, selfId).exists() && AvatarHelper.getAvatarFile(this, selfId).length() > 0) {
new AsyncTask<Void, Void, byte[]>() {
@Override
protected byte[] doInBackground(Void... params) {
try {
return Util.readFully(AvatarHelper.getInputStreamFor(CreateProfileActivity.this, selfId));
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
@Override
protected void onPostExecute(byte[] result) {
if (result != null) {
avatarBytes = result;
GlideApp.with(CreateProfileActivity.this)
.load(result)
.circleCrop()
.into(avatar);
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else if (!excludeSystem) {
SystemProfileUtil.getSystemProfileAvatar(this, new ProfileMediaConstraints()).addListener(new ListenableFuture.Listener<byte[]>() {
@Override
public void onSuccess(byte[] result) {
if (result != null) {
avatarBytes = result;
GlideApp.with(CreateProfileActivity.this)
.load(result)
.circleCrop()
.into(avatar);
}
}
@Override
public void onFailure(ExecutionException e) {
Log.w(TAG, e);
}
});
}
}
private void initializeEmojiInput() {
this.emojiToggle.attach(mediaKeyboard);
this.emojiToggle.setOnClickListener(v -> {
if (container.getCurrentInput() == mediaKeyboard) {
container.showSoftkey(name.getInput());
} else {
container.show(name.getInput(), mediaKeyboard);
}
});
this.mediaKeyboard.setProviders(0, new EmojiKeyboardProvider(this, new EmojiKeyboardProvider.EmojiEventListener() {
@Override
public void onKeyEvent(KeyEvent keyEvent) {
name.dispatchKeyEvent(keyEvent);
}
@Override
public void onEmojiSelected(String emoji) {
final int start = name.getInput().getSelectionStart();
final int end = name.getInput().getSelectionEnd();
name.getText().replace(Math.min(start, end), Math.max(start, end), emoji);
name.getInput().setSelection(start + emoji.length());
}
}));
this.container.addOnKeyboardShownListener(() -> emojiToggle.setToMedia());
this.name.setOnClickListener(v -> container.showSoftkey(name.getInput()));
}
private void startAvatarSelection() {
captureFile = AvatarSelection.startAvatarSelection(this, avatarBytes != null, true);
}
private void handleUpload() {
final String name;
final StreamDetails avatar;
if (TextUtils.isEmpty(this.name.getText().toString())) name = null;
else name = this.name.getText().toString();
if (avatarBytes == null || avatarBytes.length == 0) avatar = null;
else avatar = new StreamDetails(new ByteArrayInputStream(avatarBytes),
"image/jpeg", avatarBytes.length);
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
Context context = CreateProfileActivity.this;
byte[] profileKey = ProfileKeyUtil.getProfileKey(CreateProfileActivity.this);
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
try {
accountManager.setProfileName(profileKey, name);
TextSecurePreferences.setProfileName(context, name);
DatabaseFactory.getRecipientDatabase(context).setProfileName(Recipient.self().getId(), name);
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
try {
accountManager.setProfileAvatar(profileKey, avatar);
AvatarHelper.setAvatar(CreateProfileActivity.this, Recipient.self().getId(), avatarBytes);
TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileKeyUpdateJob());
ApplicationDependencies.getJobManager().add(new MultiDeviceProfileContentUpdateJob());
return true;
}
@Override
public void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (result) {
if (captureFile != null) captureFile.delete();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) handleFinishedLollipop();
else handleFinishedLegacy();
} else {
Toast.makeText(CreateProfileActivity.this, R.string.CreateProfileActivity_problem_setting_profile, Toast.LENGTH_LONG).show();
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void handleFinishedLegacy() {
finishButton.setProgress(0);
if (nextIntent != null) startActivity(nextIntent);
finish();
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void handleFinishedLollipop() {
int[] finishButtonLocation = new int[2];
int[] revealLocation = new int[2];
finishButton.getLocationInWindow(finishButtonLocation);
reveal.getLocationInWindow(revealLocation);
int finishX = finishButtonLocation[0] - revealLocation[0];
int finishY = finishButtonLocation[1] - revealLocation[1];
finishX += finishButton.getWidth() / 2;
finishY += finishButton.getHeight() / 2;
Animator animation = ViewAnimationUtils.createCircularReveal(reveal, finishX, finishY, 0f, (float) Math.max(reveal.getWidth(), reveal.getHeight()));
animation.setDuration(500);
animation.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {
finishButton.setProgress(0);
if (nextIntent != null) startActivity(nextIntent);
finish();
}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
});
reveal.setVisibility(View.VISIBLE);
animation.start();
}
}

View File

@@ -149,7 +149,8 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActionBarActivi
if (getIntent().hasExtra("next_intent")) {
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
} else {
startActivity(new Intent(this, ConversationListActivity.class));
// TODO [greyson] Navigation
startActivity(new Intent(this, MainActivity.class));
}
}

View File

@@ -8,18 +8,20 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import org.thoughtcrime.securesms.logging.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.AccountManagerFactory;
import org.thoughtcrime.securesms.qr.ScanListener;
@@ -61,6 +63,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
@Override
public void onCreate(Bundle bundle, boolean ready) {
getSupportActionBar().setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_arrow_left_24));
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__linked_devices);
this.deviceAddFragment = new DeviceAddFragment();
@@ -75,6 +78,16 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
} else {
initFragment(android.R.id.content, deviceListFragment, dynamicLanguage.getCurrentLocale());
}
overridePendingTransition(R.anim.slide_from_end, R.anim.slide_to_start);
}
@Override
protected void onPause() {
if (isFinishing()) {
overridePendingTransition(R.anim.slide_from_start, R.anim.slide_to_end);
}
super.onPause();
}
@Override
@@ -165,7 +178,7 @@ public class DeviceActivity extends PassphraseRequiredActionBarActivity
try {
Context context = DeviceActivity.this;
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context);
SignalServiceAccountManager accountManager = ApplicationDependencies.getSignalServiceAccountManager();
String verificationCode = accountManager.getNewDeviceVerificationCode();
String ephemeralId = uri.getQueryParameter("uuid");
String publicKeyEncoded = uri.getQueryParameter("pub_key");

View File

@@ -5,7 +5,8 @@ import android.annotation.TargetApi;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
@@ -29,7 +30,7 @@ public class DeviceAddFragment extends Fragment {
private ScanListener scanListener;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);

View File

@@ -3,7 +3,8 @@ package org.thoughtcrime.securesms;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -16,7 +17,7 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
private Uri uri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = (LinearLayout) inflater.inflate(R.layout.device_link_fragment, container, false);
this.container.findViewById(R.id.link_device).setOnClickListener(this);
@@ -31,6 +32,7 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
if (newConfiguration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
container.setOrientation(LinearLayout.HORIZONTAL);
} else {
@@ -51,6 +53,6 @@ public class DeviceLinkFragment extends Fragment implements View.OnClickListener
}
public interface LinkClickedListener {
public void onLink(Uri uri);
void onLink(Uri uri);
}
}

View File

@@ -1,17 +1,19 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v7.app.AlertDialog;
import androidx.annotation.NonNull;
import androidx.fragment.app.ListFragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.jobs.RefreshUnidentifiedDeliveryAbilityJob;
import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -25,7 +27,6 @@ import android.widget.Toast;
import com.melnykov.fab.FloatingActionButton;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -35,23 +36,19 @@ import java.io.IOException;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
public class DeviceListFragment extends ListFragment
implements LoaderManager.LoaderCallbacks<List<Device>>,
ListView.OnItemClickListener, InjectableType, Button.OnClickListener
ListView.OnItemClickListener, Button.OnClickListener
{
private static final String TAG = DeviceListFragment.class.getSimpleName();
@Inject
SignalServiceAccountManager accountManager;
private Locale locale;
private View empty;
private View progressContainer;
private FloatingActionButton addDeviceButton;
private Button.OnClickListener addDeviceButtonListener;
private SignalServiceAccountManager accountManager;
private Locale locale;
private View empty;
private View progressContainer;
private FloatingActionButton addDeviceButton;
private Button.OnClickListener addDeviceButtonListener;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -62,7 +59,7 @@ public class DeviceListFragment extends ListFragment
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
ApplicationContext.getInstance(activity).injectDependencies(this);
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
}
@Override
@@ -89,7 +86,7 @@ public class DeviceListFragment extends ListFragment
}
@Override
public Loader<List<Device>> onCreateLoader(int id, Bundle args) {
public @NonNull Loader<List<Device>> onCreateLoader(int id, Bundle args) {
empty.setVisibility(View.GONE);
progressContainer.setVisibility(View.VISIBLE);
@@ -97,7 +94,7 @@ public class DeviceListFragment extends ListFragment
}
@Override
public void onLoadFinished(Loader<List<Device>> loader, List<Device> data) {
public void onLoadFinished(@NonNull Loader<List<Device>> loader, List<Device> data) {
progressContainer.setVisibility(View.GONE);
if (data == null) {
@@ -116,7 +113,7 @@ public class DeviceListFragment extends ListFragment
}
@Override
public void onLoaderReset(Loader<List<Device>> loader) {
public void onLoaderReset(@NonNull Loader<List<Device>> loader) {
setListAdapter(null);
}
@@ -165,6 +162,7 @@ public class DeviceListFragment extends ListFragment
builder.show();
}
@SuppressLint("StaticFieldLeak")
private void handleDisconnectDevice(final long deviceId) {
new ProgressDialogAsyncTask<Void, Void, Void>(getActivity(),
R.string.DeviceListActivity_unlinking_device_no_ellipsis,
@@ -174,10 +172,6 @@ public class DeviceListFragment extends ListFragment
protected Void doInBackground(Void... params) {
try {
accountManager.removeDevice(deviceId);
ApplicationContext.getInstance(getContext())
.getJobManager()
.add(new RefreshUnidentifiedDeliveryAbilityJob(getContext()));
} catch (IOException e) {
Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
@@ -210,7 +204,7 @@ public class DeviceListFragment extends ListFragment
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
public @NonNull View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
}

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import androidx.appcompat.app.AlertDialog;
import android.view.Window;
public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActivity {

View File

@@ -7,19 +7,23 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.app.NotificationCompat;
import android.support.v4.view.ViewPager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.viewpager.widget.ViewPager;
import android.view.View;
import com.melnykov.fab.FloatingActionButton;
import com.nineoldandroids.animation.ArgbEvaluator;
import org.thoughtcrime.securesms.IntroPagerAdapter.IntroPage;
import org.thoughtcrime.securesms.database.model.Sticker;
import org.thoughtcrime.securesms.experienceupgrades.StickersIntroFragment;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
@@ -29,11 +33,17 @@ import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Collections;
import java.util.List;
public class ExperienceUpgradeActivity extends BaseActionBarActivity implements TypingIndicatorIntroFragment.Controller {
public class ExperienceUpgradeActivity extends BaseActionBarActivity
implements TypingIndicatorIntroFragment.Controller,
LinkPreviewsIntroFragment.Controller,
StickersIntroFragment.Controller
{
private static final String TAG = ExperienceUpgradeActivity.class.getSimpleName();
private static final String DISMISS_ACTION = "org.thoughtcrime.securesms.ExperienceUpgradeActivity.DISMISS_ACTION";
private static final int NOTIFICATION_ID = 1339;
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private enum ExperienceUpgrade {
SIGNAL_REBRANDING(157,
new IntroPage(0xFF2090EA,
@@ -80,7 +90,21 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
R.string.ExperienceUpgradeActivity_now_you_can_optionally_see_and_share_when_messages_are_being_typed,
null,
true);
true),
LINK_PREVIEWS(449,
new IntroPage(0xFF2090EA, LinkPreviewsIntroFragment.newInstance()),
R.string.ExperienceUpgradeActivity_introducing_link_previews,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
R.string.ExperienceUpgradeActivity_optional_link_previews_are_now_supported,
null,
true),
STICKERS(580,
new IntroPage(0xFF2090EA, StickersIntroFragment.newInstance()),
R.string.ExperienceUpgradeActivity_introducing_stickers,
R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
R.string.ExperienceUpgradeActivity_why_use_words_when_you_can_use_stickers,
null,
true);
private int version;
private List<IntroPage> pages;
@@ -150,7 +174,7 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStatusBarColor(getResources().getColor(R.color.signal_primary_dark));
dynamicTheme.onCreate(this);
final Optional<ExperienceUpgrade> upgrade = getExperienceUpgrade(this);
if (!upgrade.isPresent()) {
@@ -175,14 +199,21 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
private void onContinue(Optional<ExperienceUpgrade> seenUpgrade) {
ServiceUtil.getNotificationManager(this).cancel(NOTIFICATION_ID);
int latestVersion = seenUpgrade.isPresent() ? seenUpgrade.get().getVersion()
: Util.getCurrentApkReleaseVersion(this);
: Util.getCanonicalVersionCode();
TextSecurePreferences.setLastExperienceVersionCode(this, latestVersion);
if (seenUpgrade.isPresent() && seenUpgrade.get().nextIntent != null) {
Intent intent = new Intent(this, seenUpgrade.get().nextIntent);
Intent nextIntent = new Intent(this, ConversationListActivity.class);
// TODO [greyson] Navigation
Intent nextIntent = new Intent(this, MainActivity.class);
intent.putExtra("next_intent", nextIntent);
startActivity(intent);
} else {
@@ -197,7 +228,7 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
}
public static Optional<ExperienceUpgrade> getExperienceUpgrade(Context context) {
final int currentVersionCode = Util.getCurrentApkReleaseVersion(context);
final int currentVersionCode = Util.getCanonicalVersionCode();
final int lastSeenVersion = TextSecurePreferences.getLastExperienceVersionCode(context);
Log.i(TAG, "getExperienceUpgrade(" + lastSeenVersion + ")");
@@ -215,33 +246,18 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
}
@Override
public void onFinished() {
public void onTypingIndicatorsFinished() {
onContinue(Optional.of(ExperienceUpgrade.TYPING_INDICATORS));
}
private final class OnPageChangeListener implements ViewPager.OnPageChangeListener {
private final ArgbEvaluator evaluator = new ArgbEvaluator();
private final ExperienceUpgrade upgrade;
@Override
public void onLinkPreviewsFinished() {
onContinue(Optional.of(ExperienceUpgrade.LINK_PREVIEWS));
}
public OnPageChangeListener(ExperienceUpgrade upgrade) {
this.upgrade = upgrade;
}
@Override
public void onPageSelected(int position) {}
@Override
public void onPageScrollStateChanged(int state) {}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
final int nextPosition = (position + 1) % upgrade.getPages().size();
final int color = (Integer)evaluator.evaluate(positionOffset,
upgrade.getPage(position).backgroundColor,
upgrade.getPage(nextPosition).backgroundColor);
getWindow().setBackgroundDrawable(new ColorDrawable(color));
}
@Override
public void onStickersFinished() {
onContinue(Optional.of(ExperienceUpgrade.STICKERS));
}
public static class AppUpgradeReceiver extends BroadcastReceiver {
@@ -299,7 +315,7 @@ public class ExperienceUpgradeActivity extends BaseActionBarActivity implements
.build();
ServiceUtil.getNotificationManager(context).notify(NOTIFICATION_ID, notification);
} else if (DISMISS_ACTION.equals(intent.getAction())) {
TextSecurePreferences.setExperienceDismissedVersionCode(context, Util.getCurrentApkReleaseVersion(context));
TextSecurePreferences.setExperienceDismissedVersionCode(context, Util.getCanonicalVersionCode());
}
}
}

View File

@@ -1,8 +1,8 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

View File

@@ -20,12 +20,17 @@ package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import android.text.TextUtils;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.logging.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -40,7 +45,6 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener;
@@ -48,7 +52,6 @@ import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.RecipientsEditor;
import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
@@ -58,6 +61,7 @@ import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -88,7 +92,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private final static String TAG = GroupCreateActivity.class.getSimpleName();
public static final String GROUP_ADDRESS_EXTRA = "group_recipient";
public static final String GROUP_ID_EXTRA = "group_id";
public static final String GROUP_THREAD_EXTRA = "group_thread";
private final DynamicTheme dynamicTheme = new DynamicTheme();
@@ -115,7 +119,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
protected void onCreate(Bundle state, boolean ready) {
setContentView(R.layout.group_create_activity);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
initializeAppBar();
initializeResources();
initializeExistingGroup();
}
@@ -174,6 +178,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
addSelectedContacts(recipients.toArray(new Recipient[recipients.size()]));
}
private void initializeAppBar() {
Drawable upIcon = ContextCompat.getDrawable(this, R.drawable.ic_arrow_left_24);
getSupportActionBar().setHomeAsUpIndicator(upIcon);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
private void initializeResources() {
RecipientsEditor recipientsEditor = ViewUtil.findById(this, R.id.recipients_text);
PushRecipientsPanel recipientsPanel = ViewUtil.findById(this, R.id.recipients);
@@ -187,15 +197,15 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
recipientsEditor.setHint(R.string.recipients_panel__add_members);
recipientsPanel.setPanelChangeListener(this);
findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this)));
avatar.setOnClickListener(view -> Crop.pickImage(GroupCreateActivity.this));
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_outline_40, R.drawable.ic_group_outline_20).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this)));
avatar.setOnClickListener(view -> AvatarSelection.startAvatarSelection(this, false, false));
}
private void initializeExistingGroup() {
final Address groupAddress = getIntent().getParcelableExtra(GROUP_ADDRESS_EXTRA);
final String groupId = getIntent().getStringExtra(GROUP_ID_EXTRA);
if (groupAddress != null) {
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupAddress.toGroupString());
if (groupId != null) {
new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupId);
}
}
@@ -258,7 +268,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
startActivity(intent);
finish();
}
@@ -281,23 +291,23 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
switch (reqCode) {
case PICK_CONTACT:
List<String> selected = data.getStringArrayListExtra("contacts");
for (String contact : selected) {
Address address = Address.fromExternal(this, contact);
Recipient recipient = Recipient.from(this, address, false);
List<RecipientId> selected = data.getParcelableArrayListExtra(PushContactSelectionActivity.KEY_SELECTED_RECIPIENTS);
for (RecipientId contact : selected) {
Recipient recipient = Recipient.resolved(contact);
addSelectedContacts(recipient);
}
break;
case Crop.REQUEST_PICK:
new Crop(data.getData()).output(outputFile).asSquare().start(this);
case AvatarSelection.REQUEST_CODE_AVATAR:
AvatarSelection.circularCropImage(this, data.getData(), outputFile, R.string.CropImageActivity_group_avatar);
break;
case Crop.REQUEST_CROP:
case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
final Uri resultUri = AvatarSelection.getResultUri(data);
GlideApp.with(this)
.asBitmap()
.load(Crop.getOutput(data))
.load(resultUri)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
@@ -305,7 +315,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
setAvatar(Crop.getOutput(data), resource);
setAvatar(resultUri, resource);
}
});
}
@@ -335,15 +345,17 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
protected GroupActionResult doInBackground(Void... avoid) {
List<Address> memberAddresses = new LinkedList<>();
List<RecipientId> memberAddresses = new LinkedList<>();
for (Recipient recipient : members) {
memberAddresses.add(recipient.getAddress());
memberAddresses.add(recipient.getId());
}
memberAddresses.add(Recipient.self().getId());
String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true);
Recipient groupRecipient = Recipient.from(activity, Address.fromSerialized(groupId), true);
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true);
RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId);
Recipient groupRecipient = Recipient.resolved(groupRecipientId);
long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT);
return new GroupActionResult(groupRecipient, threadId);
}
@@ -446,7 +458,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (!activity.isFinishing()) {
Intent intent = activity.getIntent();
intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId());
intent.putExtra(GROUP_ADDRESS_EXTRA, result.get().getGroupRecipient().getAddress());
intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId());
activity.setResult(RESULT_OK, intent);
activity.finish();
}
@@ -488,8 +500,8 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if (failIfNotPush && !isPush) {
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_cannot_add_non_push_to_existing_group,
recipient.toShortString())));
} else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getAddress().serialize())) {
recipient.toShortString(activity))));
} else if (TextUtils.equals(TextSecurePreferences.getLocalNumber(activity), recipient.getE164().or(""))) {
results.add(new Result(null, false, activity.getString(R.string.GroupCreateActivity_youre_already_in_the_group)));
} else {
results.add(new Result(recipient, isPush, null));

View File

@@ -4,12 +4,13 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.provider.ContactsContract;
import android.support.v7.app.AlertDialog;
import androidx.appcompat.app.AlertDialog;
import android.text.TextUtils;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientExporter;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Util;
import java.util.LinkedList;
@@ -32,7 +33,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
@Override
protected List<Recipient> doInBackground(Void... params) {
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.getAddress().toGroupString(), true);
return DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), true);
}
@Override
@@ -66,18 +67,11 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
if (recipient.getContactUri() != null) {
Intent intent = new Intent(context, RecipientPreferenceActivity.class);
intent.putExtra(RecipientPreferenceActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(RecipientPreferenceActivity.RECIPIENT_ID, recipient.getId());
context.startActivity(intent);
} else {
final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
if (recipient.getAddress().isEmail()) {
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, recipient.getAddress().toEmailString());
} else {
intent.putExtra(ContactsContract.Intents.Insert.PHONE, recipient.getAddress().toPhoneString());
}
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
context.startActivity(intent);
context.startActivity(RecipientExporter.export(recipient).asAddContactIntent());
}
}
}
@@ -97,7 +91,7 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
public GroupMembers(List<Recipient> recipients) {
for (Recipient recipient : recipients) {
if (isLocalNumber(recipient)) {
if (recipient.isLocalNumber()) {
members.push(recipient);
} else {
members.add(recipient);
@@ -109,15 +103,10 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
List<String> recipientStrings = new LinkedList<>();
for (Recipient recipient : members) {
if (isLocalNumber(recipient)) {
if (recipient.isLocalNumber()) {
recipientStrings.add(context.getString(R.string.GroupMembersDialog_me));
} else {
String name = recipient.toShortString();
if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) {
name += " ~" + recipient.getProfileName();
}
String name = getRecipientName(recipient);
recipientStrings.add(name);
}
}
@@ -125,12 +114,20 @@ public class GroupMembersDialog extends AsyncTask<Void, Void, List<Recipient>> {
return recipientStrings.toArray(new String[members.size()]);
}
private String getRecipientName(Recipient recipient) {
if (FeatureFlags.PROFILE_DISPLAY) return recipient.getDisplayName(context);
String name = recipient.toShortString(context);
if (recipient.getName(context) == null && !TextUtils.isEmpty(recipient.getProfileName())) {
name += " ~" + recipient.getProfileName();
}
return name;
}
public Recipient get(int index) {
return members.get(index);
}
private boolean isLocalNumber(Recipient recipient) {
return Util.isOwnNumber(context, recipient.getAddress());
}
}
}

View File

@@ -0,0 +1,115 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
import java.io.Closeable;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;
/**
* The central entry point for all envelopes that have been retrieved. Envelopes must be processed
* here to guarantee proper ordering.
*/
public class IncomingMessageProcessor {
private static final String TAG = Log.tag(IncomingMessageProcessor.class);
private final Context context;
private final ReentrantLock lock;
public IncomingMessageProcessor(@NonNull Context context) {
this.context = context;
this.lock = new ReentrantLock();
}
/**
* @return An instance of a Processor that will allow you to process messages in a thread safe
* way. Must be closed.
*/
public Processor acquire() {
lock.lock();
Thread current = Thread.currentThread();
Log.d(TAG, "Lock acquired by thread " + current.getId() + " (" + current.getName() + ")");
return new Processor(context);
}
private void release() {
Thread current = Thread.currentThread();
Log.d(TAG, "Lock about to be released by thread " + current.getId() + " (" + current.getName() + ")");
lock.unlock();
}
public class Processor implements Closeable {
private final Context context;
private final PushDatabase pushDatabase;
private final MmsSmsDatabase mmsSmsDatabase;
private final JobManager jobManager;
private Processor(@NonNull Context context) {
this.context = context;
this.pushDatabase = DatabaseFactory.getPushDatabase(context);
this.mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
this.jobManager = ApplicationDependencies.getJobManager();
}
/**
* @return The id of the {@link PushDecryptMessageJob} that was scheduled to process the message, if
* one was created. Otherwise null.
*/
public @Nullable String processEnvelope(@NonNull SignalServiceEnvelope envelope) {
if (envelope.hasSource()) {
Recipient.externalPush(context, envelope.getSourceAddress());
}
if (envelope.isReceipt()) {
processReceipt(envelope);
return null;
} else if (envelope.isPreKeySignalMessage() || envelope.isSignalMessage() || envelope.isUnidentifiedSender()) {
return processMessage(envelope);
} else {
Log.w(TAG, "Received envelope of unknown type: " + envelope.getType());
return null;
}
}
private @NonNull String processMessage(@NonNull SignalServiceEnvelope envelope) {
Log.i(TAG, "Received message. Inserting in PushDatabase.");
long id = pushDatabase.insert(envelope);
PushDecryptMessageJob job = new PushDecryptMessageJob(context, id);
jobManager.add(job);
return job.getId();
}
private void processReceipt(@NonNull SignalServiceEnvelope envelope) {
Log.i(TAG, String.format(Locale.ENGLISH, "Received receipt: (XXXXX, %d)", envelope.getTimestamp()));
mmsSmsDatabase.incrementDeliveryReceiptCount(new SyncMessageId(Recipient.externalPush(context, envelope.getSourceAddress()).getId(), envelope.getTimestamp()),
System.currentTimeMillis());
}
@Override
public void close() {
release();
}
}
}

View File

@@ -1,8 +1,8 @@
package org.thoughtcrime.securesms;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import java.util.List;

View File

@@ -0,0 +1,291 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.AnimRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.concurrent.ExecutionException;
public class InviteActivity extends PassphraseRequiredActionBarActivity implements ContactSelectionListFragment.OnContactSelectedListener {
private ContactSelectionListFragment contactsFragment;
private EditText inviteText;
private ViewGroup smsSendFrame;
private Button smsSendButton;
private Animation slideInAnimation;
private Animation slideOutAnimation;
private DynamicTheme dynamicTheme = new DynamicNoActionBarInviteTheme();
private Toolbar primaryToolbar;
@Override
protected void onPreCreate() {
super.onPreCreate();
dynamicTheme.onCreate(this);
}
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_SMS);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
setContentView(R.layout.invite_activity);
initializeAppBar();
initializeResources();
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
private void initializeAppBar() {
primaryToolbar = findViewById(R.id.toolbar);
setSupportActionBar(primaryToolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__invite_friends);
}
private void initializeResources() {
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
View shareButton = ViewUtil.findById(this, R.id.share_button);
View smsButton = ViewUtil.findById(this, R.id.sms_button);
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
inviteText = ViewUtil.findById(this, R.id.invite_text);
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
updateSmsButtonText();
contactsFragment.setOnContactSelectedListener(this);
shareButton.setOnClickListener(new ShareClickListener());
smsButton.setOnClickListener(new SmsClickListener());
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
smsSendButton.setOnClickListener(new SmsSendClickListener());
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
}
private Animation loadAnimation(@AnimRes int animResId) {
final Animation animation = AnimationUtils.loadAnimation(this, animResId);
animation.setInterpolator(new FastOutSlowInInterpolator());
return animation;
}
@Override
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
updateSmsButtonText();
}
@Override
public void onContactDeselected(Optional<RecipientId> recipientId, String number) {
updateSmsButtonText();
}
private void sendSmsInvites() {
new SendSmsInvitesAsyncTask(this, inviteText.getText().toString())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
contactsFragment.getSelectedContacts()
.toArray(new SelectedContact[contactsFragment.getSelectedContacts().size()]));
}
private void updateSmsButtonText() {
smsSendButton.setText(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_to_friends,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()));
smsSendButton.setEnabled(!contactsFragment.getSelectedContacts().isEmpty());
}
@Override public void onBackPressed() {
if (smsSendFrame.getVisibility() == View.VISIBLE) {
cancelSmsSelection();
} else {
super.onBackPressed();
}
}
private void cancelSmsSelection() {
setPrimaryColorsToolbarNormal();
contactsFragment.reset();
updateSmsButtonText();
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE);
}
private void setPrimaryColorsToolbarNormal() {
primaryToolbar.setBackgroundColor(0);
primaryToolbar.getNavigationIcon().setColorFilter(null);
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.title_text_color_primary));
if (Build.VERSION.SDK_INT >= 23) {
getWindow().setStatusBarColor(ThemeUtil.getThemedColor(this, android.R.attr.statusBarColor));
getWindow().setNavigationBarColor(ThemeUtil.getThemedColor(this, android.R.attr.navigationBarColor));
WindowUtil.setLightStatusBarFromTheme(this);
}
WindowUtil.setLightNavigationBarFromTheme(this);
}
private void setPrimaryColorsToolbarForSms() {
primaryToolbar.setBackgroundColor(ContextCompat.getColor(this, R.color.signal_primary));
primaryToolbar.getNavigationIcon().setColorFilter(ThemeUtil.getThemedColor(this, R.attr.conversation_subtitle_color), PorterDuff.Mode.SRC_IN);
primaryToolbar.setTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
if (Build.VERSION.SDK_INT >= 23) {
getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.signal_primary));
WindowUtil.clearLightStatusBar(getWindow());
}
if (Build.VERSION.SDK_INT >= 27) {
getWindow().setNavigationBarColor(ContextCompat.getColor(this, R.color.signal_primary));
WindowUtil.clearLightNavigationBar(getWindow());
}
}
private class ShareClickListener implements OnClickListener {
@Override
public void onClick(View v) {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, inviteText.getText().toString());
sendIntent.setType("text/plain");
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(Intent.createChooser(sendIntent, getString(R.string.InviteActivity_invite_to_signal)));
} else {
Toast.makeText(InviteActivity.this, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
}
private class SmsClickListener implements OnClickListener {
@Override
public void onClick(View v) {
setPrimaryColorsToolbarForSms();
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
}
}
private class SmsCancelClickListener implements OnClickListener {
@Override
public void onClick(View v) {
cancelSmsSelection();
}
}
private class SmsSendClickListener implements OnClickListener {
@Override
public void onClick(View v) {
new AlertDialog.Builder(InviteActivity.this)
.setTitle(getResources().getQuantityString(R.plurals.InviteActivity_send_sms_invites,
contactsFragment.getSelectedContacts().size(),
contactsFragment.getSelectedContacts().size()))
.setMessage(inviteText.getText().toString())
.setPositiveButton(R.string.yes, (dialog, which) -> sendSmsInvites())
.setNegativeButton(R.string.no, (dialog, which) -> dialog.dismiss())
.show();
}
}
private class ContactFilterChangedListener implements OnFilterChangedListener {
@Override
public void onFilterChanged(String filter) {
contactsFragment.setQueryFilter(filter);
}
}
@SuppressLint("StaticFieldLeak")
private class SendSmsInvitesAsyncTask extends ProgressDialogAsyncTask<SelectedContact,Void,Void> {
private final String message;
SendSmsInvitesAsyncTask(Context context, String message) {
super(context, R.string.InviteActivity_sending, R.string.InviteActivity_sending);
this.message = message;
}
@Override
protected Void doInBackground(SelectedContact... contacts) {
final Context context = getContext();
if (context == null) return null;
for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context);
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
MessageSender.send(context, new OutgoingTextMessage(recipient, message, subscriptionId), -1L, true, null);
if (recipient.getContactUri() != null) {
DatabaseFactory.getRecipientDatabase(context).setHasSentInvite(recipient.getId());
}
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
final Context context = getContext();
if (context == null) return;
ViewUtil.animateOut(smsSendFrame, slideOutAnimation, View.GONE).addListener(new Listener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
contactsFragment.reset();
}
@Override
public void onFailure(ExecutionException e) {}
});
Toast.makeText(context, R.string.InviteActivity_invitations_sent, Toast.LENGTH_LONG).show();
}
}
}

View File

@@ -0,0 +1,63 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class LinkPreviewsIntroFragment extends Fragment {
private Controller controller;
public static LinkPreviewsIntroFragment newInstance() {
LinkPreviewsIntroFragment fragment = new LinkPreviewsIntroFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
public LinkPreviewsIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (!(getActivity() instanceof Controller)) {
throw new IllegalStateException("Parent activity must implement the Controller interface.");
}
controller = (Controller) getActivity();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.experience_upgrade_link_previews_fragment, container, false);
view.findViewById(R.id.experience_ok_button).setOnClickListener(v -> {
ApplicationDependencies.getJobManager().add(new MultiDeviceConfigurationUpdateJob(TextSecurePreferences.isReadReceiptsEnabled(requireContext()),
TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(requireContext()),
TextSecurePreferences.isLinkPreviewsEnabled(requireContext())));
controller.onLinkPreviewsFinished();
});
return view;
}
public interface Controller {
void onLinkPreviewsFinished();
}
}

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import androidx.fragment.app.FragmentTransaction;
import org.thoughtcrime.securesms.logging.Log;
import android.view.MenuItem;
import android.widget.Toast;

View File

@@ -0,0 +1,45 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
public class MainActivity extends PassphraseRequiredActionBarActivity {
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final MainNavigator navigator = new MainNavigator(this);
@Override
protected void onCreate(Bundle savedInstanceState, boolean ready) {
super.onCreate(savedInstanceState, ready);
setContentView(R.layout.main_activity);
navigator.onCreate(savedInstanceState);
}
@Override
protected void onPreCreate() {
super.onPreCreate();
dynamicTheme.onCreate(this);
}
@Override
protected void onResume() {
super.onResume();
dynamicTheme.onResume(this);
}
@Override
public void onBackPressed() {
if (!navigator.onBackPressed()) {
super.onBackPressed();
}
}
public @NonNull MainNavigator getNavigator() {
return navigator;
}
}

View File

@@ -0,0 +1,22 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
public class MainFragment extends Fragment {
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (!(requireActivity() instanceof MainActivity)) {
throw new IllegalStateException("Can only be used inside of MainActivity!");
}
}
protected @NonNull MainNavigator getNavigator() {
return MainNavigator.get(requireActivity());
}
}

View File

@@ -0,0 +1,104 @@
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.conversationlist.ConversationListArchiveFragment;
import org.thoughtcrime.securesms.conversationlist.ConversationListFragment;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.recipients.RecipientId;
public class MainNavigator {
private final MainActivity activity;
public MainNavigator(@NonNull MainActivity activity) {
this.activity = activity;
}
public static MainNavigator get(@NonNull Activity activity) {
if (!(activity instanceof MainActivity)) {
throw new IllegalArgumentException("Activity must be an instance of MainActivity!");
}
return ((MainActivity) activity).getNavigator();
}
public void onCreate(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
return;
}
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, ConversationListFragment.newInstance())
.commit();
}
/**
* @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.
*/
public boolean onBackPressed() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof BackHandler) {
return ((BackHandler) fragment).onBackPressed();
}
return false;
}
public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, long lastSeen, int startingPosition) {
Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, lastSeen, startingPosition);
activity.startActivity(intent);
activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
}
public void goToAppSettings() {
Intent intent = new Intent(activity, ApplicationPreferencesActivity.class);
activity.startActivity(intent);
}
public void goToArchiveList() {
getFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
.replace(R.id.fragment_container, ConversationListArchiveFragment.newInstance())
.addToBackStack(null)
.commit();
}
public void goToGroupCreation() {
Intent intent = new Intent(activity, GroupCreateActivity.class);
activity.startActivity(intent);
}
public void goToInvite() {
Intent intent = new Intent(activity, InviteActivity.class);
activity.startActivity(intent);
}
public void goToInsights() {
InsightsLauncher.showInsightsDashboard(activity.getSupportFragmentManager());
}
private @NonNull FragmentManager getFragmentManager() {
return activity.getSupportFragmentManager();
}
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.
*/
boolean onBackPressed();
}
}

View File

@@ -0,0 +1,794 @@
/*
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Pair;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.lifecycle.ViewModelProviders;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.ViewPager;
import org.thoughtcrime.securesms.animation.DepthPageTransformer;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.viewpager.ExtendedOnPageChangedListener;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AttachmentUtil;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Activity for displaying media attachments in-app
*/
public final class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
implements LoaderManager.LoaderCallbacks<Pair<Cursor, Integer>>,
MediaRailAdapter.RailItemListener,
MediaPreviewFragment.Events
{
private final static String TAG = MediaPreviewActivity.class.getSimpleName();
private static final int NOT_IN_A_THREAD = -2;
public static final String THREAD_ID_EXTRA = "thread_id";
public static final String DATE_EXTRA = "date";
public static final String SIZE_EXTRA = "size";
public static final String CAPTION_EXTRA = "caption";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
public static final String HIDE_ALL_MEDIA_EXTRA = "came_from_all_media";
public static final String SHOW_THREAD_EXTRA = "show_thread";
public static final String SORTING_EXTRA = "sorting";
private ViewPager mediaPager;
private View detailsContainer;
private TextView caption;
private View captionContainer;
private RecyclerView albumRail;
private MediaRailAdapter albumRailAdapter;
private ViewGroup playbackControlsContainer;
private Uri initialMediaUri;
private String initialMediaType;
private long initialMediaSize;
private String initialCaption;
private boolean leftIsRecent;
private MediaPreviewViewModel viewModel;
private ViewPagerListener viewPagerListener;
private int restartItem = -1;
private long threadId = NOT_IN_A_THREAD;
private boolean cameFromAllMedia;
private boolean showThread;
private MediaDatabase.Sorting sorting;
@SuppressWarnings("ConstantConditions")
@Override
protected void onCreate(Bundle bundle, boolean ready) {
this.setTheme(R.style.TextSecure_MediaPreview);
setContentView(R.layout.media_preview_activity);
setSupportActionBar(findViewById(R.id.toolbar));
viewModel = ViewModelProviders.of(this).get(MediaPreviewViewModel.class);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
showSystemUI();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
initializeViews();
initializeResources();
initializeObservers();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Override
public void onRailItemClicked(int distanceFromActive) {
mediaPager.setCurrentItem(mediaPager.getCurrentItem() + distanceFromActive);
}
@Override
public void onRailItemDeleteClicked(int distanceFromActive) {
throw new UnsupportedOperationException("Callback unsupported.");
}
@SuppressWarnings("ConstantConditions")
private void initializeActionBar() {
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem != null) {
getSupportActionBar().setTitle(getTitleText(mediaItem));
getSupportActionBar().setSubtitle(getSubTitleText(mediaItem));
}
}
private @NonNull String getTitleText(@NonNull MediaItem mediaItem) {
String from;
if (mediaItem.outgoing) from = getString(R.string.MediaPreviewActivity_you);
else if (mediaItem.recipient != null) from = mediaItem.recipient.toShortString(this);
else from = "";
if (showThread) {
String to = null;
Recipient threadRecipient = mediaItem.threadRecipient;
if (threadRecipient != null) {
if (mediaItem.outgoing || threadRecipient.isGroup()) {
if (threadRecipient.isLocalNumber()) {
from = getString(R.string.note_to_self);
} else {
to = threadRecipient.toShortString(this);
}
} else {
to = getString(R.string.MediaPreviewActivity_you);
}
}
return to != null ? getString(R.string.MediaPreviewActivity_s_to_s, from, to)
: from;
} else {
return from;
}
}
private @NonNull String getSubTitleText(@NonNull MediaItem mediaItem) {
if (mediaItem.date > 0) {
return DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
} else {
return getString(R.string.MediaPreviewActivity_draft);
}
}
@Override
public void onResume() {
super.onResume();
initializeMedia();
}
@Override
public void onPause() {
super.onPause();
restartItem = cleanupMedia();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
initializeResources();
}
private void initializeViews() {
mediaPager = findViewById(R.id.media_pager);
mediaPager.setOffscreenPageLimit(1);
mediaPager.setPageTransformer(true, new DepthPageTransformer());
viewPagerListener = new ViewPagerListener();
mediaPager.addOnPageChangeListener(viewPagerListener);
albumRail = findViewById(R.id.media_preview_album_rail);
albumRailAdapter = new MediaRailAdapter(GlideApp.with(this), this, false);
albumRail.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
albumRail.setAdapter(albumRailAdapter);
detailsContainer = findViewById(R.id.media_preview_details_container);
caption = findViewById(R.id.media_preview_caption);
captionContainer = findViewById(R.id.media_preview_caption_container);
playbackControlsContainer = findViewById(R.id.media_preview_playback_controls_container);
View toolbarLayout = findViewById(R.id.toolbar_layout);
anchorMarginsToBottomInsets(detailsContainer);
anchorMarginsToTopInsets(toolbarLayout);
showAndHideWithSystemUI(getWindow(), detailsContainer, toolbarLayout);
}
private void initializeResources() {
Intent intent = getIntent();
threadId = intent.getLongExtra(THREAD_ID_EXTRA, NOT_IN_A_THREAD);
cameFromAllMedia = intent.getBooleanExtra(HIDE_ALL_MEDIA_EXTRA, false);
showThread = intent.getBooleanExtra(SHOW_THREAD_EXTRA, false);
sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(SORTING_EXTRA, 0)];
initialMediaUri = intent.getData();
initialMediaType = intent.getType();
initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0);
initialCaption = intent.getStringExtra(CAPTION_EXTRA);
leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
restartItem = -1;
}
private void initializeObservers() {
viewModel.getPreviewData().observe(this, previewData -> {
if (previewData == null || mediaPager == null || mediaPager.getAdapter() == null) {
return;
}
if (!((MediaItemAdapter) mediaPager.getAdapter()).hasFragmentFor(mediaPager.getCurrentItem())) {
Log.d(TAG, "MediaItemAdapter wasn't ready. Posting again...");
viewModel.resubmitPreviewData();
}
View playbackControls = ((MediaItemAdapter) mediaPager.getAdapter()).getPlaybackControls(mediaPager.getCurrentItem());
if (previewData.getAlbumThumbnails().isEmpty() && previewData.getCaption() == null && playbackControls == null) {
detailsContainer.setVisibility(View.GONE);
} else {
detailsContainer.setVisibility(View.VISIBLE);
}
albumRail.setVisibility(previewData.getAlbumThumbnails().isEmpty() ? View.GONE : View.VISIBLE);
albumRailAdapter.setMedia(previewData.getAlbumThumbnails(), previewData.getActivePosition());
albumRail.smoothScrollToPosition(previewData.getActivePosition());
captionContainer.setVisibility(previewData.getCaption() == null ? View.GONE : View.VISIBLE);
caption.setText(previewData.getCaption());
if (playbackControls != null) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
playbackControls.setLayoutParams(params);
playbackControlsContainer.removeAllViews();
playbackControlsContainer.addView(playbackControls);
} else {
playbackControlsContainer.removeAllViews();
}
});
}
private void initializeMedia() {
if (!isContentTypeSupported(initialMediaType)) {
Log.w(TAG, "Unsupported media type sent to MediaPreviewActivity, finishing.");
Toast.makeText(getApplicationContext(), R.string.MediaPreviewActivity_unssuported_media_type, Toast.LENGTH_LONG).show();
finish();
}
Log.i(TAG, "Loading Part URI: " + initialMediaUri);
if (isMediaInDb()) {
LoaderManager.getInstance(this).restartLoader(0, null, this);
} else {
mediaPager.setAdapter(new SingleItemPagerAdapter(getSupportFragmentManager(), initialMediaUri, initialMediaType, initialMediaSize));
if (initialCaption != null) {
detailsContainer.setVisibility(View.VISIBLE);
captionContainer.setVisibility(View.VISIBLE);
caption.setText(initialCaption);
}
}
}
private int cleanupMedia() {
int restartItem = mediaPager.getCurrentItem();
mediaPager.removeAllViews();
mediaPager.setAdapter(null);
return restartItem;
}
private void showOverview() {
startActivity(MediaOverviewActivity.forThread(this, threadId));
}
private void forward() {
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem != null) {
Intent composeIntent = new Intent(this, ShareActivity.class);
composeIntent.putExtra(Intent.EXTRA_STREAM, mediaItem.uri);
composeIntent.setType(mediaItem.type);
startActivity(composeIntent);
}
}
@SuppressWarnings("CodeBlock2Expr")
@SuppressLint("InlinedApi")
private void saveToDisk() {
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem != null) {
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
Permissions.with(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
.onAllGranted(() -> {
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
})
.execute();
});
}
}
@SuppressLint("StaticFieldLeak")
private void deleteMedia() {
MediaItem mediaItem = getCurrentMediaItem();
if (mediaItem == null || mediaItem.attachment == null) {
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIconAttribute(R.attr.dialog_alert_icon);
builder.setTitle(R.string.MediaPreviewActivity_media_delete_confirmation_title);
builder.setMessage(R.string.MediaPreviewActivity_media_delete_confirmation_message);
builder.setCancelable(true);
builder.setPositiveButton(R.string.delete, (dialogInterface, which) -> {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
AttachmentUtil.deleteAttachment(MediaPreviewActivity.this.getApplicationContext(),
mediaItem.attachment);
return null;
}
}.execute();
finish();
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
MenuInflater inflater = this.getMenuInflater();
inflater.inflate(R.menu.media_preview, menu);
if (!isMediaInDb()) {
menu.findItem(R.id.media_preview__overview).setVisible(false);
menu.findItem(R.id.delete).setVisible(false);
}
if (cameFromAllMedia) {
menu.findItem(R.id.media_preview__overview).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case R.id.media_preview__overview: showOverview(); return true;
case R.id.media_preview__forward: forward(); return true;
case R.id.save: saveToDisk(); return true;
case R.id.delete: deleteMedia(); return true;
case android.R.id.home: finish(); return true;
}
return false;
}
private boolean isMediaInDb() {
return threadId != NOT_IN_A_THREAD;
}
private @Nullable MediaItem getCurrentMediaItem() {
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
if (adapter != null) {
return adapter.getMediaItemFor(mediaPager.getCurrentItem());
} else {
return null;
}
}
public static boolean isContentTypeSupported(final String contentType) {
return contentType != null && (contentType.startsWith("image/") || contentType.startsWith("video/"));
}
@Override
public @NonNull Loader<Pair<Cursor, Integer>> onCreateLoader(int id, Bundle args) {
return new PagingMediaLoader(this, threadId, initialMediaUri, leftIsRecent, sorting);
}
@Override
public void onLoadFinished(@NonNull Loader<Pair<Cursor, Integer>> loader, @Nullable Pair<Cursor, Integer> data) {
if (data != null) {
@SuppressWarnings("ConstantConditions")
CursorPagerAdapter adapter = new CursorPagerAdapter(getSupportFragmentManager(),this, data.first, data.second, leftIsRecent);
mediaPager.setAdapter(adapter);
adapter.setActive(true);
viewModel.setCursor(this, data.first, leftIsRecent);
int item = restartItem >= 0 ? restartItem : data.second;
mediaPager.setCurrentItem(item);
if (item == 0) {
viewPagerListener.onPageSelected(0);
}
}
}
@Override
public void onLoaderReset(@NonNull Loader<Pair<Cursor, Integer>> loader) {
}
@Override
public boolean singleTapOnMedia() {
toggleUiVisibility();
return true;
}
private void toggleUiVisibility() {
int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
if ((systemUiVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
showSystemUI();
} else {
hideSystemUI();
}
}
private void hideSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN );
}
private void showSystemUI() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN );
}
private class ViewPagerListener extends ExtendedOnPageChangedListener {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
if (adapter != null) {
MediaItem item = adapter.getMediaItemFor(position);
if (item.recipient != null) item.recipient.live().observe(MediaPreviewActivity.this, r -> initializeActionBar());
viewModel.setActiveAlbumRailItem(MediaPreviewActivity.this, position);
initializeActionBar();
}
}
@Override
public void onPageUnselected(int position) {
MediaItemAdapter adapter = (MediaItemAdapter)mediaPager.getAdapter();
if (adapter != null) {
MediaItem item = adapter.getMediaItemFor(position);
if (item.recipient != null) item.recipient.live().removeObservers(MediaPreviewActivity.this);
adapter.pause(position);
}
}
}
private static class SingleItemPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
private final Uri uri;
private final String mediaType;
private final long size;
private MediaPreviewFragment mediaPreviewFragment;
SingleItemPagerAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Uri uri,
@NonNull String mediaType,
long size)
{
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.uri = uri;
this.mediaType = mediaType;
this.size = size;
}
@Override
public int getCount() {
return 1;
}
@NonNull
@Override
public Fragment getItem(int position) {
mediaPreviewFragment = MediaPreviewFragment.newInstance(uri, mediaType, size, true);
return mediaPreviewFragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
if (mediaPreviewFragment != null) {
mediaPreviewFragment.cleanUp();
mediaPreviewFragment = null;
}
}
@Override
public MediaItem getMediaItemFor(int position) {
return new MediaItem(null, null, null, uri, mediaType, -1, true);
}
@Override
public void pause(int position) {
if (mediaPreviewFragment != null) {
mediaPreviewFragment.pause();
}
}
@Override
public @Nullable View getPlaybackControls(int position) {
if (mediaPreviewFragment != null) {
return mediaPreviewFragment.getPlaybackControls();
}
return null;
}
@Override
public boolean hasFragmentFor(int position) {
return mediaPreviewFragment != null;
}
}
private static void anchorMarginsToBottomInsets(@NonNull View viewToAnchor) {
ViewCompat.setOnApplyWindowInsetsListener(viewToAnchor, (view, insets) -> {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.setMargins(insets.getSystemWindowInsetLeft(),
layoutParams.topMargin,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
view.setLayoutParams(layoutParams);
return insets;
});
}
private static void anchorMarginsToTopInsets(@NonNull View viewToAnchor) {
ViewCompat.setOnApplyWindowInsetsListener(viewToAnchor, (view, insets) -> {
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.setMargins(insets.getSystemWindowInsetLeft(),
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
layoutParams.bottomMargin);
view.setLayoutParams(layoutParams);
return insets;
});
}
private static void showAndHideWithSystemUI(@NonNull Window window, @NonNull View... views) {
window.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
boolean hide = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
for (View view : views) {
view.animate()
.alpha(hide ? 0 : 1)
.start();
}
});
}
private static class CursorPagerAdapter extends FragmentStatePagerAdapter implements MediaItemAdapter {
@SuppressLint("UseSparseArrays")
private final Map<Integer, MediaPreviewFragment> mediaFragments = new HashMap<>();
private final Context context;
private final Cursor cursor;
private final boolean leftIsRecent;
private boolean active;
private int autoPlayPosition;
CursorPagerAdapter(@NonNull FragmentManager fragmentManager,
@NonNull Context context,
@NonNull Cursor cursor,
int autoPlayPosition,
boolean leftIsRecent)
{
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
this.context = context.getApplicationContext();
this.cursor = cursor;
this.autoPlayPosition = autoPlayPosition;
this.leftIsRecent = leftIsRecent;
}
public void setActive(boolean active) {
this.active = active;
notifyDataSetChanged();
}
@Override
public int getCount() {
if (!active) return 0;
else return cursor.getCount();
}
@NonNull
@Override
public Fragment getItem(int position) {
boolean autoPlay = autoPlayPosition == position;
int cursorPosition = getCursorPosition(position);
autoPlayPosition = -1;
cursor.moveToPosition(cursorPosition);
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor);
DatabaseAttachment attachment = mediaRecord.getAttachment();
MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay);
mediaFragments.put(position, fragment);
return fragment;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
MediaPreviewFragment removed = mediaFragments.remove(position);
if (removed != null) {
removed.cleanUp();
}
super.destroyItem(container, position, object);
}
public MediaItem getMediaItemFor(int position) {
cursor.moveToPosition(getCursorPosition(position));
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
RecipientId recipientId = mediaRecord.getRecipientId();
RecipientId threadRecipientId = mediaRecord.getThreadRecipientId();
if (mediaRecord.getAttachment().getDataUri() == null) throw new AssertionError();
return new MediaItem(Recipient.live(recipientId).get(),
Recipient.live(threadRecipientId).get(),
mediaRecord.getAttachment(),
mediaRecord.getAttachment().getDataUri(),
mediaRecord.getContentType(),
mediaRecord.getDate(),
mediaRecord.isOutgoing());
}
@Override
public void pause(int position) {
MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) mediaView.pause();
}
@Override
public @Nullable View getPlaybackControls(int position) {
MediaPreviewFragment mediaView = mediaFragments.get(position);
if (mediaView != null) return mediaView.getPlaybackControls();
return null;
}
@Override
public boolean hasFragmentFor(int position) {
return mediaFragments.containsKey(position);
}
private int getCursorPosition(int position) {
if (leftIsRecent) return position;
else return cursor.getCount() - 1 - position;
}
}
private static class MediaItem {
private final @Nullable Recipient recipient;
private final @Nullable Recipient threadRecipient;
private final @Nullable DatabaseAttachment attachment;
private final @NonNull Uri uri;
private final @NonNull String type;
private final long date;
private final boolean outgoing;
private MediaItem(@Nullable Recipient recipient,
@Nullable Recipient threadRecipient,
@Nullable DatabaseAttachment attachment,
@NonNull Uri uri,
@NonNull String type,
long date,
boolean outgoing)
{
this.recipient = recipient;
this.threadRecipient = threadRecipient;
this.attachment = attachment;
this.uri = uri;
this.type = type;
this.date = date;
this.outgoing = outgoing;
}
}
interface MediaItemAdapter {
MediaItem getMediaItemFor(int position);
void pause(int position);
@Nullable View getPlaybackControls(int position);
boolean hasFragmentFor(int position);
}
}

View File

@@ -25,10 +25,12 @@ import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.loader.app.LoaderManager.LoaderCallbacks;
import androidx.loader.content.Loader;
import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.logging.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -50,10 +52,11 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ExpirationUtil;
@@ -71,14 +74,14 @@ import java.util.Locale;
/**
* @author Jake McGinty
*/
public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks<Cursor>, RecipientModifiedListener {
public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity implements LoaderCallbacks<Cursor> {
private final static String TAG = MessageDetailsActivity.class.getSimpleName();
public final static String MESSAGE_ID_EXTRA = "message_id";
public final static String THREAD_ID_EXTRA = "thread_id";
public final static String IS_PUSH_GROUP_EXTRA = "is_push_group";
public final static String TYPE_EXTRA = "type";
public final static String ADDRESS_EXTRA = "address";
public static final String MESSAGE_ID_EXTRA = "message_id";
public static final String THREAD_ID_EXTRA = "thread_id";
public static final String IS_PUSH_GROUP_EXTRA = "is_push_group";
public static final String TYPE_EXTRA = "type";
public static final String RECIPIENT_EXTRA = "recipient_id";
private GlideRequests glideRequests;
private long threadId;
@@ -98,7 +101,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
private ListView recipientsList;
private LayoutInflater inflater;
private DynamicTheme dynamicTheme = new DynamicTheme();
private DynamicTheme dynamicTheme = new DynamicDarkActionBarTheme();
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
private boolean running;
@@ -147,10 +150,10 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Recipient recipient = Recipient.from(this, getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
recipient.addListener(this);
LiveRecipient recipient = Recipient.live(getIntent().getParcelableExtra(RECIPIENT_EXTRA));
recipient.observe(this, r -> setActionBarColor(r.getColor()));
setActionBarColor(recipient.getColor());
setActionBarColor(recipient.get().getColor());
}
private void setActionBarColor(MaterialColor color) {
@@ -162,11 +165,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
}
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> setActionBarColor(recipient.getColor()));
}
private void initializeResources() {
inflater = LayoutInflater.from(this);
View header = inflater.inflate(R.layout.message_details_header, recipientsList, false);
@@ -269,7 +267,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
toFromRes = R.string.message_details_header__from;
}
toFrom.setText(toFromRes);
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, false);
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, null, false);
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
}
@@ -364,20 +362,20 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
List<RecipientDeliveryStatus> recipients = new LinkedList<>();
if (!messageRecord.getRecipient().isGroupRecipient()) {
if (!messageRecord.getRecipient().isGroup()) {
recipients.add(new RecipientDeliveryStatus(messageRecord.getRecipient(), getStatusFor(messageRecord.getDeliveryReceiptCount(), messageRecord.getReadReceiptCount(), messageRecord.isPending()), messageRecord.isUnidentified(), -1));
} else {
List<GroupReceiptInfo> receiptInfoList = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageRecord.getId());
if (receiptInfoList.isEmpty()) {
List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().getAddress().toGroupString(), false);
List<Recipient> group = DatabaseFactory.getGroupDatabase(context).getGroupMembers(messageRecord.getRecipient().requireGroupId(), false);
for (Recipient recipient : group) {
recipients.add(new RecipientDeliveryStatus(recipient, RecipientDeliveryStatus.Status.UNKNOWN, false, -1));
}
} else {
for (GroupReceiptInfo info : receiptInfoList) {
recipients.add(new RecipientDeliveryStatus(Recipient.from(context, info.getAddress(), true),
recipients.add(new RecipientDeliveryStatus(Recipient.resolved(info.getRecipientId()),
getStatusFor(info.getStatus(), messageRecord.isPending(), messageRecord.isFailed()),
info.isUnidentified(),
info.getTimestamp()));

View File

@@ -1,7 +1,7 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -11,7 +11,9 @@ import android.widget.BaseAdapter;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Conversions;
import org.thoughtcrime.securesms.util.adapter.StableIdGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -19,11 +21,12 @@ import java.util.List;
class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.RecyclerListener {
private final Context context;
private final GlideRequests glideRequests;
private final MessageRecord record;
private final List<RecipientDeliveryStatus> members;
private final boolean isPushGroup;
private final Context context;
private final GlideRequests glideRequests;
private final MessageRecord record;
private final List<RecipientDeliveryStatus> members;
private final boolean isPushGroup;
private final StableIdGenerator<RecipientId> idGenerator;
MessageDetailsRecipientAdapter(@NonNull Context context, @NonNull GlideRequests glideRequests,
@NonNull MessageRecord record, @NonNull List<RecipientDeliveryStatus> members,
@@ -34,6 +37,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
this.record = record;
this.isPushGroup = isPushGroup;
this.members = members;
this.idGenerator = new StableIdGenerator<>();
}
@Override
@@ -48,11 +52,7 @@ class MessageDetailsRecipientAdapter extends BaseAdapter implements AbsListView.
@Override
public long getItemId(int position) {
try {
return Conversions.byteArrayToLong(MessageDigest.getInstance("SHA1").digest(members.get(position).recipient.getAddress().serialize().getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
return idGenerator.getId(members.get(position).recipient.getId());
}
@Override

View File

@@ -25,6 +25,8 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.MessageDetailsRecipientAdapter.RecipientDeliveryStatus;
import org.thoughtcrime.securesms.components.AvatarImageView;
import org.thoughtcrime.securesms.components.DeliveryStatusView;
@@ -32,11 +34,11 @@ import org.thoughtcrime.securesms.components.FromTextView;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
/**
* A simple view to show the recipients of a message
@@ -44,7 +46,7 @@ import org.thoughtcrime.securesms.util.Util;
* @author Jake McGinty
*/
public class MessageRecipientListItem extends RelativeLayout
implements RecipientModifiedListener
implements RecipientForeverObserver
{
@SuppressWarnings("unused")
private final static String TAG = MessageRecipientListItem.class.getSimpleName();
@@ -84,10 +86,12 @@ public class MessageRecipientListItem extends RelativeLayout
final RecipientDeliveryStatus member,
final boolean isPushGroup)
{
if (this.member != null) this.member.getRecipient().live().removeForeverObserver(this);
this.glideRequests = glideRequests;
this.member = member;
member.getRecipient().addListener(this);
member.getRecipient().live().observeForever(this);
fromView.setText(member.getRecipient());
contactPhotoImage.setAvatar(glideRequests, member.getRecipient(), false);
setIssueIndicators(record, isPushGroup);
@@ -138,7 +142,7 @@ public class MessageRecipientListItem extends RelativeLayout
private NetworkFailure getNetworkFailure(final MessageRecord record) {
if (record.hasNetworkFailures()) {
for (final NetworkFailure failure : record.getNetworkFailures()) {
if (failure.getAddress().equals(member.getRecipient().getAddress())) {
if (failure.getRecipientId(getContext()).equals(member.getRecipient().getId())) {
return failure;
}
}
@@ -149,7 +153,7 @@ public class MessageRecipientListItem extends RelativeLayout
private IdentityKeyMismatch getKeyMismatch(final MessageRecord record) {
if (record.isIdentityMismatchFailure()) {
for (final IdentityKeyMismatch mismatch : record.getIdentityKeyMismatches()) {
if (mismatch.getAddress().equals(member.getRecipient().getAddress())) {
if (mismatch.getRecipientId(getContext()).equals(member.getRecipient().getId())) {
return mismatch;
}
}
@@ -158,14 +162,17 @@ public class MessageRecipientListItem extends RelativeLayout
}
public void unbind() {
if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().removeListener(this);
if (this.member != null && this.member.getRecipient() != null) this.member.getRecipient().live().removeForeverObserver(this);
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> {
public void onRecipientChanged(@NonNull Recipient recipient) {
if (this.member != null && this.member.getRecipient().equals(recipient)) {
Log.d(TAG, "onRecipientChanged -- valid");
fromView.setText(recipient);
contactPhotoImage.setAvatar(glideRequests, recipient, false);
});
} else {
Log.d(TAG, "onRecipientChanged -- invalid");
}
}
}

View File

@@ -2,8 +2,8 @@ package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import java.util.concurrent.TimeUnit;

View File

@@ -0,0 +1,135 @@
/*
* Copyright (C) 2015 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.libsignal.util.guava.Optional;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.UsernameUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.IOException;
import java.util.UUID;
/**
* Activity container for starting a new conversation.
*
* @author Moxie Marlinspike
*
*/
public class NewConversationActivity extends ContactSelectionActivity
implements ContactSelectionListFragment.InviteCallback
{
@SuppressWarnings("unused")
private static final String TAG = NewConversationActivity.class.getSimpleName();
@Override
public void onCreate(Bundle bundle, boolean ready) {
super.onCreate(bundle, ready);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
Recipient recipient;
if (recipientId.isPresent()) {
recipient = Recipient.resolved(recipientId.get());
} else {
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
recipient = Recipient.external(this, number);
}
launch(recipient);
}
private void launch(Recipient recipient) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipient.getId());
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
intent.setDataAndType(getIntent().getData(), getIntent().getType());
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, existingThread);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, ThreadDatabase.DistributionTypes.DEFAULT);
startActivity(intent);
finish();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home: super.onBackPressed(); return true;
case R.id.menu_refresh: handleManualRefresh(); return true;
case R.id.menu_new_group: handleCreateGroup(); return true;
case R.id.menu_invite: handleInvite(); return true;
}
return false;
}
private void handleManualRefresh() {
contactsFragment.setRefreshing(true);
onRefresh();
}
private void handleCreateGroup() {
startActivity(new Intent(this, GroupCreateActivity.class));
}
private void handleInvite() {
startActivity(new Intent(this, InviteActivity.class));
}
@Override
protected boolean onPrepareOptionsPanel(View view, Menu menu) {
MenuInflater inflater = this.getMenuInflater();
menu.clear();
inflater.inflate(R.menu.new_conversation_activity, menu);
super.onPrepareOptionsMenu(menu);
return true;
}
@Override
public void onInvite() {
handleInvite();
}
}

View File

@@ -66,10 +66,11 @@ public class PassphraseCreateActivity extends PassphraseActivity {
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCurrentApkReleaseVersion(PassphraseCreateActivity.this));
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCanonicalVersionCode());
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setTypingIndicatorsEnabled(PassphraseCreateActivity.this, true);
TextSecurePreferences.setHasSeenWelcomeScreen(PassphraseCreateActivity.this, false);
return null;
}

View File

@@ -17,15 +17,16 @@
package org.thoughtcrime.securesms;
import android.animation.Animator;
import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import android.support.v7.widget.Toolbar;
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
import androidx.core.os.CancellationSignal;
import androidx.appcompat.widget.Toolbar;
import android.text.Editable;
import android.text.InputType;
import android.text.SpannableString;
@@ -149,6 +150,8 @@ public class PassphrasePromptActivity extends PassphraseActivity {
return false;
}
@Override
@SuppressLint("MissingSuperCall") // no fragments to dispatch to
public void onActivityResult(int requestCode, int resultcode, Intent data) {
if (requestCode != 1) return;
@@ -262,19 +265,19 @@ public class PassphrasePromptActivity extends PassphraseActivity {
assert keyguardManager != null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && !keyguardManager.isKeyguardSecure()) {
if (!keyguardManager.isKeyguardSecure()) {
Log.w(TAG ,"Keyguard not secure...");
handleAuthenticated();
return;
}
if (Build.VERSION.SDK_INT >= 16 && fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
Log.i(TAG, "Listening for fingerprints...");
fingerprintCancellationSignal = new CancellationSignal();
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
} else if (Build.VERSION.SDK_INT >= 21){
Log.i(TAG, "firing intent...");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Signal", "");
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(getString(R.string.PassphrasePromptActivity_unlock_signal), "");
startActivityForResult(intent, 1);
} else {
Log.w(TAG, "Not compatible...");
@@ -283,7 +286,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
}
private void pauseScreenLock() {
if (Build.VERSION.SDK_INT >= 16 && fingerprintCancellationSignal != null) {
if (fingerprintCancellationSignal != null) {
fingerprintCancellationSignal.cancel();
}
}

View File

@@ -0,0 +1,226 @@
package org.thoughtcrime.securesms;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.Locale;
public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarActivity implements MasterSecretListener {
private static final String TAG = PassphraseRequiredActionBarActivity.class.getSimpleName();
public static final String LOCALE_EXTRA = "locale_extra";
private static final int STATE_NORMAL = 0;
private static final int STATE_CREATE_PASSPHRASE = 1;
private static final int STATE_PROMPT_PASSPHRASE = 2;
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
private static final int STATE_EXPERIENCE_UPGRADE = 4;
private static final int STATE_WELCOME_PUSH_SCREEN = 5;
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
@Override
protected final void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onCreate()");
this.networkAccess = new SignalServiceNetworkAccess(this);
onPreCreate();
final boolean locked = KeyCachingService.isLocked(this);
routeApplicationState(locked);
super.onCreate(savedInstanceState);
if (!isFinishing()) {
initializeClearKeyReceiver();
onCreate(savedInstanceState, true);
}
}
protected void onPreCreate() {}
protected void onCreate(Bundle savedInstanceState, boolean ready) {}
@Override
protected void onResume() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onResume()");
super.onResume();
if (networkAccess.isCensored(this)) {
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
}
}
@Override
protected void onStart() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onStart()");
super.onStart();
}
@Override
protected void onPause() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onPause()");
super.onPause();
}
@Override
protected void onStop() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onStop()");
super.onStop();
}
@Override
protected void onDestroy() {
Log.d(TAG, "[" + Log.tag(getClass()) + "] onDestroy()");
super.onDestroy();
removeClearKeyReceiver(this);
}
@Override
public void onMasterSecretCleared() {
Log.d(TAG, "onMasterSecretCleared()");
if (ApplicationContext.getInstance(this).isAppVisible()) routeApplicationState(true);
else finish();
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment)
{
return initFragment(target, fragment, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@Nullable Locale locale)
{
return initFragment(target, fragment, locale, null);
}
protected <T extends Fragment> T initFragment(@IdRes int target,
@NonNull T fragment,
@Nullable Locale locale,
@Nullable Bundle extras)
{
Bundle args = new Bundle();
args.putSerializable(LOCALE_EXTRA, locale);
if (extras != null) {
args.putAll(extras);
}
fragment.setArguments(args);
getSupportFragmentManager().beginTransaction()
.replace(target, fragment)
.commitAllowingStateLoss();
return fragment;
}
private void routeApplicationState(boolean locked) {
Intent intent = getIntentForState(getApplicationState(locked));
if (intent != null) {
startActivity(intent);
finish();
}
}
private Intent getIntentForState(int state) {
Log.d(TAG, "routeApplicationState(), state: " + state);
switch (state) {
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
default: return null;
}
}
private int getApplicationState(boolean locked) {
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
return STATE_CREATE_PASSPHRASE;
} else if (locked) {
return STATE_PROMPT_PASSPHRASE;
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
return STATE_UI_BLOCKING_UPGRADE;
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
return STATE_WELCOME_PUSH_SCREEN;
} else if (ExperienceUpgradeActivity.isUpdate(this)) {
return STATE_EXPERIENCE_UPGRADE;
} else {
return STATE_NORMAL;
}
}
private Intent getCreatePassphraseIntent() {
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
}
private Intent getPromptPassphraseIntent() {
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
}
private Intent getUiBlockingUpgradeIntent() {
return getRoutedIntent(ApplicationMigrationActivity.class,
TextSecurePreferences.hasPromptedPushRegistration(this)
? getConversationListIntent()
: getPushRegistrationIntent());
}
private Intent getExperienceUpgradeIntent() {
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
}
private Intent getPushRegistrationIntent() {
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
}
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
final Intent intent = new Intent(this, destination);
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
return intent;
}
private Intent getConversationListIntent() {
// TODO [greyson] Navigation
return new Intent(this, MainActivity.class);
}
private void initializeClearKeyReceiver() {
this.clearKeyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceive() for clear key event");
onMasterSecretCleared();
}
};
IntentFilter filter = new IntentFilter(KeyCachingService.CLEAR_KEY_EVENT);
registerReceiver(clearKeyReceiver, filter, KeyCachingService.KEY_PERMISSION, null);
}
private void removeClearKeyReceiver(Context context) {
if (clearKeyReceiver != null) {
context.unregisterReceiver(clearKeyReceiver);
clearKeyReceiver = null;
}
}
}

View File

@@ -17,7 +17,7 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import androidx.fragment.app.FragmentActivity;
public class PlayServicesProblemActivity extends FragmentActivity {

View File

@@ -0,0 +1,65 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.appcompat.app.AlertDialog;
import com.google.android.gms.common.GoogleApiAvailability;
public class PlayServicesProblemFragment extends DialogFragment {
@Override
public @NonNull Dialog onCreateDialog(@Nullable Bundle bundle) {
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(getActivity());
Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(getActivity(), code, 9111);
if (dialog == null) {
return new AlertDialog.Builder(requireActivity())
.setNegativeButton(android.R.string.ok, null)
.setMessage(R.string.PlayServicesProblemFragment_the_version_of_google_play_services_you_have_installed_is_not_functioning)
.create();
} else {
return dialog;
}
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
finish();
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
finish();
}
private void finish() {
Activity activity = getActivity();
if (activity != null) activity.finish();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2011 Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.content.Intent;
import android.os.Bundle;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.contacts.SelectedContact;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.ArrayList;
import java.util.List;
/**
* Activity container for selecting a list of contacts.
*
* @author Moxie Marlinspike
*
*/
public class PushContactSelectionActivity extends ContactSelectionActivity {
public static final String KEY_SELECTED_RECIPIENTS = "recipients";
@SuppressWarnings("unused")
private final static String TAG = PushContactSelectionActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle icicle, boolean ready) {
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, true);
super.onCreate(icicle, ready);
getToolbar().setNavigationIcon(R.drawable.ic_check_24);
getToolbar().setNavigationOnClickListener(v -> {
Intent resultIntent = getIntent();
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
List<RecipientId> recipients = Stream.of(selectedContacts).map(sc -> sc.getOrCreateRecipientId(this)).toList();
resultIntent.putParcelableArrayListExtra(KEY_SELECTED_RECIPIENTS, new ArrayList<>(recipients));
setResult(RESULT_OK, resultIntent);
finish();
});
}
}

View File

@@ -0,0 +1,50 @@
package org.thoughtcrime.securesms;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.appcompat.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceConfigurationUpdateJob;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
public class ReadReceiptsIntroFragment extends Fragment {
public static ReadReceiptsIntroFragment newInstance() {
ReadReceiptsIntroFragment fragment = new ReadReceiptsIntroFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
public ReadReceiptsIntroFragment() {}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.experience_upgrade_preference_fragment, container, false);
SwitchCompat preference = ViewUtil.findById(v, R.id.preference);
preference.setChecked(TextSecurePreferences.isReadReceiptsEnabled(getContext()));
preference.setOnCheckedChangeListener((buttonView, isChecked) -> {
TextSecurePreferences.setReadReceiptsEnabled(getContext(), isChecked);
ApplicationDependencies.getJobManager()
.add(new MultiDeviceConfigurationUpdateJob(isChecked,
TextSecurePreferences.isTypingIndicatorsEnabled(requireContext()),
TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(getContext()),
TextSecurePreferences.isLinkPreviewsEnabled(getContext())));
});
return v;
}
}

View File

@@ -0,0 +1,826 @@
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.telephony.PhoneNumberUtils;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.CheckBoxPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.color.MaterialColors;
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.database.loaders.RecipientMediaLoader;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
import org.thoughtcrime.securesms.preferences.widgets.ColorPickerPreference;
import org.thoughtcrime.securesms.preferences.widgets.ContactPreference;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicDarkToolbarTheme;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.concurrent.ExecutionException;
@SuppressLint("StaticFieldLeak")
public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActivity implements LoaderManager.LoaderCallbacks<Cursor>
{
private static final String TAG = RecipientPreferenceActivity.class.getSimpleName();
public static final String RECIPIENT_ID = "recipient_address";
public static final String CAN_HAVE_SAFETY_NUMBER_EXTRA = "can_have_safety_number";
private static final String PREFERENCE_MUTED = "pref_key_recipient_mute";
private static final String PREFERENCE_MESSAGE_TONE = "pref_key_recipient_ringtone";
private static final String PREFERENCE_CALL_TONE = "pref_key_recipient_call_ringtone";
private static final String PREFERENCE_MESSAGE_VIBRATE = "pref_key_recipient_vibrate";
private static final String PREFERENCE_CALL_VIBRATE = "pref_key_recipient_call_vibrate";
private static final String PREFERENCE_BLOCK = "pref_key_recipient_block";
private static final String PREFERENCE_COLOR = "pref_key_recipient_color";
private static final String PREFERENCE_IDENTITY = "pref_key_recipient_identity";
private static final String PREFERENCE_ABOUT = "pref_key_number";
private static final String PREFERENCE_CUSTOM_NOTIFICATIONS = "pref_key_recipient_custom_notifications";
private final DynamicTheme dynamicTheme = new DynamicDarkToolbarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ImageView avatar;
private GlideRequests glideRequests;
private RecipientId recipientId;
private TextView threadPhotoRailLabel;
private ThreadPhotoRailView threadPhotoRailView;
private CollapsingToolbarLayout toolbarLayout;
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
public void onCreate(Bundle instanceState, boolean ready) {
setContentView(R.layout.recipient_preference_activity);
this.glideRequests = GlideApp.with(this);
this.recipientId = getIntent().getParcelableExtra(RECIPIENT_ID);
LiveRecipient recipient = Recipient.live(recipientId);
initializeToolbar();
setHeader(recipient.get());
recipient.observe(this, this::setHeader);
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
public void onResume() {
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.preference_fragment);
fragment.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return false;
}
@Override
public void onBackPressed() {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
private void initializeToolbar() {
this.toolbarLayout = ViewUtil.findById(this, R.id.collapsing_toolbar);
this.avatar = ViewUtil.findById(this, R.id.avatar);
this.threadPhotoRailView = ViewUtil.findById(this, R.id.recent_photos);
this.threadPhotoRailLabel = ViewUtil.findById(this, R.id.rail_label);
this.toolbarLayout.setExpandedTitleColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
this.toolbarLayout.setCollapsedTitleTextColor(ThemeUtil.getThemedColor(this, R.attr.conversation_title_color));
this.threadPhotoRailView.setListener(mediaRecord -> {
Intent intent = new Intent(RecipientPreferenceActivity.this, MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, mediaRecord.getThreadId());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize());
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, mediaRecord.getAttachment().getCaption());
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, ViewCompat.getLayoutDirection(threadPhotoRailView) == ViewCompat.LAYOUT_DIRECTION_LTR);
intent.setDataAndType(mediaRecord.getAttachment().getDataUri(), mediaRecord.getContentType());
startActivity(intent);
});
SimpleTask.run(
() -> DatabaseFactory.getThreadDatabase(this).getThreadIdFor(recipientId),
(threadId) -> {
if (threadId == null) {
Log.i(TAG, "No thread id for recipient.");
} else {
this.threadPhotoRailLabel.setOnClickListener(v -> startActivity(MediaOverviewActivity.forThread(this, threadId)));
}
}
);
Toolbar toolbar = ViewUtil.findById(this, R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setLogo(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
getWindow().setStatusBarColor(Color.TRANSPARENT);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.recipient_preference_root), (v, insets) -> {
ViewUtil.setTopMargin(toolbar, insets.getSystemWindowInsetTop());
return insets;
});
}
}
private void setHeader(@NonNull Recipient recipient) {
ContactPhoto contactPhoto = recipient.isLocalNumber() ? new ProfileContactPhoto(recipient.getId(), String.valueOf(TextSecurePreferences.getProfileAvatarId(this)))
: recipient.getContactPhoto();
FallbackContactPhoto fallbackPhoto = recipient.isLocalNumber() ? new ResourceContactPhoto(R.drawable.ic_profile_outline_40, R.drawable.ic_profile_outline_20, R.drawable.ic_person_large)
: recipient.getFallbackContactPhoto();
glideRequests.load(contactPhoto)
.fallback(fallbackPhoto.asCallCard(this))
.error(fallbackPhoto.asCallCard(this))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(this.avatar);
if (contactPhoto == null) this.avatar.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
else this.avatar.setScaleType(ImageView.ScaleType.CENTER_CROP);
this.avatar.setBackgroundColor(recipient.getColor().toActionBarColor(this));
this.toolbarLayout.setTitle(recipient.toShortString(this));
this.toolbarLayout.setContentScrimColor(recipient.getColor().toActionBarColor(this));
}
@Override
public @NonNull Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new RecipientMediaLoader(this, recipientId, RecipientMediaLoader.MediaType.GALLERY, MediaDatabase.Sorting.Newest);
}
@Override
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
if (data != null && data.getCount() > 0) {
this.threadPhotoRailLabel.setVisibility(View.VISIBLE);
this.threadPhotoRailView.setVisibility(View.VISIBLE);
} else {
this.threadPhotoRailLabel.setVisibility(View.GONE);
this.threadPhotoRailView.setVisibility(View.GONE);
}
this.threadPhotoRailView.setCursor(glideRequests, data);
Bundle bundle = new Bundle();
bundle.putParcelable(RECIPIENT_ID, recipientId);
initFragment(R.id.preference_fragment, new RecipientPreferenceFragment(), null, bundle);
}
@Override
public void onLoaderReset(@NonNull Loader<Cursor> loader) {
this.threadPhotoRailView.setCursor(glideRequests, null);
}
public static class RecipientPreferenceFragment extends CorrectedPreferenceFragment {
private LiveRecipient recipient;
private boolean canHaveSafetyNumber;
@Override
public void onCreate(Bundle icicle) {
Log.i(TAG, "onCreate (fragment)");
super.onCreate(icicle);
initializeRecipients();
this.canHaveSafetyNumber = getActivity().getIntent()
.getBooleanExtra(RecipientPreferenceActivity.CAN_HAVE_SAFETY_NUMBER_EXTRA, false);
Preference customNotificationsPref = this.findPreference(PREFERENCE_CUSTOM_NOTIFICATIONS);
if (NotificationChannels.supported()) {
((SwitchPreferenceCompat) customNotificationsPref).setChecked(recipient.get().getNotificationChannel() != null);
customNotificationsPref.setOnPreferenceChangeListener(new CustomNotificationsChangedListener());
this.findPreference(PREFERENCE_MESSAGE_TONE).setDependency(PREFERENCE_CUSTOM_NOTIFICATIONS);
this.findPreference(PREFERENCE_MESSAGE_VIBRATE).setDependency(PREFERENCE_CUSTOM_NOTIFICATIONS);
if (recipient.get().getNotificationChannel() != null) {
final Context context = requireContext();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
RecipientDatabase db = DatabaseFactory.getRecipientDatabase(getContext());
db.setMessageRingtone(recipient.getId(), NotificationChannels.getMessageRingtone(context, recipient.get()));
db.setMessageVibrate(recipient.getId(), NotificationChannels.getMessageVibrate(context, recipient.get()) ? VibrateState.ENABLED : VibrateState.DISABLED);
NotificationChannels.ensureCustomChannelConsistency(context);
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
} else {
customNotificationsPref.setVisible(false);
}
this.findPreference(PREFERENCE_MESSAGE_TONE)
.setOnPreferenceChangeListener(new RingtoneChangeListener(false));
this.findPreference(PREFERENCE_MESSAGE_TONE)
.setOnPreferenceClickListener(new RingtoneClickedListener(false));
this.findPreference(PREFERENCE_CALL_TONE)
.setOnPreferenceChangeListener(new RingtoneChangeListener(true));
this.findPreference(PREFERENCE_CALL_TONE)
.setOnPreferenceClickListener(new RingtoneClickedListener(true));
this.findPreference(PREFERENCE_MESSAGE_VIBRATE)
.setOnPreferenceChangeListener(new VibrateChangeListener(false));
this.findPreference(PREFERENCE_CALL_VIBRATE)
.setOnPreferenceChangeListener(new VibrateChangeListener(true));
this.findPreference(PREFERENCE_MUTED)
.setOnPreferenceClickListener(new MuteClickedListener());
this.findPreference(PREFERENCE_BLOCK)
.setOnPreferenceClickListener(new BlockClickedListener());
this.findPreference(PREFERENCE_COLOR)
.setOnPreferenceChangeListener(new ColorChangeListener());
((ContactPreference)this.findPreference(PREFERENCE_ABOUT))
.setListener(new AboutNumberClickedListener());
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
Log.i(TAG, "onCreatePreferences...");
addPreferencesFromResource(R.xml.recipient_preferences);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
@Override
public void onResume() {
super.onResume();
setSummaries(recipient.get());
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
findPreference(PREFERENCE_MESSAGE_TONE).getOnPreferenceChangeListener().onPreferenceChange(findPreference(PREFERENCE_MESSAGE_TONE), uri);
} else if (requestCode == 2 && resultCode == RESULT_OK && data != null) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
findPreference(PREFERENCE_CALL_TONE).getOnPreferenceChangeListener().onPreferenceChange(findPreference(PREFERENCE_CALL_TONE), uri);
}
}
@Override
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, savedInstanceState);
recyclerView.setItemAnimator(null);
recyclerView.setLayoutAnimation(null);
return recyclerView;
}
private void initializeRecipients() {
this.recipient = Recipient.live(getArguments().getParcelable(RECIPIENT_ID));
this.recipient.observe(this, this::setSummaries);
}
private void setSummaries(Recipient recipient) {
CheckBoxPreference mutePreference = (CheckBoxPreference) this.findPreference(PREFERENCE_MUTED);
Preference customPreference = this.findPreference(PREFERENCE_CUSTOM_NOTIFICATIONS);
Preference ringtoneMessagePreference = this.findPreference(PREFERENCE_MESSAGE_TONE);
Preference ringtoneCallPreference = this.findPreference(PREFERENCE_CALL_TONE);
ListPreference vibrateMessagePreference = (ListPreference) this.findPreference(PREFERENCE_MESSAGE_VIBRATE);
ListPreference vibrateCallPreference = (ListPreference) this.findPreference(PREFERENCE_CALL_VIBRATE);
ColorPickerPreference colorPreference = (ColorPickerPreference) this.findPreference(PREFERENCE_COLOR);
Preference blockPreference = this.findPreference(PREFERENCE_BLOCK);
Preference identityPreference = this.findPreference(PREFERENCE_IDENTITY);
PreferenceCategory callCategory = (PreferenceCategory)this.findPreference("call_settings");
PreferenceCategory aboutCategory = (PreferenceCategory)this.findPreference("about");
PreferenceCategory aboutDivider = (PreferenceCategory)this.findPreference("about_divider");
ContactPreference aboutPreference = (ContactPreference)this.findPreference(PREFERENCE_ABOUT);
PreferenceCategory privacyCategory = (PreferenceCategory) this.findPreference("privacy_settings");
PreferenceCategory divider = (PreferenceCategory) this.findPreference("divider");
mutePreference.setChecked(recipient.isMuted());
ringtoneMessagePreference.setSummary(ringtoneMessagePreference.isEnabled() ? getRingtoneSummary(getContext(), recipient.getMessageRingtone()) : "");
ringtoneCallPreference.setSummary(getRingtoneSummary(getContext(), recipient.getCallRingtone()));
Pair<String, Integer> vibrateMessageSummary = getVibrateSummary(getContext(), recipient.getMessageVibrate());
Pair<String, Integer> vibrateCallSummary = getVibrateSummary(getContext(), recipient.getCallVibrate());
vibrateMessagePreference.setSummary(vibrateMessagePreference.isEnabled() ? vibrateMessageSummary.first : "");
vibrateMessagePreference.setValueIndex(vibrateMessageSummary.second);
vibrateCallPreference.setSummary(vibrateCallSummary.first);
vibrateCallPreference.setValueIndex(vibrateCallSummary.second);
blockPreference.setVisible(RecipientUtil.isBlockable(recipient));
if (recipient.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
if (recipient.isLocalNumber()) {
mutePreference.setVisible(false);
customPreference.setVisible(false);
ringtoneMessagePreference.setVisible(false);
vibrateMessagePreference.setVisible(false);
if (identityPreference != null) identityPreference.setVisible(false);
if (aboutCategory != null) aboutCategory.setVisible(false);
if (aboutDivider != null) aboutDivider.setVisible(false);
if (privacyCategory != null) privacyCategory.setVisible(false);
if (divider != null) divider.setVisible(false);
if (callCategory != null) callCategory.setVisible(false);
}
if (recipient.isGroup()) {
if (colorPreference != null) colorPreference.setVisible(false);
if (identityPreference != null) identityPreference.setVisible(false);
if (callCategory != null) callCategory.setVisible(false);
if (aboutCategory != null) aboutCategory.setVisible(false);
if (aboutDivider != null) aboutDivider.setVisible(false);
if (divider != null) divider.setVisible(false);
} else {
colorPreference.setColors(MaterialColors.CONVERSATION_PALETTE.asConversationColorArray(requireActivity()));
colorPreference.setColor(recipient.getColor().toActionBarColor(requireActivity()));
if (FeatureFlags.PROFILE_DISPLAY) {
aboutPreference.setTitle(recipient.getDisplayName(requireContext()));
aboutPreference.setSummary(recipient.resolve().getE164().or(""));
} else {
aboutPreference.setTitle(formatRecipient(recipient));
aboutPreference.setSummary(recipient.getCustomLabel());
}
aboutPreference.setSecure(recipient.getRegistered() == RecipientDatabase.RegisteredState.REGISTERED);
IdentityUtil.getRemoteIdentityKey(getActivity(), recipient).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
@Override
public void onSuccess(Optional<IdentityRecord> result) {
if (result.isPresent()) {
if (identityPreference != null) identityPreference.setOnPreferenceClickListener(new IdentityClickedListener(result.get()));
if (identityPreference != null) identityPreference.setEnabled(true);
} else if (canHaveSafetyNumber) {
if (identityPreference != null) identityPreference.setSummary(R.string.RecipientPreferenceActivity_available_once_a_message_has_been_sent_or_received);
if (identityPreference != null) identityPreference.setEnabled(false);
} else {
if (identityPreference != null) getPreferenceScreen().removePreference(identityPreference);
}
}
@Override
public void onFailure(ExecutionException e) {
if (identityPreference != null) getPreferenceScreen().removePreference(identityPreference);
}
});
}
if (recipient.isMmsGroup() && privacyCategory != null) {
privacyCategory.setVisible(false);
}
}
private @NonNull String formatRecipient(@NonNull Recipient recipient) {
if (recipient.getE164().isPresent()) return PhoneNumberUtils.formatNumber(recipient.requireE164());
else if (recipient.getEmail().isPresent()) return recipient.requireEmail();
else return "";
}
private @NonNull String getRingtoneSummary(@NonNull Context context, @Nullable Uri ringtone) {
if (ringtone == null) {
return context.getString(R.string.preferences__default);
} else if (ringtone.toString().isEmpty()) {
return context.getString(R.string.preferences__silent);
} else {
Ringtone tone = RingtoneManager.getRingtone(getActivity(), ringtone);
if (tone != null) {
return tone.getTitle(context);
}
}
return context.getString(R.string.preferences__default);
}
private @NonNull Pair<String, Integer> getVibrateSummary(@NonNull Context context, @NonNull VibrateState vibrateState) {
if (vibrateState == VibrateState.DEFAULT) {
return new Pair<>(context.getString(R.string.preferences__default), 0);
} else if (vibrateState == VibrateState.ENABLED) {
return new Pair<>(context.getString(R.string.RecipientPreferenceActivity_enabled), 1);
} else {
return new Pair<>(context.getString(R.string.RecipientPreferenceActivity_disabled), 2);
}
}
private class RingtoneChangeListener implements Preference.OnPreferenceChangeListener {
private final boolean calls;
RingtoneChangeListener(boolean calls) {
this.calls = calls;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Context context = preference.getContext();
Uri value = (Uri)newValue;
Uri defaultValue;
if (calls) defaultValue = TextSecurePreferences.getCallNotificationRingtone(context);
else defaultValue = TextSecurePreferences.getNotificationRingtone(context);
if (defaultValue.equals(value)) value = null;
else if (value == null) value = Uri.EMPTY;
new AsyncTask<Uri, Void, Void>() {
@Override
protected Void doInBackground(Uri... params) {
if (calls) {
DatabaseFactory.getRecipientDatabase(context).setCallRingtone(recipient.getId(), params[0]);
} else {
DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.getId(), params[0]);
NotificationChannels.updateMessageRingtone(context, recipient.get(), params[0]);
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, value);
return false;
}
}
private class RingtoneClickedListener implements Preference.OnPreferenceClickListener {
private final boolean calls;
RingtoneClickedListener(boolean calls) {
this.calls = calls;
}
@Override
public boolean onPreferenceClick(Preference preference) {
Uri current;
Uri defaultUri;
if (calls) {
current = recipient.get().getCallRingtone();
defaultUri = TextSecurePreferences.getCallNotificationRingtone(getContext());
} else {
current = recipient.get().getMessageRingtone();
defaultUri = TextSecurePreferences.getNotificationRingtone(getContext());
}
if (current == null) current = Settings.System.DEFAULT_NOTIFICATION_URI;
else if (current.toString().isEmpty()) current = null;
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultUri);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, calls ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, current);
startActivityForResult(intent, calls ? 2 : 1);
return true;
}
}
private class VibrateChangeListener implements Preference.OnPreferenceChangeListener {
private final boolean call;
VibrateChangeListener(boolean call) {
this.call = call;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int value = Integer.parseInt((String) newValue);
final VibrateState vibrateState = VibrateState.fromId(value);
final Context context = preference.getContext();
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (call) {
DatabaseFactory.getRecipientDatabase(context).setCallVibrate(recipient.getId(), vibrateState);
}
else {
DatabaseFactory.getRecipientDatabase(context).setMessageVibrate(recipient.getId(), vibrateState);
NotificationChannels.updateMessageVibrate(context, recipient.get(), vibrateState);
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
return false;
}
}
private class ColorChangeListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Context context = getContext();
if (context == null) return true;
final int value = (Integer) newValue;
final MaterialColor selectedColor = MaterialColors.CONVERSATION_PALETTE.getByColor(context, value);
final MaterialColor currentColor = recipient.get().getColor();
if (selectedColor == null) return true;
if (preference.isEnabled() && !currentColor.equals(selectedColor)) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(context).setColor(recipient.getId(), selectedColor);
if (recipient.get().resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
ApplicationDependencies.getJobManager().add(new MultiDeviceContactUpdateJob(recipient.getId()));
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
return true;
}
}
private class MuteClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (recipient.get().isMuted()) handleUnmute(preference.getContext());
else handleMute(preference.getContext());
return true;
}
private void handleMute(@NonNull Context context) {
MuteDialog.show(context, until -> setMuted(context, recipient.get(), until));
setSummaries(recipient.get());
}
private void handleUnmute(@NonNull Context context) {
setMuted(context, recipient.get(), 0);
}
private void setMuted(@NonNull final Context context, final Recipient recipient, final long until) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(context)
.setMuted(recipient.getId(), until);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
private class IdentityClickedListener implements Preference.OnPreferenceClickListener {
private final IdentityRecord identityKey;
private IdentityClickedListener(IdentityRecord identityKey) {
Log.i(TAG, "Identity record: " + identityKey);
this.identityKey = identityKey;
}
@Override
public boolean onPreferenceClick(Preference preference) {
Intent verifyIdentityIntent = new Intent(preference.getContext(), VerifyIdentityActivity.class);
verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_EXTRA, recipient.getId());
verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey()));
verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
startActivity(verifyIdentityIntent);
return true;
}
}
private class BlockClickedListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(Preference preference) {
if (recipient.get().isBlocked()) handleUnblock(preference.getContext());
else handleBlock(preference.getContext());
return true;
}
private void handleBlock(@NonNull final Context context) {
new AsyncTask<Void, Void, Pair<Integer, Integer>>() {
@Override
protected Pair<Integer, Integer> doInBackground(Void... voids) {
int titleRes = R.string.RecipientPreferenceActivity_block_this_contact_question;
int bodyRes = R.string.RecipientPreferenceActivity_you_will_no_longer_receive_messages_and_calls_from_this_contact;
if (recipient.get().isGroup()) {
bodyRes = R.string.RecipientPreferenceActivity_block_and_leave_group_description;
if (recipient.get().isGroup() && DatabaseFactory.getGroupDatabase(context).isActive(recipient.get().requireGroupId())) {
titleRes = R.string.RecipientPreferenceActivity_block_and_leave_group;
} else {
titleRes = R.string.RecipientPreferenceActivity_block_group;
}
}
return new Pair<>(titleRes, bodyRes);
}
@Override
protected void onPostExecute(Pair<Integer, Integer> titleAndBody) {
new AlertDialog.Builder(context)
.setTitle(titleAndBody.first)
.setMessage(titleAndBody.second)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_block, (dialog, which) -> {
setBlocked(context, recipient.get(), true);
}).show();
}
}.execute();
}
private void handleUnblock(@NonNull Context context) {
int titleRes = R.string.RecipientPreferenceActivity_unblock_this_contact_question;
int bodyRes = R.string.RecipientPreferenceActivity_you_will_once_again_be_able_to_receive_messages_and_calls_from_this_contact;
if (recipient.resolve().isGroup()) {
titleRes = R.string.RecipientPreferenceActivity_unblock_this_group_question;
bodyRes = R.string.RecipientPreferenceActivity_unblock_this_group_description;
}
new AlertDialog.Builder(context)
.setTitle(titleRes)
.setMessage(bodyRes)
.setCancelable(true)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.RecipientPreferenceActivity_unblock, (dialog, which) -> setBlocked(context, recipient.get(), false)).show();
}
private void setBlocked(@NonNull final Context context, final Recipient recipient, final boolean blocked) {
SignalExecutors.BOUNDED.execute(() -> {
if (blocked) {
RecipientUtil.block(context, recipient);
} else {
RecipientUtil.unblock(context, recipient);
}
});
}
}
private class AboutNumberClickedListener implements ContactPreference.Listener {
@Override
public void onMessageClicked() {
CommunicationActions.startConversation(getContext(), recipient.get(), null);
}
@Override
public void onSecureCallClicked() {
CommunicationActions.startVoiceCall(getActivity(), recipient.get());
}
@Override
public void onSecureVideoClicked() {
CommunicationActions.startVideoCall(getActivity(), recipient.get());
}
@Override
public void onInSecureCallClicked() {
try {
Intent dialIntent = new Intent(Intent.ACTION_DIAL,
Uri.parse("tel:" + recipient.get().requireE164()));
startActivity(dialIntent);
} catch (ActivityNotFoundException anfe) {
Log.w(TAG, anfe);
Dialogs.showAlertDialog(getContext(),
getString(R.string.ConversationActivity_calls_not_supported),
getString(R.string.ConversationActivity_this_device_does_not_appear_to_support_dial_actions));
}
}
}
private class CustomNotificationsChangedListener implements Preference.OnPreferenceChangeListener {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final Context context = preference.getContext();
final boolean enabled = (boolean) newValue;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
if (enabled) {
String channel = NotificationChannels.createChannelFor(context, recipient.get());
DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), channel);
} else {
NotificationChannels.deleteChannelFor(context, recipient.get());
DatabaseFactory.getRecipientDatabase(context).setNotificationChannel(recipient.getId(), null);
}
return null;
}
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
return true;
}
}
}
}

View File

@@ -0,0 +1,379 @@
/*
* Copyright (C) 2014-2017 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Process;
import android.provider.OpenableColumns;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.stickers.StickerLocator;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* An activity to quickly share content with contacts
*
* @author Jake McGinty
*/
public class ShareActivity extends PassphraseRequiredActionBarActivity
implements ContactSelectionListFragment.OnContactSelectedListener, SwipeRefreshLayout.OnRefreshListener
{
private static final String TAG = ShareActivity.class.getSimpleName();
public static final String EXTRA_THREAD_ID = "thread_id";
public static final String EXTRA_RECIPIENT_ID = "recipient_id";
public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private ContactSelectionListFragment contactsFragment;
private SearchToolbar searchToolbar;
private ImageView searchAction;
private View progressWheel;
private Uri resolvedExtra;
private String mimeType;
private boolean isPassingAlongMedia;
@Override
protected void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle icicle, boolean ready) {
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
int mode = DisplayMode.FLAG_PUSH | DisplayMode.FLAG_ACTIVE_GROUPS;
if (TextSecurePreferences.isSmsEnabled(this)) {
mode |= DisplayMode.FLAG_SMS;
}
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, mode);
}
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
getIntent().putExtra(ContactSelectionListFragment.RECENTS, true);
setContentView(R.layout.share_activity);
initializeToolbar();
initializeResources();
initializeSearch();
initializeMedia();
}
@Override
protected void onNewIntent(Intent intent) {
Log.i(TAG, "onNewIntent()");
super.onNewIntent(intent);
setIntent(intent);
initializeMedia();
}
@Override
public void onResume() {
Log.i(TAG, "onResume()");
super.onResume();
dynamicTheme.onResume(this);
dynamicLanguage.onResume(this);
}
@Override
public void onPause() {
super.onPause();
if (!isPassingAlongMedia && resolvedExtra != null) {
BlobProvider.getInstance().delete(this, resolvedExtra);
if (!isFinishing()) {
finish();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (searchToolbar.isVisible()) searchToolbar.collapse();
else super.onBackPressed();
}
private void initializeToolbar() {
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
}
private void initializeResources() {
progressWheel = findViewById(R.id.progress_wheel);
searchToolbar = findViewById(R.id.search_toolbar);
searchAction = findViewById(R.id.search_action);
contactsFragment = (ContactSelectionListFragment) getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
contactsFragment.setOnContactSelectedListener(this);
contactsFragment.setOnRefreshListener(this);
}
private void initializeSearch() {
searchAction.setOnClickListener(v -> searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2),
searchAction.getY() + (searchAction.getHeight() / 2)));
searchToolbar.setListener(new SearchToolbar.SearchListener() {
@Override
public void onSearchTextChange(String text) {
if (contactsFragment != null) {
contactsFragment.setQueryFilter(text);
}
}
@Override
public void onSearchClosed() {
if (contactsFragment != null) {
contactsFragment.resetQueryFilter();
}
}
});
}
private void initializeMedia() {
final Context context = this;
isPassingAlongMedia = false;
Uri streamExtra = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
mimeType = getMimeType(streamExtra);
if (streamExtra != null && PartAuthority.isLocalUri(streamExtra)) {
isPassingAlongMedia = true;
resolvedExtra = streamExtra;
handleResolvedMedia(getIntent(), false);
} else {
contactsFragment.getView().setVisibility(View.GONE);
progressWheel.setVisibility(View.VISIBLE);
new ResolveMediaTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, streamExtra);
}
}
private void handleResolvedMedia(Intent intent, boolean animate) {
long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1);
int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1);
RecipientId recipientId = null;
if (intent.hasExtra(EXTRA_RECIPIENT_ID)) {
recipientId = RecipientId.from(intent.getStringExtra(EXTRA_RECIPIENT_ID));
}
boolean hasResolvedDestination = threadId != -1 && recipientId != null && distributionType != -1;
if (hasResolvedDestination) {
createConversation(threadId, recipientId, distributionType);
} else if (animate) {
ViewUtil.fadeIn(contactsFragment.requireView(), 300);
ViewUtil.fadeOut(progressWheel, 300);
} else {
contactsFragment.requireView().setVisibility(View.VISIBLE);
progressWheel.setVisibility(View.GONE);
}
}
private void createConversation(long threadId, @NonNull RecipientId recipientId, int distributionType) {
final Intent intent = getBaseShareIntent(ConversationActivity.class);
intent.putExtra(ConversationActivity.RECIPIENT_EXTRA, recipientId);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.DISTRIBUTION_TYPE_EXTRA, distributionType);
isPassingAlongMedia = true;
startActivity(intent);
}
private Intent getBaseShareIntent(final @NonNull Class<?> target) {
final Intent intent = new Intent(this, target);
final String textExtra = getIntent().getStringExtra(Intent.EXTRA_TEXT);
final ArrayList<Media> mediaExtra = getIntent().getParcelableArrayListExtra(ConversationActivity.MEDIA_EXTRA);
final StickerLocator stickerExtra = getIntent().getParcelableExtra(ConversationActivity.STICKER_EXTRA);
intent.putExtra(ConversationActivity.TEXT_EXTRA, textExtra);
intent.putExtra(ConversationActivity.MEDIA_EXTRA, mediaExtra);
intent.putExtra(ConversationActivity.STICKER_EXTRA, stickerExtra);
if (resolvedExtra != null) intent.setDataAndType(resolvedExtra, mimeType);
return intent;
}
private String getMimeType(@Nullable Uri uri) {
if (uri != null) {
final String mimeType = MediaUtil.getMimeType(getApplicationContext(), uri);
if (mimeType != null) return mimeType;
}
return MediaUtil.getCorrectedMimeType(getIntent().getType());
}
@Override
public void onContactSelected(Optional<RecipientId> recipientId, String number) {
SimpleTask.run(this.getLifecycle(), () -> {
Recipient recipient;
if (recipientId.isPresent()) {
recipient = Recipient.resolved(recipientId.get());
} else {
Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.");
recipient = Recipient.external(this, number);
}
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
return new Pair<>(existingThread, recipient);
}, result -> {
createConversation(result.first(), result.second().getId(), ThreadDatabase.DistributionTypes.DEFAULT);
});
}
@Override
public void onContactDeselected(@NonNull Optional<RecipientId> recipientId, String number) {
}
@Override
public void onRefresh() {
}
@SuppressLint("StaticFieldLeak")
private class ResolveMediaTask extends AsyncTask<Uri, Void, Uri> {
private final Context context;
ResolveMediaTask(Context context) {
this.context = context;
}
@Override
protected Uri doInBackground(Uri... uris) {
try {
if (uris.length != 1 || uris[0] == null) {
return null;
}
InputStream inputStream;
if ("file".equals(uris[0].getScheme())) {
inputStream = openFileUri(uris[0]);
} else {
inputStream = context.getContentResolver().openInputStream(uris[0]);
}
if (inputStream == null) {
return null;
}
Cursor cursor = getContentResolver().query(uris[0], new String[] {OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null);
String fileName = null;
Long fileSize = null;
try {
if (cursor != null && cursor.moveToFirst()) {
try {
fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
} catch (IllegalArgumentException e) {
Log.w(TAG, e);
}
}
} finally {
if (cursor != null) cursor.close();
}
return BlobProvider.getInstance()
.forData(inputStream, fileSize == null ? 0 : fileSize)
.withMimeType(mimeType)
.withFileName(fileName)
.createForMultipleSessionsOnDisk(context);
} catch (IOException ioe) {
Log.w(TAG, ioe);
return null;
}
}
@Override
protected void onPostExecute(Uri uri) {
resolvedExtra = uri;
handleResolvedMedia(getIntent(), true);
}
private InputStream openFileUri(Uri uri) throws IOException {
FileInputStream fin = new FileInputStream(uri.getPath());
int owner = FileUtils.getFileDescriptorOwner(fin.getFD());
if (owner == -1 || owner == Process.myUid()) {
fin.close();
throw new IOException("File owned by application");
}
return fin;
}
}
}

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