Compare commits

..

88 Commits

Author SHA1 Message Date
B. Petersen b8da4bc9c7 bump version 2018-11-20 18:56:38 +01:00
B. Petersen b83b2e9e29 Update messenger-backend submodule. 2018-11-20 18:50:28 +01:00
Angelo Fuchs 7f97bd0ea9 closes #114 when a user taps a picture or document (or the non-player segment of an audio message) he is asked if he wants to engage in a chat, just like he would if tapping a text. 2018-11-20 14:46:48 +01:00
Angelo Fuchs f0abb05e0a removed double filtering of messages. closes #128 - If there are new messages that only belong to one group the senders are now shown properly if the messages should be hidden. 2018-11-20 12:22:24 +01:00
Angelo Fuchs 7db7c5beeb proper handling of events in the background threads #128 2018-11-20 11:51:24 +01:00
Angelo Fuchs 50e22ddcc9 changing the display for multi-sender notifications for groups #128 2018-11-19 15:23:14 +01:00
Angelo Fuchs 85e2203244 notifications are now recreated once on startup of the app (e.G. after a restart of the phone). 2018-11-19 12:22:06 +01:00
Angelo Fuchs 7b2ef54922 notifications are now updated when a chat is seen #128 2018-11-19 11:22:27 +01:00
Angelo Fuchs 4043338f90 selecting chat background #50 for lower SDK versions. 2018-11-19 11:21:55 +01:00
Angelo Fuchs 269cb006c4 Update messenger-backend submodule. 2018-11-19 09:53:03 +01:00
B. Petersen 3daaa51d13 update issue-template 2018-11-17 23:51:52 +01:00
B. Petersen 099f3ad109 Update messenger-backend submodule. 2018-11-16 12:18:28 +01:00
B. Petersen 91ec15330b no interrupt-idle an activity starting, seems too much 2018-11-16 12:18:09 +01:00
Florian Haar 7a5f382315 Allow for customized Background image #50 2018-11-16 08:41:39 +01:00
B. Petersen 0cfc61fde9 handle DC_EVENT_ERROR_NETWORK, closes #129 2018-11-16 00:08:49 +01:00
B. Petersen 0a060fbb74 Update messenger-backend submodule. 2018-11-15 23:23:26 +01:00
B. Petersen 8a8034c33e tweak imap-thread 2018-11-15 16:38:33 +01:00
Angelo Fuchs 313e67fa57 multiple different senders in a chat are now distinguished #128 2018-11-15 12:13:41 +01:00
B. Petersen 0613b879fe remove dead code: BackoffReceiver, MmsBodyProvider 2018-11-15 02:26:36 +01:00
B. Petersen 155e748bd1 remove some verbose logging 2018-11-15 02:25:15 +01:00
B. Petersen 40e57b3286 remove header boilerplate 2018-11-15 02:11:44 +01:00
B. Petersen 90bf08845b remove dead code 2018-11-15 02:03:48 +01:00
B. Petersen 328132e041 adapt permissions 2018-11-15 01:55:53 +01:00
B. Petersen baec8d229d remove dead close: telephony 2018-11-15 01:32:57 +01:00
B. Petersen 740c7bd210 format 2018-11-15 01:17:40 +01:00
B. Petersen 032c38b3d7 remove dead code: sms 2018-11-15 01:15:15 +01:00
B. Petersen 2fbcf3f7f9 ignore warning that the apk is not searchable; this is a feature :) 2018-11-15 01:13:45 +01:00
B. Petersen 4a811d4f37 remove dead code: sms 2018-11-15 00:59:32 +01:00
B. Petersen 1f9ae91488 remove dead code: AccountAuthenticator 2018-11-15 00:49:51 +01:00
B. Petersen 3e76970257 remove dead code: verify identity 2018-11-15 00:00:46 +01:00
B. Petersen f022088ed9 remove dead code 2018-11-14 23:56:42 +01:00
B. Petersen e60c143a42 remove dead code: signal registration service 2018-11-14 23:44:01 +01:00
B. Petersen 5861ca14d6 remove no unused injections 2018-11-14 22:58:05 +01:00
B. Petersen 99f5948fc7 use DcContext.sendMsg() instead of MessageSender 2018-11-14 18:19:34 +01:00
B. Petersen c2e9ba3763 implement reply-from-notification, closes #126 2018-11-14 18:06:13 +01:00
B. Petersen 9743011f25 remove now unused greenrobot.eventbus 2018-11-14 17:56:07 +01:00
B. Petersen 7fb244b0be remove dead code 2018-11-14 17:36:47 +01:00
B. Petersen a79a33751b remove dead code 2018-11-14 17:28:27 +01:00
B. Petersen 1f4ccc2944 rework MessageRetrievalService into KeepAliveService 2018-11-14 17:12:59 +01:00
B. Petersen 7776939716 remove dead code: gcm receiver 2018-11-14 15:10:55 +01:00
B. Petersen d9810443fc remove dead code: SignalCommunicationModule and releated jobs and classes 2018-11-14 14:57:32 +01:00
B. Petersen 17d01b27f2 webrtc: remove dead code 2018-11-14 14:01:15 +01:00
B. Petersen fd2e034f13 webrtc: remove dependency from MessageNotifier 2018-11-14 14:01:01 +01:00
björn petersen 361e153605 Merge pull request #125 from deltachat/connect
connect permanently
2018-11-14 03:39:48 +01:00
B. Petersen 564915ab9f add out boot receiver 2018-11-14 03:37:55 +01:00
B. Petersen 69a887fe61 Update messenger-backend submodule. 2018-11-14 03:28:50 +01:00
B. Petersen e6020f9707 use permanent notification to keep threads alive 2018-11-14 03:28:25 +01:00
B. Petersen b2ee6320f4 remove header boilerplate 2018-11-14 00:12:51 +01:00
B. Petersen 98af653204 add network state receiver and call dc_maybe_network() on re-connect 2018-11-14 00:09:44 +01:00
B. Petersen 56527d3f3f remove signal's network state receiver 2018-11-13 23:55:03 +01:00
B. Petersen 1355f59a94 add a timer to restart smtp- and imap-threads if needed 2018-11-13 23:32:08 +01:00
B. Petersen c02df7a370 remove signals exponential backoff 2018-11-13 23:07:06 +01:00
B. Petersen 89e8e80665 adapt to new API, use lambda for threads 2018-11-13 21:40:03 +01:00
Angelo Fuchs 3ef1650b51 only produce a notification if something new happens, not if something gets noticed. #117 2018-11-13 14:39:27 +01:00
Angelo Fuchs 4739b8c165 in case ndk-build is done from gradle, this folder needs to be ignored. 2018-11-13 14:38:28 +01:00
B. Petersen ebdb5f382d Update messenger-backend submodule. 2018-11-13 00:20:09 +01:00
daniel.boehrs fa8e9cefd3 verified-icon not always visible in chat-titlebar #120 2018-11-09 15:23:32 +01:00
daniel.boehrs b459e9bd0e avatars from system contacts #55 2018-11-09 12:49:25 +01:00
Florian Haar a336ce3e2c Allow for customized Background image #50 2018-11-09 10:16:32 +01:00
Lars-Magnus Skog 986d86f82e Merge pull request #121 from JellyRollJunior/master
Fix error in README.md
2018-11-09 04:15:44 +01:00
Brandon Lin f202ca1eb2 Fix error in README.md
-removed double usage of "can"
2018-11-08 18:32:13 -08:00
Angelo Fuchs f1b2dd581a addendum #113. the notification event is running on the background thread now, the default for event handlers is the main thread, though. 2018-11-04 23:35:59 +02:00
Angelo Fuchs 5ed18c106c close #113. the notification event is running on the background thread now, the default for event handlers is the main thread, though. 2018-11-04 22:58:05 +02:00
Angelo Fuchs e051eecf48 closes #111 media gallery image selection now goes to the right image. 2018-11-04 21:42:40 +02:00
Angelo Fuchs 5fcf183215 don't display notifications for the currently active thread. closes #32 notifications are now completle 2018-11-04 16:58:18 +02:00
Angelo Fuchs df494a49a8 some cleanup, no functional change. 2018-11-04 14:16:53 +02:00
Angelo Fuchs 564b837d93 activating signalling (sound and vibration) for #32 2018-11-04 13:57:44 +02:00
Angelo Fuchs 8c9cd3dd3d having the proper icon for #32 notifications. 2018-11-02 17:10:35 +02:00
Florian Haar c290194c9a Allow for customized Background image #50 2018-11-02 13:39:03 +01:00
Florian Haar cf2bdefcee forward selected messages #74 2018-11-02 11:02:51 +01:00
Florian Haar 4f9c692dbd forward selected messages #74 2018-11-02 10:04:17 +01:00
daniel.boehrs dd67248066 avatars for contact / new chat view #55 2018-11-02 09:01:43 +01:00
Angelo Fuchs d4f1af7521 for #32 (notifications) the type of thread id in ConversationActivity is now int (not long). Where its too hard, I added a cast and a todo. 2018-11-01 21:22:13 +02:00
Angelo Fuchs df62bfe8de #32 grouping together the actions of marking messages as notified. 2018-11-01 21:22:13 +02:00
Angelo Fuchs b8929c181a added todos for future work. 2018-11-01 21:22:13 +02:00
Angelo Fuchs 9fa44b3e68 #32 notification stuff running in background thread so glide is satisfied. 2018-11-01 21:22:13 +02:00
daniel.boehrs 755d081c64 Adjust avatar fallback colors #55 2018-11-01 10:42:26 +01:00
daniel.boehrs b635384dd5 Make GroupCreateActivity aware of verified joins #92 2018-11-01 10:04:51 +01:00
daniel.boehrs 1fb07c9f80 unlocking does not work #112 2018-11-01 08:54:14 +01:00
Angelo Fuchs 5a9220356c notifications for new messages are displayed and hidden appropriately #32 2018-10-31 14:01:40 +02:00
Angelo Fuchs c8accec761 added convenience function when you already have a dc context 2018-10-31 14:01:40 +02:00
Angelo Fuchs cad5c5c185 generating slides from messages 2018-10-31 14:01:39 +02:00
Angelo Fuchs acaf836af9 DcMsg no longer requires the Recipient. 2018-10-31 14:01:39 +02:00
daniel.boehrs 5221b4c8fd Show qr and self reference on group edit #92 2018-10-30 16:19:18 +01:00
daniel.boehrs 5ee4320a6c Prepare ContactAccessor to load photos for #55 2018-10-30 14:02:26 +01:00
daniel.boehrs 92a0a83187 Rewrite profile image handling for #55 2018-10-30 12:16:02 +01:00
daniel.boehrs 76a8a724ff Adjust UI #55 2018-10-30 12:16:02 +01:00
B. Petersen d8ccf5ed6d add a function to get profile image of a contact 2018-10-30 11:21:55 +01:00
233 changed files with 1439 additions and 19823 deletions
+14 -38
View File
@@ -1,48 +1,24 @@
<!--
Please read the guidelines linked above, especially when you are a first time contributor. Remove this text and provide some basic informations below, if applicable. Remove template content which is not applicable.
This is a bug report forum, if you like to discuss a new feature or submit suggestions, please use the community forum at https://support.delta.chat
Please fill in as much as you can below (leaving out if not applicable is ok). This is a bug report and feature-tracking forum, if you like to discuss a new feature, please use the community forum at https://support.delta.chat.
-->
**Delta Chat version**
<!-- See settings dialog … -->
**Expected behavior**
<!-- What is the expected output? -->
**Actual behavior**
<!-- What do you see instead? -->
**Steps to reproduce the problem**
1.
2.
**Screen snapshots**
<!-- If applicable … -->
**Debug logs**
<details>
<summary>Logs</summary>
```
- Platform (android/blackberry/anbox):
- Device:
- Delta Chat Version:
- Expected behavior:
- Actual behavior:
- Steps to reproduce the problem:
- Screenshots:
- Logs:
<!--
If applicable, debug logs can be copied from within the Delta Chat app:
Open the _Settings_ menu -> _About_ -> _version number|Info_.
Debug logs can be copied from within the Delta Chat app with
Settings menu -> Advanced -> Debug log
Alternatively from the Android system log:
`adb logcat -v time -s DeltaChat`
` adb logcat -v time -s DeltaChat `
This log contains private data (e.g. mail address, provider information) which shall be removed or anonymised prior to posting.
Logs may contain private data
which shall be removed or anonymised prior to posting.
-->
```
</details>
+3
View File
@@ -27,3 +27,6 @@ jni/libspeex/.deps/
# ignore private scripts and directories, eg. local2github.prv.sh
*.prv*
# contains files for ndk-build when done from gradle.
.externalNativeBuild
+9 -107
View File
@@ -15,67 +15,22 @@
<uses-feature android:name="android.hardware.portrait" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"/>
<uses-permission android:name="android.permission.READ_PROFILE"/>
<uses-permission android:name="android.permission.WRITE_PROFILE"/>
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
tools:ignore="ProtectedPermissions"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
<!-- For sending/receiving events -->
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<!-- Normal -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- So we can add a TextSecure 'Account' -->
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<!-- For conversation 'shortcuts' on the desktop -->
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
<!-- For fixing MMS -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- Set image as wallpaper -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<application android:name=".ApplicationContext"
android:icon="@mipmap/ic_launcher_dev"
@@ -84,7 +39,8 @@
tools:replace="android:allowBackup"
android:allowBackup="false"
android:theme="@style/TextSecure.LightTheme"
android:largeHeap="true">
android:largeHeap="true"
tools:ignore="GoogleAppIndexingWarning">
<meta-data
android:name="com.google.android.geo.API_KEY"
@@ -96,12 +52,6 @@
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<activity android:name="org.thoughtcrime.securesms.WebRtcCallActivity"
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=".InviteActivity"
android:theme="@style/TextSecure.HighlightTheme"
android:windowSoftInputMode="stateHidden"
@@ -230,9 +180,6 @@
android:label="@string/AndroidManifest__change_passphrase"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".VerifyIdentityActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ApplicationPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -300,12 +247,12 @@
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".QrScanActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".QrShowActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".QrShowActivity"
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".ClearProfileAvatarActivity"
android:theme="@style/Theme.AppCompat.Dialog.Alert"
@@ -335,24 +282,9 @@
android:theme="@style/TextSecure.LightTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<service android:enabled="true" android:name="org.thoughtcrime.securesms.service.WebRtcCallService"/>
<service android:enabled="true" android:exported="false" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.MessageRetrievalService"/>
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
</service>
<service android:name=".service.ContactsSyncAdapterService" android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter"/>
</intent-filter>
<meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" />
<meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contactsformat" />
</service>
<service android:name=".connect.KeepAliveService" android:enabled="true"/>
<service android:name=".service.DirectShareService"
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
@@ -363,12 +295,7 @@
<service android:name=".service.GenericForegroundService"/>
<receiver android:name=".gcm.GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="org.thoughtcrime.securesms" />
</intent-filter>
</receiver>
<receiver android:name=".connect.TimerReceiver" android:enabled="true"/>
<receiver android:name=".notifications.MarkReadReceiver"
android:enabled="true"
@@ -402,18 +329,11 @@
<receiver android:name=".service.ExpirationListener" />
<receiver android:name=".jobmanager.requirements.BackoffReceiver" />
<provider android:name=".providers.PartProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}.provider.securesms" />
<provider android:name=".providers.MmsBodyProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}.provider.securesms.mms" />
<provider android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
@@ -437,24 +357,6 @@
</intent-filter>
</receiver>
<receiver android:name=".service.DirectoryRefreshListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.RotateSignedPreKeyListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver android:name=".service.PersistentConnectionBootListener">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
<receiver android:name=".notifications.MessageNotifier$ReminderReceiver">
<intent-filter>
<action android:name="org.thoughtcrime.securesms.MessageNotifier.REMINDER_ACTION"/>
-34
View File
@@ -1,34 +0,0 @@
TextSecure provides encrypted text messages for Android.
Copyright 2011 Whisper Systems
This software has the follow third party dependencies:
Bouncy Castle 1.42
http://www.bouncycastle.org/
MIT License
Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ZXing
http://code.google.com/p/zxing/
Apache License 2.0
Copyright 2009 ZXing authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+1 -1
View File
@@ -14,7 +14,7 @@ For ready-to-use binaries, please go to https://delta.chat .
When checking out _deltachat-android-ii_, make sure also to check out the
subproject _deltachat-core_:
- When using Git, you can can do this initially by
- When using Git, you can do this initially by
`$ git clone --recursive https://github.com/deltachat/deltachat-android-ii`
or later by `git submodule update --init --recursive`. If you do this in your
home directory, this results in the folder `~/deltachat-android-ii` which is just fine.
+2 -4
View File
@@ -94,7 +94,6 @@ dependencies {
annotationProcessor 'com.github.bumptech.glide:compiler:4.5.0'
compile 'com.makeramen:roundedimageview:2.1.0'
compile 'com.pnikosis:materialish-progress:1.5'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'pl.tajchert:waitingdots:0.1.0'
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
compile 'com.melnykov:floatingactionbutton:1.3.0'
@@ -184,7 +183,6 @@ dependencyVerification {
'com.github.chrisbanes:PhotoView:ed06775308da260e1fd86d1d3288988fcd3d80db24ce0d7c9fcfedc39e622292',
'com.github.bumptech.glide:glide:997de7ac95be6c944d3b8cbe13de11307736ea45451c1b09a6cec7c328ead59f',
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',
@@ -255,8 +253,8 @@ android {
}
defaultConfig {
versionCode 398
versionName "0.0.8"
versionCode 399
versionName "0.0.9"
applicationId "chat.delta.androidii"
minSdkVersion 14
+14 -8
View File
@@ -238,27 +238,27 @@ JNIEXPORT jint Java_com_b44t_messenger_DcContext_isConfigured(JNIEnv *env, jobje
}
JNIEXPORT void Java_com_b44t_messenger_DcContext_performJobs(JNIEnv *env, jobject obj)
JNIEXPORT void Java_com_b44t_messenger_DcContext_performImapJobs(JNIEnv *env, jobject obj)
{
dc_perform_imap_jobs(get_dc_context(env, obj));
}
JNIEXPORT void Java_com_b44t_messenger_DcContext_idle(JNIEnv *env, jobject obj)
JNIEXPORT void Java_com_b44t_messenger_DcContext_performImapIdle(JNIEnv *env, jobject obj)
{
dc_perform_imap_idle(get_dc_context(env, obj));
}
JNIEXPORT void Java_com_b44t_messenger_DcContext_interruptIdle(JNIEnv *env, jobject obj)
JNIEXPORT void Java_com_b44t_messenger_DcContext_performImapFetch(JNIEnv *env, jobject obj)
{
dc_interrupt_imap_idle(get_dc_context(env, obj));
dc_perform_imap_fetch(get_dc_context(env, obj));
}
JNIEXPORT void Java_com_b44t_messenger_DcContext_fetch(JNIEnv *env, jobject obj)
JNIEXPORT void Java_com_b44t_messenger_DcContext_interruptImapIdle(JNIEnv *env, jobject obj)
{
dc_perform_imap_fetch(get_dc_context(env, obj));
dc_interrupt_imap_idle(get_dc_context(env, obj));
}
@@ -274,9 +274,9 @@ JNIEXPORT void Java_com_b44t_messenger_DcContext_performSmtpIdle(JNIEnv *env, jo
}
JNIEXPORT void Java_com_b44t_messenger_DcContext_interruptSmtpIdle(JNIEnv *env, jobject obj)
JNIEXPORT void Java_com_b44t_messenger_DcContext_maybeNetwork(JNIEnv *env, jobject obj)
{
dc_interrupt_smtp_idle(get_dc_context(env, obj));
dc_maybe_network(get_dc_context(env, obj));
}
@@ -1181,6 +1181,12 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcContact_getNameNAddr(JNIEnv *env, jo
}
JNIEXPORT jstring Java_com_b44t_messenger_DcContact_getProfileImage(JNIEnv *env, jobject obj)
{
return JSTRING_NEW("");
}
JNIEXPORT jboolean Java_com_b44t_messenger_DcContact_isBlocked(JNIEnv *env, jobject obj)
{
return (jboolean)(dc_contact_is_blocked(get_dc_contact(env, obj))!=0);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

-5
View File
@@ -1,11 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent" android:layout_width="match_parent">
<ImageView
android:id="@+id/background_imageview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
<org.thoughtcrime.securesms.components.InputAwareLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+1 -1
View File
@@ -15,7 +15,7 @@
android:paddingBottom="6dp"
android:clipChildren="false"
android:clipToPadding="false"
android:background="@color/white">
android:background="?attr/colorBackgroundFloating">
<FrameLayout
android:layout_width="0dp"
+11 -10
View File
@@ -45,6 +45,17 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/group_indicator"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_group_white_24dp"
android:visibility="gone"
android:layout_marginTop="1dp"
android:layout_marginRight="5dp"
android:layout_marginEnd="5dp" />
<org.thoughtcrime.securesms.components.FromTextView
android:id="@+id/from_text"
style="@style/Signal.Text.Body"
@@ -57,16 +68,6 @@
android:textColor="?attr/conversation_list_item_contact_color"
tools:text="Jules Bonnot" />
<ImageView
android:id="@+id/verified_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_verified"
android:visibility="visible"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp" />
</LinearLayout>
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
+10 -26
View File
@@ -48,33 +48,17 @@
android:layout_toEndOf="@id/contact_photo_image"
android:layout_toRightOf="@id/contact_photo_image">
<LinearLayout
android:orientation="horizontal"
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/title"
style="@style/TextSecure.TitleTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/title"
style="@style/TextSecure.TitleTextStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="18dp"
android:transitionName="recipient_name"
tools:text="Jules Bonnot" />
<ImageView
android:id="@+id/verified_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:src="@drawable/ic_verified"
android:visibility="gone" />
</LinearLayout>
android:layout_height="wrap_content"
android:drawablePadding="5dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="18dp"
android:transitionName="recipient_name"
tools:text="Jules Bonnot" />
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
android:id="@+id/subtitle"
+2 -1
View File
@@ -13,6 +13,7 @@
android:horizontalSpacing="0dp"
android:verticalSpacing="0dp"
android:numColumns="auto_fit"
android:gravity="center"/>
android:gravity="center"
android:background="?emoji_tab_strip_background"/>
</FrameLayout>
+3
View File
@@ -305,6 +305,7 @@
<string name="ShareActivity_share_with">Share with</string>
<string name="ShareActivity_unable_to_open_media">Can\'t find an app able to open this media.</string>
<string name="ShareActivity_file_not_found">Could not find %1$s.</string>
<string name="ShareActivity_forward_message_to_user_info">Forward selected messages to %1$s?</string>
<!-- ExperienceUpgradeActivity -->
<string name="ExperienceUpgradeActivity_welcome_to_signal_dgaf">Welcome to Delta Chat.</string>
@@ -1533,5 +1534,7 @@
<string name="conversation__menu_unarchive_chat">Unarchive chat</string>
<string name="conversation__menu_delete_chat">Delete chat</string>
<string name="conversation__no_messages">No messages.</string>
<string name="permanent_notification_subtitle">Tap to open.</string>
<string name="error_no_network">Network unavailable.</string>
</resources>
+1 -21
View File
@@ -1,28 +1,8 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
public class DcChat {
public static final int DC_CHAT_NO_CHAT = 0;
public final static int DC_CHAT_ID_DEADDROP = 1;
public final static int DC_CHAT_ID_STARRED = 5;
public final static int DC_CHAT_ID_ARCHIVED_LINK = 6;
-23
View File
@@ -1,28 +1,5 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
import android.support.annotation.NonNull;
public class DcChatlist {
+9 -31
View File
@@ -1,28 +1,5 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
public class DcContact {
public final static int DC_CONTACT_ID_SELF = 1;
@@ -59,14 +36,15 @@ public class DcContact {
return getAddr();
}
public native int getId ();
public native String getName ();
public native String getDisplayName();
public native String getFirstName ();
public native String getAddr ();
public native String getNameNAddr ();
public native boolean isBlocked ();
public native boolean isVerified ();
public native int getId ();
public native String getName ();
public native String getDisplayName ();
public native String getFirstName ();
public native String getAddr ();
public native String getNameNAddr ();
public native String getProfileImage();
public native boolean isBlocked ();
public native boolean isVerified ();
// working with raw c-data
private long contactCPtr; // CAVE: the name is referenced in the JNI
+6 -28
View File
@@ -1,28 +1,5 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
import android.support.annotation.NonNull;
public class DcContext {
@@ -35,6 +12,7 @@ public class DcContext {
public final static int DC_EVENT_INFO = 100;
public final static int DC_EVENT_WARNING = 300;
public final static int DC_EVENT_ERROR = 400;
public final static int DC_EVENT_ERROR_NETWORK = 401;
public final static int DC_EVENT_MSGS_CHANGED = 2000;
public final static int DC_EVENT_INCOMING_MSG = 2005;
public final static int DC_EVENT_MSG_DELIVERED = 2010;
@@ -86,13 +64,13 @@ public class DcContext {
public native void configure ();
public native void stopOngoingProcess ();
public native int isConfigured ();
public native void performJobs ();
public native void fetch ();
public native void idle ();
public native void interruptIdle ();
public native void performImapJobs ();
public native void performImapFetch ();
public native void performImapIdle ();
public native void interruptImapIdle ();
public native void performSmtpJobs ();
public native void performSmtpIdle ();
public native void interruptSmtpIdle ();
public native void maybeNetwork ();
public native void setConfig (String key, String value);
public void setConfigInt (String key, int value) { setConfig(key, Integer.toString(value)); }
public native String getConfig (String key, String def);
+24 -31
View File
@@ -1,30 +1,11 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
import android.os.AsyncTask;
import org.thoughtcrime.securesms.util.Util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
public class DcEventCenter {
@@ -33,9 +14,20 @@ public class DcEventCenter {
public interface DcEventDelegate {
void handleEvent(int eventId, Object data1, Object data2);
default boolean runOnMain() {
return true;
}
}
/**
* @deprecated use addObserver(int, DcEventDelegate) instead.
*/
@Deprecated
public void addObserver(DcEventDelegate observer, int eventId) {
addObserver(eventId, observer);
}
public void addObserver(int eventId, DcEventDelegate observer) {
synchronized (LOCK) {
ArrayList<DcEventDelegate> idObservers = allObservers.get(eventId);
if (idObservers == null) {
@@ -45,7 +37,7 @@ public class DcEventCenter {
}
}
public void removeObserver(DcEventDelegate observer, int eventId) {
public void removeObserver(int eventId, DcEventDelegate observer) {
synchronized (LOCK) {
ArrayList<DcEventDelegate> idObservers = allObservers.get(eventId);
if (idObservers != null) {
@@ -54,11 +46,9 @@ public class DcEventCenter {
}
}
public void removeObservers(Object observer) {
public void removeObservers(DcEventDelegate observer) {
synchronized (LOCK) {
Enumeration<Integer> enumKey = allObservers.keys();
while(enumKey.hasMoreElements()) {
Integer eventId = enumKey.nextElement();
for(Integer eventId : allObservers.keySet()) {
ArrayList<DcEventDelegate> idObservers = allObservers.get(eventId);
if (idObservers != null) {
idObservers.remove(observer);
@@ -71,9 +61,12 @@ public class DcEventCenter {
synchronized (LOCK) {
ArrayList<DcEventDelegate> idObservers = allObservers.get(eventId);
if (idObservers != null) {
for (int i = 0; i < idObservers.size(); i++) {
DcEventDelegate observer = idObservers.get(i);
observer.handleEvent(eventId, data1, data2);
for (DcEventDelegate observer : idObservers) {
if(observer.runOnMain()) {
Util.runOnMain(() -> observer.handleEvent(eventId, data1, data2));
} else {
Util.runOnBackground(() -> observer.handleEvent(eventId, data1, data2));
}
}
}
}
-23
View File
@@ -1,28 +1,5 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
public class DcLot {
public final static int DC_TEXT1_DRAFT = 1;
-32
View File
@@ -1,35 +1,9 @@
/*******************************************************************************
*
* Delta Chat Java Adapter
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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 com.b44t.messenger;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientProvider;
import java.io.File;
import java.util.Set;
@@ -193,12 +167,6 @@ public class DcMsg {
return null;
}
public Recipient getIndividualRecipient() {
// TODO: remove the dependency to org.thoughtcrime.securesms.recipients.Recipient
RecipientProvider.RecipientDetails recipientDetails = new RecipientProvider.RecipientDetails("individualRecipient", null, false, null, null);
return new Recipient(Address.fromContact(getFromId()), recipientDetails);
}
// working with raw c-data
private long msgCPtr; // CAVE: the name is referenced in the JNI
private native void unrefMsgCPtr ();
@@ -16,6 +16,7 @@
*/
package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.arch.lifecycle.DefaultLifecycleObserver;
import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.ProcessLifecycleOwner;
@@ -25,23 +26,18 @@ import android.support.annotation.NonNull;
import android.support.multidex.MultiDexApplication;
import android.util.Log;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.crypto.PRNGFixes;
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.dependencies.DependencyInjector;
import org.thoughtcrime.securesms.jobmanager.persistence.JavaJobSerializer;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirementProvider;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirementProvider;
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirementProvider;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.PeerConnectionFactory.InitializationOptions;
import org.webrtc.voiceengine.WebRtcAudioManager;
@@ -52,8 +48,6 @@ import org.whispersystems.libsignal.util.AndroidSignalProtocolLogger;
import java.util.HashSet;
import java.util.Set;
import dagger.ObjectGraph;
/**
* Will be called once when the TextSecure process is created.
*
@@ -68,7 +62,6 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private ExpiringMessageManager expiringMessageManager;
private JobManager jobManager;
private ObjectGraph objectGraph;
private volatile boolean isAppVisible;
@@ -87,34 +80,28 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
initializeRandomNumberFix();
initializeLogging();
initializeDependencyInjection();
initializeJobManager();
initializeExpiringMessageManager();
//initializeSignedPreKeyCheck(); -- keys are generated in the core, however, not sure if this is needed for the lock screen
initializePeriodicTasks();
initializeWebRtc();
initializeIncomingMessageNotifier();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
isAppVisible = true;
Log.i(TAG, "App is now visible.");
executePendingContactSync();
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
isAppVisible = false;
Log.i(TAG, "App is no longer visible.");
ScreenLockUtil.shouldLockApp = true;
}
@Override
public void injectDependencies(Object object) {
if (object instanceof InjectableType) {
objectGraph.inject(object);
}
}
public JobManager getJobManager() {
@@ -137,35 +124,43 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
SignalProtocolLoggerProvider.setProvider(new AndroidSignalProtocolLogger());
}
@SuppressLint("StaticFieldLeak")
private void initializeIncomingMessageNotifier() {
DcEventCenter dcEventCenter = dcContext.eventCenter;
dcEventCenter.addObserver(DcContext.DC_EVENT_INCOMING_MSG, new DcEventCenter.DcEventDelegate() {
@Override
public void handleEvent(int eventId, Object data1, Object data2) {
MessageNotifier.updateNotification(dcContext.context, ((Long) data1).intValue());
}
@Override
public boolean runOnMain() {
return false;
}
});
// in five seconds, the system should be up and ready so we can start issuing notifications.
Util.runOnBackgroundDelayed(() -> {
MessageNotifier.updateNotification(dcContext.context);
}, 5000);
}
private void initializeJobManager() {
this.jobManager = JobManager.newBuilder(this)
.withName("TextSecureJobs")
.withDependencyInjector(this)
.withJobSerializer(new JavaJobSerializer())
.withRequirementProviders(new MasterSecretRequirementProvider(this),
new NetworkRequirementProvider(this),
new SqlCipherMigrationRequirementProvider())
.withConsumerThreads(5)
.build();
}
private void initializeDependencyInjection() {
this.objectGraph = ObjectGraph.create(new SignalCommunicationModule(this, new SignalServiceNetworkAccess(this)),
new AxolotlStorageModule(this));
}
private void initializeSignedPreKeyCheck() {
if (!TextSecurePreferences.isSignedPreKeyRegistered(this)) {
jobManager.add(new CreateSignedPreKeyJob(this));
}
}
private void initializeExpiringMessageManager() {
this.expiringMessageManager = new ExpiringMessageManager(this);
}
private void initializePeriodicTasks() {
//RotateSignedPreKeyListener.schedule(this); -- no key rotation in the ui part
//DirectoryRefreshListener.schedule(this); -- directory in this sense seems to be the address book
//LocalBackupListener.schedule(this); -- disabled for now, there is no automatic backup; maybe the implicit IMAP backup is sufficient
@@ -208,10 +203,4 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
Log.w(TAG, e);
}
}
private void executePendingContactSync() {
if (TextSecurePreferences.needsFullContactSync(this)) {
ApplicationContext.getInstance(this).getJobManager().add(new MultiDeviceContactUpdateJob(this, true));
}
}
}
@@ -49,7 +49,8 @@ public abstract class BaseActionBarActivity extends AppCompatActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ScreenLockUtil.REQUEST_CODE_CONFIRM_CREDENTIALS) {
if (resultCode == RESULT_OK) {
shouldLockApp = false;
} else {
@@ -8,11 +8,8 @@ import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.List;
import java.util.Locale;
@@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
import com.b44t.messenger.DcLot;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.mms.GlideRequests;
@@ -24,7 +24,6 @@ import android.util.Log;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -128,12 +127,6 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActionB
@Override
protected Void doInBackground(Context... params) {
try {
DirectoryHelper.refreshDirectory(params[0], true);
} catch (IOException e) {
Log.w(TAG, e);
}
return null;
}
@@ -84,6 +84,7 @@ public class ContactSelectionListFragment extends Fragment
public static final String REFRESHABLE = "refreshable";
public static final String RECENTS = "recents";
public static final String SELECT_VERIFIED_EXTRA = "select_verified";
public static final String FROM_SHARE_ACTIVITY_EXTRA = "from_share_activity";
private ApplicationDcContext dcContext;
@@ -233,6 +234,10 @@ public class ContactSelectionListFragment extends Fragment
return getActivity().getIntent().getBooleanExtra(SELECT_VERIFIED_EXTRA, false);
}
private boolean isFromShareActivity() {
return getActivity().getIntent().getBooleanExtra(FROM_SHARE_ACTIVITY_EXTRA, false);
}
private void initializeCursor() {
ContactSelectionListAdapter adapter = new ContactSelectionListAdapter(getActivity(),
GlideApp.with(this),
@@ -267,11 +272,12 @@ public class ContactSelectionListFragment extends Fragment
@Override
public Loader<DcContactsLoader.Ret> onCreateLoader(int id, Bundle args) {
boolean addCreateGroupLinks = !isMulti();
boolean addCreateGroupLinks = isFromShareActivity() ? false : !isMulti();
int listflags = DcContext.DC_GCL_ADD_SELF;
if(isSelectVerfied()) {
listflags = DcContext.DC_GCL_VERIFIED_ONLY;
}
return new DcContactsLoader(getActivity(), listflags, cursorFilter, addCreateGroupLinks);
}
@@ -34,10 +34,8 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.provider.Browser;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.WindowCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
@@ -58,7 +56,6 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.webkit.MimeTypeMap;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
@@ -69,11 +66,7 @@ import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import com.b44t.messenger.DcMsg;
import com.google.android.gms.location.places.ui.PlacePicker;
import com.google.protobuf.ByteString;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.audio.AudioRecorder;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
@@ -106,12 +99,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.DraftDatabase;
import org.thoughtcrime.securesms.database.DraftDatabase.Draft;
import org.thoughtcrime.securesms.database.DraftDatabase.Drafts;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
@@ -120,14 +109,11 @@ import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.LocationSlide;
import org.thoughtcrime.securesms.mms.MediaConstraints;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.mms.QuoteId;
import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.GroupShareProfileView;
@@ -136,12 +122,9 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.scribbles.ScribbleActivity;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.CharacterCalculator.CharacterState;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -168,7 +151,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thoughtcrime.securesms.TransportOption.Type;
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
/**
* Activity for displaying a message thread, as well as
@@ -228,12 +210,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
protected HidingLinearLayout quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer;
private InputPanel inputPanel;
private ImageView imageView;
private Recipient recipient;
private ApplicationDcContext dcContext;
private DcChat dcChat = new DcChat(0);
private long threadId;
private int threadId;
private boolean archived;
private final boolean isSecureText = true;
private boolean isDefaultSms = true;
@@ -278,7 +259,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
public void onSuccess(Boolean result) {
if (result != null && result) {
Log.i(TAG, "Finished loading draft");
Util.runOnMain(() -> {
if (fragment != null && fragment.isResumed()) {
fragment.moveToLastSeen();
@@ -330,7 +310,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
@@ -363,13 +342,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
fragment.setLastSeen(System.currentTimeMillis());
markLastSeen();
AudioSlidePlayer.stopAll();
EventBus.getDefault().unregister(this);
}
@Override
protected void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
@Override
@@ -548,11 +525,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
inputPanel.onKeyboardShown();
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(ReminderUpdateEvent event) {
updateReminders();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
@@ -647,6 +619,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleEditPushGroup() {
Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class);
intent.putExtra(GroupCreateActivity.GROUP_ADDRESS_EXTRA, recipient.getAddress());
if (dcChat.isVerified()) {
intent.putExtra(GroupCreateActivity.GROUP_CREATE_VERIFIED_EXTRA, true);
}
startActivityForResult(intent, GROUP_EDIT);
}
@@ -816,7 +791,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
quickAttachmentDrawer = ViewUtil.findById(this, R.id.quick_attachment_drawer);
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
imageView = ViewUtil.findById(this, R.id.background_imageview);
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
@@ -862,8 +836,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
String backgroundImagePath = TextSecurePreferences.getBackgroundImagePath(this);
Drawable image = Drawable.createFromPath(backgroundImagePath);
imageView.setImageDrawable(image);
if(!backgroundImagePath.isEmpty()) {
Drawable image = Drawable.createFromPath(backgroundImagePath);
getWindow().setBackgroundDrawable(image);
}
}
protected void initializeActionBar() {
@@ -877,7 +853,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void initializeResources() {
threadId = getIntent().getLongExtra(THREAD_ID_EXTRA, -1);
threadId = getIntent().getIntExtra(THREAD_ID_EXTRA, -1);
if(threadId == DcChat.DC_CHAT_NO_CHAT)
throw new IllegalStateException("can't display a conversation for no chat.");
dcChat = dcContext.getChat((int)threadId);
recipient = dcContext.getRecipient(dcChat);
archived = getIntent().getBooleanExtra(IS_ARCHIVED_EXTRA, false);
@@ -1010,8 +988,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
return drafts;
}
protected ListenableFuture<Long> saveDraft() {
final SettableFuture<Long> future = new SettableFuture<>();
protected ListenableFuture<Integer> saveDraft() {
final SettableFuture<Integer> future = new SettableFuture<>();
if (this.recipient == null) {
future.set(threadId);
@@ -1019,29 +997,25 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
final Drafts drafts = getDraftsForCurrentState();
final long thisThreadId = this.threadId;
final int thisThreadId = this.threadId;
new AsyncTask<Long, Void, Long>() {
new AsyncTask<Integer, Void, Integer>() {
@Override
protected Long doInBackground(Long... params) {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(ConversationActivity.this);
DraftDatabase draftDatabase = DatabaseFactory.getDraftDatabase(ConversationActivity.this);
long threadId = params[0];
protected Integer doInBackground(Integer... params) {
int threadId = params[0];
if (drafts.size() > 0 && threadId>0) {
draftDatabase.insertDrafts(threadId, drafts);
threadDatabase.updateSnippet(threadId, drafts.getSnippet(ConversationActivity.this),
drafts.getUriSnippet(),
System.currentTimeMillis(), Types.BASE_DRAFT_TYPE, true);
// todo: the following line only works for text drafts
dcContext.setDraft(dcChat.getId(), drafts.getSnippet(dcContext.context));
} else if (threadId > 0) {
threadDatabase.update(threadId, false);
dcContext.setDraft(dcChat.getId(), null);
}
return threadId;
}
@Override
protected void onPostExecute(Long result) {
protected void onPostExecute(Integer result) {
future.set(result);
}
@@ -1124,14 +1098,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void markThreadAsRead() {
new AsyncTask<Long, Void, Void>() {
new AsyncTask<Integer, Void, Void>() {
@Override
protected Void doInBackground(Long... params) {
protected Void doInBackground(Integer... params) {
Context context = ConversationActivity.this;
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(params[0], false);
ApplicationDcContext dcContext = DcHelper.getContext(context);
int[] messageIds = dcContext.getChatMsgs(((ConversationActivity) context).threadId, 0, 0);
dcContext.markseenMsgs(messageIds);
MessageNotifier.updateNotification(context);
MarkReadReceiver.process(context, messageIds);
MessageNotifier.updateNotification(context, threadId, false);
// MarkReadReceiver.process(context, messageIds);
return null;
}
@@ -1139,16 +1115,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
private void markLastSeen() {
new AsyncTask<Long, Void, Void>() {
new AsyncTask<Integer, Void, Void>() {
@Override
protected Void doInBackground(Long... params) {
DatabaseFactory.getThreadDatabase(ConversationActivity.this).setLastSeen(params[0]);
protected Void doInBackground(Integer... params) {
dcContext.marknoticedChat(params[0]);
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
}
protected void sendComplete(long threadId) {
protected void sendComplete(int threadId) {
boolean refreshFragment = (threadId != this.threadId);
this.threadId = threadId;
@@ -1585,7 +1561,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
@Override
public void setThreadId(long threadId) {
public void setThreadId(int threadId) {
this.threadId = threadId;
}
@@ -21,7 +21,6 @@ import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
@@ -71,16 +70,11 @@ import org.thoughtcrime.securesms.contactshare.ContactUtil;
import org.thoughtcrime.securesms.contactshare.SharedContactDetailsActivity;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Collections;
@@ -90,6 +84,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import static org.thoughtcrime.securesms.ShareActivity.EXTRA_MSG_IDS;
@SuppressLint("StaticFieldLeak")
public class ConversationFragment extends Fragment
implements LoaderManager.LoaderCallbacks<int[]>,
@@ -222,7 +218,6 @@ public class ConversationFragment extends Fragment
public void moveToLastSeen() {
if (lastSeen <= 0) {
Log.i(TAG, "No need to move to last seen.");
return;
}
@@ -236,7 +231,7 @@ public class ConversationFragment extends Fragment
}
private void initializeResources() {
this.threadId = this.getActivity().getIntent().getLongExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.threadId = this.getActivity().getIntent().getIntExtra(ConversationActivity.THREAD_ID_EXTRA, -1);
this.recipient = Recipient.from(getActivity(), Address.fromChat((int)this.threadId), true);
this.lastSeen = this.getActivity().getIntent().getLongExtra(ConversationActivity.LAST_SEEN_EXTRA, -1);
this.startingPosition = this.getActivity().getIntent().getIntExtra(ConversationActivity.STARTING_POSITION_EXTRA, -1);
@@ -284,7 +279,7 @@ public class ConversationFragment extends Fragment
}
if (messageRecords.size() > 1) {
menu.findItem(R.id.menu_context_forward).setVisible(false);
// menu.findItem(R.id.menu_context_forward).setVisible(false);
menu.findItem(R.id.menu_context_reply).setVisible(false);
menu.findItem(R.id.menu_context_details).setVisible(false);
menu.findItem(R.id.menu_context_save_attachment).setVisible(false);
@@ -425,9 +420,11 @@ public class ConversationFragment extends Fragment
.show();
}
private void handleForwardMessage(DcMsg message) {
private void handleForwardMessage(final Set<DcMsg> messageRecords) {
Intent composeIntent = new Intent(getActivity(), ShareActivity.class);
composeIntent.putExtra(Intent.EXTRA_TEXT, message.getDisplayBody().toString());
int[] msgIds = DcMsg.msgSetToIds(messageRecords);
composeIntent.putExtra(EXTRA_MSG_IDS, msgIds);
// composeIntent.putExtra(Intent.EXTRA_TEXT, messageRecords.getDisplayBody().toString());
/* TODO: handle fowarding media
if (message.isMms()) {
MmsMessageRecord mediaMessage = (MmsMessageRecord) message;
@@ -582,7 +579,7 @@ public class ConversationFragment extends Fragment
}
public interface ConversationFragmentListener {
void setThreadId(long threadId);
void setThreadId(int threadId);
void handleReplyMessage(MessageRecord messageRecord);
}
@@ -751,21 +748,6 @@ public class ConversationFragment extends Fragment
actionMode.setTitle(String.valueOf(getListAdapter().getSelectedItems().size()));
}
}
else if(threadId==DcChat.DC_CHAT_ID_DEADDROP) {
new AlertDialog.Builder(getActivity())
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int chatId = dcContext.createChatByMsgId(messageRecord.getId());
if( chatId != 0 ) {
Intent intent = new Intent(getActivity(), ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)chatId);
startActivity(intent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.setMessage(getActivity().getString(R.string.new_conversation_activity__ask_start_chat_with, dcContext.getContact(messageRecord.getFromId()).getDisplayName()))
.show();
}
else if(messageRecord.isSetupMessage()) {
querySetupCode(messageRecord,null);
}
@@ -873,9 +855,9 @@ public class ConversationFragment extends Fragment
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_ADD_EDIT_CONTACT && getContext() != null) {
ApplicationContext.getInstance(getContext().getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(getContext().getApplicationContext(), false));
// ApplicationContext.getInstance(getContext().getApplicationContext())
// .getJobManager()
// .add(new DirectoryRefreshJob(getContext().getApplicationContext(), false));
}
}
@@ -933,7 +915,8 @@ public class ConversationFragment extends Fragment
actionMode.finish();
return true;
case R.id.menu_context_forward:
handleForwardMessage(getSelectedMessageRecord());
// handleForwardMessage(getSelectedMessageRecord());
handleForwardMessage(getListAdapter().getSelectedItems());
actionMode.finish();
return true;
case R.id.menu_context_resend:
@@ -24,6 +24,7 @@ import android.graphics.PorterDuff;
import android.support.annotation.DimenRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
@@ -391,7 +392,14 @@ public class ConversationItem extends LinearLayout
if (sharedContactStub.resolved()) sharedContactStub.get().setVisibility(GONE);
//noinspection ConstantConditions
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), showControls);
if(dcChat.getId() == DcChat.DC_CHAT_ID_DEADDROP) { // no audio on dead drops
// TODO: replace the currently defunct display of a play button with some notification text
// to inform the user that here would be audio, if this were a proper chat, then ask the user
// if he wants to start the chat on click.
audioViewStub.get().setEnabled(false);
audioViewStub.get().setOnClickListener(passthroughClickListener);
} else
audioViewStub.get().setAudio(new AudioSlide(context, messageRecord), showControls);
audioViewStub.get().setOnLongClickListener(passthroughClickListener);
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -676,9 +684,26 @@ public class ConversationItem extends LinearLayout
}
}
private void handleDeadDropClick() {
new AlertDialog.Builder(context)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
int chatId = dcContext.createChatByMsgId(messageRecord.getId());
if( chatId != 0 ) {
Intent intent = new Intent(context, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, chatId);
context.startActivity(intent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.setMessage(context.getString(R.string.new_conversation_activity__ask_start_chat_with, dcContext.getContact(messageRecord.getFromId()).getDisplayName()))
.show();
}
private class ThumbnailClickListener implements SlideClickListener {
public void onClick(final View v, final Slide slide) {
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
if (dcChat.getId() == DcChat.DC_CHAT_ID_DEADDROP) {
handleDeadDropClick();
} else if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
performClick();
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
@@ -720,7 +745,9 @@ public class ConversationItem extends LinearLayout
}
public void onClick(View v) {
if (!shouldInterceptClicks(messageRecord) && parent != null) {
if (dcChat.getId() == DcChat.DC_CHAT_ID_DEADDROP) {
handleDeadDropClick();
} else if (!shouldInterceptClicks(messageRecord) && parent != null) {
parent.onClick(v);
}
}
@@ -32,7 +32,6 @@ import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.lock.RegistrationLockDialog;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.QrScanHandler;
import org.thoughtcrime.securesms.search.SearchFragment;
@@ -76,8 +75,6 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
initializeSearchListener();
RegistrationLockDialog.showReminderIfNecessary(this);
TooltipCompat.setTooltipText(searchAction, getText(R.string.SearchToolbar_search_for_conversations_contacts_and_messages));
}
@@ -176,11 +173,11 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
}
@Override
public void onCreateConversation(long threadId, long lastSeen) {
public void onCreateConversation(int threadId, long lastSeen) {
openConversation(threadId, lastSeen, -1);
}
public void openConversation(long threadId, long lastSeen, int startingPosition) {
public void openConversation(int threadId, long lastSeen, int startingPosition) {
searchToolbar.clearFocus();
Intent intent = new Intent(this, ConversationActivity.class);
@@ -211,7 +208,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
private void handleDeaddrop() {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)DcChat.DC_CHAT_ID_DEADDROP);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, DcChat.DC_CHAT_ID_DEADDROP);
startActivity(intent);
}
@@ -228,6 +225,7 @@ public class ConversationListActivity extends PassphraseRequiredActionBarActivit
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
QrScanHandler qrScanHandler = new QrScanHandler(this);
@@ -51,7 +51,7 @@ public class ConversationListArchiveActivity extends PassphraseRequiredActionBar
}
@Override
public void onCreateConversation(long threadId, long lastSeenTime) {
public void onCreateConversation(int threadId, long lastSeenTime) {
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(ConversationActivity.IS_ARCHIVED_EXTRA, true);
@@ -19,7 +19,6 @@ package org.thoughtcrime.securesms;
import android.annotation.SuppressLint;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
@@ -57,9 +56,6 @@ import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.ConversationListAdapter.ItemClickListener;
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
@@ -72,9 +68,7 @@ import org.thoughtcrime.securesms.components.reminder.ShareReminder;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcChatlistLoader;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
@@ -162,7 +156,6 @@ public class ConversationListFragment extends Fragment
updateReminders(true);
list.getAdapter().notifyDataSetChanged();
EventBus.getDefault().register(this);
}
@Override
@@ -170,7 +163,6 @@ public class ConversationListFragment extends Fragment
super.onPause();
fab.stopPulse();
EventBus.getDefault().unregister(this);
}
public ConversationListAdapter getListAdapter() {
@@ -322,7 +314,7 @@ public class ConversationListFragment extends Fragment
actionMode.setTitle(String.valueOf(getListAdapter().getBatchSelections().size()));
}
private void handleCreateConversation(long threadId, long lastSeen) {
private void handleCreateConversation(int threadId, long lastSeen) {
((ConversationSelectedListener)getActivity()).onCreateConversation(threadId, lastSeen);
}
@@ -368,7 +360,8 @@ public class ConversationListFragment extends Fragment
@Override
public void onItemClick(ConversationListItem item) {
if (actionMode == null) {
long threadId = item.getThreadId();
// the next line will fail for signal calls, DC uses int, though.
int threadId = (int)item.getThreadId();
if (threadId==DcChat.DC_CHAT_ID_DEADDROP) {
DcContext dcContext = DcHelper.getContext(getActivity());
@@ -421,7 +414,7 @@ public class ConversationListFragment extends Fragment
}
public interface ConversationSelectedListener {
void onCreateConversation(long threadId, long lastSeen);
void onCreateConversation(int threadId, long lastSeen);
void onSwitchToArchive();
}
@@ -472,11 +465,6 @@ public class ConversationListFragment extends Fragment
actionMode = null;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(ReminderUpdateEvent event) {
updateReminders(false);
}
private class ArchiveListenerCallback extends ItemTouchHelper.SimpleCallback {
ArchiveListenerCallback() {
@@ -20,8 +20,6 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.Spannable;
@@ -53,6 +51,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.search.model.MessageResult;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ResUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -82,7 +81,7 @@ public class ConversationListItem extends RelativeLayout
private TextView archivedView;
private DeliveryStatusView deliveryStatusIndicator;
private ImageView unreadIndicator;
private ImageView verifiedIndicator;
private ImageView groupIndicator;
private long lastSeen;
private int unreadCount;
@@ -106,7 +105,7 @@ public class ConversationListItem extends RelativeLayout
this.contactPhotoImage = findViewById(R.id.contact_photo_image);
this.archivedView = findViewById(R.id.archived);
this.unreadIndicator = findViewById(R.id.unread_indicator);
this.verifiedIndicator = findViewById(R.id.verified_indicator);
this.groupIndicator = findViewById(R.id.group_indicator);
ViewUtil.setTextViewGravityStart(this.fromView, getContext());
ViewUtil.setTextViewGravityStart(this.subjectView, getContext());
@@ -170,8 +169,10 @@ public class ConversationListItem extends RelativeLayout
else {
this.contactPhotoImage.setAvatar(glideRequests, recipient, true);
}
verifiedIndicator.setVisibility(thread.isVerified() ? VISIBLE : GONE);
fromView.setCompoundDrawablesWithIntrinsicBounds(0, 0, thread.isVerified() ? R.drawable.ic_verified : 0, 0);
groupIndicator.setVisibility(recipient.isGroupRecipient() ? VISIBLE : GONE);
int color = ResUtil.getColor(getContext(), R.attr.conversation_list_item_contact_color);
groupIndicator.setColorFilter(color);
}
public void bind(@NonNull Recipient contact,
@@ -77,9 +77,9 @@ public class ConversationPopupActivity extends ConversationActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_expand:
saveDraft().addListener(new ListenableFuture.Listener<Long>() {
saveDraft().addListener(new ListenableFuture.Listener<Integer>() {
@Override
public void onSuccess(Long result) {
public void onSuccess(Integer result) {
ActivityOptionsCompat transition = ActivityOptionsCompat.makeScaleUpAnimation(getWindow().getDecorView(), 0, 0, getWindow().getAttributes().width, getWindow().getAttributes().height);
Intent intent = new Intent(ConversationPopupActivity.this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, result);
@@ -112,7 +112,7 @@ public class ConversationPopupActivity extends ConversationActivity {
}
@Override
protected void sendComplete(long threadId) {
protected void sendComplete(int threadId) {
super.sendComplete(threadId);
finish();
}
@@ -26,7 +26,6 @@ public class ConversationTitleView extends RelativeLayout {
private AvatarImageView avatar;
private TextView title;
private TextView subtitle;
private ImageView verified;
public ConversationTitleView(Context context) {
this(context, null);
@@ -45,7 +44,6 @@ public class ConversationTitleView extends RelativeLayout {
this.content = ViewUtil.findById(this, R.id.content);
this.title = ViewUtil.findById(this, R.id.title);
this.subtitle = ViewUtil.findById(this, R.id.subtitle);
this.verified = ViewUtil.findById(this, R.id.verified_indicator);
this.avatar = ViewUtil.findById(this, R.id.contact_photo_image);
ViewUtil.setTextViewGravityStart(this.title, getContext());
@@ -61,7 +59,7 @@ public class ConversationTitleView extends RelativeLayout {
} else if (recipient != null && recipient.isMuted()) {
title.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_volume_off_white_18dp, 0, 0, 0);
} else*/ {
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
//title.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
}
if (dcChat != null) {
@@ -70,7 +68,7 @@ public class ConversationTitleView extends RelativeLayout {
}
public void setVerified(boolean verified) {
this.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
title.setCompoundDrawablesWithIntrinsicBounds(0, 0, verified ? R.drawable.ic_verified : 0, 0);
}
public void hideAvatar() {
@@ -1,15 +1,12 @@
package org.thoughtcrime.securesms;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
@@ -17,24 +14,16 @@ import android.widget.TextView;
import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.ExecutionException;
public class ConversationUpdateItem extends LinearLayout
implements RecipientModifiedListener, BindableConversationItem
@@ -95,7 +84,7 @@ public class ConversationUpdateItem extends LinearLayout
private void bind(@NonNull DcMsg messageRecord, @NonNull Locale locale) {
this.messageRecord = messageRecord;
this.sender = messageRecord.getIndividualRecipient();
this.sender = Recipient.fromMsg(DcHelper.getContext(getContext()), messageRecord.getId());
this.locale = locale;
this.sender.addListener(this);
@@ -38,12 +38,9 @@ import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
import org.thoughtcrime.securesms.components.emoji.EmojiToggle;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
@@ -77,7 +74,7 @@ import javax.inject.Inject;
import static android.provider.MediaStore.EXTRA_OUTPUT;
@SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity implements InjectableType {
public class CreateProfileActivity extends BaseActionBarActivity {
private static final String TAG = CreateProfileActivity.class.getSimpleName();
@@ -309,7 +306,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
private void initializeProfileAvatar(boolean excludeSystem) {
String address = DcHelper.get(this, DcHelper.CONFIG_ADDRESS);
if (AvatarHelper.getAvatarFile(this, address).exists() && AvatarHelper.getAvatarFile(this, address).length() > 0) {
if (AvatarHelper.getSelfAvatarFile(this, address).exists() && AvatarHelper.getSelfAvatarFile(this, address).length() > 0) {
new AsyncTask<Void, Void, byte[]>() {
@Override
protected byte[] doInBackground(Void... params) {
@@ -452,14 +449,14 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
TextSecurePreferences.setProfileName(context, name);
try {
AvatarHelper.setAvatar(CreateProfileActivity.this, DcHelper.get(context, DcHelper.CONFIG_ADDRESS), avatarBytes);
AvatarHelper.setSelfAvatar(CreateProfileActivity.this, DcHelper.get(context, DcHelper.CONFIG_ADDRESS), avatarBytes);
TextSecurePreferences.setProfileAvatarId(CreateProfileActivity.this, new SecureRandom().nextInt());
} catch (IOException e) {
Log.w(TAG, e);
return false;
}
ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob(context));
// ApplicationContext.getInstance(context).getJobManager().add(new MultiDeviceProfileKeyUpdateJob(context));
return true;
}
@@ -21,43 +21,20 @@ import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase.Reader;
import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VersionTracker;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
public class DatabaseUpgradeActivity extends BaseActivity {
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 46;
public static final int MMS_BODY_VERSION = 46;
@@ -118,11 +95,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
Log.w("DatabaseUpgradeActivity", "Upgrading...");
setContentView(R.layout.database_upgrade_activity);
ProgressBar indeterminateProgress = findViewById(R.id.indeterminate_progress);
ProgressBar determinateProgress = findViewById(R.id.determinate_progress);
new DatabaseUpgradeTask(indeterminateProgress, determinateProgress)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, VersionTracker.getLastSeenVersion(this));
} else {
VersionTracker.updateLastSeenVersion(this);
updateNotifications(this);
@@ -175,204 +147,4 @@ public class DatabaseUpgradeActivity extends BaseActivity {
public void setProgress(int progress, int total);
}
@SuppressLint("StaticFieldLeak")
private class DatabaseUpgradeTask extends AsyncTask<Integer, Double, Void>
implements DatabaseUpgradeListener
{
private final ProgressBar indeterminateProgress;
private final ProgressBar determinateProgress;
DatabaseUpgradeTask(ProgressBar indeterminateProgress, ProgressBar determinateProgress) {
this.indeterminateProgress = indeterminateProgress;
this.determinateProgress = determinateProgress;
}
@Override
protected Void doInBackground(Integer... params) {
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
Log.w("DatabaseUpgradeActivity", "Running background upgrade..");
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
if (params[0] < CURVE25519_VERSION) {
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
}
if (params[0] < NO_V1_VERSION) {
File v1sessions = new File(context.getFilesDir(), "sessions");
if (v1sessions.exists() && v1sessions.isDirectory()) {
File[] contents = v1sessions.listFiles();
if (contents != null) {
for (File session : contents) {
session.delete();
}
}
v1sessions.delete();
}
}
if (params[0] < SIGNED_PREKEY_VERSION) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new CreateSignedPreKeyJob(context));
}
if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
// new TextSecureSessionStore(context, masterSecret).migrateSessions();
// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
scheduleMessagesInPushDatabase(context);;
}
if (params[0] < CONTACTS_ACCOUNT_VERSION) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(getApplicationContext(), false));
}
if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
schedulePendingIncomingParts(context);
}
if (params[0] < REDPHONE_SUPPORT_VERSION) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new RefreshAttributesJob(getApplicationContext()));
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(getApplicationContext(), false));
}
if (params[0] < PROFILES) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(getApplicationContext(), false));
}
if (params[0] < SCREENSHOTS) {
boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
TextSecurePreferences.setScreenSecurityEnabled(getApplicationContext(), screenSecurity);
}
if (params[0] < PERSISTENT_BLOBS) {
File externalDir = context.getExternalFilesDir(null);
if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
for (File blob : externalDir.listFiles()) {
if (blob.exists() && blob.isFile()) blob.delete();
}
}
}
if (params[0] < INTERNALIZE_CONTACTS) {
if (TextSecurePreferences.isPushRegistered(getApplicationContext())) {
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(getApplicationContext(), true);
}
}
if (params[0] < SQLCIPHER) {
scheduleMessagesInPushDatabase(context);
}
if (params[0] < SQLCIPHER_COMPLETE) {
File file = context.getDatabasePath("messages.db");
if (file != null && file.exists()) file.delete();
}
if (params[0] < REMOVE_JOURNAL) {
File file = context.getDatabasePath("messages.db-journal");
if (file != null && file.exists()) file.delete();
}
if (params[0] < REMOVE_CACHE) {
try {
FileUtils.deleteDirectoryContents(context.getCacheDir());
} catch (IOException e) {
Log.w(TAG, e);
}
}
return null;
}
private void schedulePendingIncomingParts(Context context) {
final AttachmentDatabase attachmentDb = DatabaseFactory.getAttachmentDatabase(context);
final MmsDatabase mmsDb = DatabaseFactory.getMmsDatabase(context);
final List<DatabaseAttachment> pendingAttachments = DatabaseFactory.getAttachmentDatabase(context).getPendingAttachments();
Log.w(TAG, pendingAttachments.size() + " pending parts.");
for (DatabaseAttachment attachment : pendingAttachments) {
final Reader reader = mmsDb.readerFor(mmsDb.getMessage(attachment.getMmsId()));
final MessageRecord record = reader.getNext();
if (attachment.hasData()) {
Log.w(TAG, "corrected a pending media part " + attachment.getAttachmentId() + "that already had data.");
attachmentDb.setTransferState(attachment.getMmsId(), attachment.getAttachmentId(), AttachmentDatabase.TRANSFER_PROGRESS_DONE);
} else if (record != null && !record.isOutgoing() && record.isPush()) {
Log.w(TAG, "queuing new attachment download job for incoming push part " + attachment.getAttachmentId() + ".");
ApplicationContext.getInstance(context)
.getJobManager()
.add(new AttachmentDownloadJob(context, attachment.getMmsId(), attachment.getAttachmentId(), false));
}
reader.close();
}
}
private void scheduleMessagesInPushDatabase(Context context) {
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
Cursor pushReader = null;
try {
pushReader = pushDatabase.getPending();
while (pushReader != null && pushReader.moveToNext()) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new PushDecryptJob(getApplicationContext(),
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID))));
}
} finally {
if (pushReader != null)
pushReader.close();
}
}
@Override
protected void onProgressUpdate(Double... update) {
indeterminateProgress.setVisibility(View.GONE);
determinateProgress.setVisibility(View.VISIBLE);
double scaler = update[0];
determinateProgress.setProgress((int)Math.floor(determinateProgress.getMax() * scaler));
}
@Override
protected void onPostExecute(Void result) {
VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this);
updateNotifications(DatabaseUpgradeActivity.this);
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
finish();
}
@Override
public void setProgress(int progress, int total) {
publishProgress(((double)progress / (double)total));
}
}
}
@@ -37,6 +37,8 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition;
@@ -50,10 +52,10 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactColors;
import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FileUtils;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter;
import org.thoughtcrime.securesms.util.SelectedRecipientsAdapter.OnRecipientDeletedListener;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -61,21 +63,17 @@ import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static com.b44t.messenger.DcContact.DC_CONTACT_ID_SELF;
/**
* Activity to create and update groups
*
* @author Jake McGinty
*/
public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
implements OnRecipientDeletedListener
implements OnRecipientDeletedListener, DcEventCenter.DcEventDelegate
{
private final static String TAG = GroupCreateActivity.class.getSimpleName();
@@ -92,7 +90,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private ApplicationDcContext dcContext;
private boolean createVerified;
private boolean verified;
private EditText groupName;
private ListView lv;
private ImageView avatar;
@@ -112,11 +110,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
dcContext = DcHelper.getContext(this);
setContentView(R.layout.group_create_activity);
//noinspection ConstantConditions
createVerified = getIntent().getBooleanExtra(GROUP_CREATE_VERIFIED_EXTRA, false);
verified = getIntent().getBooleanExtra(GROUP_CREATE_VERIFIED_EXTRA, false);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
initializeExistingGroup();
initializeResources();
dcContext.eventCenter.addObserver(this, DcContext.DC_EVENT_CHAT_MODIFIED);
dcContext.eventCenter.addObserver(this, DcContext.DC_EVENT_CONTACTS_CHANGED);
}
@Override
@@ -127,6 +128,12 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
updateViewState();
}
@Override
protected void onDestroy() {
dcContext.eventCenter.removeObservers(this);
super.onDestroy();
}
@SuppressWarnings("ConstantConditions")
private void updateViewState() {
avatar.setEnabled(true);
@@ -136,7 +143,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
if(isEdit()) {
title = getString(R.string.GroupCreateActivity_actionbar_edit_title);
}
else if(createVerified) {
else if(verified) {
title = getString(R.string.GroupCreateActivity_actionbar_verified_title);
}
else {
@@ -149,6 +156,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
new AddMembersTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipients);
}
@Override
public void handleEvent(int eventId, Object data1, Object data2) {
if (eventId== DcContext.DC_EVENT_CHAT_MODIFIED || eventId==DcContext.DC_EVENT_CONTACTS_CHANGED) {
initializeExistingGroup();
}
}
private static class AddMembersTask extends AsyncTask<Recipient,Void,List<AddMembersTask.Result>> {
static class Result {
Optional<Recipient> recipient;
@@ -201,7 +215,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
lv.setAdapter(adapter);
findViewById(R.id.add_member_button).setOnClickListener(new AddRecipientButtonListener());
if (createVerified) {
if (verified) {
verifyButton.setOnClickListener(new ShowQrButtonListener());
verifyButton.setVisibility(View.VISIBLE);
}
@@ -272,7 +286,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
String groupName = getGroupName();
if (showGroupNameEmptyToast(groupName)) return;
int chatId = dcContext.createGroupChat(createVerified, groupName);
int chatId = dcContext.createGroupChat(verified, groupName);
Set<Recipient> recipients = getAdapter().getRecipients();
for(Recipient recipient : recipients) {
@@ -282,37 +296,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
dcContext.addContactToChat(chatId, contactId);
}
}
setGroupAvatar(chatId);
AvatarHelper.setGroupAvatar(this, chatId, avatarBmp);
Intent intent = new Intent(this, ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)chatId);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, chatId);
startActivity(intent);
finish();
}
private void setGroupAvatar(int chatId) {
if(avatarBmp == null) {
return;
}
String avatarPath = dcContext.getChat(chatId).getProfileImage();
if (avatarPath != null && !avatarPath.isEmpty()) {
File oldImage = new File(avatarPath);
if (oldImage.exists()) {
//noinspection ResultOfMethodCallIgnored
oldImage.delete();
}
}
avatarPath = FileUtils.getFilePathForChatAvatar(this, chatId, System.currentTimeMillis());
FileOutputStream outStream;
try {
outStream = new FileOutputStream(avatarPath);
avatarBmp.compress(Bitmap.CompressFormat.JPEG, 85, outStream);
dcContext.setChatProfileImage(chatId, avatarPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private boolean showGroupNameEmptyToast(String groupName) {
if(groupName == null) {
Toast.makeText(this, getString(R.string.GroupCreateActivity_please_enter_group_name), Toast.LENGTH_LONG).show();
@@ -332,7 +323,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
dcContext.setChatName(editGroupChatId, groupName);
updateGroupParticipants();
setGroupAvatar(editGroupChatId);
AvatarHelper.setGroupAvatar(this, editGroupChatId, avatarBmp);
Intent intent = new Intent();
Recipient recipient = dcContext.getRecipient(ApplicationDcContext.RECIPIENT_TYPE_CHAT, editGroupChatId);
@@ -344,7 +335,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
private void updateGroupParticipants() {
SparseBooleanArray currentChatContactIds = new SparseBooleanArray();
for(int chatContactId : dcContext.getChatContacts(editGroupChatId)) {
currentChatContactIds.put(chatContactId, chatContactId == DC_CONTACT_ID_SELF);
currentChatContactIds.put(chatContactId, false);
}
Set<Recipient> recipients = getAdapter().getRecipients();
@@ -430,7 +421,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View v) {
Intent intent = new Intent(GroupCreateActivity.this, ContactMultiSelectionActivity.class);
intent.putExtra(ContactSelectionListFragment.SELECT_VERIFIED_EXTRA, createVerified);
intent.putExtra(ContactSelectionListFragment.SELECT_VERIFIED_EXTRA, verified);
if (isEdit()) {
intent.putExtra(ContactSelectionListFragment.DISPLAY_MODE, DisplayMode.FLAG_PUSH);
} else {
@@ -445,6 +436,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
public void onClick(View view) {
Intent qrIntent = new Intent(GroupCreateActivity.this, QrShowActivity.class);
if (isEdit()) {
qrIntent.putExtra(QrShowActivity.CHAT_ID, editGroupChatId);
}
startActivity(qrIntent);
}
}
@@ -469,24 +463,16 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
@Override
protected void onPostExecute(Recipient recipient) {
super.onPostExecute(recipient);
activity.fllExistingGroup(recipient);
activity.fillExistingGroup(recipient);
}
}
private void fllExistingGroup(Recipient recipient) {
private void fillExistingGroup(Recipient recipient) {
List<Recipient> participants = recipient.getParticipants();
Recipient[] participantsArray = new Recipient[participants.size()];
participantsArray = participants.toArray(participantsArray);
if (!isFinishing()) {
Recipient ownAddress = null;
for(Recipient participant : participants) {
if(participant.getAddress().getDcContactId() == DC_CONTACT_ID_SELF) {
ownAddress = participant;
} else {
addSelectedContacts(participant);
}
}
if (ownAddress != null) {
participants.remove(ownAddress);
}
addSelectedContacts(participantsArray);
groupName.setText(recipient.getName());
SelectedRecipientsAdapter adapter = new SelectedRecipientsAdapter(this, participants);
adapter.setOnRecipientDeletedListener(this);
@@ -19,25 +19,13 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
import org.thoughtcrime.securesms.components.ContactFilterToolbar;
import org.thoughtcrime.securesms.components.ContactFilterToolbar.OnFilterChangedListener;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import java.util.concurrent.ExecutionException;
public class InviteActivity extends PassphraseRequiredActionBarActivity {
@@ -292,7 +292,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity
intent.putExtra(MediaPreviewActivity.DC_MSG_ID, mediaRecord.getId());
intent.putExtra(MediaPreviewActivity.ADDRESS_EXTRA, recipient.getAddress());
intent.putExtra(MediaPreviewActivity.OUTGOING_EXTRA, mediaRecord.isOutgoing());
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, true);
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, false);
context.startActivity(intent);
}
@@ -202,10 +202,10 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
}
} else {
messageRecord = dcContext.getMsg(msgId);
initialMedia = new MediaItem(Recipient.from(context, msgId), Uri.fromFile(messageRecord.getFileAsFile()),
initialMedia = new MediaItem(Recipient.fromChat(context, msgId), Uri.fromFile(messageRecord.getFileAsFile()),
messageRecord.getFilemime(), messageRecord.getId(), messageRecord.getDateReceived(),
messageRecord.getFilebytes(), messageRecord.isOutgoing());
conversationRecipient = Recipient.from(context, msgId);
conversationRecipient = Recipient.fromChat(context, msgId);
}
leftIsRecent = getIntent().getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
restartItem = -1;
@@ -564,7 +564,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity
if (msg.getFile() == null) throw new AssertionError();
return new MediaItem(Recipient.from(context, msg.getId()),
return new MediaItem(Recipient.fromChat(context, msg.getId()),
Uri.fromFile(msg.getFileAsFile()),
msg.getFilemime(),
msg.getId(),
@@ -101,7 +101,7 @@ public class NewConversationActivity extends ContactSelectionActivity {
intent.putExtra(ConversationActivity.TEXT_EXTRA, getIntent().getStringExtra(ConversationActivity.TEXT_EXTRA));
intent.setDataAndType(getIntent().getData(), getIntent().getType());
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, (long)chatId);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, chatId);
startActivity(intent);
finish();
}
@@ -12,10 +12,7 @@ import android.support.v4.app.Fragment;
import android.util.Log;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import java.util.Locale;
@@ -27,14 +24,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
private static final int STATE_NORMAL = 0;
private static final int STATE_NEEDS_CONFIGURE = 1;
private SignalServiceNetworkAccess networkAccess;
private BroadcastReceiver clearKeyReceiver;
private boolean isVisible;
@Override
protected final void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "onCreate(" + savedInstanceState + ")");
this.networkAccess = new SignalServiceNetworkAccess(this);
onPreCreate();
final boolean locked = KeyCachingService.isLocked(this);
@@ -57,9 +52,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
super.onResume();
KeyCachingService.registerPassphraseActivityStarted(this);
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
else ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
isVisible = true;
}
@@ -69,8 +61,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
super.onPause();
KeyCachingService.registerPassphraseActivityStopped(this);
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
isVisible = false;
}
@@ -31,6 +31,7 @@ public class QrShowActivity extends AppCompatActivity implements DcEventCenter.D
public final static int BLACK = 0xFF000000;
public final static int WIDTH = 400;
public final static int HEIGHT = 400;
public final static String CHAT_ID = "chat_id";
public int numJoiners;
@@ -50,7 +51,7 @@ public class QrShowActivity extends AppCompatActivity implements DcEventCenter.D
Bundle extras = getIntent().getExtras();
int chatId = 0;
if (extras != null) {
chatId = extras.getInt("chat_id");
chatId = extras.getInt(CHAT_ID);
}
ActionBar supportActionBar = getSupportActionBar();
@@ -91,13 +92,13 @@ public class QrShowActivity extends AppCompatActivity implements DcEventCenter.D
e.printStackTrace();
}
dcEventCenter.addObserver(this, DcContext.DC_EVENT_SECUREJOIN_INVITER_PROGRESS);
dcEventCenter.addObserver(DcContext.DC_EVENT_SECUREJOIN_INVITER_PROGRESS, this);
}
@Override
protected void onDestroy() {
super.onDestroy();
dcEventCenter.removeObserver(this, DcContext.DC_EVENT_SECUREJOIN_INVITER_PROGRESS);
dcEventCenter.removeObservers(this);
}
@Override
@@ -41,16 +41,12 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.color.MaterialColors;
import org.thoughtcrime.securesms.components.ThreadPhotoRailView;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
import org.thoughtcrime.securesms.database.loaders.ThreadMediaLoader;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.permissions.Permissions;
@@ -64,7 +60,6 @@ import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -369,25 +364,26 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
if (recipient.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
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);
}
});
// 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);
// }
// });
getPreferenceScreen().removePreference(identityPreference);
}
}
@@ -532,11 +528,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
protected Void doInBackground(Void... params) {
DatabaseFactory.getRecipientDatabase(context).setColor(recipient, selectedColor);
if (recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceContactUpdateJob(context, recipient.getAddress()));
}
// if (recipient.resolve().getRegistered() == RecipientDatabase.RegisteredState.REGISTERED) {
// ApplicationContext.getInstance(context)
// .getJobManager()
// .add(new MultiDeviceContactUpdateJob(context, recipient.getAddress()));
// }
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -589,11 +585,11 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
@Override
public boolean onPreferenceClick(Preference preference) {
Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class);
verifyIdentityIntent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, recipient.getAddress());
verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey()));
verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
startActivity(verifyIdentityIntent);
// Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class);
// verifyIdentityIntent.putExtra(VerifyIdentityActivity.ADDRESS_EXTRA, recipient.getAddress());
// verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey()));
// verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
// startActivity(verifyIdentityIntent);
return true;
}
@@ -645,9 +641,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
DatabaseFactory.getRecipientDatabase(context)
.setBlocked(recipient, blocked);
ApplicationContext.getInstance(context)
.getJobManager()
.add(new MultiDeviceBlockedUpdateJob(context));
// ApplicationContext.getInstance(context)
// .getJobManager()
// .add(new MultiDeviceBlockedUpdateJob(context));
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@@ -38,6 +38,8 @@ import android.view.View;
import android.widget.ImageView;
import org.thoughtcrime.securesms.components.SearchToolbar;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader;
import org.thoughtcrime.securesms.contacts.ContactsCursorLoader.DisplayMode;
import org.thoughtcrime.securesms.database.Address;
@@ -46,6 +48,7 @@ import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
import org.thoughtcrime.securesms.util.DynamicTheme;
@@ -57,6 +60,7 @@ import org.thoughtcrime.securesms.util.ViewUtil;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* An activity to quickly share content with contacts
@@ -71,6 +75,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
public static final String EXTRA_THREAD_ID = "thread_id";
public static final String EXTRA_ADDRESS_MARSHALLED = "address_marshalled";
public static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
public static final String EXTRA_MSG_IDS = "message_ids";
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -82,6 +87,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
private Uri resolvedExtra;
private String mimeType;
private boolean isPassingAlongMedia;
private ApplicationDcContext dcContext;
@Override
protected void onPreCreate() {
@@ -100,6 +106,12 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
getIntent().putExtra(ContactSelectionListFragment.REFRESHABLE, false);
getIntent().putExtra(ContactSelectionListFragment.RECENTS, true);
getIntent().putExtra(ContactSelectionListFragment.MULTI_SELECT, false);
getIntent().putExtra(ContactSelectionListFragment.FROM_SHARE_ACTIVITY_EXTRA, true);
dcContext = DcHelper.getContext(this);
setContentView(R.layout.share_activity);
@@ -218,7 +230,7 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
}
private void handleResolvedMedia(Intent intent, boolean animate) {
long threadId = intent.getLongExtra(EXTRA_THREAD_ID, -1);
int threadId = intent.getIntExtra(EXTRA_THREAD_ID, -1);
int distributionType = intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, -1);
Address address = null;
@@ -244,10 +256,9 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
}
}
private void createConversation(long threadId) {
private void createConversation(int threadId) {
final Intent intent = getBaseShareIntent(ConversationActivity.class);
intent.putExtra(ConversationActivity.THREAD_ID_EXTRA, threadId);
isPassingAlongMedia = true;
startActivity(intent);
}
@@ -271,9 +282,16 @@ public class ShareActivity extends PassphraseRequiredActionBarActivity
@Override
public void onContactSelected(int specialId, String number) {
Recipient recipient = Recipient.from(this, Address.fromExternal(this, number), true);
long existingThread = DatabaseFactory.getThreadDatabase(this).getThreadIdIfExistsFor(recipient);
createConversation(existingThread);
Recipient recipient = dcContext.getRecipient(ApplicationDcContext.RECIPIENT_TYPE_CONTACT, specialId);
Dialogs.showResponseDialog(this, "", getString(R.string.ShareActivity_forward_message_to_user_info, dcContext.getContact(specialId).getDisplayName()), (dialogInterface, i) -> {
int chatId = dcContext.getChatIdByContactId(specialId);
if(chatId == 0){
chatId = dcContext.createChatByContactId(specialId);
}
int[] value = getIntent().getIntArrayExtra(EXTRA_MSG_IDS);
dcContext.forwardMsgs(value, chatId);
createConversation(chatId);
});
}
@Override
@@ -1,671 +0,0 @@
/*
* Copyright (C) 2016-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.Manifest;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.SwitchCompat;
import android.text.Html;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.components.camera.CameraView;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.qr.QrCode;
import org.thoughtcrime.securesms.qr.ScanListener;
import org.thoughtcrime.securesms.qr.ScanningThread;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.fingerprint.Fingerprint;
import org.whispersystems.libsignal.fingerprint.FingerprintParsingException;
import org.whispersystems.libsignal.fingerprint.FingerprintVersionMismatchException;
import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
/**
* Activity for verifying identity keys.
*
* @author Moxie Marlinspike
*/
@SuppressLint("StaticFieldLeak")
public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity implements RecipientModifiedListener, ScanListener, View.OnClickListener {
private static final String TAG = VerifyIdentityActivity.class.getSimpleName();
public static final String ADDRESS_EXTRA = "address";
public static final String IDENTITY_EXTRA = "recipient_identity";
public static final String VERIFIED_EXTRA = "verified_state";
private final DynamicTheme dynamicTheme = new DynamicTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
private VerifyDisplayFragment displayFragment = new VerifyDisplayFragment();
private VerifyScanFragment scanFragment = new VerifyScanFragment();
@Override
public void onPreCreate() {
dynamicTheme.onCreate(this);
dynamicLanguage.onCreate(this);
}
@Override
protected void onCreate(Bundle state, boolean ready) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
Recipient recipient = Recipient.from(this, (Address)getIntent().getParcelableExtra(ADDRESS_EXTRA), true);
recipient.addListener(this);
setActionBarNotificationBarColor(recipient.getColor());
Bundle extras = new Bundle();
extras.putParcelable(VerifyDisplayFragment.REMOTE_ADDRESS, getIntent().getParcelableExtra(ADDRESS_EXTRA));
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
extras.putString(VerifyDisplayFragment.REMOTE_NUMBER, recipient.getAddress().toPhoneString());
extras.putParcelable(VerifyDisplayFragment.LOCAL_IDENTITY, new IdentityKeyParcelable(IdentityKeyUtil.getIdentityKey(this)));
extras.putString(VerifyDisplayFragment.LOCAL_NUMBER, TextSecurePreferences.getLocalNumber(this));
extras.putBoolean(VerifyDisplayFragment.VERIFIED_STATE, getIntent().getBooleanExtra(VERIFIED_EXTRA, false));
scanFragment.setScanListener(this);
displayFragment.setClickListener(this);
initFragment(android.R.id.content, displayFragment, dynamicLanguage.getCurrentLocale(), extras);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: finish(); return true;
}
return false;
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> setActionBarNotificationBarColor(recipient.getColor()));
}
@Override
public void onQrDataFound(final String data) {
Util.runOnMain(() -> {
((Vibrator)getSystemService(Context.VIBRATOR_SERVICE)).vibrate(50);
getSupportFragmentManager().popBackStack();
displayFragment.setScannedFingerprint(data);
});
}
@Override
public void onClick(View v) {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.ifNecessary()
.withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied))
.onAllGranted(() -> {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom,
R.anim.slide_from_bottom, R.anim.slide_to_top);
transaction.replace(android.R.id.content, scanFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
})
.onAnyDenied(() -> Toast.makeText(this, R.string.VerifyIdentityActivity_unable_to_scan_qr_code_without_camera_permission, Toast.LENGTH_LONG).show())
.execute();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void setActionBarNotificationBarColor(MaterialColor color) {
getSupportActionBar().setBackgroundDrawable(new ColorDrawable(color.toActionBarColor(this)));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().setStatusBarColor(color.toStatusBarColor(this));
}
}
public static class VerifyDisplayFragment extends Fragment implements RecipientModifiedListener, CompoundButton.OnCheckedChangeListener {
public static final String REMOTE_ADDRESS = "remote_address";
public static final String REMOTE_NUMBER = "remote_number";
public static final String REMOTE_IDENTITY = "remote_identity";
public static final String LOCAL_IDENTITY = "local_identity";
public static final String LOCAL_NUMBER = "local_number";
public static final String VERIFIED_STATE = "verified_state";
private Recipient recipient;
private String localNumber;
private String remoteNumber;
private IdentityKey localIdentity;
private IdentityKey remoteIdentity;
private Fingerprint fingerprint;
private View container;
private View numbersContainer;
private ImageView qrCode;
private ImageView qrVerified;
private TextView tapLabel;
private TextView description;
private View.OnClickListener clickListener;
private SwitchCompat verified;
private TextView[] codes = new TextView[12];
private boolean animateSuccessOnDraw = false;
private boolean animateFailureOnDraw = false;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment);
this.numbersContainer = ViewUtil.findById(container, R.id.number_table);
this.qrCode = ViewUtil.findById(container, R.id.qr_code);
this.verified = ViewUtil.findById(container, R.id.verified_switch);
this.qrVerified = ViewUtil.findById(container, R.id.qr_verified);
this.description = ViewUtil.findById(container, R.id.description);
this.tapLabel = ViewUtil.findById(container, R.id.tap_label);
this.codes[0] = ViewUtil.findById(container, R.id.code_first);
this.codes[1] = ViewUtil.findById(container, R.id.code_second);
this.codes[2] = ViewUtil.findById(container, R.id.code_third);
this.codes[3] = ViewUtil.findById(container, R.id.code_fourth);
this.codes[4] = ViewUtil.findById(container, R.id.code_fifth);
this.codes[5] = ViewUtil.findById(container, R.id.code_sixth);
this.codes[6] = ViewUtil.findById(container, R.id.code_seventh);
this.codes[7] = ViewUtil.findById(container, R.id.code_eighth);
this.codes[8] = ViewUtil.findById(container, R.id.code_ninth);
this.codes[9] = ViewUtil.findById(container, R.id.code_tenth);
this.codes[10] = ViewUtil.findById(container, R.id.code_eleventh);
this.codes[11] = ViewUtil.findById(container, R.id.code_twelth);
this.qrCode.setOnClickListener(clickListener);
this.registerForContextMenu(numbersContainer);
this.verified.setChecked(getArguments().getBoolean(VERIFIED_STATE, false));
this.verified.setOnCheckedChangeListener(this);
return container;
}
@Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Address address = getArguments().getParcelable(REMOTE_ADDRESS);
IdentityKeyParcelable localIdentityParcelable = getArguments().getParcelable(LOCAL_IDENTITY);
IdentityKeyParcelable remoteIdentityParcelable = getArguments().getParcelable(REMOTE_IDENTITY);
if (address == null) throw new AssertionError("Address required");
if (localIdentityParcelable == null) throw new AssertionError("local identity required");
if (remoteIdentityParcelable == null) throw new AssertionError("remote identity required");
this.localNumber = getArguments().getString(LOCAL_NUMBER);
this.localIdentity = localIdentityParcelable.get();
this.remoteNumber = getArguments().getString(REMOTE_NUMBER);
this.recipient = Recipient.from(getActivity(), address, true);
this.remoteIdentity = remoteIdentityParcelable.get();
this.recipient.addListener(this);
new AsyncTask<Void, Void, Fingerprint>() {
@Override
protected Fingerprint doInBackground(Void... params) {
return new NumericFingerprintGenerator(5200).createFor(localNumber, localIdentity,
remoteNumber, remoteIdentity);
}
@Override
protected void onPostExecute(Fingerprint fingerprint) {
VerifyDisplayFragment.this.fingerprint = fingerprint;
setFingerprintViews(fingerprint, true);
getActivity().supportInvalidateOptionsMenu();
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
setHasOptionsMenu(true);
}
@Override
public void onModified(final Recipient recipient) {
Util.runOnMain(() -> setRecipientText(recipient));
}
@Override
public void onResume() {
super.onResume();
setRecipientText(recipient);
if (fingerprint != null) {
setFingerprintViews(fingerprint, false);
}
if (animateSuccessOnDraw) {
animateSuccessOnDraw = false;
animateVerifiedSuccess();
} else if (animateFailureOnDraw) {
animateFailureOnDraw = false;
animateVerifiedFailure();
}
}
@Override
public void onDestroy() {
super.onDestroy();
recipient.removeListener(this);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo menuInfo)
{
super.onCreateContextMenu(menu, view, menuInfo);
if (fingerprint != null) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.verify_display_fragment_context_menu, menu);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (fingerprint == null) return super.onContextItemSelected(item);
switch (item.getItemId()) {
case R.id.menu_copy: handleCopyToClipboard(fingerprint, codes.length); return true;
case R.id.menu_compare: handleCompareWithClipboard(fingerprint); return true;
default: return super.onContextItemSelected(item);
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (fingerprint != null) {
inflater.inflate(R.menu.verify_identity, menu);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.verify_identity__share: handleShare(fingerprint, codes.length); return true;
}
return false;
}
public void setScannedFingerprint(String scanned) {
try {
if (fingerprint.getScannableFingerprint().compareTo(scanned.getBytes("ISO-8859-1"))) {
this.animateSuccessOnDraw = true;
} else {
this.animateFailureOnDraw = true;
}
} catch (FingerprintVersionMismatchException e) {
Log.w(TAG, e);
if (e.getOurVersion() < e.getTheirVersion()) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_a_newer_version_of_Signal, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_your_contact_is_running_an_old_version_of_signal, Toast.LENGTH_LONG).show();
}
} catch (FingerprintParsingException e) {
Log.w(TAG, e);
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_the_scanned_qr_code_is_not_a_correctly_formatted_safety_number, Toast.LENGTH_LONG).show();
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
public void setClickListener(View.OnClickListener listener) {
this.clickListener = listener;
}
private @NonNull String getFormattedSafetyNumbers(@NonNull Fingerprint fingerprint, int segmentCount) {
String[] segments = getSegments(fingerprint, segmentCount);
StringBuilder result = new StringBuilder();
for (int i = 0; i < segments.length; i++) {
result.append(segments[i]);
if (i != segments.length - 1) {
if (((i+1) % 4) == 0) result.append('\n');
else result.append(' ');
}
}
return result.toString();
}
private void handleCopyToClipboard(Fingerprint fingerprint, int segmentCount) {
Util.writeTextToClipboard(getActivity(), getFormattedSafetyNumbers(fingerprint, segmentCount));
}
private void handleCompareWithClipboard(Fingerprint fingerprint) {
String clipboardData = Util.readTextFromClipboard(getActivity());
if (clipboardData == null) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
return;
}
String numericClipboardData = clipboardData.replaceAll("\\D", "");
if (TextUtils.isEmpty(numericClipboardData) || numericClipboardData.length() != 60) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_safety_number_to_compare_was_found_in_the_clipboard, Toast.LENGTH_LONG).show();
return;
}
if (fingerprint.getDisplayableFingerprint().getDisplayText().equals(numericClipboardData)) {
animateVerifiedSuccess();
} else {
animateVerifiedFailure();
}
}
private void handleShare(@NonNull Fingerprint fingerprint, int segmentCount) {
String shareString =
getString(R.string.VerifyIdentityActivity_our_signal_safety_number) + "\n" +
getFormattedSafetyNumbers(fingerprint, segmentCount) + "\n";
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, shareString);
intent.setType("text/plain");
try {
startActivity(Intent.createChooser(intent, getString(R.string.VerifyIdentityActivity_share_safety_number_via)));
} catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.VerifyIdentityActivity_no_app_to_share_to, Toast.LENGTH_LONG).show();
}
}
private void setRecipientText(Recipient recipient) {
description.setText(Html.fromHtml(String.format(getActivity().getString(R.string.verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s), recipient.toShortString())));
description.setMovementMethod(LinkMovementMethod.getInstance());
}
private void setFingerprintViews(Fingerprint fingerprint, boolean animate) {
String[] segments = getSegments(fingerprint, codes.length);
for (int i=0;i<codes.length;i++) {
if (animate) setCodeSegment(codes[i], segments[i]);
else codes[i].setText(segments[i]);
}
byte[] qrCodeData = fingerprint.getScannableFingerprint().getSerialized();
String qrCodeString = new String(qrCodeData, Charset.forName("ISO-8859-1"));
Bitmap qrCodeBitmap = QrCode.create(qrCodeString);
qrCode.setImageBitmap(qrCodeBitmap);
if (animate) {
ViewUtil.fadeIn(qrCode, 1000);
ViewUtil.fadeIn(tapLabel, 1000);
} else {
qrCode.setVisibility(View.VISIBLE);
tapLabel.setVisibility(View.VISIBLE);
}
}
private void setCodeSegment(final TextView codeView, String segment) {
if (Build.VERSION.SDK_INT >= 11) {
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(0, Integer.parseInt(segment));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();
codeView.setText(String.format("%05d", value));
}
});
valueAnimator.setEvaluator(new TypeEvaluator<Integer>() {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
return Math.round(startValue + (endValue - startValue) * fraction);
}
});
valueAnimator.setDuration(1000);
valueAnimator.start();
} else {
codeView.setText(segment);
}
}
private String[] getSegments(Fingerprint fingerprint, int segmentCount) {
String[] segments = new String[segmentCount];
String digits = fingerprint.getDisplayableFingerprint().getDisplayText();
int partSize = digits.length() / segmentCount;
for (int i=0;i<segmentCount;i++) {
segments[i] = digits.substring(i * partSize, (i * partSize) + partSize);
}
return segments;
}
private Bitmap createVerifiedBitmap(int width, int height, @DrawableRes int id) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Bitmap check = BitmapFactory.decodeResource(getResources(), id);
float offset = (width - check.getWidth()) / 2;
canvas.drawBitmap(check, offset, offset, null);
return bitmap;
}
private void animateVerifiedSuccess() {
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_check_white_48dp);
qrVerified.setImageBitmap(qrSuccess);
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.MULTIPLY);
animateVerified();
}
private void animateVerifiedFailure() {
Bitmap qrBitmap = ((BitmapDrawable)qrCode.getDrawable()).getBitmap();
Bitmap qrSuccess = createVerifiedBitmap(qrBitmap.getWidth(), qrBitmap.getHeight(), R.drawable.ic_close_white_48dp);
qrVerified.setImageBitmap(qrSuccess);
qrVerified.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.MULTIPLY);
animateVerified();
}
private void animateVerified() {
ScaleAnimation scaleAnimation = new ScaleAnimation(0, 1, 0, 1,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new OvershootInterpolator());
scaleAnimation.setDuration(800);
scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
qrVerified.postDelayed(new Runnable() {
@Override
public void run() {
ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0, 1, 0,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f,
ScaleAnimation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setInterpolator(new AnticipateInterpolator());
scaleAnimation.setDuration(500);
ViewUtil.animateOut(qrVerified, scaleAnimation, View.GONE);
}
}, 2000);
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
ViewUtil.animateIn(qrVerified, scaleAnimation);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, final boolean isChecked) {
new AsyncTask<Recipient, Void, Void>() {
@Override
protected Void doInBackground(Recipient... params) {
synchronized (SESSION_LOCK) {
if (isChecked) {
Log.w(TAG, "Saving identity: " + params[0].getAddress());
DatabaseFactory.getIdentityDatabase(getActivity())
.saveIdentity(params[0].getAddress(),
remoteIdentity,
VerifiedStatus.VERIFIED, false,
System.currentTimeMillis(), true);
} else {
DatabaseFactory.getIdentityDatabase(getActivity())
.setVerified(params[0].getAddress(),
remoteIdentity,
VerifiedStatus.DEFAULT);
}
ApplicationContext.getInstance(getActivity())
.getJobManager()
.add(new MultiDeviceVerifiedUpdateJob(getActivity(),
recipient.getAddress(),
remoteIdentity,
isChecked ? VerifiedStatus.VERIFIED :
VerifiedStatus.DEFAULT));
IdentityUtil.markIdentityVerified(getActivity(), recipient, isChecked, false);
}
return null;
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, recipient);
}
}
public static class VerifyScanFragment extends Fragment {
private View container;
private CameraView cameraView;
private ScanningThread scanningThread;
private ScanListener scanListener;
public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment);
this.cameraView = ViewUtil.findById(container, R.id.scanner);
return container;
}
@Override
public void onResume() {
super.onResume();
this.scanningThread = new ScanningThread();
this.scanningThread.setScanListener(scanListener);
this.scanningThread.setCharacterSet("ISO-8859-1");
this.cameraView.onResume();
this.cameraView.setPreviewCallback(scanningThread);
this.scanningThread.start();
}
@Override
public void onPause() {
super.onPause();
this.cameraView.onPause();
this.scanningThread.stopScanning();
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
this.cameraView.onPause();
this.cameraView.onResume();
this.cameraView.setPreviewCallback(scanningThread);
}
public void setScanListener(ScanListener listener) {
if (this.scanningThread != null) scanningThread.setScanListener(listener);
this.scanListener = listener;
}
}
}
@@ -1,406 +0,0 @@
/*
* Copyright (C) 2016 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.app.Activity;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.webrtc.WebRtcAnswerDeclineButton;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallControls;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallScreen;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.SignalProtocolAddress;
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
public class WebRtcCallActivity extends Activity {
private static final String TAG = WebRtcCallActivity.class.getSimpleName();
private static final int STANDARD_DELAY_FINISH = 1000;
public static final int BUSY_SIGNAL_DELAY_FINISH = 5500;
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
public static final String END_CALL_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".END_CALL_ACTION";
private WebRtcCallScreen callScreen;
private SignalServiceNetworkAccess networkAccess;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.w(TAG, "onCreate()");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.webrtc_call_activity);
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
initializeResources();
}
@Override
public void onResume() {
Log.w(TAG, "onResume()");
super.onResume();
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStarted(this);
initializeScreenshotSecurity();
EventBus.getDefault().register(this);
}
@Override
public void onNewIntent(Intent intent){
Log.w(TAG, "onNewIntent");
if (ANSWER_ACTION.equals(intent.getAction())) {
handleAnswerCall();
} else if (DENY_ACTION.equals(intent.getAction())) {
handleDenyCall();
} else if (END_CALL_ACTION.equals(intent.getAction())) {
handleEndCall();
}
}
@Override
public void onPause() {
Log.w(TAG, "onPause");
super.onPause();
if (!networkAccess.isCensored(this)) MessageRetrievalService.registerActivityStopped(this);
EventBus.getDefault().unregister(this);
}
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
super.onConfigurationChanged(newConfiguration);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
private void initializeScreenshotSecurity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
TextSecurePreferences.isScreenSecurityEnabled(this))
{
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}
private void initializeResources() {
callScreen = ViewUtil.findById(this, R.id.callScreen);
callScreen.setHangupButtonListener(new HangupButtonListener());
callScreen.setIncomingCallActionListener(new IncomingCallActionListener());
callScreen.setAudioMuteButtonListener(new AudioMuteButtonListener());
callScreen.setVideoMuteButtonListener(new VideoMuteButtonListener());
callScreen.setCameraFlipButtonListener(new CameraFlipButtonListener());
callScreen.setSpeakerButtonListener(new SpeakerButtonListener());
callScreen.setBluetoothButtonListener(new BluetoothButtonListener());
networkAccess = new SignalServiceNetworkAccess(this);
}
private void handleSetMuteAudio(boolean enabled) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_AUDIO);
intent.putExtra(WebRtcCallService.EXTRA_MUTE, enabled);
startService(intent);
}
private void handleSetMuteVideo(boolean muted) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_SET_MUTE_VIDEO);
intent.putExtra(WebRtcCallService.EXTRA_MUTE, muted);
startService(intent);
}
private void handleFlipCamera() {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_FLIP_CAMERA);
startService(intent);
}
private void handleAnswerCall() {
WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
if (event != null) {
Permissions.with(this)
.request(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.ifNecessary()
.withRationaleDialog(getString(R.string.WebRtcCallActivity_to_answer_the_call_from_s_give_signal_access_to_your_microphone, event.getRecipient().toShortString()),
R.drawable.ic_mic_white_48dp, R.drawable.ic_videocam_white_48dp)
.withPermanentDenialDialog(getString(R.string.WebRtcCallActivity_signal_requires_microphone_and_camera_permissions_in_order_to_make_or_receive_calls))
.onAllGranted(() -> {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_answering));
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_ANSWER_CALL);
startService(intent);
})
.onAnyDenied(this::handleDenyCall)
.execute();
}
}
private void handleDenyCall() {
WebRtcViewModel event = EventBus.getDefault().getStickyEvent(WebRtcViewModel.class);
if (event != null) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_DENY_CALL);
startService(intent);
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ending_call));
delayedFinish();
}
}
private void handleEndCall() {
Log.w(TAG, "Hangup pressed, handling termination now...");
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_LOCAL_HANGUP);
startService(intent);
}
private void handleIncomingCall(@NonNull WebRtcViewModel event) {
callScreen.setIncomingCall(event.getRecipient());
}
private void handleOutgoingCall(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_dialing));
}
private void handleTerminate(@NonNull Recipient recipient /*, int terminationType */) {
Log.w(TAG, "handleTerminate called");
callScreen.setActiveCall(recipient, getString(R.string.RedPhone_ending_call));
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
delayedFinish();
}
private void handleCallRinging(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_ringing));
}
private void handleCallBusy(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_busy));
delayedFinish(BUSY_SIGNAL_DELAY_FINISH);
}
private void handleCallConnected(@NonNull WebRtcViewModel event) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_connected), "");
}
private void handleRecipientUnavailable(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_recipient_unavailable));
delayedFinish();
}
private void handleServerFailure(@NonNull WebRtcViewModel event) {
callScreen.setActiveCall(event.getRecipient(), getString(R.string.RedPhone_network_failed));
delayedFinish();
}
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.RedPhone_number_not_registered);
dialog.setIconAttribute(R.attr.dialog_alert_icon);
dialog.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice);
dialog.setCancelable(true);
dialog.setPositiveButton(R.string.RedPhone_got_it, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
}
});
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
WebRtcCallActivity.this.handleTerminate(event.getRecipient());
}
});
dialog.show();
}
private void handleUntrustedIdentity(@NonNull WebRtcViewModel event) {
final IdentityKey theirIdentity = event.getIdentityKey();
final Recipient recipient = event.getRecipient();
callScreen.setUntrustedIdentity(recipient, theirIdentity);
callScreen.setAcceptIdentityListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
synchronized (SESSION_LOCK) {
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this);
identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.getAddress().serialize(), 1), theirIdentity, true);
}
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.putExtra(WebRtcCallService.EXTRA_REMOTE_ADDRESS, recipient.getAddress());
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL);
startService(intent);
}
});
callScreen.setCancelIdentityButton(new View.OnClickListener() {
@Override
public void onClick(View v) {
handleTerminate(recipient);
}
});
}
private void delayedFinish() {
delayedFinish(STANDARD_DELAY_FINISH);
}
private void delayedFinish(int delayMillis) {
callScreen.postDelayed(new Runnable() {
public void run() {
WebRtcCallActivity.this.finish();
}
}, delayMillis);
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventMainThread(final WebRtcViewModel event) {
Log.w(TAG, "Got message from service: " + event);
switch (event.getState()) {
case CALL_CONNECTED: handleCallConnected(event); break;
case NETWORK_FAILURE: handleServerFailure(event); break;
case CALL_RINGING: handleCallRinging(event); break;
case CALL_DISCONNECTED: handleTerminate(event.getRecipient()); break;
case NO_SUCH_USER: handleNoSuchUser(event); break;
case RECIPIENT_UNAVAILABLE: handleRecipientUnavailable(event); break;
case CALL_INCOMING: handleIncomingCall(event); break;
case CALL_OUTGOING: handleOutgoingCall(event); break;
case CALL_BUSY: handleCallBusy(event); break;
case UNTRUSTED_IDENTITY: handleUntrustedIdentity(event); break;
}
callScreen.setRemoteVideoEnabled(event.isRemoteVideoEnabled());
callScreen.updateAudioState(event.isBluetoothAvailable(), event.isMicrophoneEnabled());
callScreen.setControlsEnabled(event.getState() != WebRtcViewModel.State.CALL_INCOMING);
callScreen.setLocalVideoState(event.getLocalCameraState());
}
private class HangupButtonListener implements WebRtcCallScreen.HangupButtonListener {
public void onClick() {
handleEndCall();
}
}
private class AudioMuteButtonListener implements WebRtcCallControls.MuteButtonListener {
@Override
public void onToggle(boolean isMuted) {
WebRtcCallActivity.this.handleSetMuteAudio(isMuted);
}
}
private class VideoMuteButtonListener implements WebRtcCallControls.MuteButtonListener {
@Override
public void onToggle(boolean isMuted) {
WebRtcCallActivity.this.handleSetMuteVideo(isMuted);
}
}
private class CameraFlipButtonListener implements WebRtcCallControls.CameraFlipButtonListener {
@Override
public void onToggle() {
WebRtcCallActivity.this.handleFlipCamera();
}
}
private class SpeakerButtonListener implements WebRtcCallControls.SpeakerButtonListener {
@Override
public void onSpeakerChange(boolean isSpeaker) {
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
audioManager.setSpeakerphoneOn(isSpeaker);
if (isSpeaker && audioManager.isBluetoothScoOn()) {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
}
}
}
private class BluetoothButtonListener implements WebRtcCallControls.BluetoothButtonListener {
@Override
public void onBluetoothChange(boolean isBluetooth) {
AudioManager audioManager = ServiceUtil.getAudioManager(WebRtcCallActivity.this);
if (isBluetooth) {
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
} else {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
}
}
}
private class IncomingCallActionListener implements WebRtcAnswerDeclineButton.AnswerDeclineListener {
@Override
public void onAnswered() {
WebRtcCallActivity.this.handleAnswerCall();
}
@Override
public void onDeclined() {
WebRtcCallActivity.this.handleDenyCall();
}
}
}
@@ -24,10 +24,6 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.CustomDefaultPreference.CustomDefaultPreferenceDialogFragmentCompat.CustomPreferenceValidator;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.net.URI;
import java.net.URISyntaxException;
public class CustomDefaultPreference extends DialogPreference {
private static final String TAG = CustomDefaultPreference.class.getSimpleName();
@@ -56,11 +52,6 @@ public class CustomDefaultPreference extends DialogPreference {
setDialogLayoutResource(R.layout.custom_default_preference_dialog);
}
public CustomDefaultPreference setValidator(CustomPreferenceValidator validator) {
this.validator = validator;
return this;
}
public CustomDefaultPreference setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
this.setSummary(getSummary());
@@ -193,46 +184,6 @@ public class CustomDefaultPreference extends DialogPreference {
}
}
public static class UriValidator implements CustomPreferenceValidator {
@Override
public boolean isValid(String value) {
if (TextUtils.isEmpty(value)) return true;
try {
new URI(value);
return true;
} catch (URISyntaxException mue) {
return false;
}
}
}
public static class HostnameValidator implements CustomPreferenceValidator {
@Override
public boolean isValid(String value) {
if (TextUtils.isEmpty(value)) return true;
try {
URI uri = new URI(null, value, null, null);
return true;
} catch (URISyntaxException mue) {
return false;
}
}
}
public static class PortValidator implements CustomPreferenceValidator {
@Override
public boolean isValid(String value) {
try {
Integer.parseInt(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}
private class SelectionLister implements AdapterView.OnItemSelectedListener {
@Override
@@ -1,221 +0,0 @@
package org.thoughtcrime.securesms.components.webrtc;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import com.tomergoldst.tooltips.ToolTip;
import com.tomergoldst.tooltips.ToolTipsManager;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AccessibleToggleButton;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
public class WebRtcCallControls extends LinearLayout {
private static final String TAG = WebRtcCallControls.class.getSimpleName();
private AccessibleToggleButton audioMuteButton;
private AccessibleToggleButton videoMuteButton;
private AccessibleToggleButton speakerButton;
private AccessibleToggleButton bluetoothButton;
private AccessibleToggleButton cameraFlipButton;
private boolean cameraFlipAvailable;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WebRtcCallControls(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
public WebRtcCallControls(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public WebRtcCallControls(Context context) {
super(context);
initialize();
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.webrtc_call_controls, this, true);
this.speakerButton = ViewUtil.findById(this, R.id.speakerButton);
this.bluetoothButton = ViewUtil.findById(this, R.id.bluetoothButton);
this.audioMuteButton = ViewUtil.findById(this, R.id.muteButton);
this.videoMuteButton = ViewUtil.findById(this, R.id.video_mute_button);
this.cameraFlipButton = ViewUtil.findById(this, R.id.camera_flip_button);
}
public void setAudioMuteButtonListener(final MuteButtonListener listener) {
audioMuteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
listener.onToggle(b);
}
});
}
public void setVideoMuteButtonListener(final MuteButtonListener listener) {
videoMuteButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
boolean videoMuted = !isChecked;
listener.onToggle(videoMuted);
cameraFlipButton.setVisibility(!videoMuted && cameraFlipAvailable ? View.VISIBLE : View.GONE);
}
});
}
public void setCameraFlipButtonListener(final CameraFlipButtonListener listener) {
cameraFlipButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listener.onToggle();
cameraFlipButton.setBackgroundResource(isChecked ? R.drawable.webrtc_camera_front_button
: R.drawable.webrtc_camera_rear_button);
cameraFlipButton.setEnabled(false);
}
});
}
public void setSpeakerButtonListener(final SpeakerButtonListener listener) {
speakerButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listener.onSpeakerChange(isChecked);
updateAudioState(bluetoothButton.getVisibility() == View.VISIBLE);
}
});
}
public void setBluetoothButtonListener(final BluetoothButtonListener listener) {
bluetoothButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listener.onBluetoothChange(isChecked);
updateAudioState(true);
}
});
}
public void updateAudioState(boolean isBluetoothAvailable) {
AudioManager audioManager = ServiceUtil.getAudioManager(getContext());
if (!isBluetoothAvailable) {
bluetoothButton.setVisibility(View.GONE);
} else {
bluetoothButton.setVisibility(View.VISIBLE);
}
if (audioManager.isBluetoothScoOn()) {
bluetoothButton.setChecked(true, false);
speakerButton.setChecked(false, false);
} else if (audioManager.isSpeakerphoneOn()) {
speakerButton.setChecked(true, false);
bluetoothButton.setChecked(false, false);
} else {
speakerButton.setChecked(false, false);
bluetoothButton.setChecked(false, false);
}
}
public boolean isVideoEnabled() {
return videoMuteButton.isChecked();
}
public void setVideoEnabled(boolean enabled) {
videoMuteButton.setChecked(enabled, false);
}
public void setVideoAvailable(boolean available) {
videoMuteButton.setVisibility(available ? VISIBLE : GONE);
}
public void setCameraFlipButtonEnabled(boolean enabled) {
cameraFlipButton.setChecked(enabled, false);
}
public void setCameraFlipAvailable(boolean available) {
cameraFlipAvailable = available;
}
public void setCameraFlipClickable(boolean clickable) {
setControlEnabled(cameraFlipButton, clickable);
}
public void setMicrophoneEnabled(boolean enabled) {
audioMuteButton.setChecked(!enabled, false);
}
public void setControlsEnabled(boolean enabled) {
setControlEnabled(speakerButton, enabled);
setControlEnabled(bluetoothButton, enabled);
setControlEnabled(videoMuteButton, enabled);
setControlEnabled(cameraFlipButton, enabled);
setControlEnabled(audioMuteButton, enabled);
}
private void setControlEnabled(@NonNull View view, boolean enabled) {
if (enabled) {
view.setAlpha(1.0f);
view.setEnabled(true);
} else {
view.setAlpha(0.3f);
view.setEnabled(false);
}
}
public void displayVideoTooltip(ViewGroup viewGroup) {
if (Build.VERSION.SDK_INT > 15 && videoMuteButton.getVisibility() == VISIBLE) {
final ToolTipsManager toolTipsManager = new ToolTipsManager();
ToolTip toolTip = new ToolTip.Builder(getContext(), videoMuteButton, viewGroup,
getContext().getString(R.string.WebRtcCallControls_tap_to_enable_your_video),
ToolTip.POSITION_BELOW).build();
toolTipsManager.show(toolTip);
videoMuteButton.postDelayed(() -> toolTipsManager.findAndDismiss(videoMuteButton), 4000);
}
}
public static interface MuteButtonListener {
public void onToggle(boolean isMuted);
}
public static interface CameraFlipButtonListener {
public void onToggle();
}
public static interface SpeakerButtonListener {
public void onSpeakerChange(boolean isSpeaker);
}
public static interface BluetoothButtonListener {
public void onBluetoothChange(boolean isBluetooth);
}
}
@@ -1,352 +0,0 @@
/*
* Copyright (C) 2016 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.components.webrtc;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.view.ViewCompat;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.VerifySpan;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.webrtc.CameraState;
import org.webrtc.SurfaceViewRenderer;
import org.whispersystems.libsignal.IdentityKey;
/**
* A UI widget that encapsulates the entire in-call screen
* for both initiators and responders.
*
* @author Moxie Marlinspike
*
*/
public class WebRtcCallScreen extends FrameLayout implements RecipientModifiedListener {
@SuppressWarnings("unused")
private static final String TAG = WebRtcCallScreen.class.getSimpleName();
private ImageView photo;
private SurfaceViewRenderer localRenderer;
private PercentFrameLayout localRenderLayout;
private PercentFrameLayout remoteRenderLayout;
private TextView name;
private TextView phoneNumber;
private TextView label;
private TextView elapsedTime;
private View untrustedIdentityContainer;
private TextView untrustedIdentityExplanation;
private Button acceptIdentityButton;
private Button cancelIdentityButton;
private TextView status;
private FloatingActionButton endCallButton;
private WebRtcCallControls controls;
private RelativeLayout expandedInfo;
private ViewGroup callHeader;
private WebRtcAnswerDeclineButton incomingCallButton;
private Recipient recipient;
private boolean minimized;
public WebRtcCallScreen(Context context) {
super(context);
initialize();
}
public WebRtcCallScreen(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public WebRtcCallScreen(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialize();
}
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message, @Nullable String sas) {
setCard(personInfo, message);
setConnected(WebRtcCallService.localRenderer, WebRtcCallService.remoteRenderer);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
endCallButton.show();
}
public void setActiveCall(@NonNull Recipient personInfo, @NonNull String message) {
setCard(personInfo, message);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
endCallButton.show();
}
public void setIncomingCall(Recipient personInfo) {
setCard(personInfo, getContext().getString(R.string.CallScreen_Incoming_call));
endCallButton.setVisibility(View.INVISIBLE);
incomingCallButton.setVisibility(View.VISIBLE);
incomingCallButton.startRingingAnimation();
}
public void setUntrustedIdentity(Recipient personInfo, IdentityKey untrustedIdentity) {
String name = recipient.toShortString();
String introduction = String.format(getContext().getString(R.string.WebRtcCallScreen_new_safety_numbers), name, name);
SpannableString spannableString = new SpannableString(introduction + " " + getContext().getString(R.string.WebRtcCallScreen_you_may_wish_to_verify_this_contact));
spannableString.setSpan(new VerifySpan(getContext(), personInfo.getAddress(), untrustedIdentity),
introduction.length()+1, spannableString.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setPersonInfo(personInfo);
incomingCallButton.stopRingingAnimation();
incomingCallButton.setVisibility(View.GONE);
this.status.setText(R.string.WebRtcCallScreen_new_safety_number_title);
this.untrustedIdentityContainer.setVisibility(View.VISIBLE);
this.untrustedIdentityExplanation.setText(spannableString);
this.untrustedIdentityExplanation.setMovementMethod(LinkMovementMethod.getInstance());
this.endCallButton.setVisibility(View.INVISIBLE);
}
public void setIncomingCallActionListener(WebRtcAnswerDeclineButton.AnswerDeclineListener listener) {
incomingCallButton.setAnswerDeclineListener(listener);
}
public void setAudioMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
this.controls.setAudioMuteButtonListener(listener);
}
public void setVideoMuteButtonListener(WebRtcCallControls.MuteButtonListener listener) {
this.controls.setVideoMuteButtonListener(listener);
}
public void setCameraFlipButtonListener(WebRtcCallControls.CameraFlipButtonListener listener) {
this.controls.setCameraFlipButtonListener(listener);
}
public void setSpeakerButtonListener(WebRtcCallControls.SpeakerButtonListener listener) {
this.controls.setSpeakerButtonListener(listener);
}
public void setBluetoothButtonListener(WebRtcCallControls.BluetoothButtonListener listener) {
this.controls.setBluetoothButtonListener(listener);
}
public void setHangupButtonListener(final HangupButtonListener listener) {
endCallButton.setOnClickListener(v -> listener.onClick());
}
public void setAcceptIdentityListener(OnClickListener listener) {
this.acceptIdentityButton.setOnClickListener(listener);
}
public void setCancelIdentityButton(OnClickListener listener) {
this.cancelIdentityButton.setOnClickListener(listener);
}
public void updateAudioState(boolean isBluetoothAvailable, boolean isMicrophoneEnabled) {
this.controls.updateAudioState(isBluetoothAvailable);
this.controls.setMicrophoneEnabled(isMicrophoneEnabled);
}
public void setControlsEnabled(boolean enabled) {
this.controls.setControlsEnabled(enabled);
}
public void setLocalVideoState(@NonNull CameraState cameraState) {
this.controls.setVideoAvailable(cameraState.getCameraCount() > 0);
this.controls.setVideoEnabled(cameraState.isEnabled());
this.controls.setCameraFlipAvailable(cameraState.getCameraCount() > 1);
this.controls.setCameraFlipClickable(cameraState.getActiveDirection() != CameraState.Direction.PENDING);
this.controls.setCameraFlipButtonEnabled(cameraState.getActiveDirection() == CameraState.Direction.BACK);
if (this.localRenderer != null) {
this.localRenderer.setMirror(cameraState.getActiveDirection() == CameraState.Direction.FRONT);
}
if (this.localRenderLayout.isHidden() == cameraState.isEnabled()) {
this.localRenderLayout.setHidden(!cameraState.isEnabled());
this.localRenderLayout.requestLayout();
}
}
public void setRemoteVideoEnabled(boolean enabled) {
if (enabled && this.remoteRenderLayout.isHidden()) {
this.photo.setVisibility(View.INVISIBLE);
setMinimized(true);
this.remoteRenderLayout.setHidden(false);
this.remoteRenderLayout.requestLayout();
if (localRenderLayout.isHidden()) this.controls.displayVideoTooltip(callHeader);
} else if (!enabled && !this.remoteRenderLayout.isHidden()){
setMinimized(false);
this.photo.setVisibility(View.VISIBLE);
this.remoteRenderLayout.setHidden(true);
this.remoteRenderLayout.requestLayout();
}
}
public boolean isVideoEnabled() {
return controls.isVideoEnabled();
}
private void initialize() {
LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.webrtc_call_screen, this, true);
this.elapsedTime = findViewById(R.id.elapsedTime);
this.photo = findViewById(R.id.photo);
this.localRenderLayout = findViewById(R.id.local_render_layout);
this.remoteRenderLayout = findViewById(R.id.remote_render_layout);
this.phoneNumber = findViewById(R.id.phoneNumber);
this.name = findViewById(R.id.name);
this.label = findViewById(R.id.label);
this.status = findViewById(R.id.callStateLabel);
this.controls = findViewById(R.id.inCallControls);
this.endCallButton = findViewById(R.id.hangup_fab);
this.incomingCallButton = findViewById(R.id.answer_decline_button);
this.untrustedIdentityContainer = findViewById(R.id.untrusted_layout);
this.untrustedIdentityExplanation = findViewById(R.id.untrusted_explanation);
this.acceptIdentityButton = findViewById(R.id.accept_safety_numbers);
this.cancelIdentityButton = findViewById(R.id.cancel_safety_numbers);
this.expandedInfo = findViewById(R.id.expanded_info);
this.callHeader = findViewById(R.id.call_info_1);
this.localRenderLayout.setHidden(true);
this.remoteRenderLayout.setHidden(true);
this.minimized = false;
this.remoteRenderLayout.setOnClickListener(v -> setMinimized(!minimized));
}
private void setConnected(SurfaceViewRenderer localRenderer,
SurfaceViewRenderer remoteRenderer)
{
if (localRenderLayout.getChildCount() == 0 && remoteRenderLayout.getChildCount() == 0) {
if (localRenderer.getParent() != null) {
((ViewGroup)localRenderer.getParent()).removeView(localRenderer);
}
if (remoteRenderer.getParent() != null) {
((ViewGroup)remoteRenderer.getParent()).removeView(remoteRenderer);
}
localRenderLayout.setPosition(7, 70, 25, 25);
localRenderLayout.setSquare(true);
remoteRenderLayout.setPosition(0, 0, 100, 100);
localRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
remoteRenderer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
localRenderer.setMirror(true);
localRenderer.setZOrderMediaOverlay(true);
localRenderLayout.addView(localRenderer);
remoteRenderLayout.addView(remoteRenderer);
this.localRenderer = localRenderer;
}
}
private void setPersonInfo(final @NonNull Recipient recipient) {
this.recipient = recipient;
this.recipient.addListener(this);
GlideApp.with(getContext().getApplicationContext())
.load(recipient.getContactPhoto(getContext()))
.fallback(recipient.getFallbackContactPhoto().asCallCard(getContext()))
.error(recipient.getFallbackContactPhoto().asCallCard(getContext()))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(this.photo);
this.name.setText(recipient.getName());
if (recipient.getName() == null && !TextUtils.isEmpty(recipient.getProfileName())) {
this.phoneNumber.setText(recipient.getAddress().serialize() + " (~" + recipient.getProfileName() + ")");
} else {
this.phoneNumber.setText(recipient.getAddress().serialize());
}
}
private void setCard(Recipient recipient, String status) {
setPersonInfo(recipient);
this.status.setText(status);
this.untrustedIdentityContainer.setVisibility(View.GONE);
}
private void setMinimized(boolean minimized) {
if (minimized) {
ViewCompat.animate(callHeader).translationY(-1 * expandedInfo.getHeight());
ViewCompat.animate(status).alpha(0);
ViewCompat.animate(endCallButton).translationY(endCallButton.getHeight() + ViewUtil.dpToPx(getContext(), 40));
ViewCompat.animate(endCallButton).alpha(0);
this.minimized = true;
} else {
ViewCompat.animate(callHeader).translationY(0);
ViewCompat.animate(status).alpha(1);
ViewCompat.animate(endCallButton).translationY(0);
ViewCompat.animate(endCallButton).alpha(1).withEndAction(() -> {
// Note: This is to work around an Android bug, see #6225
endCallButton.requestLayout();
});
this.minimized = false;
}
}
@Override
public void onModified(Recipient recipient) {
Util.runOnMain(() -> {
if (recipient == WebRtcCallScreen.this.recipient) {
setPersonInfo(recipient);
}
});
}
public interface HangupButtonListener {
void onClick();
}
}
@@ -1,23 +1,11 @@
/*
* Copyright (C) 2018 Delta Chat contributors
*
* 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.connect;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
@@ -38,11 +26,14 @@ import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.avatars.SystemContactPhoto;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientProvider;
import org.thoughtcrime.securesms.util.Hash;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import java.io.BufferedInputStream;
@@ -56,7 +47,6 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class ApplicationDcContext extends DcContext {
@IntDef({RECIPIENT_TYPE_CHAT, RECIPIENT_TYPE_CONTACT})
@@ -66,6 +56,7 @@ public class ApplicationDcContext extends DcContext {
public static final int RECIPIENT_TYPE_CONTACT = 1;
public Context context;
public volatile boolean isScreenOn = false;
public ApplicationDcContext(Context context) {
super("android-dev");
@@ -92,7 +83,28 @@ public class ApplicationDcContext extends DcContext {
}
new ForegroundDetector(ApplicationContext.getInstance(context));
startThreads();
startThreads(0);
TimerReceiver.scheduleNextAlarm(context);
BroadcastReceiver networkStateReceiver = new NetworkStateReceiver();
context.registerReceiver(networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION));
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
BroadcastReceiver screenReceiver = new ScreenReceiver();
context.registerReceiver(screenReceiver, filter);
try {
PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
isScreenOn = pm.isScreenOn();
} catch (Exception e) {
}
if( !isScreenOn ) {
context.startService(new Intent(context, KeepAliveService.class));
}
}
public File getImexDir()
@@ -176,13 +188,30 @@ public class ApplicationDcContext extends DcContext {
participants.add(getRecipient(RECIPIENT_TYPE_CONTACT, contactId));
}
RecipientProvider.RecipientDetails recipientDetails = new RecipientProvider.RecipientDetails(chat.getName(), null, false, null, participants);
return new Recipient(Address.fromChat(chat.getId()), recipientDetails);
Recipient recipient = new Recipient(Address.fromChat(chat.getId()), recipientDetails);
if (!chat.isGroup()) {
String identifier = Hash.sha256(chat.getName() + chat.getSubtitle());
Uri systemContactPhoto = TextSecurePreferences.getSystemContactPhoto(context, identifier);
if (systemContactPhoto != null) {
recipient.setSystemContactPhoto(systemContactPhoto);
}
}
return recipient;
}
@NonNull
public Recipient getRecipient(DcContact contact) {
RecipientProvider.RecipientDetails recipientDetails = new RecipientProvider.RecipientDetails(contact.getDisplayName(), null, false, null, null);
return new Recipient(Address.fromContact(contact.getId()), recipientDetails);
Recipient recipient = new Recipient(Address.fromContact(contact.getId()), recipientDetails);
String identifier = Hash.sha256(contact.getName() + contact.getAddr());
Uri systemContactPhoto = TextSecurePreferences.getSystemContactPhoto(context, identifier);
if (systemContactPhoto != null) {
recipient.setSystemContactPhoto(systemContactPhoto);
}
if (contact.getId() == DcContact.DC_CONTACT_ID_SELF) {
recipient.setProfileAvatar("SELF");
}
return recipient;
}
@NonNull
@@ -229,7 +258,8 @@ public class ApplicationDcContext extends DcContext {
public PowerManager.WakeLock afterForgroundWakeLock = null;
public void startThreads()
public final static int INTERRUPT_IDLE = 0x01; // interrupt idle if the thread is already running
public void startThreads(int flags)
{
synchronized(threadsCritical) {
@@ -239,33 +269,36 @@ public class ApplicationDcContext extends DcContext {
imapThreadStartedVal = false;
}
imapThread = new Thread(new Runnable() {
@Override
public void run() {
// raise the starting condition
// after acquiring a wakelock so that the process is not terminated.
// as imapWakeLock is not reference counted that would result in a wakelock-gap is not needed here.
imapThread = new Thread(() -> {
// raise the starting condition
// after acquiring a wakelock so that the process is not terminated.
// as imapWakeLock is not reference counted that would result in a wakelock-gap is not needed here.
imapWakeLock.acquire();
synchronized (imapThreadStartedCond) {
imapThreadStartedVal = true;
imapThreadStartedCond.notifyAll();
}
Log.i("DeltaChat", "###################### IMAP-Thread started. ######################");
while (true) {
imapWakeLock.acquire();
synchronized (imapThreadStartedCond) {
imapThreadStartedVal = true;
imapThreadStartedCond.notifyAll();
}
Log.i("DeltaChat", "###################### IMAP-Thread started. ######################");
while (true) {
imapWakeLock.acquire();
performJobs();
fetch();
imapWakeLock.release();
idle();
}
performImapJobs();
performImapFetch();
imapWakeLock.release();
performImapIdle();
}
}, "imapThread");
imapThread.setPriority(Thread.NORM_PRIORITY);
imapThread.start();
}
else
{
if ((flags&INTERRUPT_IDLE)!=0) {
interruptImapIdle();
}
}
if (smtpThread == null || !smtpThread.isAlive()) {
@@ -273,24 +306,21 @@ public class ApplicationDcContext extends DcContext {
smtpThreadStartedVal = false;
}
smtpThread = new Thread(new Runnable() {
@Override
public void run() {
smtpThread = new Thread(() -> {
smtpWakeLock.acquire();
synchronized (smtpThreadStartedCond) {
smtpThreadStartedVal = true;
smtpThreadStartedCond.notifyAll();
}
Log.i("DeltaChat", "###################### SMTP-Thread started. ######################");
while (true) {
smtpWakeLock.acquire();
synchronized (smtpThreadStartedCond) {
smtpThreadStartedVal = true;
smtpThreadStartedCond.notifyAll();
}
Log.i("DeltaChat", "###################### SMTP-Thread started. ######################");
while (true) {
smtpWakeLock.acquire();
performSmtpJobs();
smtpWakeLock.release();
performSmtpIdle();
}
performSmtpJobs();
smtpWakeLock.release();
performSmtpIdle();
}
}, "smtpThread");
smtpThread.setPriority(Thread.MAX_PRIORITY);
@@ -320,6 +350,23 @@ public class ApplicationDcContext extends DcContext {
}
/***********************************************************************************************
* Tools
**********************************************************************************************/
public boolean isNetworkConnected() {
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null && netInfo.isConnected()) {
return true;
}
} catch (Exception e) {
}
return false;
}
/***********************************************************************************************
* Event Handling
**********************************************************************************************/
@@ -355,6 +402,35 @@ public class ApplicationDcContext extends DcContext {
}
}
private void handleError(int event, boolean popUp, String string)
{
// log error
boolean showAsToast;
Log.e("DeltaChat", string);
synchronized (lastErrorLock) {
lastErrorString = string;
showAsToast = showNextErrorAsToast;
showNextErrorAsToast = true;
}
// show error to user
Util.runOnMain(() -> {
if (popUp && showAsToast) {
String toastString = string;
if(event==DC_EVENT_ERROR_NETWORK) {
if(!isNetworkConnected()) {
toastString = context.getString(R.string.error_no_network);
}
}
if (ForegroundDetector.getInstance().isForeground()) {
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show();
}
}
});
}
@Override
public long handleEvent(final int event, long data1, long data2) {
switch(event) {
@@ -367,23 +443,11 @@ public class ApplicationDcContext extends DcContext {
break;
case DC_EVENT_ERROR:
Log.e("DeltaChat", dataToString(data2));
synchronized (lastErrorLock) {
lastErrorString = dataToString(data2);
}
Util.runOnMain(new Runnable() {
@Override
public void run() {
synchronized (lastErrorLock) {
if (showNextErrorAsToast) {
if (ForegroundDetector.getInstance().isForeground()) {
Toast.makeText(context, lastErrorString, Toast.LENGTH_LONG).show();
}
}
showNextErrorAsToast = true;
}
}
});
handleError(event, true, dataToString(data2));
break;
case DC_EVENT_ERROR_NETWORK:
handleError(event, data1!=0, dataToString(data2));
break;
case DC_EVENT_HTTP_GET:
@@ -427,15 +491,9 @@ public class ApplicationDcContext extends DcContext {
{
final Object data1obj = data1IsString(event)? dataToString(data1) : data1;
final Object data2obj = data2IsString(event)? dataToString(data2) : data2;
Util.runOnMain(new Runnable() {
@Override
public void run() {
if(eventCenter!=null) {
eventCenter.sendToObservers(event, data1obj, data2obj);
}
}
});
if(eventCenter!=null) {
eventCenter.sendToObservers(event, data1obj, data2obj);
}
}
break;
}
@@ -1,25 +1,3 @@
/*******************************************************************************
*
* Delta Chat Android
* (C) 2017 Björn Petersen
* Contact: r10s@b44t.com, http://b44t.com
*
* 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.connect;
import android.content.ContentProvider;
@@ -31,7 +9,6 @@ import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileNotFoundException;
public class AttachmentsContentProvider extends ContentProvider {
/* We save all attachments in our private files-directory
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.connect;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.connect;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.connect;
import android.content.Context;
import android.support.annotation.NonNull;
import android.util.Log;
@@ -1,29 +1,13 @@
/*
* Copyright (C) 2018 Delta Chat contributors
*
* 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.connect;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Intent;
import android.os.Bundle;
import org.thoughtcrime.securesms.ApplicationContext;
@SuppressLint("NewApi")
public class ForegroundDetector implements Application.ActivityLifecycleCallbacks {
@@ -53,8 +37,10 @@ public class ForegroundDetector implements Application.ActivityLifecycleCallback
public void onActivityStarted(Activity activity) {
refs++;
//applicationContext.stopService(new Intent(applicationContext, KeepAliveService.class));
application.dcContext.startThreads(); // we call this without checking getPermanentPush() to have a simple guarantee that push is always active when the app is in foregroud (startIdleThread makes sure the thread is not started twice)
activity.stopService(new Intent(activity, KeepAliveService.class));
// probably no need to interrupt idle as this is done in the TimerReceiver every 5 minutes
application.dcContext.startThreads(0);
}
@@ -1,4 +0,0 @@
package org.thoughtcrime.securesms;
public class IncomingMessageNotifier {
}
@@ -0,0 +1,87 @@
package org.thoughtcrime.securesms.connect;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import org.thoughtcrime.securesms.ConversationListActivity;
import org.thoughtcrime.securesms.R;
import java.util.Locale;
public class KeepAliveService extends Service {
static KeepAliveService s_this = null;
@Override
public void onCreate() {
Log.i("DeltaChat", "*** KeepAliveService.onCreate()");
// there's nothing more to do here as all initialisation stuff is already done in
// ApplicationLoader.onCreate() which is called before this broadcast is sended.
s_this = this;
setSelfAsForeground();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// START_STICKY ensured, the service is recreated as soon it is terminated for any reasons.
// as ApplicationLoader.onCreate() is called before a service starts, there is no more to do here,
// the app is just running fine.
Log.i("DeltaChat", "*** KeepAliveService.onStartCommand()");
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void onDestroy() {
Log.i("DeltaChat", "*** KeepAliveService.onDestroy()");
// the service will be restarted due to START_STICKY automatically, there's nothing more to do.
}
private void setSelfAsForeground() {
stopForeground(true);
startForeground(FG_NOTIFICATION_ID, createNotification()); // TODO: if we target Android O, we should use startServiceInForeground()
}
static public KeepAliveService getInstance()
{
return s_this; // may be null
}
/* The notification
* A notification is required for a foreground service; and without a foreground service,
* Delta Chat won't get new messages reliable
**********************************************************************************************/
public static final int FG_NOTIFICATION_ID = 4142;
private Notification createNotification()
{
ApplicationDcContext dcContext = DcHelper.getContext(this);
Intent intent = new Intent(this, ConversationListActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// a notification _must_ contain a small icon, a title and a text, see https://developer.android.com/guide/topics/ui/notifiers/notifications.html#Required
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentTitle(getString(R.string.app_name));
builder.setContentText(getString(R.string.MessageRetrievalService_background_connection_enabled));
if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ) {
builder.setPriority(NotificationCompat.PRIORITY_MIN);
}
builder.setWhen(0);
builder.setContentIntent(contentIntent);
builder.setSmallIcon(R.drawable.icon_notification);
return builder.build();
}
}
@@ -0,0 +1,34 @@
package org.thoughtcrime.securesms.connect;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
public class NetworkStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = manager.getActiveNetworkInfo();
if (ni != null && ni.getState() == NetworkInfo.State.CONNECTED) {
Log.i("DeltaChat", "++++++++++++++++++ Connected ++++++++++++++++++");
ApplicationDcContext dcContext = DcHelper.getContext(context);
dcContext.startThreads(0); // no need to interrupt idle as maybeNetwork() is called below
dcContext.waitForThreadsRunning();
dcContext.maybeNetwork();
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,26 @@
package org.thoughtcrime.securesms.connect;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ApplicationDcContext dcContext = DcHelper.getContext(context);
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
Log.i("DeltaChat", "-------------------- Screen off --------------------");
dcContext.isScreenOn = false;
context.startService(new Intent(context, KeepAliveService.class));
}
else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
Log.i("DeltaChat", "-------------------- Screen on --------------------");
dcContext.isScreenOn = true;
context.stopService(new Intent(context, KeepAliveService.class));
}
}
}
@@ -0,0 +1,56 @@
// restart the smtp and imap threads every 5 minutes if they were killed by the os.
// however, we try to prevent the killing by the permanent notification KeepAliveService.
package org.thoughtcrime.securesms.connect;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
public class TimerReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Log.i("DeltaChat", "-------------------- on receive timer --------------------");
scheduleNextAlarm(context);
ApplicationDcContext dcContext = DcHelper.getContext(context);
// start threads (if not up) and interrupt idle in case it is stalled for any reason
// (re-doing IMAP-IDLE should imply a NOOP call then, https://www.isode.com/whitepapers/imap-idle.html )
dcContext.startThreads(ApplicationDcContext.INTERRUPT_IDLE);
dcContext.waitForThreadsRunning();
}
public static void scheduleNextAlarm(Context context)
{
try {
Intent intent = new Intent(context, TimerReceiver.class);
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
long triggerAtMillis = System.currentTimeMillis() + 5 * 60 * 1000;
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Activity.ALARM_SERVICE);
if( Build.VERSION.SDK_INT >= 23 ) {
// a simple AlarmManager.set() is no longer send in the new DOZE mode
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, alarmIntent);
}
else {
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, alarmIntent);
}
}
catch(Exception e) {
Log.e("DeltaChat", "Cannot create alarm.", e);
}
}
}
@@ -33,6 +33,8 @@ import android.text.TextUtils;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.util.Hash;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.Collection;
@@ -61,6 +63,8 @@ public class ContactAccessor {
private static final int CONTACT_CURSOR_MAIL = 1;
private static final int CONTACT_CURSOR_CONTACT_ID = 2;
public static final String PUSH_COLUMN = "push";
private static final ContactAccessor instance = new ContactAccessor();
@@ -84,16 +88,24 @@ public class ContactAccessor {
}
public Cursor getAllSystemContacts(Context context) {
return context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[] {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.ADDRESS}, null, null, null);
String[] projection = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.ADDRESS, ContactsContract.Data.CONTACT_ID};
return context.getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, null, null, null);
}
public String getAllSystemContactsAsString(Context context) {
Cursor systemContactsCursor = getAllSystemContacts(context);
StringBuilder result = new StringBuilder();
List<String> mailList = new ArrayList<>();
Set<String> contactPhotoIdentifiers = new HashSet<>();
while (systemContactsCursor != null && systemContactsCursor.moveToNext()) {
String name = systemContactsCursor.getString(CONTACT_CURSOR_NAME);
String mail= systemContactsCursor.getString(CONTACT_CURSOR_MAIL);
String mail = systemContactsCursor.getString(CONTACT_CURSOR_MAIL);
String contactId = systemContactsCursor.getString(CONTACT_CURSOR_CONTACT_ID);
if (contactId != null) {
String identifier = name + mail;
String hashedIdentifierAndId = Hash.sha256(identifier) + "|" + contactId;
contactPhotoIdentifiers.add(hashedIdentifierAndId);
}
if (mail != null && !mail.isEmpty() && !mailList.contains(mail)) {
mailList.add(mail);
if (name.isEmpty()) {
@@ -102,6 +114,7 @@ public class ContactAccessor {
result.append(name).append("\n").append(mail).append("\n");
}
}
TextSecurePreferences.setSystemContactPhotos(context, contactPhotoIdentifiers);
return result.toString();
}
@@ -1,28 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import java.util.List;
public abstract class ContactIdentityManager {
public static ContactIdentityManager getInstance(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH)
return new ContactIdentityManagerICS(context);
else
return new ContactIdentityManagerGingerbread(context);
}
protected final Context context;
public ContactIdentityManager(Context context) {
this.context = context.getApplicationContext();
}
public abstract Uri getSelfIdentityUri();
public abstract boolean isSelfIdentityAutoDetected();
public abstract List<Long> getSelfIdentityRawContactIds();
}
@@ -1,134 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
import android.telephony.TelephonyManager;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.util.ArrayList;
import java.util.List;
class ContactIdentityManagerGingerbread extends ContactIdentityManager {
public ContactIdentityManagerGingerbread(Context context) {
super(context);
}
@Override
public Uri getSelfIdentityUri() {
String contactUriString = TextSecurePreferences.getIdentityContactUri(context);
if (hasLocalNumber()) return getContactUriForNumber(getLocalNumber());
else if (contactUriString != null) return Uri.parse(contactUriString);
return null;
}
@Override
public boolean isSelfIdentityAutoDetected() {
return hasLocalNumber() && getContactUriForNumber(getLocalNumber()) != null;
}
@Override
public List<Long> getSelfIdentityRawContactIds() {
long selfIdentityContactId = getSelfIdentityContactId();
if (selfIdentityContactId == -1)
return null;
Cursor cursor = null;
ArrayList<Long> rawContactIds = new ArrayList<Long>();
try {
cursor = context.getContentResolver().query(RawContacts.CONTENT_URI,
new String[] {RawContacts._ID},
RawContacts.CONTACT_ID + " = ?",
new String[] {selfIdentityContactId+""},
null);
if (cursor == null || cursor.getCount() == 0)
return null;
while (cursor.moveToNext()) {
rawContactIds.add(Long.valueOf(cursor.getLong(0)));
}
return rawContactIds;
} finally {
if (cursor != null)
cursor.close();
}
}
private Uri getContactUriForNumber(String number) {
String[] PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID,
};
Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
private long getSelfIdentityContactId() {
Uri contactUri = getSelfIdentityUri();
if (contactUri == null)
return -1;
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(contactUri,
new String[] {ContactsContract.Contacts._ID},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return cursor.getLong(0);
} else {
return -1;
}
} finally {
if (cursor != null)
cursor.close();
}
}
private String getLocalNumber() {
return ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE))
.getLine1Number();
}
private boolean hasLocalNumber() {
String number = getLocalNumber();
return (number != null) && (number.trim().length() > 0);
}
}
@@ -1,79 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.PhoneLookup;
import java.util.LinkedList;
import java.util.List;
class ContactIdentityManagerICS extends ContactIdentityManager {
public ContactIdentityManagerICS(Context context) {
super(context);
}
@SuppressLint("NewApi")
@Override
public Uri getSelfIdentityUri() {
String[] PROJECTION = new String[] {
PhoneLookup.DISPLAY_NAME,
PhoneLookup.LOOKUP_KEY,
PhoneLookup._ID,
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_URI,
PROJECTION, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
return Contacts.getLookupUri(cursor.getLong(2), cursor.getString(1));
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
@Override
public boolean isSelfIdentityAutoDetected() {
return true;
}
@SuppressLint("NewApi")
@Override
public List<Long> getSelfIdentityRawContactIds() {
List<Long> results = new LinkedList<Long>();
String[] PROJECTION = new String[] {
ContactsContract.Profile._ID
};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(ContactsContract.Profile.CONTENT_RAW_CONTACTS_URI,
PROJECTION, null, null, null);
if (cursor == null || cursor.getCount() == 0)
return null;
while (cursor.moveToNext()) {
results.add(cursor.getLong(0));
}
return results;
} finally {
if (cursor != null)
cursor.close();
}
}
}
@@ -1,49 +0,0 @@
package org.thoughtcrime.securesms.contacts;
import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.util.Log;
import org.thoughtcrime.securesms.util.DirectoryHelper;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import java.io.IOException;
public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = ContactsSyncAdapter.class.getSimpleName();
public ContactsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult)
{
Log.w(TAG, "onPerformSync(" + authority +")");
if (TextSecurePreferences.isPushRegistered(getContext())) {
try {
DirectoryHelper.refreshDirectory(getContext(), true);
} catch (IOException e) {
Log.w(TAG, e);
}
}
}
@Override
public void onSyncCanceled() {
Log.w(TAG, "onSyncCanceled()");
}
@Override
public void onSyncCanceled(Thread thread) {
Log.w(TAG, "onSyncCanceled(" + thread + ")");
}
}
@@ -1,35 +0,0 @@
/**
* 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.contacts;
/**
* Name and number tuple.
*
* @author Moxie Marlinspike
*
*/
public class NameAndNumber {
public String name;
public String number;
public NameAndNumber(String name, String number) {
this.name = name;
this.number = number;
}
public NameAndNumber() {}
}
@@ -1,416 +0,0 @@
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import android.support.v7.widget.AppCompatMultiAutoCompleteTextView;
import android.telephony.PhoneNumberUtils;
import android.text.Annotation;
import android.text.Editable;
import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.widget.MultiAutoCompleteTextView;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientsFormatter;
import java.util.ArrayList;
import java.util.List;
/**
* Provide UI for editing the recipients of multi-media messages.
*/
public class RecipientsEditor extends AppCompatMultiAutoCompleteTextView {
private int mLongPressedPosition = -1;
private final RecipientsEditorTokenizer mTokenizer;
private char mLastSeparator = ',';
private Context mContext;
public RecipientsEditor(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mTokenizer = new RecipientsEditorTokenizer(context, this);
setTokenizer(mTokenizer);
// For the focus to move to the message body when soft Next is pressed
setImeOptions(EditorInfo.IME_ACTION_NEXT);
/*
* The point of this TextWatcher is that when the user chooses
* an address completion from the AutoCompleteTextView menu, it
* is marked up with Annotation objects to tie it back to the
* address book entry that it came from. If the user then goes
* back and edits that part of the text, it no longer corresponds
* to that address book entry and needs to have the Annotations
* claiming that it does removed.
*/
addTextChangedListener(new TextWatcher() {
private Annotation[] mAffected;
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
mAffected = ((Spanned) s).getSpans(start, start + count,
Annotation.class);
}
public void onTextChanged(CharSequence s, int start,
int before, int after) {
if (before == 0 && after == 1) { // inserting a character
char c = s.charAt(start);
if (c == ',' || c == ';') {
// Remember the delimiter the user typed to end this recipient. We'll
// need it shortly in terminateToken().
mLastSeparator = c;
}
}
}
public void afterTextChanged(Editable s) {
if (mAffected != null) {
for (Annotation a : mAffected) {
s.removeSpan(a);
}
}
mAffected = null;
}
});
}
@Override
public boolean enoughToFilter() {
if (!super.enoughToFilter()) {
return false;
}
// If the user is in the middle of editing an existing recipient, don't offer the
// auto-complete menu. Without this, when the user selects an auto-complete menu item,
// it will get added to the list of recipients so we end up with the old before-editing
// recipient and the new post-editing recipient. As a precedent, gmail does not show
// the auto-complete menu when editing an existing recipient.
int end = getSelectionEnd();
int len = getText().length();
return end == len;
}
public int getRecipientCount() {
return mTokenizer.getNumbers().size();
}
public List<String> getNumbers() {
return mTokenizer.getNumbers();
}
// public Recipients constructContactsFromInput() {
// return RecipientFactory.getRecipientsFromString(mContext, mTokenizer.getRawString(), false);
// }
private boolean isValidAddress(String number, boolean isMms) {
/*if (isMms) {
return MessageUtils.isValidMmsAddress(number);
} else {*/
// TODO: PhoneNumberUtils.isWellFormedSmsAddress() only check if the number is a valid
// GSM SMS address. If the address contains a dialable char, it considers it a well
// formed SMS addr. CDMA doesn't work that way and has a different parser for SMS
// address (see CdmaSmsAddress.parse(String address)). We should definitely fix this!!!
return PhoneNumberUtils.isWellFormedSmsAddress(number);
}
public boolean hasValidRecipient(boolean isMms) {
for (String number : mTokenizer.getNumbers()) {
if (isValidAddress(number, isMms))
return true;
}
return false;
}
/*public boolean hasInvalidRecipient(boolean isMms) {
for (String number : mTokenizer.getNumbers()) {
if (!isValidAddress(number, isMms)) {
/* TODO if (MmsConfig.getEmailGateway() == null) {
return true;
} else if (!MessageUtils.isAlias(number)) {
return true;
}
}
}
return false;
}*/
public String formatInvalidNumbers(boolean isMms) {
StringBuilder sb = new StringBuilder();
for (String number : mTokenizer.getNumbers()) {
if (!isValidAddress(number, isMms)) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(number);
}
}
return sb.toString();
}
/*public boolean containsEmail() {
if (TextUtils.indexOf(getText(), '@') == -1)
return false;
List<String> numbers = mTokenizer.getNumbers();
for (String number : numbers) {
if (Mms.isEmailAddress(number))
return true;
}
return false;
}*/
public static CharSequence contactToToken(Recipient c) {
String name = c.getName();
String number = c.getAddress().serialize();
SpannableString s = new SpannableString(RecipientsFormatter.formatNameAndNumber(name, number));
int len = s.length();
if (len == 0) {
return s;
}
s.setSpan(new Annotation("number", c.getAddress().serialize()), 0, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return s;
}
public void populate(List<Recipient> list) {
SpannableStringBuilder sb = new SpannableStringBuilder();
for (Recipient c : list) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(contactToToken(c));
}
setText(sb);
}
private int pointToPosition(int x, int y) {
x -= getCompoundPaddingLeft();
y -= getExtendedPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
if (layout == null) {
return -1;
}
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
return off;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
if (action == MotionEvent.ACTION_DOWN) {
mLongPressedPosition = pointToPosition(x, y);
}
return super.onTouchEvent(ev);
}
private static String getNumberAt(Spanned sp, int start, int end, Context context) {
return getFieldAt("number", sp, start, end, context);
}
private static int getSpanLength(Spanned sp, int start, int end, Context context) {
// TODO: there's a situation where the span can lose its annotations:
// - add an auto-complete contact
// - add another auto-complete contact
// - delete that second contact and keep deleting into the first
// - we lose the annotation and can no longer get the span.
// Need to fix this case because it breaks auto-complete contacts with commas in the name.
Annotation[] a = sp.getSpans(start, end, Annotation.class);
if (a.length > 0) {
return sp.getSpanEnd(a[0]);
}
return 0;
}
private static String getFieldAt(String field, Spanned sp, int start, int end,
Context context) {
Annotation[] a = sp.getSpans(start, end, Annotation.class);
String fieldValue = getAnnotation(a, field);
if (TextUtils.isEmpty(fieldValue)) {
fieldValue = TextUtils.substring(sp, start, end);
}
return fieldValue;
}
private static String getAnnotation(Annotation[] a, String key) {
for (int i = 0; i < a.length; i++) {
if (a[i].getKey().equals(key)) {
return a[i].getValue();
}
}
return "";
}
private class RecipientsEditorTokenizer
implements MultiAutoCompleteTextView.Tokenizer {
private final MultiAutoCompleteTextView mList;
private final Context mContext;
RecipientsEditorTokenizer(Context context, MultiAutoCompleteTextView list) {
mList = list;
mContext = context;
}
/**
* Returns the start of the token that ends at offset
* <code>cursor</code> within <code>text</code>.
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
char c;
while (i > 0 && (c = text.charAt(i - 1)) != ',' && c != ';') {
i--;
}
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
return i;
}
/**
* Returns the end of the token (minus trailing punctuation)
* that begins at offset <code>cursor</code> within <code>text</code>.
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
char c;
while (i < len) {
if ((c = text.charAt(i)) == ',' || c == ';') {
return i;
} else {
i++;
}
}
return len;
}
/**
* Returns <code>text</code>, modified, if necessary, to ensure that
* it ends with a token terminator (for example a space or comma).
* It is a method from the MultiAutoCompleteTextView.Tokenizer interface.
*/
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
char c;
if (i > 0 && ((c = text.charAt(i - 1)) == ',' || c == ';')) {
return text;
} else {
// Use the same delimiter the user just typed.
// This lets them have a mixture of commas and semicolons in their list.
String separator = mLastSeparator + " ";
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + separator);
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text + separator;
}
}
}
public String getRawString() {
return mList.getText().toString();
}
public List<String> getNumbers() {
Spanned sp = mList.getText();
int len = sp.length();
List<String> list = new ArrayList<String>();
int start = 0;
int i = 0;
while (i < len + 1) {
char c;
if ((i == len) || ((c = sp.charAt(i)) == ',') || (c == ';')) {
if (i > start) {
list.add(getNumberAt(sp, start, i, mContext));
// calculate the recipients total length. This is so if the name contains
// commas or semis, we'll skip over the whole name to the next
// recipient, rather than parsing this single name into multiple
// recipients.
int spanLen = getSpanLength(sp, start, i, mContext);
if (spanLen > i) {
i = spanLen;
}
}
i++;
while ((i < len) && (sp.charAt(i) == ' ')) {
i++;
}
start = i;
} else {
i++;
}
}
return list;
}
}
static class RecipientContextMenuInfo implements ContextMenuInfo {
final Recipient recipient;
RecipientContextMenuInfo(Recipient r) {
recipient = r;
}
}
}
@@ -1,67 +1,30 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
public class GroupRecordContactPhoto extends LocalFileContactPhoto {
public class GroupRecordContactPhoto implements ContactPhoto {
public GroupRecordContactPhoto(Context context, Address address) {
super(context, address);
}
private final int chatId;
@Override
public boolean isProfilePhoto() {
return false;
}
private final Address address;
@Override
int getId() {
return address.getDcChatId();
}
private final String path;
public GroupRecordContactPhoto(Context context, Address address) {
this.address = address;
chatId = address.getDcChatId();
path = DcHelper.getContext(context).getChat(chatId).getProfileImage();
}
@Override
public InputStream openInputStream(Context context) throws IOException {
return new FileInputStream(path);
}
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return null;
}
@Override
public boolean isProfilePhoto() {
return false;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes());
messageDigest.update(Conversions.longToByteArray(chatId));
messageDigest.update(path.getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof GroupRecordContactPhoto)) return false;
GroupRecordContactPhoto that = (GroupRecordContactPhoto)other;
return this.address.equals(that.address) && this.chatId == that.chatId && this.path.equals(that.path);
}
@Override
public int hashCode() {
return this.address.hashCode() ^ chatId;
}
@Override
String getPath(Context context) {
String profileImage = DcHelper.getContext(context).getChat(getId()).getProfileImage();
return profileImage != null ? profileImage : "";
}
}
@@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
public abstract class LocalFileContactPhoto implements ContactPhoto {
final Address address;
private final int id;
private final String path;
LocalFileContactPhoto(Context context, Address address) {
this.address = address;
id = getId();
path = getPath(context);
}
@Override
public InputStream openInputStream(Context context) throws IOException {
return new FileInputStream(path);
}
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return isProfilePhoto() ? Uri.fromFile(AvatarHelper.getSelfAvatarFile(context, address)) : null;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(address.serialize().getBytes());
messageDigest.update(Conversions.longToByteArray(id));
messageDigest.update(path.getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof LocalFileContactPhoto)) return false;
LocalFileContactPhoto that = (LocalFileContactPhoto) other;
return this.address.equals(that.address) && this.id == that.id && this.path.equals(that.path);
}
@Override
public int hashCode() {
return this.address.hashCode() ^ id;
}
abstract int getId();
abstract String getPath(Context context);
}
@@ -0,0 +1,60 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
public class MyProfileContactPhoto implements ContactPhoto {
private final @NonNull String address;
private final @NonNull String avatarObject;
public MyProfileContactPhoto(@NonNull String address, @NonNull String avatarObject) {
this.address = address;
this.avatarObject = avatarObject;
}
@Override
public InputStream openInputStream(Context context) throws IOException {
return AvatarHelper.getInputStreamFor(context, address);
}
@Override
public @Nullable
Uri getUri(@NonNull Context context) {
return Uri.fromFile(AvatarHelper.getSelfAvatarFile(context, address));
}
@Override
public boolean isProfilePhoto() {
return true;
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
messageDigest.update(address.getBytes());
messageDigest.update(avatarObject.getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof MyProfileContactPhoto)) return false;
MyProfileContactPhoto that = (MyProfileContactPhoto) other;
return this.address.equals(that.address) && this.avatarObject.equals(that.avatarObject);
}
@Override
public int hashCode() {
return address.hashCode() ^ avatarObject.hashCode();
}
}
@@ -1,59 +1,30 @@
package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.database.Address;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
public class ProfileContactPhoto extends LocalFileContactPhoto {
public class ProfileContactPhoto implements ContactPhoto {
public ProfileContactPhoto(Context context, Address address) {
super(context, address);
}
private final @NonNull String address;
private final @NonNull String avatarObject;
@Override
public boolean isProfilePhoto() {
return true;
}
public ProfileContactPhoto(@NonNull String address, @NonNull String avatarObject) {
this.address = address;
this.avatarObject = avatarObject;
}
@Override
int getId() {
return address.getDcContactId();
}
@Override
public InputStream openInputStream(Context context) throws IOException {
return AvatarHelper.getInputStreamFor(context, address);
}
@Override
String getPath(Context context) {
String profileImage = DcHelper.getContext(context).getContact(getId()).getProfileImage();
return profileImage != null ? profileImage : "";
}
@Override
public @Nullable Uri getUri(@NonNull Context context) {
return Uri.fromFile(AvatarHelper.getAvatarFile(context, address));
}
@Override
public boolean isProfilePhoto() {
return true;
}
@Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update(address.getBytes());
messageDigest.update(avatarObject.getBytes());
}
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof ProfileContactPhoto)) return false;
ProfileContactPhoto that = (ProfileContactPhoto)other;
return this.address.equals(that.address) && this.avatarObject.equals(that.avatarObject);
}
@Override
public int hashCode() {
return address.hashCode() ^ avatarObject.hashCode();
}
}
@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.contacts.avatars;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.util.Conversions;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.MessageDigest;
@@ -26,8 +26,8 @@ public class SystemContactPhoto implements ContactPhoto {
}
@Override
public InputStream openInputStream(Context context) throws FileNotFoundException {
return context.getContentResolver().openInputStream(contactPhotoUri);
public InputStream openInputStream(Context context) {
return ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), contactPhotoUri, false);
}
@Nullable
@@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.RecipientDatabase;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -251,9 +250,9 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActionBarAct
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CODE_ADD_EDIT_CONTACT && contact != null) {
ApplicationContext.getInstance(getApplicationContext())
.getJobManager()
.add(new DirectoryRefreshJob(getApplicationContext(), false));
// ApplicationContext.getInstance(getApplicationContext())
// .getJobManager()
// .add(new DirectoryRefreshJob(getApplicationContext(), false));
}
}
}
@@ -1,92 +0,0 @@
/*
* Copyright (C) 2013-2018 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.crypto;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.PreKeyStore;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import org.whispersystems.libsignal.util.Medium;
import java.util.LinkedList;
import java.util.List;
public class PreKeyUtil {
@SuppressWarnings("unused")
private static final String TAG = PreKeyUtil.class.getName();
private static final int BATCH_SIZE = 100;
public synchronized static List<PreKeyRecord> generatePreKeys(Context context) {
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
List<PreKeyRecord> records = new LinkedList<>();
int preKeyIdOffset = TextSecurePreferences.getNextPreKeyId(context);
for (int i=0;i<BATCH_SIZE;i++) {
int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE;
ECKeyPair keyPair = Curve.generateKeyPair();
PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair);
preKeyStore.storePreKey(preKeyId, record);
records.add(record);
}
TextSecurePreferences.setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE);
return records;
}
public synchronized static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair, boolean active) {
try {
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
int signedPreKeyId = TextSecurePreferences.getNextSignedPreKeyId(context);
ECKeyPair keyPair = Curve.generateKeyPair();
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature);
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
TextSecurePreferences.setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE);
if (active) {
TextSecurePreferences.setActiveSignedPreKeyId(context, signedPreKeyId);
}
return record;
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
public static synchronized void setActiveSignedPreKeyId(Context context, int id) {
TextSecurePreferences.setActiveSignedPreKeyId(context, id);
}
public static synchronized int getActiveSignedPreKeyId(Context context) {
return TextSecurePreferences.getActiveSignedPreKeyId(context);
}
}
@@ -1,128 +0,0 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.state.IdentityKeyStore;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.PreKeyStore;
import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SessionStore;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import java.util.List;
public class SignalProtocolStoreImpl implements SignalProtocolStore {
private final PreKeyStore preKeyStore;
private final SignedPreKeyStore signedPreKeyStore;
private final IdentityKeyStore identityKeyStore;
private final SessionStore sessionStore;
public SignalProtocolStoreImpl(Context context) {
this.preKeyStore = new TextSecurePreKeyStore(context);
this.signedPreKeyStore = new TextSecurePreKeyStore(context);
this.identityKeyStore = new TextSecureIdentityKeyStore(context);
this.sessionStore = new TextSecureSessionStore(context);
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return identityKeyStore.getIdentityKeyPair();
}
@Override
public int getLocalRegistrationId() {
return identityKeyStore.getLocalRegistrationId();
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return identityKeyStore.saveIdentity(address, identityKey);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
return identityKeyStore.isTrustedIdentity(address, identityKey, direction);
}
@Override
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
return preKeyStore.loadPreKey(preKeyId);
}
@Override
public void storePreKey(int preKeyId, PreKeyRecord record) {
preKeyStore.storePreKey(preKeyId, record);
}
@Override
public boolean containsPreKey(int preKeyId) {
return preKeyStore.containsPreKey(preKeyId);
}
@Override
public void removePreKey(int preKeyId) {
preKeyStore.removePreKey(preKeyId);
}
@Override
public SessionRecord loadSession(SignalProtocolAddress axolotlAddress) {
return sessionStore.loadSession(axolotlAddress);
}
@Override
public List<Integer> getSubDeviceSessions(String number) {
return sessionStore.getSubDeviceSessions(number);
}
@Override
public void storeSession(SignalProtocolAddress axolotlAddress, SessionRecord record) {
sessionStore.storeSession(axolotlAddress, record);
}
@Override
public boolean containsSession(SignalProtocolAddress axolotlAddress) {
return sessionStore.containsSession(axolotlAddress);
}
@Override
public void deleteSession(SignalProtocolAddress axolotlAddress) {
sessionStore.deleteSession(axolotlAddress);
}
@Override
public void deleteAllSessions(String number) {
sessionStore.deleteAllSessions(number);
}
@Override
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
return signedPreKeyStore.loadSignedPreKey(signedPreKeyId);
}
@Override
public List<SignedPreKeyRecord> loadSignedPreKeys() {
return signedPreKeyStore.loadSignedPreKeys();
}
@Override
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record);
}
@Override
public boolean containsSignedPreKey(int signedPreKeyId) {
return signedPreKeyStore.containsSignedPreKey(signedPreKeyId);
}
@Override
public void removeSignedPreKey(int signedPreKeyId) {
signedPreKeyStore.removeSignedPreKey(signedPreKeyId);
}
}
@@ -1,140 +0,0 @@
package org.thoughtcrime.securesms.crypto.storage;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.crypto.SessionUtil;
import org.thoughtcrime.securesms.database.Address;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.state.IdentityKeyStore;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.concurrent.TimeUnit;
public class TextSecureIdentityKeyStore implements IdentityKeyStore {
private static final int TIMESTAMP_THRESHOLD_SECONDS = 5;
private static final String TAG = TextSecureIdentityKeyStore.class.getSimpleName();
private static final Object LOCK = new Object();
private final Context context;
public TextSecureIdentityKeyStore(Context context) {
this.context = context;
}
@Override
public IdentityKeyPair getIdentityKeyPair() {
return IdentityKeyUtil.getIdentityKeyPair(context);
}
@Override
public int getLocalRegistrationId() {
return TextSecurePreferences.getLocalRegistrationId(context);
}
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
Address signalAddress = Address.fromExternal(context, address.getName());
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(signalAddress);
if (!identityRecord.isPresent()) {
Log.w(TAG, "Saving new identity...");
identityDatabase.saveIdentity(signalAddress, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
return false;
}
if (!identityRecord.get().getIdentityKey().equals(identityKey)) {
Log.w(TAG, "Replacing existing identity...");
VerifiedStatus verifiedStatus;
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED ||
identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED)
{
verifiedStatus = VerifiedStatus.UNVERIFIED;
} else {
verifiedStatus = VerifiedStatus.DEFAULT;
}
identityDatabase.saveIdentity(signalAddress, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
IdentityUtil.markIdentityUpdate(context, Recipient.from(context, signalAddress, true));
SessionUtil.archiveSiblingSessions(context, address);
return true;
}
if (isNonBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Setting approval status...");
identityDatabase.setApproval(signalAddress, nonBlockingApproval);
return false;
}
return false;
}
}
@Override
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
return saveIdentity(address, identityKey, false);
}
@Override
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
synchronized (LOCK) {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
String ourNumber = TextSecurePreferences.getLocalNumber(context);
Address theirAddress = Address.fromExternal(context, address.getName());
if (ourNumber.equals(address.getName()) || Address.fromSerialized(ourNumber).equals(theirAddress)) {
return identityKey.equals(IdentityKeyUtil.getIdentityKey(context));
}
switch (direction) {
case SENDING: return isTrustedForSending(identityKey, identityDatabase.getIdentity(theirAddress));
case RECEIVING: return true;
default: throw new AssertionError("Unknown direction: " + direction);
}
}
}
private boolean isTrustedForSending(IdentityKey identityKey, Optional<IdentityRecord> identityRecord) {
if (!identityRecord.isPresent()) {
Log.w(TAG, "Nothing here, returning true...");
return true;
}
if (!identityKey.equals(identityRecord.get().getIdentityKey())) {
Log.w(TAG, "Identity keys don't match...");
return false;
}
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
Log.w(TAG, "Needs unverified approval!");
return false;
}
if (isNonBlockingApprovalRequired(identityRecord.get())) {
Log.w(TAG, "Needs non-blocking approval!");
return false;
}
return true;
}
private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
return !identityRecord.isFirstUse() &&
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
!identityRecord.isApprovedNonBlocking();
}
}
@@ -117,7 +117,7 @@ public class Address implements Parcelable, Comparable<Address> {
return formatter;
} else {
return new ExternalAddressFormatter(Util.getSimCountryIso(context).or("US"), true);
return new ExternalAddressFormatter("US", true);
}
}
@@ -178,7 +178,6 @@ public class GroupDatabase extends Database {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
recipient.setName(title);
recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null);
recipient.setParticipants(Stream.of(members).map(memberAddress -> Recipient.from(context, memberAddress, true)).toList());
});
@@ -202,7 +201,6 @@ public class GroupDatabase extends Database {
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> {
recipient.setName(title);
recipient.setGroupAvatarId(avatar != null ? avatar.getId() : null);
});
notifyConversationListListeners();
@@ -236,7 +234,6 @@ public class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?",
new String[] {groupId});
Recipient.applyCached(Address.fromSerialized(groupId), recipient -> recipient.setGroupAvatarId(avatarId == 0 ? null : avatarId));
}
public void updateMembers(String groupId, List<Address> members) {
@@ -24,7 +24,6 @@ import android.support.annotation.Nullable;
import net.sqlcipher.database.SQLiteDatabase;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.libsignal.IdentityKey;
@@ -123,9 +122,6 @@ public class IdentityDatabase extends Database {
contentValues.put(FIRST_USE, firstUse ? 1 : 0);
database.replace(TABLE_NAME, null, contentValues);
EventBus.getDefault().post(new IdentityRecord(address, identityKey, verifiedStatus,
firstUse, timestamp, nonBlockingApproval));
}
public void setApproval(Address address, boolean nonBlockingApproval) {
@@ -148,7 +144,6 @@ public class IdentityDatabase extends Database {
if (updated > 0) {
Optional<IdentityRecord> record = getIdentity(address);
if (record.isPresent()) EventBus.getDefault().post(record.get());
}
}
@@ -19,23 +19,18 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.annimon.stream.Stream;
import com.google.android.mms.pdu_alt.NotificationInd;
import com.google.android.mms.pdu_alt.PduHeaders;
import net.sqlcipher.database.SQLiteDatabase;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.attachments.AttachmentId;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
@@ -50,25 +45,14 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.Quote;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
import org.thoughtcrime.securesms.mms.QuoteModel;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.Closeable;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -169,11 +153,8 @@ public class MmsDatabase extends MessagingDatabase {
private final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache();
private final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache();
private final JobManager jobManager;
public MmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
@Override
@@ -198,22 +179,6 @@ public class MmsDatabase extends MessagingDatabase {
return 0;
}
public void addFailures(long messageId, List<NetworkFailure> failure) {
try {
addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList.class);
} catch (IOException e) {
Log.w(TAG, e);
}
}
public void removeFailure(long messageId, NetworkFailure failure) {
try {
removeFromDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList.class);
} catch (IOException e) {
Log.w(TAG, e);
}
}
public void incrementReceiptCount(SyncMessageId messageId, long timestamp, boolean deliveryReceipt, boolean readReceipt) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
@@ -275,24 +240,6 @@ public class MmsDatabase extends MessagingDatabase {
}
}
private long getThreadIdFor(IncomingMediaMessage retrieved) throws RecipientFormattingException, MmsException {
if (retrieved.getGroupId() != null) {
Recipient groupRecipients = Recipient.from(context, retrieved.getGroupId(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
} else {
Recipient sender = Recipient.from(context, retrieved.getFrom(), true);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(sender);
}
}
private long getThreadIdFor(@NonNull NotificationInd notification) {
String fromString = notification.getFrom() != null && notification.getFrom().getTextString() != null
? Util.toIsoString(notification.getFrom().getTextString())
: "";
Recipient recipient = Recipient.from(context, Address.fromExternal(context, fromString), false);
return DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
}
private Cursor rawQuery(@NonNull String where, @Nullable String[] arguments) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
return database.rawQuery("SELECT " + Util.join(MMS_PROJECTION, ",") +
@@ -323,35 +270,6 @@ public class MmsDatabase extends MessagingDatabase {
}
}
public void markAsOutbox(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE, Optional.of(threadId));
}
public void markAsForcedSms(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.PUSH_MESSAGE_BIT, Types.MESSAGE_FORCE_SMS_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsPendingInsecureSmsFallback(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK, Optional.of(threadId));
notifyConversationListeners(threadId);
}
// public void markAsSending(long messageId) {
// long threadId = getThreadIdForMessage(messageId);
// updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENDING_TYPE, Optional.of(threadId));
// notifyConversationListeners(threadId);
// }
public void markAsSentFailed(long messageId) {
long threadId = getThreadIdForMessage(messageId);
updateMailboxBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_SENT_FAILED_TYPE, Optional.of(threadId));
notifyConversationListeners(threadId);
}
@Override
public void markAsSent(long messageId, boolean secure) {
long threadId = getThreadIdForMessage(messageId);
@@ -359,47 +277,6 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
public void markDownloadState(long messageId, long state) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(STATUS, state);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""});
notifyConversationListeners(getThreadIdForMessage(messageId));
}
public void markAsNoSession(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
// public void markAsSecure(long messageId) {
// updateMailboxBitmask(messageId, 0, Types.SECURE_MESSAGE_BIT, Optional.<Long>absent());
// }
public void markAsInsecure(long messageId) {
updateMailboxBitmask(messageId, Types.SECURE_MESSAGE_BIT, 0, Optional.<Long>absent());
}
// public void markAsPush(long messageId) {
// updateMailboxBitmask(messageId, 0, Types.PUSH_MESSAGE_BIT, Optional.<Long>absent());
// }
public void markAsDecryptFailed(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsDecryptDuplicate(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_DUPLICATE_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
public void markAsLegacyVersion(long messageId, long threadId) {
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT, Optional.of(threadId));
notifyConversationListeners(threadId);
}
@Override
public void markExpireStarted(long messageId) {
markExpireStarted(messageId, System.currentTimeMillis());
@@ -417,16 +294,6 @@ public class MmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
public void markAsNotified(long id) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(NOTIFIED, 1);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
}
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
}
@@ -467,151 +334,6 @@ public class MmsDatabase extends MessagingDatabase {
return result;
}
public List<Pair<Long, Long>> setTimestampRead(SyncMessageId messageId, long proposedExpireStarted) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED, ADDRESS}, DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())}, null, null, null, null);
while (cursor.moveToNext()) {
Address theirAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
Address ourAddress = messageId.getAddress();
if (ourAddress.equals(theirAddress) || theirAddress.isGroup()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues values = new ContentValues();
values.put(READ, 1);
if (expiresIn > 0) {
values.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, values, ID_WHERE, new String[]{String.valueOf(id)});
DatabaseFactory.getThreadDatabase(context).updateReadState(threadId);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
notifyConversationListeners(threadId);
}
}
} finally {
if (cursor != null)
cursor.close();
}
return expiring;
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
"WHERE " + ID + " = ?",
new String[] {body, messageId + ""});
long threadId = getThreadIdForMessage(messageId);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
return new Pair<>(messageId, threadId);
}
public Optional<MmsNotificationInfo> getNotification(long messageId) {
Cursor cursor = null;
try {
cursor = rawQuery(RAW_ID_WHERE, new String[] {String.valueOf(messageId)});
if (cursor != null && cursor.moveToNext()) {
return Optional.of(new MmsNotificationInfo(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)),
cursor.getString(cursor.getColumnIndexOrThrow(CONTENT_LOCATION)),
cursor.getString(cursor.getColumnIndexOrThrow(TRANSACTION_ID)),
cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID))));
} else {
return Optional.absent();
}
} finally {
if (cursor != null)
cursor.close();
}
}
public OutgoingMediaMessage getOutgoingMessage(long messageId)
throws MmsException, NoSuchMessageException
{
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
Cursor cursor = null;
try {
cursor = rawQuery(RAW_ID_WHERE, new String[] {String.valueOf(messageId)});
if (cursor != null && cursor.moveToNext()) {
List<DatabaseAttachment> associatedAttachments = attachmentDatabase.getAttachmentsForMessage(messageId);
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String body = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
int subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
String address = cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
int distributionType = DatabaseFactory.getThreadDatabase(context).getDistributionType(threadId);
long quoteId = cursor.getLong(cursor.getColumnIndexOrThrow(QUOTE_ID));
String quoteAuthor = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_AUTHOR));
String quoteText = cursor.getString(cursor.getColumnIndexOrThrow(QUOTE_BODY));
List<Attachment> quoteAttachments = Stream.of(associatedAttachments).filter(Attachment::isQuote).map(a -> (Attachment)a).toList();
List<Contact> contacts = getSharedContacts(cursor, associatedAttachments);
Set<Attachment> contactAttachments = new HashSet<>(Stream.of(contacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList());
List<Attachment> attachments = Stream.of(associatedAttachments).filterNot(Attachment::isQuote).filterNot(contactAttachments::contains).map(a -> (Attachment)a).toList();
Recipient recipient = Recipient.from(context, Address.fromSerialized(address), false);
QuoteModel quote = null;
if (quoteId > 0 && (!TextUtils.isEmpty(quoteText) || !quoteAttachments.isEmpty())) {
quote = new QuoteModel(quoteId, Address.fromSerialized(quoteAuthor), quoteText, quoteAttachments);
}
if (body != null && (Types.isGroupQuit(outboxType) || Types.isGroupUpdate(outboxType))) {
return new OutgoingGroupMediaMessage(recipient, body, attachments, timestamp, 0, quote, contacts);
} else if (Types.isExpirationTimerUpdate(outboxType)) {
return new OutgoingExpirationUpdateMessage(recipient, timestamp, expiresIn);
}
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient, body, attachments, timestamp, subscriptionId, expiresIn, distributionType, quote, contacts);
if (Types.isSecureType(outboxType)) {
return new OutgoingSecureMediaMessage(message);
}
return message;
}
throw new NoSuchMessageException("No record found for id: " + messageId);
} catch (IOException e) {
throw new MmsException(e);
} finally {
if (cursor != null)
cursor.close();
}
}
private List<Contact> getSharedContacts(@NonNull Cursor cursor, @NonNull List<DatabaseAttachment> attachments) {
String serializedContacts = cursor.getString(cursor.getColumnIndexOrThrow(SHARED_CONTACTS));
@@ -650,313 +372,6 @@ public class MmsDatabase extends MessagingDatabase {
return Collections.emptyList();
}
public long copyMessageInbox(long messageId) throws MmsException {
try {
OutgoingMediaMessage request = getOutgoingMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(ADDRESS, request.getRecipient().getAddress().serialize());
contentValues.put(DATE_SENT, request.getSentTimeMillis());
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT);
contentValues.put(THREAD_ID, getThreadIdForMessage(messageId));
contentValues.put(READ, 1);
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
contentValues.put(EXPIRES_IN, request.getExpiresIn());
List<Attachment> attachments = new LinkedList<>();
for (Attachment attachment : request.getAttachments()) {
DatabaseAttachment databaseAttachment = (DatabaseAttachment)attachment;
attachments.add(new DatabaseAttachment(databaseAttachment.getAttachmentId(),
databaseAttachment.getMmsId(),
databaseAttachment.hasData(),
databaseAttachment.hasThumbnail(),
databaseAttachment.getContentType(),
AttachmentDatabase.TRANSFER_PROGRESS_DONE,
databaseAttachment.getSize(),
databaseAttachment.getFileName(),
databaseAttachment.getLocation(),
databaseAttachment.getKey(),
databaseAttachment.getRelay(),
databaseAttachment.getDigest(),
databaseAttachment.getFastPreflightId(),
databaseAttachment.isVoiceNote(),
databaseAttachment.getWidth(),
databaseAttachment.getHeight(),
databaseAttachment.isQuote()));
}
return insertMediaMessage(request.getBody(),
attachments,
new LinkedList<>(),
request.getSharedContacts(),
contentValues,
null);
} catch (NoSuchMessageException e) {
throw new MmsException(e);
}
}
private Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation,
long threadId, long mailbox)
throws MmsException
{
if (threadId == -1 || retrieved.isGroupMessage()) {
try {
threadId = getThreadIdFor(retrieved);
} catch (RecipientFormattingException e) {
Log.w("MmsDatabase", e);
if (threadId == -1)
throw new MmsException(e);
}
}
ContentValues contentValues = new ContentValues();
contentValues.put(DATE_SENT, retrieved.getSentTimeMillis());
contentValues.put(ADDRESS, retrieved.getFrom().serialize());
contentValues.put(MESSAGE_BOX, mailbox);
contentValues.put(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF);
contentValues.put(THREAD_ID, threadId);
contentValues.put(CONTENT_LOCATION, contentLocation);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(PART_COUNT, retrieved.getAttachments().size());
contentValues.put(SUBSCRIPTION_ID, retrieved.getSubscriptionId());
contentValues.put(EXPIRES_IN, retrieved.getExpiresIn());
contentValues.put(READ, retrieved.isExpirationUpdate() ? 1 : 0);
if (!contentValues.containsKey(DATE_SENT)) {
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
}
List<Attachment> quoteAttachments = new LinkedList<>();
if (retrieved.getQuote() != null) {
contentValues.put(QUOTE_ID, retrieved.getQuote().getId());
contentValues.put(QUOTE_BODY, retrieved.getQuote().getText());
contentValues.put(QUOTE_AUTHOR, retrieved.getQuote().getAuthor().serialize());
quoteAttachments = retrieved.getQuote().getAttachments();
}
if (retrieved.isPushMessage() && isDuplicate(retrieved, threadId)) {
Log.w(TAG, "Ignoring duplicate media message (" + retrieved.getSentTimeMillis() + ")");
return Optional.absent();
}
long messageId = insertMediaMessage(retrieved.getBody(), retrieved.getAttachments(), quoteAttachments, retrieved.getSharedContacts(), contentValues, null);
if (!Types.isExpirationTimerUpdate(mailbox)) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
}
notifyConversationListeners(threadId);
// jobManager.add(new TrimThreadJob(context, threadId));
return Optional.of(new InsertResult(messageId, threadId));
}
public Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved,
String contentLocation, long threadId)
throws MmsException
{
long type = Types.BASE_INBOX_TYPE;
if (retrieved.isPushMessage()) {
type |= Types.PUSH_MESSAGE_BIT;
}
if (retrieved.isExpirationUpdate()) {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
return insertMessageInbox(retrieved, contentLocation, threadId, type);
}
public Optional<InsertResult> insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId)
throws MmsException
{
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT;
if (retrieved.isPushMessage()) {
type |= Types.PUSH_MESSAGE_BIT;
}
if (retrieved.isExpirationUpdate()) {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
return insertMessageInbox(retrieved, "", threadId, type);
}
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long threadId = getThreadIdFor(notification);
ContentValues contentValues = new ContentValues();
ContentValuesBuilder contentBuilder = new ContentValuesBuilder(contentValues);
Log.w(TAG, "Message received type: " + notification.getMessageType());
contentBuilder.add(CONTENT_LOCATION, notification.getContentLocation());
contentBuilder.add(DATE_SENT, System.currentTimeMillis());
contentBuilder.add(EXPIRY, notification.getExpiry());
contentBuilder.add(MESSAGE_SIZE, notification.getMessageSize());
contentBuilder.add(TRANSACTION_ID, notification.getTransactionId());
contentBuilder.add(MESSAGE_TYPE, notification.getMessageType());
if (notification.getFrom() != null) {
contentValues.put(ADDRESS, Address.fromExternal(context, Util.toIsoString(notification.getFrom().getTextString())).serialize());
}
contentValues.put(MESSAGE_BOX, Types.BASE_INBOX_TYPE);
contentValues.put(THREAD_ID, threadId);
contentValues.put(STATUS, Status.DOWNLOAD_INITIALIZED);
contentValues.put(DATE_RECEIVED, generatePduCompatTimestamp());
contentValues.put(READ, Util.isDefaultSmsProvider(context) ? 0 : 1);
contentValues.put(SUBSCRIPTION_ID, subscriptionId);
if (!contentValues.containsKey(DATE_SENT))
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED));
long messageId = db.insert(TABLE_NAME, null, contentValues);
return new Pair<>(messageId, threadId);
}
public void markIncomingNotificationReceived(long threadId) {
notifyConversationListeners(threadId);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
if (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context)) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
// jobManager.add(new TrimThreadJob(context, threadId));
}
public long insertMessageOutbox(@NonNull OutgoingMediaMessage message,
long threadId, boolean forceSms,
@Nullable SmsDatabase.InsertListener insertListener)
throws MmsException
{
long type = Types.BASE_SENDING_TYPE;
if (message.isSecure()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
if (message.isGroup()) {
if (((OutgoingGroupMediaMessage)message).isGroupUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (((OutgoingGroupMediaMessage)message).isGroupQuit()) type |= Types.GROUP_QUIT_BIT;
}
if (message.isExpirationUpdate()) {
type |= Types.EXPIRATION_TIMER_UPDATE_BIT;
}
Map<Address, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(message.getSentTimeMillis());
Map<Address, Long> earlyReadReceipts = earlyReadReceiptCache.remove(message.getSentTimeMillis());
ContentValues contentValues = new ContentValues();
contentValues.put(DATE_SENT, message.getSentTimeMillis());
contentValues.put(MESSAGE_TYPE, PduHeaders.MESSAGE_TYPE_SEND_REQ);
contentValues.put(MESSAGE_BOX, type);
contentValues.put(THREAD_ID, threadId);
contentValues.put(READ, 1);
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
List<Attachment> quoteAttachments = new LinkedList<>();
if (message.getOutgoingQuote() != null) {
contentValues.put(QUOTE_ID, message.getOutgoingQuote().getId());
contentValues.put(QUOTE_AUTHOR, message.getOutgoingQuote().getAuthor().serialize());
contentValues.put(QUOTE_BODY, message.getOutgoingQuote().getText());
quoteAttachments.addAll(message.getOutgoingQuote().getAttachments());
}
long messageId = insertMediaMessage(message.getBody(), message.getAttachments(), quoteAttachments, message.getSharedContacts(), contentValues, insertListener);
if (message.getRecipient().getAddress().isGroup()) {
List<Recipient> members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(message.getRecipient().getAddress().toGroupString(), false);
GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
receiptDatabase.insert(Stream.of(members).map(Recipient::getAddress).toList(),
messageId, GroupReceiptDatabase.STATUS_UNDELIVERED, message.getSentTimeMillis());
for (Address address : earlyDeliveryReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_DELIVERED, -1);
for (Address address : earlyReadReceipts.keySet()) receiptDatabase.update(address, messageId, GroupReceiptDatabase.STATUS_READ, -1);
}
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
// jobManager.add(new TrimThreadJob(context, threadId));
return messageId;
}
private long insertMediaMessage(@Nullable String body,
@NonNull List<Attachment> attachments,
@NonNull List<Attachment> quoteAttachments,
@NonNull List<Contact> sharedContacts,
@NonNull ContentValues contentValues,
@Nullable SmsDatabase.InsertListener insertListener)
throws MmsException
{
SQLiteDatabase db = databaseHelper.getWritableDatabase();
AttachmentDatabase partsDatabase = DatabaseFactory.getAttachmentDatabase(context);
List<Attachment> allAttachments = new LinkedList<>();
List<Attachment> contactAttachments = Stream.of(sharedContacts).map(Contact::getAvatarAttachment).filter(a -> a != null).toList();
allAttachments.addAll(attachments);
allAttachments.addAll(contactAttachments);
contentValues.put(BODY, body);
contentValues.put(PART_COUNT, allAttachments.size());
db.beginTransaction();
try {
long messageId = db.insert(TABLE_NAME, null, contentValues);
Map<Attachment, AttachmentId> insertedAttachments = partsDatabase.insertAttachmentsForMessage(messageId, allAttachments, quoteAttachments);
String serializedContacts = getSerializedSharedContacts(messageId, insertedAttachments, sharedContacts);
if (!TextUtils.isEmpty(serializedContacts)) {
ContentValues contactValues = new ContentValues();
contactValues.put(SHARED_CONTACTS, serializedContacts);
SQLiteDatabase database = databaseHelper.getReadableDatabase();
int rows = database.update(TABLE_NAME, contactValues, ID + " = ?", new String[]{ String.valueOf(messageId) });
if (rows <= 0) {
Log.w(TAG, "Failed to update message with shared contact data.");
}
}
db.setTransactionSuccessful();
return messageId;
} finally {
db.endTransaction();
if (insertListener != null) {
insertListener.onComplete();
}
notifyConversationListeners(contentValues.getAsLong(THREAD_ID));
DatabaseFactory.getThreadDatabase(context).update(contentValues.getAsLong(THREAD_ID), true);
}
}
public boolean delete(long messageId) {
long threadId = getThreadIdForMessage(messageId);
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
@@ -978,45 +393,6 @@ public class MmsDatabase extends MessagingDatabase {
deleteThreads(singleThreadSet);
}
private @Nullable String getSerializedSharedContacts(long mmsId, @NonNull Map<Attachment, AttachmentId> insertedAttachmentIds, @NonNull List<Contact> contacts) {
if (contacts.isEmpty()) return null;
JSONArray sharedContactJson = new JSONArray();
for (Contact contact : contacts) {
try {
AttachmentId attachmentId = null;
if (contact.getAvatarAttachment() != null) {
attachmentId = insertedAttachmentIds.get(contact.getAvatarAttachment());
}
Avatar updatedAvatar = new Avatar(attachmentId,
contact.getAvatarAttachment(),
contact.getAvatar() != null && contact.getAvatar().isProfile());
Contact updatedContact = new Contact(contact, updatedAvatar);
sharedContactJson.put(new JSONObject(updatedContact.serialize()));
} catch (JSONException | IOException e) {
Log.w(TAG, "Failed to serialize shared contact. Skipping it.", e);
}
}
return sharedContactJson.toString();
}
private boolean isDuplicate(IncomingMediaMessage message, long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
new String[]{String.valueOf(message.getSentTimeMillis()), message.getFrom().serialize(), String.valueOf(threadId)},
null, null, null, "1");
try {
return cursor != null && cursor.moveToFirst();
} finally {
if (cursor != null) cursor.close();
}
}
/*package*/ void deleteThreads(Set<Long> threadIds) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
String where = "";
@@ -1077,105 +453,14 @@ public class MmsDatabase extends MessagingDatabase {
database.delete(TABLE_NAME, null, null);
}
public Cursor getCarrierMmsInformation(String apn) {
Uri uri = Uri.withAppendedPath(Uri.parse("content://telephony/carriers"), "current");
String selection = TextUtils.isEmpty(apn) ? null : "apn = ?";
String[] selectionArgs = TextUtils.isEmpty(apn) ? null : new String[] {apn.trim()};
try {
return context.getContentResolver().query(uri, null, selection, selectionArgs, null);
} catch (NullPointerException npe) {
// NOTE - This is dumb, but on some devices there's an NPE in the Android framework
// for the provider of this call, which gets rethrown back to here through a binder
// call.
throw new IllegalArgumentException(npe);
}
}
public Reader readerFor(Cursor cursor) {
return new Reader(cursor);
}
public OutgoingMessageReader readerFor(OutgoingMediaMessage message, long threadId) {
return new OutgoingMessageReader(message, threadId);
}
public static class Status {
public static final int DOWNLOAD_INITIALIZED = 1;
public static final int DOWNLOAD_NO_CONNECTIVITY = 2;
public static final int DOWNLOAD_CONNECTING = 3;
public static final int DOWNLOAD_SOFT_FAILURE = 4;
public static final int DOWNLOAD_HARD_FAILURE = 5;
public static final int DOWNLOAD_APN_UNAVAILABLE = 6;
}
public static class MmsNotificationInfo {
private final Address from;
private final String contentLocation;
private final String transactionId;
private final int subscriptionId;
MmsNotificationInfo(@Nullable String from, String contentLocation, String transactionId, int subscriptionId) {
this.from = from == null ? null : Address.fromSerialized(from);
this.contentLocation = contentLocation;
this.transactionId = transactionId;
this.subscriptionId = subscriptionId;
}
public String getContentLocation() {
return contentLocation;
}
public String getTransactionId() {
return transactionId;
}
public int getSubscriptionId() {
return subscriptionId;
}
public @Nullable Address getFrom() {
return from;
}
}
public class OutgoingMessageReader {
private final OutgoingMediaMessage message;
private final long id;
private final long threadId;
public OutgoingMessageReader(OutgoingMediaMessage message, long threadId) {
try {
this.message = message;
this.id = SecureRandom.getInstance("SHA1PRNG").nextLong();
this.threadId = threadId;
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public MessageRecord getCurrent() {
SlideDeck slideDeck = new SlideDeck(context, message.getAttachments());
return new MediaMmsMessageRecord(context, id, message.getRecipient(), message.getRecipient(),
1, System.currentTimeMillis(), System.currentTimeMillis(),
0, threadId, message.getBody(),
slideDeck, slideDeck.getSlides().size(),
message.isSecure() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
new LinkedList<IdentityKeyMismatch>(),
new LinkedList<NetworkFailure>(),
message.getSubscriptionId(),
message.getExpiresIn(),
System.currentTimeMillis(), 0,
message.getOutgoingQuote() != null ?
new Quote(message.getOutgoingQuote().getId(),
message.getOutgoingQuote().getAuthor(),
message.getOutgoingQuote().getText(),
new SlideDeck(context, message.getOutgoingQuote().getAttachments())) :
null,
message.getSharedContacts());
}
}
public class Reader implements Closeable {
@@ -1346,9 +631,4 @@ public class MmsDatabase extends MessagingDatabase {
}
}
}
private long generatePduCompatTimestamp() {
final long time = System.currentTimeMillis();
return time - (time % 1000);
}
}
@@ -20,36 +20,21 @@ package org.thoughtcrime.securesms.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import com.annimon.stream.Stream;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteStatement;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.whispersystems.libsignal.util.guava.Optional;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -103,11 +88,8 @@ public class SmsDatabase extends MessagingDatabase {
private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache();
private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache();
private final JobManager jobManager;
public SmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
super(context, databaseHelper);
this.jobManager = ApplicationContext.getInstance(context).getJobManager();
}
protected String getTableName() {
@@ -147,21 +129,6 @@ public class SmsDatabase extends MessagingDatabase {
}
}
public int getMessageCount() {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(TABLE_NAME, new String[] {"COUNT(*)"}, null, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) return cursor.getInt(0);
else return 0;
} finally {
if (cursor != null)
cursor.close();
}
}
public int getMessageCountForThread(long threadId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = null;
@@ -180,67 +147,11 @@ public class SmsDatabase extends MessagingDatabase {
return 0;
}
public void markAsEndSession(long id) {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.END_SESSION_BIT);
}
public void markAsPreKeyBundle(long id) {
updateTypeBitmask(id, Types.KEY_EXCHANGE_MASK, Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT);
}
public void markAsInvalidVersionKeyExchange(long id) {
updateTypeBitmask(id, 0, Types.KEY_EXCHANGE_INVALID_VERSION_BIT);
}
public void markAsSecure(long id) {
updateTypeBitmask(id, 0, Types.SECURE_MESSAGE_BIT);
}
public void markAsInsecure(long id) {
updateTypeBitmask(id, Types.SECURE_MESSAGE_BIT, 0);
}
public void markAsPush(long id) {
updateTypeBitmask(id, 0, Types.PUSH_MESSAGE_BIT);
}
public void markAsForcedSms(long id) {
updateTypeBitmask(id, Types.PUSH_MESSAGE_BIT, Types.MESSAGE_FORCE_SMS_BIT);
}
public void markAsDecryptFailed(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_FAILED_BIT);
}
public void markAsDecryptDuplicate(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_DUPLICATE_BIT);
}
public void markAsNoSession(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
}
public void markAsLegacyVersion(long id) {
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
}
public void markAsOutbox(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_OUTBOX_TYPE);
}
public void markAsPendingInsecureSmsFallback(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_PENDING_INSECURE_SMS_FALLBACK);
}
@Override
public void markAsSent(long id, boolean isSecure) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_TYPE | (isSecure ? Types.PUSH_MESSAGE_BIT | Types.SECURE_MESSAGE_BIT : 0));
}
public void markAsMissedCall(long id) {
updateTypeBitmask(id, Types.TOTAL_MASK, Types.MISSED_CALL_TYPE);
}
@Override
public void markExpireStarted(long id) {
markExpireStarted(id, System.currentTimeMillis());
@@ -260,32 +171,6 @@ public class SmsDatabase extends MessagingDatabase {
notifyConversationListeners(threadId);
}
public void markStatus(long id, int status) {
Log.w("MessageDatabase", "Updating ID: " + id + " to status: " + status);
ContentValues contentValues = new ContentValues();
contentValues.put(STATUS, status);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {id+""});
long threadId = getThreadIdForMessage(id);
DatabaseFactory.getThreadDatabase(context).update(threadId, false);
notifyConversationListeners(threadId);
}
public void markAsSentFailed(long id) {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_FAILED_TYPE);
}
public void markAsNotified(long id) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(NOTIFIED, 1);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(id)});
}
public void incrementReceiptCount(SyncMessageId messageId, boolean deliveryReceipt, boolean readReceipt) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
Cursor cursor = null;
@@ -328,50 +213,6 @@ public class SmsDatabase extends MessagingDatabase {
}
}
public List<Pair<Long, Long>> setTimestampRead(SyncMessageId messageId, long proposedExpireStarted) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
List<Pair<Long, Long>> expiring = new LinkedList<>();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS, TYPE, EXPIRES_IN, EXPIRE_STARTED},
DATE_SENT + " = ?", new String[] {String.valueOf(messageId.getTimetamp())},
null, null, null, null);
while (cursor.moveToNext()) {
Address theirAddress = messageId.getAddress();
Address ourAddress = Address.fromSerialized(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
if (ourAddress.equals(theirAddress)) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
long expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN));
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED));
expireStarted = expireStarted > 0 ? Math.min(proposedExpireStarted, expireStarted) : proposedExpireStarted;
ContentValues contentValues = new ContentValues();
contentValues.put(READ, 1);
if (expiresIn > 0) {
contentValues.put(EXPIRE_STARTED, expireStarted);
expiring.add(new Pair<>(id, expiresIn));
}
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {cursor.getLong(cursor.getColumnIndexOrThrow(ID)) + ""});
DatabaseFactory.getThreadDatabase(context).updateReadState(threadId);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
notifyConversationListeners(threadId);
}
}
} finally {
if (cursor != null) cursor.close();
}
return expiring;
}
public List<MarkedMessageInfo> setMessagesRead(long threadId) {
return setMessagesRead(THREAD_ID + " = ? AND " + READ + " = 0", new String[] {String.valueOf(threadId)});
}
@@ -411,277 +252,12 @@ public class SmsDatabase extends MessagingDatabase {
return results;
}
public Pair<Long, Long> updateBundleMessageBody(long messageId, String body) {
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT;
return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type);
}
public void updateMessageBody(long messageId, String body) {
long type = 0;
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
}
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
TYPE + " = (" + TYPE + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
"WHERE " + ID + " = ?",
new String[] {body, messageId + ""});
long threadId = getThreadIdForMessage(messageId);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
notifyConversationListListeners();
return new Pair<>(messageId, threadId);
}
public Pair<Long, Long> copyMessageInbox(long messageId) {
try {
SmsMessageRecord record = getMessage(messageId);
ContentValues contentValues = new ContentValues();
contentValues.put(TYPE, (record.getType() & ~Types.BASE_TYPE_MASK) | Types.BASE_INBOX_TYPE);
contentValues.put(ADDRESS, record.getIndividualRecipient().getAddress().serialize());
contentValues.put(ADDRESS_DEVICE_ID, record.getRecipientDeviceId());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, record.getDateSent());
contentValues.put(PROTOCOL, 31337);
contentValues.put(READ, 0);
contentValues.put(BODY, record.getBody());
contentValues.put(THREAD_ID, record.getThreadId());
contentValues.put(EXPIRES_IN, record.getExpiresIn());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long newMessageId = db.insert(TABLE_NAME, null, contentValues);
DatabaseFactory.getThreadDatabase(context).update(record.getThreadId(), true);
notifyConversationListeners(record.getThreadId());
// jobManager.add(new TrimThreadJob(context, record.getThreadId()));
return new Pair<>(newMessageId, record.getThreadId());
} catch (NoSuchMessageException e) {
throw new AssertionError(e);
}
}
public @NonNull Pair<Long, Long> insertReceivedCall(@NonNull Address address) {
return insertCallLog(address, Types.INCOMING_CALL_TYPE, false);
}
public @NonNull Pair<Long, Long> insertOutgoingCall(@NonNull Address address) {
return insertCallLog(address, Types.OUTGOING_CALL_TYPE, false);
}
public @NonNull Pair<Long, Long> insertMissedCall(@NonNull Address address) {
return insertCallLog(address, Types.MISSED_CALL_TYPE, true);
}
private @NonNull Pair<Long, Long> insertCallLog(@NonNull Address address, long type, boolean unread) {
Recipient recipient = Recipient.from(context, address, true);
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, address.serialize());
values.put(ADDRESS_DEVICE_ID, 1);
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, System.currentTimeMillis());
values.put(READ, unread ? 0 : 1);
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
notifyConversationListeners(threadId);
// jobManager.add(new TrimThreadJob(context, threadId));
if (unread) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
return new Pair<>(messageId, threadId);
}
protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type) {
if (message.isJoined()) {
type = (type & (Types.TOTAL_MASK - Types.BASE_TYPE_MASK)) | Types.JOINED_TYPE;
} else if (message.isPreKeyBundle()) {
type |= Types.KEY_EXCHANGE_BIT | Types.KEY_EXCHANGE_BUNDLE_BIT;
} else if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) {
type |= Types.SECURE_MESSAGE_BIT;
if (((IncomingGroupMessage)message).isUpdate()) type |= Types.GROUP_UPDATE_BIT;
else if (((IncomingGroupMessage)message).isQuit()) type |= Types.GROUP_QUIT_BIT;
} else if (message.isEndSession()) {
type |= Types.SECURE_MESSAGE_BIT;
type |= Types.END_SESSION_BIT;
}
if (message.isPush()) type |= Types.PUSH_MESSAGE_BIT;
if (message.isIdentityUpdate()) type |= Types.KEY_EXCHANGE_IDENTITY_UPDATE_BIT;
if (message.isContentPreKeyBundle()) type |= Types.KEY_EXCHANGE_CONTENT_FORMAT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
Recipient recipient = Recipient.from(context, message.getSender(), true);
Recipient groupRecipient;
if (message.getGroupId() == null) {
groupRecipient = null;
} else {
groupRecipient = Recipient.from(context, message.getGroupId(), true);
}
boolean unread = (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
message.isSecureMessage() || message.isGroup() || message.isPreKeyBundle()) &&
!message.isIdentityUpdate() && !message.isIdentityDefault() && !message.isIdentityVerified();
long threadId;
if (groupRecipient == null) threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient);
else threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient);
ContentValues values = new ContentValues(6);
values.put(ADDRESS, message.getSender().serialize());
values.put(ADDRESS_DEVICE_ID, message.getSenderDeviceId());
values.put(DATE_RECEIVED, System.currentTimeMillis());
values.put(DATE_SENT, message.getSentTimestampMillis());
values.put(PROTOCOL, message.getProtocol());
values.put(READ, unread ? 0 : 1);
values.put(SUBSCRIPTION_ID, message.getSubscriptionId());
values.put(EXPIRES_IN, message.getExpiresIn());
if (!TextUtils.isEmpty(message.getPseudoSubject()))
values.put(SUBJECT, message.getPseudoSubject());
values.put(REPLY_PATH_PRESENT, message.isReplyPathPresent());
values.put(SERVICE_CENTER, message.getServiceCenterAddress());
values.put(BODY, message.getMessageBody());
values.put(TYPE, type);
values.put(THREAD_ID, threadId);
if (message.isPush() && isDuplicate(message, threadId)) {
Log.w(TAG, "Duplicate message (" + message.getSentTimestampMillis() + "), ignoring...");
return Optional.absent();
} else {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values);
if (unread) {
DatabaseFactory.getThreadDatabase(context).incrementUnread(threadId, 1);
}
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
}
if (message.getSubscriptionId() != -1) {
DatabaseFactory.getRecipientDatabase(context).setDefaultSubscriptionId(recipient, message.getSubscriptionId());
}
notifyConversationListeners(threadId);
// if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
// jobManager.add(new TrimThreadJob(context, threadId));
// }
return Optional.of(new InsertResult(messageId, threadId));
}
}
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message) {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE);
}
public long insertMessageOutbox(long threadId, OutgoingTextMessage message,
boolean forceSms, long date, InsertListener insertListener)
{
long type = Types.BASE_SENDING_TYPE;
if (message.isKeyExchange()) type |= Types.KEY_EXCHANGE_BIT;
else if (message.isSecureMessage()) type |= (Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT);
else if (message.isEndSession()) type |= Types.END_SESSION_BIT;
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
if (message.isIdentityVerified()) type |= Types.KEY_EXCHANGE_IDENTITY_VERIFIED_BIT;
else if (message.isIdentityDefault()) type |= Types.KEY_EXCHANGE_IDENTITY_DEFAULT_BIT;
Address address = message.getRecipient().getAddress();
Map<Address, Long> earlyDeliveryReceipts = earlyDeliveryReceiptCache.remove(date);
Map<Address, Long> earlyReadReceipts = earlyReadReceiptCache.remove(date);
ContentValues contentValues = new ContentValues(6);
contentValues.put(ADDRESS, address.serialize());
contentValues.put(THREAD_ID, threadId);
contentValues.put(BODY, message.getMessageBody());
contentValues.put(DATE_RECEIVED, System.currentTimeMillis());
contentValues.put(DATE_SENT, date);
contentValues.put(READ, 1);
contentValues.put(TYPE, type);
contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId());
contentValues.put(EXPIRES_IN, message.getExpiresIn());
contentValues.put(DELIVERY_RECEIPT_COUNT, Stream.of(earlyDeliveryReceipts.values()).mapToLong(Long::longValue).sum());
contentValues.put(READ_RECEIPT_COUNT, Stream.of(earlyReadReceipts.values()).mapToLong(Long::longValue).sum());
SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, ADDRESS, contentValues);
if (insertListener != null) {
insertListener.onComplete();
}
if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId);
}
DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true);
notifyConversationListeners(threadId);
// if (!message.isIdentityVerified() && !message.isIdentityDefault()) {
// jobManager.add(new TrimThreadJob(context, threadId));
// }
return messageId;
}
Cursor getMessages(int skip, int limit) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, null, null, null, null, ID, skip + "," + limit);
}
Cursor getOutgoingMessages() {
String outgoingSelection = TYPE + " & " + Types.BASE_TYPE_MASK + " = " + Types.BASE_OUTBOX_TYPE;
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, outgoingSelection, null, null, null, null);
}
public Cursor getExpirationStartedMessages() {
String where = EXPIRE_STARTED + " > 0";
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null);
}
public SmsMessageRecord getMessage(long messageId) throws NoSuchMessageException {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[]{messageId + ""}, null, null, null);
Reader reader = new Reader(cursor);
SmsMessageRecord record = reader.getNext();
reader.close();
if (record == null) throw new NoSuchMessageException("No message for ID: " + messageId);
else return record;
}
public Cursor getMessageCursor(long messageId) {
SQLiteDatabase db = databaseHelper.getReadableDatabase();
return db.query(TABLE_NAME, MESSAGE_PROJECTION, ID_WHERE, new String[] {messageId + ""}, null, null, null);
@@ -697,23 +273,6 @@ public class SmsDatabase extends MessagingDatabase {
return threadDeleted;
}
public void ensureMigration() {
databaseHelper.getWritableDatabase();
}
private boolean isDuplicate(IncomingTextMessage message, long threadId) {
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = database.query(TABLE_NAME, null, DATE_SENT + " = ? AND " + ADDRESS + " = ? AND " + THREAD_ID + " = ?",
new String[]{String.valueOf(message.getSentTimestampMillis()), message.getSender().serialize(), String.valueOf(threadId)},
null, null, null, "1");
try {
return cursor != null && cursor.moveToFirst();
} finally {
if (cursor != null) cursor.close();
}
}
/*package */void deleteThread(long threadId) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete(TABLE_NAME, THREAD_ID + " = ?", new String[] {threadId+""});
@@ -750,34 +309,6 @@ public class SmsDatabase extends MessagingDatabase {
db.delete(TABLE_NAME, null, null);
}
/*package*/ SQLiteDatabase beginTransaction() {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.beginTransaction();
return database;
}
/*package*/ void endTransaction(SQLiteDatabase database) {
database.setTransactionSuccessful();
database.endTransaction();
}
/*package*/ SQLiteStatement createInsertStatement(SQLiteDatabase database) {
return database.compileStatement("INSERT INTO " + TABLE_NAME + " (" + ADDRESS + ", " +
PERSON + ", " +
DATE_SENT + ", " +
DATE_RECEIVED + ", " +
PROTOCOL + ", " +
READ + ", " +
STATUS + ", " +
TYPE + ", " +
REPLY_PATH_PRESENT + ", " +
SUBJECT + ", " +
BODY + ", " +
SERVICE_CENTER +
", " + THREAD_ID + ") " +
" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
}
public static class Status {
public static final int STATUS_NONE = -1;
public static final int STATUS_COMPLETE = 0;
@@ -789,37 +320,6 @@ public class SmsDatabase extends MessagingDatabase {
return new Reader(cursor);
}
public OutgoingMessageReader readerFor(OutgoingTextMessage message, long threadId) {
return new OutgoingMessageReader(message, threadId);
}
public class OutgoingMessageReader {
private final OutgoingTextMessage message;
private final long id;
private final long threadId;
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
try {
this.message = message;
this.threadId = threadId;
this.id = SecureRandom.getInstance("SHA1PRNG").nextLong();
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public MessageRecord getCurrent() {
return new SmsMessageRecord(context, id, message.getMessageBody(),
message.getRecipient(), message.getRecipient(),
1, System.currentTimeMillis(), System.currentTimeMillis(),
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
threadId, 0, new LinkedList<IdentityKeyMismatch>(),
message.getSubscriptionId(), message.getExpiresIn(),
System.currentTimeMillis(), 0);
}
}
public class Reader {
private final Cursor cursor;
@@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.database.SessionDatabase;
import org.thoughtcrime.securesms.database.SignedPreKeyDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
@@ -115,7 +114,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
// ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
}
SessionStoreMigrationHelper.migrateSessions(context, db);
@@ -141,7 +140,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)");
if (!PreKeyMigrationHelper.migratePreKeys(context, db)) {
ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
// ApplicationContext.getInstance(context).getJobManager().add(new RefreshPreKeysJob(context));
}
}
@@ -1,58 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import android.util.Log;
import org.thoughtcrime.securesms.util.AsyncLoader;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class DeviceListLoader extends AsyncLoader<List<DeviceInfo>> {
private static final String TAG = DeviceListLoader.class.getSimpleName();
private final SignalServiceAccountManager accountManager;
public DeviceListLoader(Context context, SignalServiceAccountManager accountManager) {
super(context);
this.accountManager = accountManager;
}
@Override
public List<DeviceInfo> loadInBackground() {
try {
List<DeviceInfo> devices = accountManager.getDevices();
Iterator<DeviceInfo> iterator = devices.iterator();
while (iterator.hasNext()) {
if ((iterator.next().getId() == SignalServiceAddress.DEFAULT_DEVICE_ID)) {
iterator.remove();
}
}
Collections.sort(devices, new DeviceInfoComparator());
return devices;
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
private static class DeviceInfoComparator implements Comparator<DeviceInfo> {
@Override
public int compare(DeviceInfo lhs, DeviceInfo rhs) {
if (lhs.getCreated() < rhs.getCreated()) return -1;
else if (lhs.getCreated() != rhs.getCreated()) return 1;
else return 0;
}
}
}
@@ -1,33 +0,0 @@
package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
import org.whispersystems.libsignal.state.SignedPreKeyStore;
import dagger.Module;
import dagger.Provides;
@Module (complete = false, injects = {CleanPreKeysJob.class})
public class AxolotlStorageModule {
private final Context context;
public AxolotlStorageModule(Context context) {
this.context = context;
}
@Provides SignedPreKeyStoreFactory provideSignedPreKeyStoreFactory() {
return new SignedPreKeyStoreFactory() {
@Override
public SignedPreKeyStore create() {
return new SignalProtocolStoreImpl(context);
}
};
}
public static interface SignedPreKeyStoreFactory {
public SignedPreKeyStore create();
}
}
@@ -1,4 +0,0 @@
package org.thoughtcrime.securesms.dependencies;
public interface InjectableType {
}
@@ -1,182 +0,0 @@
package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AvatarDownloadJob;
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceProfileKeyUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadReceiptUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
import org.thoughtcrime.securesms.jobs.RequestGroupInfoJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RotateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.preferences.AppProtectionPreferenceFragment;
import org.thoughtcrime.securesms.push.SecurityEventListener;
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
import org.thoughtcrime.securesms.service.MessageRetrievalService;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
import org.whispersystems.signalservice.api.util.CredentialsProvider;
import org.whispersystems.signalservice.api.websocket.ConnectivityListener;
import dagger.Module;
import dagger.Provides;
@Module(complete = false, injects = {CleanPreKeysJob.class,
CreateSignedPreKeyJob.class,
PushGroupSendJob.class,
PushTextSendJob.class,
PushMediaSendJob.class,
AttachmentDownloadJob.class,
RefreshPreKeysJob.class,
MessageRetrievalService.class,
PushNotificationReceiveJob.class,
MultiDeviceContactUpdateJob.class,
MultiDeviceGroupUpdateJob.class,
MultiDeviceReadUpdateJob.class,
MultiDeviceBlockedUpdateJob.class,
RefreshAttributesJob.class,
RequestGroupInfoJob.class,
PushGroupUpdateJob.class,
AvatarDownloadJob.class,
RotateSignedPreKeyJob.class,
WebRtcCallService.class,
RetrieveProfileJob.class,
MultiDeviceVerifiedUpdateJob.class,
CreateProfileActivity.class,
RetrieveProfileAvatarJob.class,
MultiDeviceProfileKeyUpdateJob.class,
SendReadReceiptJob.class,
MultiDeviceReadReceiptUpdateJob.class,
AppProtectionPreferenceFragment.class})
public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();
private final Context context;
private final SignalServiceNetworkAccess networkAccess;
private SignalServiceAccountManager accountManager;
private SignalServiceMessageSender messageSender;
private SignalServiceMessageReceiver messageReceiver;
public SignalCommunicationModule(Context context, SignalServiceNetworkAccess networkAccess) {
this.context = context;
this.networkAccess = networkAccess;
}
@Provides
synchronized SignalServiceAccountManager provideSignalAccountManager() {
if (this.accountManager == null) {
this.accountManager = new SignalServiceAccountManager(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT);
}
return this.accountManager;
}
@Provides
synchronized SignalServiceMessageSender provideSignalMessageSender() {
if (this.messageSender == null) {
this.messageSender = new SignalServiceMessageSender(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
new SignalProtocolStoreImpl(context),
BuildConfig.USER_AGENT,
Optional.fromNullable(MessageRetrievalService.getPipe()),
Optional.of(new SecurityEventListener(context)));
} else {
this.messageSender.setMessagePipe(MessageRetrievalService.getPipe());
}
return this.messageSender;
}
@Provides
synchronized SignalServiceMessageReceiver provideSignalMessageReceiver() {
if (this.messageReceiver == null) {
this.messageReceiver = new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
new DynamicCredentialsProvider(context),
BuildConfig.USER_AGENT,
new PipeConnectivityListener());
}
return this.messageReceiver;
}
private static class DynamicCredentialsProvider implements CredentialsProvider {
private final Context context;
private DynamicCredentialsProvider(Context context) {
this.context = context.getApplicationContext();
}
@Override
public String getUser() {
return TextSecurePreferences.getLocalNumber(context);
}
@Override
public String getPassword() {
return TextSecurePreferences.getPushServerPassword(context);
}
@Override
public String getSignalingKey() {
return TextSecurePreferences.getSignalingKey(context);
}
}
private class PipeConnectivityListener implements ConnectivityListener {
@Override
public void onConnected() {
Log.w(TAG, "onConnected()");
}
@Override
public void onConnecting() {
Log.w(TAG, "onConnecting()");
}
@Override
public void onDisconnected() {
Log.w(TAG, "onDisconnected()");
}
@Override
public void onAuthenticationFailure() {
Log.w(TAG, "onAuthenticationFailure()");
TextSecurePreferences.setUnauthorizedReceived(context, true);
EventBus.getDefault().post(new ReminderUpdateEvent());
}
}
}
@@ -1,19 +0,0 @@
package org.thoughtcrime.securesms.events;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.attachments.Attachment;
public class PartProgressEvent {
public final Attachment attachment;
public final long total;
public final long progress;
public PartProgressEvent(@NonNull Attachment attachment, long total, long progress) {
this.attachment = attachment;
this.total = total;
this.progress = progress;
}
}

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