mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-19 19:18:27 +00:00
Add support for SN verification
// FREEBIE
This commit is contained in:
parent
58273997b9
commit
76c28cfa7a
@ -61,7 +61,7 @@ dependencies {
|
||||
|
||||
compile 'org.whispersystems:jobmanager:1.0.2'
|
||||
compile 'org.whispersystems:libpastelog:1.0.7'
|
||||
compile 'org.whispersystems:signal-service-android:2.5.10'
|
||||
compile 'org.whispersystems:signal-service-android:2.5.11'
|
||||
compile 'org.whispersystems:webrtc-android:M57-S2'
|
||||
|
||||
compile "me.leolin:ShortcutBadger:1.1.16"
|
||||
@ -135,7 +135,7 @@ dependencyVerification {
|
||||
'com.google.android.exoplayer:exoplayer:955085aa611a8f7cf6c61b88ae03d1a392f4ad94c9bfbc153f3dedb9ffb14718',
|
||||
'org.whispersystems:jobmanager:506f679fc2fcf7bb6d10f00f41d6f6ea0abf75c70dc95b913398661ad538a181',
|
||||
'org.whispersystems:libpastelog:bb331d9a98240fc139101128ba836c1edec3c40e000597cdbb29ebf4cbf34d88',
|
||||
'org.whispersystems:signal-service-android:0c6937decce77b94807d100a16bcffc9e69489057b4430ca07a5bce618637b6e',
|
||||
'org.whispersystems:signal-service-android:355c2b139a7587bbde899261a0344bc6a32c1cf0baa5ac748f6c86190c07374c',
|
||||
'org.whispersystems:webrtc-android:9d11e39d4b3823713e5b1486226e0ce09f989d6f47f52da1815e406c186701d5',
|
||||
'me.leolin:ShortcutBadger:e3cb3e7625892129b0c92dd5e4bc649faffdd526d5af26d9c45ee31ff8851774',
|
||||
'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb',
|
||||
@ -169,7 +169,7 @@ dependencyVerification {
|
||||
'com.google.android.gms:play-services-base:0ca636a8fc9a5af45e607cdcd61783bf5d561cbbb0f862021ce69606eee5ad49',
|
||||
'com.google.android.gms:play-services-basement:95dd882c5ffba15b9a99de3fefb05d3a01946623af67454ca00055d222f85a8d',
|
||||
'com.google.android.gms:play-services-iid:54e919f9957b8b7820da7ee9b83471d00d0cac1cf08ddea8b5b41aea80bb1a70',
|
||||
'org.whispersystems:signal-service-java:1de66a4068523098951529a363b4f02436e654d3d3fcf9228154b8c2cf94945b',
|
||||
'org.whispersystems:signal-service-java:02956dc0c22d8a6c7cf613b2e40cf54aec15a5e1cc5acab4d0f8b82bc22f2e0d',
|
||||
'org.whispersystems:signal-protocol-android:b05cd9570d2e262afeb6610b70f473a936c54dd23a7c967d76e8f288766731fd',
|
||||
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
|
||||
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
|
||||
|
BIN
res/drawable-hdpi/ic_check_circle_white_18dp.png
Normal file
BIN
res/drawable-hdpi/ic_check_circle_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 294 B |
BIN
res/drawable-mdpi/ic_check_circle_white_18dp.png
Normal file
BIN
res/drawable-mdpi/ic_check_circle_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 217 B |
BIN
res/drawable-xhdpi/ic_check_circle_white_18dp.png
Normal file
BIN
res/drawable-xhdpi/ic_check_circle_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 354 B |
BIN
res/drawable-xxhdpi/ic_check_circle_white_18dp.png
Normal file
BIN
res/drawable-xxhdpi/ic_check_circle_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 492 B |
BIN
res/drawable-xxxhdpi/ic_check_circle_white_18dp.png
Normal file
BIN
res/drawable-xxxhdpi/ic_check_circle_white_18dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
@ -23,6 +23,12 @@
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false">
|
||||
|
||||
<ViewStub android:id="@+id/unverified_banner_stub"
|
||||
android:layout="@layout/conversation_activity_unverified_banner_stub"
|
||||
android:inflatedId="@+id/unverified_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/reminder_stub"
|
||||
android:layout="@layout/conversation_activity_reminderview_stub"
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.identity.UnverifiedBannerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/unverified_banner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
@ -21,14 +21,31 @@
|
||||
style="@style/TextSecure.TitleTextStyle"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<TextView android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:gravity="center_vertical"
|
||||
android:textDirection="ltr"
|
||||
style="@style/TextSecure.SubtitleTextStyle"/>
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView android:id="@+id/verified_indicator"
|
||||
android:src="@drawable/ic_check_circle_white_18dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="3dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:alpha="0.7"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"
|
||||
android:layout_gravity="center_vertical|start"
|
||||
android:gravity="center_vertical"
|
||||
android:textDirection="ltr"
|
||||
tools:text="(123) 123-1234"
|
||||
style="@style/TextSecure.SubtitleTextStyle"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.thoughtcrime.securesms.ConversationTitleView>
|
42
res/layout/unverified_banner_view.xml
Normal file
42
res/layout/unverified_banner_view.xml
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/reminder_background"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@+id/cancel"
|
||||
android:orientation="horizontal"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@drawable/ic_alert"/>
|
||||
|
||||
<TextView android:id="@+id/unverified_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="16dp"
|
||||
tools:text="Your safety number with Jules Bonnot has changed and is no longer verified"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/cancel"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@+id/container"
|
||||
android:nextFocusRight="@+id/container"
|
||||
android:src="@drawable/ic_close_white_24dp"
|
||||
android:contentDescription="@string/InviteActivity_cancel"/>
|
||||
|
||||
</LinearLayout>
|
@ -12,17 +12,25 @@
|
||||
android:background="?verification_background"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
<FrameLayout android:layout_width="250dp"
|
||||
android:layout_height="250dp">
|
||||
|
||||
<TextView android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/verify_display_fragment__loading"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.SquareImageView
|
||||
android:id="@+id/qr_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="20dp"
|
||||
android:padding="20dp"
|
||||
android:background="@drawable/qr_code_background"
|
||||
tools:src="@drawable/splash_logo"/>
|
||||
android:visibility="invisible"
|
||||
tools:src="@drawable/splash_logo"
|
||||
tools:visibility="invisible"/>
|
||||
|
||||
<TextView android:id="@+id/tap_label"
|
||||
android:layout_width="wrap_content"
|
||||
@ -31,6 +39,7 @@
|
||||
android:layout_marginBottom="35dp"
|
||||
android:textColor="@color/gray50"
|
||||
android:textSize="11sp"
|
||||
android:visibility="invisible"
|
||||
android:text="@string/verify_display_fragment__tap_to_scan"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.SquareImageView
|
||||
@ -146,10 +155,30 @@
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
<LinearLayout android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:paddingLeft="20dp">
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/verified_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:layout_marginLeft="5dp"
|
||||
android:textSize="17dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/verify_display_fragment__verified"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="25dp"
|
||||
android:layout_marginTop="15dp"
|
||||
android:textSize="17sp"
|
||||
android:lineSpacingExtra="3sp"
|
||||
android:text="@string/verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s"/>
|
||||
|
@ -421,6 +421,10 @@
|
||||
<string name="MessageRecord_you_set_disappearing_message_time_to_s">You set disappearing message time to %1$s.</string>
|
||||
<string name="MessageRecord_s_set_disappearing_message_time_to_s">%1$s set disappearing message time to %2$s.</string>
|
||||
<string name="MessageRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified">You marked your safety number with %s verified</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device">You marked your safety number with %s verified from another device</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_unverified">You marked your safety number with %s unverified</string>
|
||||
<string name="MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device">You marked your safety number with %s unverified from another device</string>
|
||||
|
||||
|
||||
<!-- PassphraseChangeActivity -->
|
||||
@ -594,6 +598,8 @@
|
||||
<string name="ThreadRecord_disappearing_message_time_updated_to_s">Disappearing message time set to %s</string>
|
||||
<string name="ThreadRecord_safety_number_changed">Safety number changed</string>
|
||||
<string name="ThreadRecord_your_safety_number_with_s_has_changed">Your safety number with %s has changed.</string>
|
||||
<string name="ThreadRecord_you_marked_verified">You marked verified</string>
|
||||
<string name="ThreadRecord_you_marked_unverified">You marked unverified</string>
|
||||
|
||||
<!-- UpdateApkReadyListener -->
|
||||
<string name="UpdateApkReadyListener_Signal_update">Signal update</string>
|
||||
@ -851,6 +857,24 @@
|
||||
</plurals>
|
||||
|
||||
<string name="expiration_weeks_abbreviated">%dw</string>
|
||||
|
||||
<!-- unverified safety numbers -->
|
||||
<string name="IdentityUtil_unverified_banner_one">Your safety number with %s has changed and is no longer verified</string>
|
||||
<string name="IdentityUtil_unverified_banner_two">Your safety numbers with %1$s and %2$s are no longer verified</string>
|
||||
<string name="IdentityUtil_unverified_banner_many">Your safety numbers with %1$s, %2$s, and %3$s are no longer verified</string>
|
||||
|
||||
<string name="IdentityUtil_unverified_dialog_one">Your safety number with %1$s has changed and is no longer verified. This could either mean that someone is trying to intercept your communication, or that %1$s simply reinstalled Signal.</string>
|
||||
<string name="IdentityUtil_unverified_dialog_two">Your safety numbers with %1$s and %2$s are no longer verified. This could either mean that someone is trying to intercept your communication, or that they simply reinstalled Signal.</string>
|
||||
<string name="IdentityUtil_unverified_dialog_many">Your safety numbers with %1$s, %2$s, and %3$s are no longer verified. This could either mean that someone is trying to intercept your communication, or that they simply reinstalled Signal.</string>
|
||||
|
||||
<string name="IdentityUtil_untrusted_dialog_one">Your safety number with %s just changed.</string>
|
||||
<string name="IdentityUtil_untrusted_dialog_two">Your safety number with %1$s and %2$s just changed.</string>
|
||||
<string name="IdentityUtil_untrusted_dialog_many">Your safety number with %1$s, %2$s, and %3$s just changed.</string>
|
||||
|
||||
<plurals name="identity_others">
|
||||
<item quantity="one">%d other</item>
|
||||
<item quantity="other">%d others</item>
|
||||
</plurals>
|
||||
|
||||
<!-- giphy_activity -->
|
||||
<string name="giphy_activity_toolbar__search_gifs_and_stickers">Search GIFs and stickers</string>
|
||||
@ -1022,8 +1046,10 @@
|
||||
<string name="recipients_panel__add_members">Add members</string>
|
||||
|
||||
<!-- verify_display_fragment -->
|
||||
<string name="verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s"><![CDATA[If you wish to verify the security of your end-to-end encryption with %s, compare the number above with the number on their device. Alternatively, you can scan the code on their phone, or ask them to scan your code. <a href="https://whispersystems.org/redirect/safety-numbers">Learn more about verifying safety numbers</a>]]></string>
|
||||
<string name="verify_display_fragment__if_you_wish_to_verify_the_security_of_your_end_to_end_encryption_with_s"><![CDATA[If you wish to verify the security of your encryption with %s, compare the number above with the number on their device. Alternatively, you can scan the code on their phone, or ask them to scan your code. <a href="https://whispersystems.org/redirect/safety-numbers">Learn more.</a>]]></string>
|
||||
<string name="verify_display_fragment__tap_to_scan">Tap to scan</string>
|
||||
<string name="verify_display_fragment__loading">Loading...</string>
|
||||
<string name="verify_display_fragment__verified">Verified</string>
|
||||
|
||||
<!-- verify_identity -->
|
||||
<string name="verify_identity__share_safety_number">Share safety number</string>
|
||||
@ -1375,6 +1401,10 @@
|
||||
|
||||
<!-- transport_selection_list_item -->
|
||||
<string name="transport_selection_list_item__transport_icon">Transport icon</string>
|
||||
<string name="UntrustedSendDialog_send_message">Send message?</string>
|
||||
<string name="UntrustedSendDialog_send">Send</string>
|
||||
<string name="UnverifiedSendDialog_send_message">Send message?</string>
|
||||
<string name="UnverifiedSendDialog_send">Send</string>
|
||||
|
||||
|
||||
<!-- EOF -->
|
||||
|
@ -110,7 +110,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(number, 1);
|
||||
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext());
|
||||
|
||||
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true, true);
|
||||
identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true);
|
||||
}
|
||||
|
||||
processMessageRecord(messageRecord);
|
||||
|
@ -85,11 +85,15 @@ import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer;
|
||||
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.AttachmentDrawerListener;
|
||||
import org.thoughtcrime.securesms.components.camera.QuickAttachmentDrawer.DrawerState;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
|
||||
import org.thoughtcrime.securesms.components.identity.UntrustedSendDialog;
|
||||
import org.thoughtcrime.securesms.components.identity.UnverifiedBannerView;
|
||||
import org.thoughtcrime.securesms.components.identity.UnverifiedSendDialog;
|
||||
import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||
import org.thoughtcrime.securesms.components.reminder.InviteReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ReminderView;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
@ -98,12 +102,16 @@ 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.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns.Types;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientPreferenceEvent;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.RecipientsPreferences;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager;
|
||||
@ -141,6 +149,7 @@ 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.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -155,11 +164,13 @@ 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.concurrent.ExecutionException;
|
||||
|
||||
import static org.thoughtcrime.securesms.TransportOption.Type;
|
||||
import static org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord;
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext;
|
||||
|
||||
/**
|
||||
@ -199,19 +210,20 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private static final int PICK_GIF = 9;
|
||||
private static final int SMS_DEFAULT = 10;
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
protected ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private Button makeDefaultSmsButton;
|
||||
private InputAwareLayout container;
|
||||
private View composePanel;
|
||||
protected Stub<ReminderView> reminderView;
|
||||
private MasterSecret masterSecret;
|
||||
protected ComposeText composeText;
|
||||
private AnimatingToggle buttonToggle;
|
||||
private SendButton sendButton;
|
||||
private ImageButton attachButton;
|
||||
protected ConversationTitleView titleView;
|
||||
private TextView charactersLeft;
|
||||
private ConversationFragment fragment;
|
||||
private Button unblockButton;
|
||||
private Button makeDefaultSmsButton;
|
||||
private InputAwareLayout container;
|
||||
private View composePanel;
|
||||
protected Stub<ReminderView> reminderView;
|
||||
private Stub<UnverifiedBannerView> unverifiedBannerView;
|
||||
|
||||
private AttachmentTypeSelector attachmentTypeSelector;
|
||||
private AttachmentManager attachmentManager;
|
||||
@ -231,8 +243,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
private boolean isDefaultSms = true;
|
||||
private boolean isMmsEnabled = true;
|
||||
|
||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
private final IdentityRecordList identityRecords = new IdentityRecordList();
|
||||
private final DynamicTheme dynamicTheme = new DynamicTheme();
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
@ -308,6 +321,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
initializeEnabledCheck();
|
||||
initializeMmsEnabledCheck();
|
||||
initializeIdentityRecords();
|
||||
composeText.setTransport(sendButton.getSelectedTransport());
|
||||
|
||||
titleView.setTitle(recipients);
|
||||
@ -317,7 +331,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
MessageNotifier.setVisibleThread(threadId);
|
||||
markThreadAsRead();
|
||||
markIdentitySeen();
|
||||
|
||||
Log.w(TAG, "onResume() Finished: " + (System.currentTimeMillis() - getIntent().getLongExtra(TIMING_EXTRA, 0)));
|
||||
}
|
||||
@ -854,6 +867,56 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void handleUnverifiedRecipients() {
|
||||
List<Recipient> unverifiedRecipients = identityRecords.getUnverifiedRecipients(this);
|
||||
List<IdentityRecord> unverifiedRecords = identityRecords.getUnverifiedRecords();
|
||||
String message = IdentityUtil.getUnverifiedSendDialogDescription(this, unverifiedRecipients);
|
||||
|
||||
if (message == null) return;
|
||||
|
||||
new UnverifiedSendDialog(this, message, unverifiedRecords, new UnverifiedSendDialog.ResendListener() {
|
||||
@Override
|
||||
public void onResendMessage() {
|
||||
initializeIdentityRecords().addListener(new ListenableFuture.Listener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
sendMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void handleUntrustedRecipients() {
|
||||
List<Recipient> untrustedRecipients = identityRecords.getUntrustedRecipients(this);
|
||||
List<IdentityRecord> untrustedRecords = identityRecords.getUntrustedRecords();
|
||||
String untrustedMessage = IdentityUtil.getUntrustedSendDialogDescription(this, untrustedRecipients);
|
||||
|
||||
if (untrustedMessage == null) return;
|
||||
|
||||
new UntrustedSendDialog(this, untrustedMessage, untrustedRecords, new UntrustedSendDialog.ResendListener() {
|
||||
@Override
|
||||
public void onResendMessage() {
|
||||
initializeIdentityRecords().addListener(new ListenableFuture.Listener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
sendMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(ExecutionException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
private void handleSecurityChange(boolean isSecureText, boolean isDefaultSms) {
|
||||
this.isSecureText = isSecureText;
|
||||
this.isDefaultSms = isDefaultSms;
|
||||
@ -1027,6 +1090,64 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> initializeIdentityRecords() {
|
||||
final SettableFuture<Boolean> future = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Recipients, Void, Pair<IdentityRecordList, String>>() {
|
||||
@Override
|
||||
protected @NonNull Pair<IdentityRecordList, String> doInBackground(Recipients... params) {
|
||||
try {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
|
||||
IdentityRecordList identityRecordList = new IdentityRecordList();
|
||||
Recipients recipients = params[0];
|
||||
|
||||
if (recipients.isGroupRecipient()) {
|
||||
recipients = DatabaseFactory.getGroupDatabase(ConversationActivity.this)
|
||||
.getGroupMembers(GroupUtil.getDecodedId(recipients.getPrimaryRecipient().getNumber()), false);
|
||||
}
|
||||
|
||||
for (long recipientId : recipients.getIds()) {
|
||||
Log.w(TAG, "Loading identity for: " + recipientId);
|
||||
identityRecordList.add(identityDatabase.getIdentity(recipientId));
|
||||
}
|
||||
|
||||
String message = null;
|
||||
|
||||
if (identityRecordList.isUnverified()) {
|
||||
message = IdentityUtil.getUnverifiedBannerDescription(ConversationActivity.this, identityRecordList.getUnverifiedRecipients(ConversationActivity.this));
|
||||
}
|
||||
|
||||
return new Pair<>(identityRecordList, message);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull Pair<IdentityRecordList, String> result) {
|
||||
Log.w(TAG, "Got identity records: " + result.first.isUnverified());
|
||||
identityRecords.replaceWith(result.first);
|
||||
|
||||
if (result.second != null) {
|
||||
Log.w(TAG, "Replacing banner...");
|
||||
unverifiedBannerView.get().display(result.second, result.first.getUnverifiedRecords(),
|
||||
new UnverifiedClickedListener(),
|
||||
new UnverifiedDismissedListener());
|
||||
} else if (unverifiedBannerView.resolved()) {
|
||||
Log.w(TAG, "Clearing banner...");
|
||||
unverifiedBannerView.get().hide();
|
||||
}
|
||||
|
||||
titleView.setVerified(identityRecords.isVerified());
|
||||
|
||||
future.set(true);
|
||||
}
|
||||
|
||||
}.execute(recipients);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
private void initializeViews() {
|
||||
titleView = (ConversationTitleView) getSupportActionBar().getCustomView();
|
||||
buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
|
||||
@ -1040,6 +1161,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
composePanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
container = ViewUtil.findById(this, R.id.layout_container);
|
||||
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
|
||||
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
|
||||
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);
|
||||
@ -1157,6 +1279,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
public void run() {
|
||||
titleView.setTitle(recipients);
|
||||
titleView.setVerified(identityRecords.isVerified());
|
||||
setBlockedUserState(recipients, isSecureText, isDefaultSms);
|
||||
setActionBarColor(recipients.getColor());
|
||||
invalidateOptionsMenu();
|
||||
@ -1172,6 +1295,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onIdentityRecordUpdate(final IdentityRecord event) {
|
||||
initializeIdentityRecords();
|
||||
}
|
||||
|
||||
private void initializeReceivers() {
|
||||
securityUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
@ -1443,17 +1571,6 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}.execute(threadId);
|
||||
}
|
||||
|
||||
private void markIdentitySeen() {
|
||||
new AsyncTask<Recipient, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Recipient... params) {
|
||||
DatabaseFactory.getIdentityDatabase(ConversationActivity.this)
|
||||
.setSeen(params[0].getRecipientId());
|
||||
return null;
|
||||
}
|
||||
}.execute(recipients.getPrimaryRecipient());
|
||||
}
|
||||
|
||||
protected void sendComplete(long threadId) {
|
||||
boolean refreshFragment = (threadId != this.threadId);
|
||||
this.threadId = threadId;
|
||||
@ -1490,6 +1607,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
if ((!recipients.isSingleRecipient() || recipients.isEmailRecipient()) && !isMmsEnabled) {
|
||||
handleManualMmsRequired();
|
||||
} else if (!forceSms && identityRecords.isUnverified()) {
|
||||
handleUnverifiedRecipients();
|
||||
} else if (!forceSms && identityRecords.isUntrusted()) {
|
||||
handleUntrustedRecipients();
|
||||
} else if (attachmentManager.isAttachmentPresent() || !recipients.isSingleRecipient() || recipients.isGroupRecipient() || recipients.isEmailRecipient()) {
|
||||
sendMediaMessage(forceSms, expiresIn, subscriptionId);
|
||||
} else {
|
||||
@ -1886,4 +2007,68 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UnverifiedDismissedListener implements UnverifiedBannerView.DismissListener {
|
||||
@Override
|
||||
public void onDismissed(final List<IdentityRecord> unverifiedIdentities) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(ConversationActivity.this);
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
for (IdentityRecord identityRecord : unverifiedIdentities) {
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
initializeIdentityRecords();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
}
|
||||
|
||||
private class UnverifiedClickedListener implements UnverifiedBannerView.ClickListener {
|
||||
@Override
|
||||
public void onClicked(final List<IdentityRecord> unverifiedIdentities) {
|
||||
Log.w(TAG, "onClicked: " + unverifiedIdentities.size());
|
||||
if (unverifiedIdentities.size() == 1) {
|
||||
Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, unverifiedIdentities.get(0).getRecipientId());
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(0).getIdentityKey()));
|
||||
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
|
||||
|
||||
startActivity(intent);
|
||||
} else {
|
||||
String[] unverifiedNames = new String[unverifiedIdentities.size()];
|
||||
|
||||
for (int i=0;i<unverifiedIdentities.size();i++) {
|
||||
unverifiedNames[i] = RecipientFactory.getRecipientForId(ConversationActivity.this, unverifiedIdentities.get(i).getRecipientId(), false).toShortString();
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this);
|
||||
builder.setIconAttribute(R.attr.dialog_alert_icon);
|
||||
builder.setTitle("No longer verified");
|
||||
builder.setItems(unverifiedNames, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(ConversationActivity.this, VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, unverifiedIdentities.get(which).getRecipientId());
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(unverifiedIdentities.get(which).getIdentityKey()));
|
||||
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
|
||||
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
builder.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +240,10 @@ public class ConversationAdapter <V extends View & BindableConversationItem>
|
||||
@Override
|
||||
public int getItemViewType(@NonNull MessageRecord messageRecord) {
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() || messageRecord.isJoined() ||
|
||||
messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() || messageRecord.isIdentityUpdate()) {
|
||||
messageRecord.isExpirationTimerUpdate() || messageRecord.isEndSession() ||
|
||||
messageRecord.isIdentityUpdate() || messageRecord.isIdentityVerified() ||
|
||||
messageRecord.isIdentityDefault())
|
||||
{
|
||||
return MESSAGE_TYPE_UPDATE;
|
||||
} else if (hasAudio(messageRecord)) {
|
||||
if (messageRecord.isOutgoing()) return MESSAGE_TYPE_AUDIO_OUTGOING;
|
||||
|
@ -223,7 +223,8 @@ public class ConversationFragment extends Fragment
|
||||
for (MessageRecord messageRecord : messageRecords) {
|
||||
if (messageRecord.isGroupAction() || messageRecord.isCallLog() ||
|
||||
messageRecord.isJoined() || messageRecord.isExpirationTimerUpdate() ||
|
||||
messageRecord.isEndSession() || messageRecord.isIdentityUpdate())
|
||||
messageRecord.isEndSession() || messageRecord.isIdentityUpdate() ||
|
||||
messageRecord.isIdentityVerified() || messageRecord.isIdentityDefault())
|
||||
{
|
||||
actionMessage = true;
|
||||
break;
|
||||
|
@ -5,6 +5,7 @@ import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@ -18,6 +19,7 @@ public class ConversationTitleView extends LinearLayout {
|
||||
|
||||
private TextView title;
|
||||
private TextView subtitle;
|
||||
private ImageView verified;
|
||||
|
||||
public ConversationTitleView(Context context) {
|
||||
this(context, null);
|
||||
@ -32,8 +34,9 @@ public class ConversationTitleView extends LinearLayout {
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
this.title = (TextView) findViewById(R.id.title);
|
||||
this.subtitle = (TextView) findViewById(R.id.subtitle);
|
||||
this.title = (TextView) findViewById(R.id.title);
|
||||
this.subtitle = (TextView) findViewById(R.id.subtitle);
|
||||
this.verified = (ImageView) findViewById(R.id.verified_indicator);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.title, getContext());
|
||||
ViewUtil.setTextViewGravityStart(this.subtitle, getContext());
|
||||
@ -53,6 +56,10 @@ public class ConversationTitleView extends LinearLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void setVerified(boolean verified) {
|
||||
this.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void setComposeTitle() {
|
||||
this.title.setText(R.string.ConversationActivity_compose_message);
|
||||
this.subtitle.setText(null);
|
||||
|
@ -16,6 +16,8 @@ import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
@ -24,7 +26,6 @@ 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.IdentityKey;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Locale;
|
||||
@ -96,6 +97,8 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
else if (messageRecord.isExpirationTimerUpdate()) setTimerRecord(messageRecord);
|
||||
else if (messageRecord.isEndSession()) setEndSessionRecord(messageRecord);
|
||||
else if (messageRecord.isIdentityUpdate()) setIdentityRecord(messageRecord);
|
||||
else if (messageRecord.isIdentityVerified() ||
|
||||
messageRecord.isIdentityDefault()) setIdentityVerifyUpdate(messageRecord);
|
||||
else throw new AssertionError("Neither group nor log nor joined.");
|
||||
|
||||
if (batchSelected.contains(messageRecord)) setSelected(true);
|
||||
@ -132,6 +135,15 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
date.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setIdentityVerifyUpdate(final MessageRecord messageRecord) {
|
||||
if (messageRecord.isIdentityVerified()) icon.setImageResource(R.drawable.ic_check_white_24dp);
|
||||
else icon.setImageResource(R.drawable.ic_info_outline_white_24dp);
|
||||
|
||||
icon.setColorFilter(new PorterDuffColorFilter(Color.parseColor("#757575"), PorterDuff.Mode.MULTIPLY));
|
||||
body.setText(messageRecord.getDisplayBody());
|
||||
date.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setGroupRecord(MessageRecord messageRecord) {
|
||||
icon.setImageResource(R.drawable.ic_group_grey600_24dp);
|
||||
icon.clearColorFilter();
|
||||
@ -193,20 +205,25 @@ public class ConversationUpdateItem extends LinearLayout
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (!messageRecord.isIdentityUpdate() || !batchSelected.isEmpty()) {
|
||||
if ((!messageRecord.isIdentityUpdate() &&
|
||||
!messageRecord.isIdentityDefault() &&
|
||||
!messageRecord.isIdentityVerified()) ||
|
||||
!batchSelected.isEmpty())
|
||||
{
|
||||
if (parent != null) parent.onClick(v);
|
||||
return;
|
||||
}
|
||||
|
||||
final Recipient sender = ConversationUpdateItem.this.sender;
|
||||
|
||||
IdentityUtil.getRemoteIdentityKey(getContext(), masterSecret, sender).addListener(new ListenableFuture.Listener<Optional<IdentityKey>>() {
|
||||
IdentityUtil.getRemoteIdentityKey(getContext(), sender).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
|
||||
@Override
|
||||
public void onSuccess(Optional<IdentityKey> result) {
|
||||
public void onSuccess(Optional<IdentityRecord> result) {
|
||||
if (result.isPresent()) {
|
||||
Intent intent = new Intent(getContext(), VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, sender.getRecipientId());
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(result.get()));
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, sender.getRecipientId());
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(result.get().getIdentityKey()));
|
||||
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, result.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
|
||||
|
||||
getContext().startActivity(intent);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import android.support.v4.app.Fragment;
|
||||
import android.support.v4.preference.PreferenceFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
@ -32,6 +33,8 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.RecipientPreferenceDatabase.VibrateState;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
@ -302,9 +305,9 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
if (recipients.isBlocked()) blockPreference.setTitle(R.string.RecipientPreferenceActivity_unblock);
|
||||
else blockPreference.setTitle(R.string.RecipientPreferenceActivity_block);
|
||||
|
||||
IdentityUtil.getRemoteIdentityKey(getActivity(), masterSecret, recipients.getPrimaryRecipient()).addListener(new ListenableFuture.Listener<Optional<IdentityKey>>() {
|
||||
IdentityUtil.getRemoteIdentityKey(getActivity(), recipients.getPrimaryRecipient()).addListener(new ListenableFuture.Listener<Optional<IdentityRecord>>() {
|
||||
@Override
|
||||
public void onSuccess(Optional<IdentityKey> result) {
|
||||
public void onSuccess(Optional<IdentityRecord> result) {
|
||||
if (result.isPresent()) {
|
||||
if (identityPreference != null) identityPreference.setOnPreferenceClickListener(new IdentityClickedListener(result.get()));
|
||||
if (identityPreference != null) identityPreference.setEnabled(true);
|
||||
@ -458,17 +461,19 @@ public class RecipientPreferenceActivity extends PassphraseRequiredActionBarActi
|
||||
|
||||
private class IdentityClickedListener implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private final IdentityKey identityKey;
|
||||
private final IdentityRecord identityKey;
|
||||
|
||||
private IdentityClickedListener(IdentityKey identityKey) {
|
||||
private IdentityClickedListener(IdentityRecord identityKey) {
|
||||
Log.w(TAG, "Identity record: " + identityKey);
|
||||
this.identityKey = identityKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent verifyIdentityIntent = new Intent(getActivity(), VerifyIdentityActivity.class);
|
||||
verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, recipients.getPrimaryRecipient().getRecipientId());
|
||||
verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(identityKey));
|
||||
verifyIdentityIntent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, recipients.getPrimaryRecipient().getRecipientId());
|
||||
verifyIdentityIntent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey.getIdentityKey()));
|
||||
verifyIdentityIntent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, identityKey.getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED);
|
||||
startActivity(verifyIdentityIntent);
|
||||
|
||||
return true;
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.animation.TypeEvaluator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -26,13 +28,18 @@ 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.animation.AnimatorCompatHelper;
|
||||
import android.support.v4.animation.ValueAnimatorCompat;
|
||||
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;
|
||||
@ -49,7 +56,7 @@ import android.view.animation.Animation;
|
||||
import android.view.animation.AnticipateInterpolator;
|
||||
import android.view.animation.OvershootInterpolator;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@ -59,14 +66,18 @@ import org.thoughtcrime.securesms.components.camera.CameraView;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceVerifiedUpdateJob;
|
||||
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.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
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;
|
||||
@ -80,6 +91,8 @@ import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
/**
|
||||
* Activity for verifying identity keys.
|
||||
*
|
||||
@ -89,8 +102,9 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
private static final String TAG = VerifyIdentityActivity.class.getSimpleName();
|
||||
|
||||
public static final String RECIPIENT_ID = "recipient_id";
|
||||
public static final String RECIPIENT_IDENTITY = "recipient_identity";
|
||||
public static final String RECIPIENT_ID_EXTRA = "recipient_id";
|
||||
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();
|
||||
@ -110,16 +124,18 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setTitle(R.string.AndroidManifest__verify_safety_number);
|
||||
|
||||
Recipient recipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(RECIPIENT_ID, -1), true);
|
||||
Recipient recipient = RecipientFactory.getRecipientForId(this, getIntent().getLongExtra(RECIPIENT_ID_EXTRA, -1), true);
|
||||
recipient.addListener(this);
|
||||
|
||||
setActionBarNotificationBarColor(recipient.getColor());
|
||||
|
||||
Bundle extras = new Bundle();
|
||||
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(RECIPIENT_IDENTITY));
|
||||
extras.putLong(VerifyDisplayFragment.REMOTE_RECIPIENT_ID, getIntent().getLongExtra(RECIPIENT_ID_EXTRA, -1));
|
||||
extras.putParcelable(VerifyDisplayFragment.REMOTE_IDENTITY, getIntent().getParcelableExtra(IDENTITY_EXTRA));
|
||||
extras.putString(VerifyDisplayFragment.REMOTE_NUMBER, Util.canonicalizeNumber(this, recipient.getNumber()));
|
||||
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);
|
||||
@ -212,16 +228,19 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
|
||||
|
||||
public static class VerifyDisplayFragment extends Fragment implements Recipients.RecipientsModifiedListener {
|
||||
public static class VerifyDisplayFragment extends Fragment implements Recipient.RecipientModifiedListener, CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
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 REMOTE_RECIPIENT_ID = "remote_recipient_id";
|
||||
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 Recipients recipient;
|
||||
private String localNumber;
|
||||
private String remoteNumber;
|
||||
private MasterSecret masterSecret;
|
||||
private Recipient recipient;
|
||||
private String localNumber;
|
||||
private String remoteNumber;
|
||||
|
||||
private IdentityKey localIdentity;
|
||||
private IdentityKey remoteIdentity;
|
||||
@ -232,8 +251,10 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
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;
|
||||
@ -244,8 +265,10 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
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);
|
||||
@ -262,6 +285,9 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
this.qrCode.setOnClickListener(clickListener);
|
||||
this.registerForContextMenu(numbersContainer);
|
||||
|
||||
this.verified.setChecked(getArguments().getBoolean(VERIFIED_STATE, false));
|
||||
this.verified.setOnCheckedChangeListener(this);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@ -269,23 +295,37 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
|
||||
this.masterSecret = getArguments().getParcelable("master_secret");
|
||||
this.localNumber = getArguments().getString(LOCAL_NUMBER);
|
||||
this.localIdentity = ((IdentityKeyParcelable)getArguments().getParcelable(LOCAL_IDENTITY)).get();
|
||||
this.remoteNumber = getArguments().getString(REMOTE_NUMBER);
|
||||
this.recipient = RecipientFactory.getRecipientsFromString(getActivity(), this.remoteNumber, true);
|
||||
this.recipient = RecipientFactory.getRecipientForId(getActivity(), getArguments().getLong(REMOTE_RECIPIENT_ID), true);
|
||||
this.remoteIdentity = ((IdentityKeyParcelable)getArguments().getParcelable(REMOTE_IDENTITY)).get();
|
||||
this.fingerprint = new NumericFingerprintGenerator(5200).createFor(localNumber, localIdentity,
|
||||
remoteNumber, remoteIdentity);
|
||||
|
||||
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);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onModified(Recipients recipients) {
|
||||
public void onModified(final Recipient recipient) {
|
||||
Util.runOnMain(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setFingerprintViews(fingerprint);
|
||||
setRecipientText(recipient);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -294,7 +334,11 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
setFingerprintViews(fingerprint);
|
||||
setRecipientText(recipient);
|
||||
|
||||
if (fingerprint != null) {
|
||||
setFingerprintViews(fingerprint, false);
|
||||
}
|
||||
|
||||
if (animateSuccessOnDraw) {
|
||||
animateSuccessOnDraw = false;
|
||||
@ -398,12 +442,19 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void setFingerprintViews(Fingerprint fingerprint) {
|
||||
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 digits = fingerprint.getDisplayableFingerprint().getDisplayText();
|
||||
int partSize = digits.length() / codes.length;
|
||||
|
||||
for (int i=0;i<codes.length;i++) {
|
||||
codes[i].setText(digits.substring(i * partSize, (i * partSize) + partSize));
|
||||
String substring = digits.substring(i * partSize, (i * partSize) + partSize);
|
||||
if (animate) setCodeSegment(codes[i], substring);
|
||||
else codes[i].setText(substring);
|
||||
}
|
||||
|
||||
byte[] qrCodeData = fingerprint.getScannableFingerprint().getSerialized();
|
||||
@ -411,8 +462,41 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
Bitmap qrCodeBitmap = QrCode.create(qrCodeString);
|
||||
|
||||
qrCode.setImageBitmap(qrCodeBitmap);
|
||||
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());
|
||||
|
||||
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 Bitmap createVerifiedBitmap(int width, int height, @DrawableRes int id) {
|
||||
@ -478,6 +562,41 @@ public class VerifyIdentityActivity extends PassphraseRequiredActionBarActivity
|
||||
|
||||
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].getRecipientId());
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.saveIdentity(params[0].getRecipientId(),
|
||||
remoteIdentity,
|
||||
VerifiedStatus.VERIFIED, false,
|
||||
System.currentTimeMillis(), true);
|
||||
} else {
|
||||
DatabaseFactory.getIdentityDatabase(getActivity())
|
||||
.setVerified(params[0].getRecipientId(),
|
||||
remoteIdentity,
|
||||
VerifiedStatus.DEFAULT);
|
||||
}
|
||||
|
||||
ApplicationContext.getInstance(getActivity())
|
||||
.getJobManager()
|
||||
.add(new MultiDeviceVerifiedUpdateJob(getActivity(),
|
||||
recipient.getNumber(),
|
||||
remoteIdentity,
|
||||
isChecked ? VerifiedStatus.VERIFIED :
|
||||
VerifiedStatus.DEFAULT));
|
||||
|
||||
IdentityUtil.markIdentityVerified(getActivity(), new MasterSecretUnion(masterSecret), recipient, isChecked, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}.execute(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VerifyScanFragment extends Fragment {
|
||||
|
@ -258,7 +258,7 @@ public class WebRtcCallActivity extends Activity {
|
||||
public void onClick(View v) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(WebRtcCallActivity.this);
|
||||
identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.getNumber(), 1), theirIdentity, true, true);
|
||||
identityKeyStore.saveIdentity(new SignalProtocolAddress(recipient.getNumber(), 1), theirIdentity, true);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
|
||||
|
@ -0,0 +1,66 @@
|
||||
package org.thoughtcrime.securesms.components.identity;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class UntrustedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
|
||||
|
||||
private final List<IdentityRecord> untrustedRecords;
|
||||
private final ResendListener resendListener;
|
||||
|
||||
public UntrustedSendDialog(@NonNull Context context,
|
||||
@NonNull String message,
|
||||
@NonNull List<IdentityRecord> untrustedRecords,
|
||||
@NonNull ResendListener resendListener)
|
||||
{
|
||||
super(context);
|
||||
this.untrustedRecords = untrustedRecords;
|
||||
this.resendListener = resendListener;
|
||||
|
||||
setTitle(R.string.UntrustedSendDialog_send_message);
|
||||
setIconAttribute(R.attr.dialog_alert_icon);
|
||||
setMessage(message);
|
||||
setPositiveButton(R.string.UntrustedSendDialog_send, this);
|
||||
setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
resendListener.onResendMessage();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public interface ResendListener {
|
||||
public void onResendMessage();
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package org.thoughtcrime.securesms.components.identity;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UnverifiedBannerView extends LinearLayout {
|
||||
|
||||
private static final String TAG = UnverifiedBannerView.class.getSimpleName();
|
||||
|
||||
private View container;
|
||||
private TextView text;
|
||||
private ImageView closeButton;
|
||||
|
||||
public UnverifiedBannerView(Context context) {
|
||||
super(context);
|
||||
initialize();
|
||||
}
|
||||
|
||||
public UnverifiedBannerView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
|
||||
public UnverifiedBannerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public UnverifiedBannerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize();
|
||||
}
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.unverified_banner_view, this, true);
|
||||
this.container = ViewUtil.findById(this, R.id.container);
|
||||
this.text = ViewUtil.findById(this, R.id.unverified_text);
|
||||
this.closeButton = ViewUtil.findById(this, R.id.cancel);
|
||||
}
|
||||
|
||||
public void display(@NonNull final String text,
|
||||
@NonNull final List<IdentityRecord> unverifiedIdentities,
|
||||
@NonNull final ClickListener clickListener,
|
||||
@NonNull final DismissListener dismissListener)
|
||||
{
|
||||
this.text.setText(text);
|
||||
setVisibility(View.VISIBLE);
|
||||
|
||||
this.container.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Log.w(TAG, "onClick()");
|
||||
clickListener.onClicked(unverifiedIdentities);
|
||||
}
|
||||
});
|
||||
|
||||
this.closeButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
hide();
|
||||
dismissListener.onDismissed(unverifiedIdentities);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void hide() {
|
||||
setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public interface DismissListener {
|
||||
public void onDismissed(List<IdentityRecord> unverifiedIdentities);
|
||||
}
|
||||
|
||||
public interface ClickListener {
|
||||
public void onClicked(List<IdentityRecord> unverifiedIdentities);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
package org.thoughtcrime.securesms.components.identity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class UnverifiedSendDialog extends AlertDialog.Builder implements DialogInterface.OnClickListener {
|
||||
|
||||
private final List<IdentityRecord> untrustedRecords;
|
||||
private final ResendListener resendListener;
|
||||
|
||||
public UnverifiedSendDialog(@NonNull Context context,
|
||||
@NonNull String message,
|
||||
@NonNull List<IdentityRecord> untrustedRecords,
|
||||
@NonNull ResendListener resendListener)
|
||||
{
|
||||
super(context);
|
||||
this.untrustedRecords = untrustedRecords;
|
||||
this.resendListener = resendListener;
|
||||
|
||||
setTitle(R.string.UnverifiedSendDialog_send_message);
|
||||
setIconAttribute(R.attr.dialog_alert_icon);
|
||||
setMessage(message);
|
||||
setPositiveButton(R.string.UnverifiedSendDialog_send, this);
|
||||
setNegativeButton(android.R.string.cancel, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setVerified(identityRecord.getRecipientId(),
|
||||
identityRecord.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
resendListener.onResendMessage();
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public interface ResendListener {
|
||||
public void onResendMessage();
|
||||
}
|
||||
}
|
@ -6,9 +6,12 @@ import android.support.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SessionUtil {
|
||||
|
||||
public static boolean hasSession(Context context, MasterSecret masterSecret, Recipient recipient) {
|
||||
@ -21,4 +24,23 @@ public class SessionUtil {
|
||||
|
||||
return sessionStore.containsSession(axolotlAddress);
|
||||
}
|
||||
|
||||
public static void archiveSiblingSessions(Context context, SignalProtocolAddress address) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
List<Integer> devices = sessionStore.getSubDeviceSessions(address.getName());
|
||||
devices.add(1);
|
||||
|
||||
for (int device : devices) {
|
||||
if (device != address.getDeviceId()) {
|
||||
SignalProtocolAddress sibling = new SignalProtocolAddress(address.getName(), device);
|
||||
|
||||
if (sessionStore.containsSession(sibling)) {
|
||||
SessionRecord sessionRecord = sessionStore.loadSession(sibling);
|
||||
sessionRecord.archiveCurrentState();
|
||||
sessionStore.storeSession(sibling, sessionRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,11 @@ 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.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.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
@ -42,9 +44,7 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
return TextSecurePreferences.getLocalRegistrationId(context);
|
||||
}
|
||||
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey,
|
||||
boolean blockingApproval, boolean nonBlockingApproval)
|
||||
{
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey, boolean nonBlockingApproval) {
|
||||
synchronized (LOCK) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, address.getName(), true);
|
||||
@ -53,20 +53,29 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
if (!identityRecord.isPresent()) {
|
||||
Log.w(TAG, "Saving new identity...");
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, true, System.currentTimeMillis(), blockingApproval, nonBlockingApproval);
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, VerifiedStatus.DEFAULT, true, System.currentTimeMillis(), nonBlockingApproval);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!identityRecord.get().getIdentityKey().equals(identityKey)) {
|
||||
Log.w(TAG, "Replacing existing identity...");
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, false, System.currentTimeMillis(), blockingApproval, nonBlockingApproval);
|
||||
VerifiedStatus verifiedStatus;
|
||||
|
||||
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED) {
|
||||
verifiedStatus = VerifiedStatus.UNVERIFIED;
|
||||
} else {
|
||||
verifiedStatus = VerifiedStatus.DEFAULT;
|
||||
}
|
||||
|
||||
identityDatabase.saveIdentity(recipientId, identityKey, verifiedStatus, false, System.currentTimeMillis(), nonBlockingApproval);
|
||||
IdentityUtil.markIdentityUpdate(context, recipients.getPrimaryRecipient());
|
||||
SessionUtil.archiveSiblingSessions(context, address);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isBlockingApprovalRequired(identityRecord.get()) || isNonBlockingApprovalRequired(identityRecord.get())) {
|
||||
if (isNonBlockingApprovalRequired(identityRecord.get())) {
|
||||
Log.w(TAG, "Setting approval status...");
|
||||
identityDatabase.setApproval(recipientId, blockingApproval, nonBlockingApproval);
|
||||
identityDatabase.setApproval(recipientId, nonBlockingApproval);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -76,7 +85,7 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
@Override
|
||||
public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
|
||||
return saveIdentity(address, identityKey, !TextSecurePreferences.isSendingIdentityApprovalRequired(context), false);
|
||||
return saveIdentity(address, identityKey, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,8 +119,8 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isBlockingApprovalRequired(identityRecord.get())) {
|
||||
Log.w(TAG, "Needs blocking approval!");
|
||||
if (identityRecord.get().getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
Log.w(TAG, "Needs unverified approval!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -123,12 +132,6 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isBlockingApprovalRequired(IdentityRecord identityRecord) {
|
||||
return !identityRecord.isFirstUse() &&
|
||||
TextSecurePreferences.isSendingIdentityApprovalRequired(context) &&
|
||||
!identityRecord.isApprovedBlocking();
|
||||
}
|
||||
|
||||
private boolean isNonBlockingApprovalRequired(IdentityRecord identityRecord) {
|
||||
return !identityRecord.isFirstUse() &&
|
||||
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(TIMESTAMP_THRESHOLD_SECONDS) &&
|
||||
|
@ -871,14 +871,11 @@ public class DatabaseFactory {
|
||||
if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) {
|
||||
db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE identities ADD COLUMN seen INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE identities ADD COLUMN blocking_approval INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0");
|
||||
db.execSQL("ALTER TABLE identities ADD COLuMN verified INTEGER DEFAULT 0");
|
||||
|
||||
db.execSQL("DROP INDEX archived_index");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)");
|
||||
|
||||
db.execSQL("UPDATE identities SET blocking_approval = '1'");
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
|
@ -21,9 +21,10 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
@ -35,17 +36,14 @@ public class IdentityDatabase extends Database {
|
||||
|
||||
private static final String TAG = IdentityDatabase.class.getSimpleName();
|
||||
|
||||
private static final Uri CHANGE_URI = Uri.parse("content://textsecure/identities");
|
||||
|
||||
private static final String TABLE_NAME = "identities";
|
||||
private static final String ID = "_id";
|
||||
private static final String RECIPIENT = "recipient";
|
||||
private static final String IDENTITY_KEY = "key";
|
||||
private static final String TIMESTAMP = "timestamp";
|
||||
private static final String FIRST_USE = "first_use";
|
||||
private static final String SEEN = "seen";
|
||||
private static final String BLOCKING_APPROVAL = "blocking_approval";
|
||||
private static final String NONBLOCKING_APPROVAL = "nonblocking_approval";
|
||||
private static final String VERIFIED = "verified";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
@ -53,14 +51,41 @@ public class IdentityDatabase extends Database {
|
||||
IDENTITY_KEY + " TEXT, " +
|
||||
FIRST_USE + " INTEGER DEFAULT 0, " +
|
||||
TIMESTAMP + " INTEGER DEFAULT 0, " +
|
||||
SEEN + " INTEGER DEFAULT 0, " +
|
||||
BLOCKING_APPROVAL + " INTEGER DEFAULT 0, " +
|
||||
VERIFIED + " INTEGER DEFAULT 0, " +
|
||||
NONBLOCKING_APPROVAL + " INTEGER DEFAULT 0);";
|
||||
|
||||
public enum VerifiedStatus {
|
||||
DEFAULT, VERIFIED, UNVERIFIED;
|
||||
|
||||
public int toInt() {
|
||||
if (this == DEFAULT) return 0;
|
||||
else if (this == VERIFIED) return 1;
|
||||
else if (this == UNVERIFIED) return 2;
|
||||
else throw new AssertionError();
|
||||
}
|
||||
|
||||
public static VerifiedStatus forState(int state) {
|
||||
if (state == 0) return DEFAULT;
|
||||
else if (state == 1) return VERIFIED;
|
||||
else if (state == 2) return UNVERIFIED;
|
||||
else throw new AssertionError("No such state: " + state);
|
||||
}
|
||||
}
|
||||
|
||||
IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
|
||||
public Cursor getIdentities() {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
return database.query(TABLE_NAME, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public @Nullable IdentityReader readerFor(@Nullable Cursor cursor) {
|
||||
if (cursor == null) return null;
|
||||
return new IdentityReader(cursor);
|
||||
}
|
||||
|
||||
public Optional<IdentityRecord> getIdentity(long recipientId) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
Cursor cursor = null;
|
||||
@ -70,15 +95,7 @@ public class IdentityDatabase extends Database {
|
||||
new String[] {recipientId + ""}, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||
long seen = cursor.getLong(cursor.getColumnIndexOrThrow(SEEN));
|
||||
boolean blockingApproval = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKING_APPROVAL)) == 1;
|
||||
boolean nonblockingApproval = cursor.getInt(cursor.getColumnIndexOrThrow(NONBLOCKING_APPROVAL)) == 1;
|
||||
boolean firstUse = cursor.getInt(cursor.getColumnIndexOrThrow(FIRST_USE)) == 1;
|
||||
IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
||||
|
||||
return Optional.of(new IdentityRecord(identity, firstUse, timestamp, seen, blockingApproval, nonblockingApproval));
|
||||
return Optional.of(getIdentityRecord(cursor));
|
||||
}
|
||||
} catch (InvalidKeyException | IOException e) {
|
||||
throw new AssertionError(e);
|
||||
@ -89,8 +106,8 @@ public class IdentityDatabase extends Database {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public void saveIdentity(long recipientId, IdentityKey identityKey, boolean firstUse,
|
||||
long timestamp, boolean blockingApproval, boolean nonBlockingApproval)
|
||||
public void saveIdentity(long recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus,
|
||||
boolean firstUse, long timestamp, boolean nonBlockingApproval)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
||||
@ -99,60 +116,74 @@ public class IdentityDatabase extends Database {
|
||||
contentValues.put(RECIPIENT, recipientId);
|
||||
contentValues.put(IDENTITY_KEY, identityKeyString);
|
||||
contentValues.put(TIMESTAMP, timestamp);
|
||||
contentValues.put(BLOCKING_APPROVAL, blockingApproval ? 1 : 0);
|
||||
contentValues.put(VERIFIED, verifiedStatus.toInt());
|
||||
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval ? 1 : 0);
|
||||
contentValues.put(FIRST_USE, firstUse ? 1 : 0);
|
||||
contentValues.put(SEEN, 0);
|
||||
|
||||
database.replace(TABLE_NAME, null, contentValues);
|
||||
|
||||
context.getContentResolver().notifyChange(CHANGE_URI, null);
|
||||
EventBus.getDefault().post(new IdentityRecord(recipientId, identityKey, verifiedStatus,
|
||||
firstUse, timestamp, nonBlockingApproval));
|
||||
}
|
||||
|
||||
public void setApproval(long recipientId, boolean blockingApproval, boolean nonBlockingApproval) {
|
||||
public void setApproval(long recipientId, boolean nonBlockingApproval) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
ContentValues contentValues = new ContentValues(2);
|
||||
contentValues.put(BLOCKING_APPROVAL, blockingApproval);
|
||||
contentValues.put(NONBLOCKING_APPROVAL, nonBlockingApproval);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, RECIPIENT + " = ?",
|
||||
new String[] {String.valueOf(recipientId)});
|
||||
|
||||
context.getContentResolver().notifyChange(CHANGE_URI, null);
|
||||
}
|
||||
|
||||
public void setSeen(long recipientId) {
|
||||
Log.w(TAG, "Setting seen to current time: " + recipientId);
|
||||
public void setVerified(long recipientId, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(SEEN, System.currentTimeMillis());
|
||||
contentValues.put(VERIFIED, verifiedStatus.toInt());
|
||||
|
||||
database.update(TABLE_NAME, contentValues, RECIPIENT + " = ? AND " + SEEN + " = 0",
|
||||
new String[] {String.valueOf(recipientId)});
|
||||
database.update(TABLE_NAME, contentValues, RECIPIENT + " = ? AND " + IDENTITY_KEY + " = ?",
|
||||
new String[] {String.valueOf(recipientId),
|
||||
Base64.encodeBytes(identityKey.serialize())});
|
||||
}
|
||||
|
||||
private IdentityRecord getIdentityRecord(@NonNull Cursor cursor) throws IOException, InvalidKeyException {
|
||||
long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT));
|
||||
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP));
|
||||
int verifiedStatus = cursor.getInt(cursor.getColumnIndexOrThrow(VERIFIED));
|
||||
boolean nonblockingApproval = cursor.getInt(cursor.getColumnIndexOrThrow(NONBLOCKING_APPROVAL)) == 1;
|
||||
boolean firstUse = cursor.getInt(cursor.getColumnIndexOrThrow(FIRST_USE)) == 1;
|
||||
IdentityKey identity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
||||
|
||||
return new IdentityRecord(recipientId, identity, VerifiedStatus.forState(verifiedStatus), firstUse, timestamp, nonblockingApproval);
|
||||
}
|
||||
|
||||
public static class IdentityRecord {
|
||||
|
||||
private final IdentityKey identitykey;
|
||||
private final boolean firstUse;
|
||||
private final long timestamp;
|
||||
private final long seen;
|
||||
private final boolean blockingApproval;
|
||||
private final boolean nonblockingApproval;
|
||||
private final long recipientId;
|
||||
private final IdentityKey identitykey;
|
||||
private final VerifiedStatus verifiedStatus;
|
||||
private final boolean firstUse;
|
||||
private final long timestamp;
|
||||
private final boolean nonblockingApproval;
|
||||
|
||||
private IdentityRecord(IdentityKey identitykey, boolean firstUse, long timestamp,
|
||||
long seen, boolean blockingApproval, boolean nonblockingApproval)
|
||||
private IdentityRecord(long recipientId,
|
||||
IdentityKey identitykey, VerifiedStatus verifiedStatus,
|
||||
boolean firstUse, long timestamp, boolean nonblockingApproval)
|
||||
{
|
||||
this.recipientId = recipientId;
|
||||
this.identitykey = identitykey;
|
||||
this.verifiedStatus = verifiedStatus;
|
||||
this.firstUse = firstUse;
|
||||
this.timestamp = timestamp;
|
||||
this.seen = seen;
|
||||
this.blockingApproval = blockingApproval;
|
||||
this.nonblockingApproval = nonblockingApproval;
|
||||
}
|
||||
|
||||
public long getRecipientId() {
|
||||
return recipientId;
|
||||
}
|
||||
|
||||
public IdentityKey getIdentityKey() {
|
||||
return identitykey;
|
||||
}
|
||||
@ -161,12 +192,8 @@ public class IdentityDatabase extends Database {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public long getSeen() {
|
||||
return seen;
|
||||
}
|
||||
|
||||
public boolean isApprovedBlocking() {
|
||||
return blockingApproval;
|
||||
public VerifiedStatus getVerifiedStatus() {
|
||||
return verifiedStatus;
|
||||
}
|
||||
|
||||
public boolean isApprovedNonBlocking() {
|
||||
@ -177,6 +204,35 @@ public class IdentityDatabase extends Database {
|
||||
return firstUse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{recipientId: " + recipientId + ", identityKey: " + identitykey + ", verifiedStatus: " + verifiedStatus + ", firstUse: " + firstUse + "}";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class IdentityReader {
|
||||
private final Cursor cursor;
|
||||
|
||||
public IdentityReader(@NonNull Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
|
||||
public @Nullable IdentityRecord getNext() {
|
||||
if (cursor.moveToNext()) {
|
||||
try {
|
||||
return getIdentityRecord(cursor);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,15 +50,15 @@ public interface MmsSmsColumns {
|
||||
protected static final long MESSAGE_FORCE_SMS_BIT = 0x40;
|
||||
|
||||
// Key Exchange Information
|
||||
protected static final long KEY_EXCHANGE_MASK = 0xFF00;
|
||||
protected static final long KEY_EXCHANGE_BIT = 0x8000;
|
||||
protected static final long KEY_EXCHANGE_STALE_BIT = 0x4000;
|
||||
protected static final long KEY_EXCHANGE_PROCESSED_BIT = 0x2000;
|
||||
protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000;
|
||||
protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800;
|
||||
protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400;
|
||||
protected static final long KEY_EXCHANGE_IDENTITY_UPDATE_BIT = 0x200;
|
||||
protected static final long KEY_EXCHANGE_CONTENT_FORMAT = 0x100;
|
||||
protected static final long KEY_EXCHANGE_MASK = 0xFF00;
|
||||
protected static final long KEY_EXCHANGE_BIT = 0x8000;
|
||||
protected static final long KEY_EXCHANGE_IDENTITY_VERIFIED_BIT = 0x4000;
|
||||
protected static final long KEY_EXCHANGE_IDENTITY_DEFAULT_BIT = 0x2000;
|
||||
protected static final long KEY_EXCHANGE_CORRUPTED_BIT = 0x1000;
|
||||
protected static final long KEY_EXCHANGE_INVALID_VERSION_BIT = 0x800;
|
||||
protected static final long KEY_EXCHANGE_BUNDLE_BIT = 0x400;
|
||||
protected static final long KEY_EXCHANGE_IDENTITY_UPDATE_BIT = 0x200;
|
||||
protected static final long KEY_EXCHANGE_CONTENT_FORMAT = 0x100;
|
||||
|
||||
// Secure Message Information
|
||||
protected static final long SECURE_MESSAGE_BIT = 0x800000;
|
||||
@ -112,7 +112,7 @@ public interface MmsSmsColumns {
|
||||
public static boolean isPendingMessageType(long type) {
|
||||
return
|
||||
(type & BASE_TYPE_MASK) == BASE_OUTBOX_TYPE ||
|
||||
(type & BASE_TYPE_MASK) == BASE_SENDING_TYPE;
|
||||
(type & BASE_TYPE_MASK) == BASE_SENDING_TYPE;
|
||||
}
|
||||
|
||||
public static boolean isPendingSmsFallbackType(long type) {
|
||||
@ -152,12 +152,12 @@ public interface MmsSmsColumns {
|
||||
return (type & KEY_EXCHANGE_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isStaleKeyExchange(long type) {
|
||||
return (type & KEY_EXCHANGE_STALE_BIT) != 0;
|
||||
public static boolean isIdentityVerified(long type) {
|
||||
return (type & KEY_EXCHANGE_IDENTITY_VERIFIED_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isProcessedKeyExchange(long type) {
|
||||
return (type & KEY_EXCHANGE_PROCESSED_BIT) != 0;
|
||||
public static boolean isIdentityDefault(long type) {
|
||||
return (type & KEY_EXCHANGE_IDENTITY_DEFAULT_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isCorruptedKeyExchange(long type) {
|
||||
|
@ -521,6 +521,9 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
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;
|
||||
|
||||
Recipients recipients;
|
||||
|
||||
if (message.getSender() != null) {
|
||||
@ -540,7 +543,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
|
||||
boolean unread = (org.thoughtcrime.securesms.util.Util.isDefaultSmsProvider(context) ||
|
||||
message.isSecureMessage() || message.isGroup() || message.isPreKeyBundle()) &&
|
||||
!message.isIdentityUpdate();
|
||||
!message.isIdentityUpdate() && !message.isIdentityDefault() && !message.isIdentityVerified();
|
||||
|
||||
long threadId;
|
||||
|
||||
@ -577,7 +580,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
DatabaseFactory.getThreadDatabase(context).setUnread(threadId);
|
||||
}
|
||||
|
||||
if (!message.isIdentityUpdate()) {
|
||||
if (!message.isIdentityUpdate() && !message.isIdentityVerified() && !message.isIdentityDefault()) {
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId, true);
|
||||
}
|
||||
|
||||
@ -605,6 +608,9 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
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;
|
||||
|
||||
String address = message.getRecipients().getPrimaryRecipient().getNumber();
|
||||
|
||||
ContentValues contentValues = new ContentValues(6);
|
||||
|
@ -0,0 +1,116 @@
|
||||
package org.thoughtcrime.securesms.database.identity;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class IdentityRecordList {
|
||||
|
||||
private static final String TAG = IdentityRecordList.class.getSimpleName();
|
||||
|
||||
private final List<IdentityRecord> identityRecords = new LinkedList<>();
|
||||
|
||||
public void add(Optional<IdentityRecord> identityRecord) {
|
||||
if (identityRecord.isPresent()) {
|
||||
identityRecords.add(identityRecord.get());
|
||||
}
|
||||
}
|
||||
|
||||
public void replaceWith(IdentityRecordList identityRecordList) {
|
||||
identityRecords.clear();
|
||||
identityRecords.addAll(identityRecordList.identityRecords);
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (identityRecord.getVerifiedStatus() != VerifiedStatus.VERIFIED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return identityRecords.size() > 0;
|
||||
}
|
||||
|
||||
public boolean isUnverified() {
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isUntrusted() {
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (isUntrusted(identityRecord)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<IdentityRecord> getUntrustedRecords() {
|
||||
List<IdentityRecord> results = new LinkedList<>();
|
||||
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (isUntrusted(identityRecord)) {
|
||||
results.add(identityRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<Recipient> getUntrustedRecipients(Context context) {
|
||||
List<Recipient> untrusted = new LinkedList<>();
|
||||
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (isUntrusted(identityRecord)) {
|
||||
untrusted.add(RecipientFactory.getRecipientForId(context, identityRecord.getRecipientId(), false));
|
||||
}
|
||||
}
|
||||
|
||||
return untrusted;
|
||||
}
|
||||
|
||||
public List<IdentityRecord> getUnverifiedRecords() {
|
||||
List<IdentityRecord> results = new LinkedList<>();
|
||||
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
results.add(identityRecord);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<Recipient> getUnverifiedRecipients(Context context) {
|
||||
List<Recipient> unverified = new LinkedList<>();
|
||||
|
||||
for (IdentityRecord identityRecord : identityRecords) {
|
||||
if (identityRecord.getVerifiedStatus() == VerifiedStatus.UNVERIFIED) {
|
||||
unverified.add(RecipientFactory.getRecipientForId(context, identityRecord.getRecipientId(), false));
|
||||
}
|
||||
}
|
||||
|
||||
return unverified;
|
||||
}
|
||||
|
||||
private boolean isUntrusted(IdentityRecord identityRecord) {
|
||||
return !identityRecord.isApprovedNonBlocking() &&
|
||||
System.currentTimeMillis() - identityRecord.getTimestamp() < TimeUnit.SECONDS.toMillis(5);
|
||||
}
|
||||
|
||||
}
|
@ -70,7 +70,9 @@ public abstract class DisplayRecord {
|
||||
}
|
||||
|
||||
public boolean isPending() {
|
||||
return MmsSmsColumns.Types.isPendingMessageType(type);
|
||||
return MmsSmsColumns.Types.isPendingMessageType(type) &&
|
||||
!MmsSmsColumns.Types.isIdentityVerified(type) &&
|
||||
!MmsSmsColumns.Types.isIdentityDefault(type);
|
||||
}
|
||||
|
||||
public boolean isOutgoing() {
|
||||
|
@ -114,6 +114,12 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
: emphasisAdded(context.getString(R.string.MessageRecord_s_set_disappearing_message_time_to_s, getIndividualRecipient().toShortString(), time));
|
||||
} else if (isIdentityUpdate()) {
|
||||
return emphasisAdded(context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, getIndividualRecipient().toShortString()));
|
||||
} else if (isIdentityVerified()) {
|
||||
if (isOutgoing()) return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified, getIndividualRecipient().toShortString()));
|
||||
else return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_verified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
} else if (isIdentityDefault()) {
|
||||
if (isOutgoing()) return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified, getIndividualRecipient().toShortString()));
|
||||
else return emphasisAdded(context.getString(R.string.MessageRecord_you_marked_your_safety_number_with_s_unverified_from_another_device, getIndividualRecipient().toShortString()));
|
||||
} else if (getBody().getBody().length() > MAX_DISPLAY_LENGTH) {
|
||||
return new SpannableString(getBody().getBody().substring(0, MAX_DISPLAY_LENGTH));
|
||||
}
|
||||
@ -140,12 +146,12 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
return SmsDatabase.Types.isForcedSms(type);
|
||||
}
|
||||
|
||||
public boolean isStaleKeyExchange() {
|
||||
return SmsDatabase.Types.isStaleKeyExchange(type);
|
||||
public boolean isIdentityVerified() {
|
||||
return SmsDatabase.Types.isIdentityVerified(type);
|
||||
}
|
||||
|
||||
public boolean isProcessedKeyExchange() {
|
||||
return SmsDatabase.Types.isProcessedKeyExchange(type);
|
||||
public boolean isIdentityDefault() {
|
||||
return SmsDatabase.Types.isIdentityDefault(type);
|
||||
}
|
||||
|
||||
public boolean isIdentityMismatchFailure() {
|
||||
|
@ -64,10 +64,6 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
public SpannableString getDisplayBody() {
|
||||
if (SmsDatabase.Types.isFailedDecryptType(type)) {
|
||||
return emphasisAdded(context.getString(R.string.MessageDisplayHelper_bad_encrypted_message));
|
||||
} else if (isProcessedKeyExchange()) {
|
||||
return new SpannableString("");
|
||||
} else if (isStaleKeyExchange()) {
|
||||
return emphasisAdded(context.getString(R.string.ConversationItem_error_received_stale_key_exchange_message));
|
||||
} else if (isCorruptedKeyExchange()) {
|
||||
return emphasisAdded(context.getString(R.string.SmsMessageRecord_received_corrupted_key_exchange_message));
|
||||
} else if (isInvalidVersionKeyExchange()) {
|
||||
|
@ -105,6 +105,10 @@ public class ThreadRecord extends DisplayRecord {
|
||||
} else if (SmsDatabase.Types.isIdentityUpdate(type)) {
|
||||
if (getRecipients().isGroupRecipient()) return emphasisAdded(context.getString(R.string.ThreadRecord_safety_number_changed));
|
||||
else return emphasisAdded(context.getString(R.string.ThreadRecord_your_safety_number_with_s_has_changed, getRecipients().getPrimaryRecipient().toShortString()));
|
||||
} else if (SmsDatabase.Types.isIdentityVerified(type)) {
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_verified));
|
||||
} else if (SmsDatabase.Types.isIdentityDefault(type)) {
|
||||
return emphasisAdded(context.getString(R.string.ThreadRecord_you_marked_unverified));
|
||||
} else {
|
||||
if (TextUtils.isEmpty(getBody().getBody())) {
|
||||
return new SpannableString(emphasisAdded(context.getString(R.string.ThreadRecord_media_message)));
|
||||
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceGroupUpdateJob;
|
||||
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;
|
||||
@ -61,7 +62,8 @@ import dagger.Provides;
|
||||
AvatarDownloadJob.class,
|
||||
RotateSignedPreKeyJob.class,
|
||||
WebRtcCallService.class,
|
||||
RetrieveProfileJob.class})
|
||||
RetrieveProfileJob.class,
|
||||
MultiDeviceVerifiedUpdateJob.class})
|
||||
public class SignalCommunicationModule {
|
||||
|
||||
private final Context context;
|
||||
|
@ -0,0 +1,153 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.util.Log;
|
||||
|
||||
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.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class MultiDeviceVerifiedUpdateJob extends ContextJob implements InjectableType {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TAG = MultiDeviceVerifiedUpdateJob.class.getSimpleName();
|
||||
|
||||
@Inject
|
||||
transient SignalCommunicationModule.SignalMessageSenderFactory messageSenderFactory;
|
||||
|
||||
private final String destination;
|
||||
private final byte[] identityKey;
|
||||
private final VerifiedStatus verifiedStatus;
|
||||
|
||||
public MultiDeviceVerifiedUpdateJob(Context context, String destination, IdentityKey identityKey, VerifiedStatus verifiedStatus) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.withPersistence()
|
||||
.withGroupId("__MULTI_DEVICE_VERIFIED_UPDATE__")
|
||||
.create());
|
||||
|
||||
this.destination = destination;
|
||||
this.identityKey = identityKey.serialize();
|
||||
this.verifiedStatus = verifiedStatus;
|
||||
}
|
||||
|
||||
public MultiDeviceVerifiedUpdateJob(Context context) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.withPersistence()
|
||||
.withGroupId("__MULTI_DEVICE_VERIFIED_UPDATE__")
|
||||
.create());
|
||||
this.destination = null;
|
||||
this.identityKey = null;
|
||||
this.verifiedStatus = null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException, UntrustedIdentityException {
|
||||
try {
|
||||
if (!TextSecurePreferences.isMultiDevice(context)) {
|
||||
Log.w(TAG, "Not multi device...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination != null) sendSpecificUpdate(destination, identityKey, verifiedStatus);
|
||||
else sendFullUpdate();
|
||||
|
||||
} catch (InvalidNumberException | InvalidKeyException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendSpecificUpdate(String destination, byte[] identityKey, VerifiedStatus verifiedStatus)
|
||||
throws IOException, UntrustedIdentityException, InvalidNumberException, InvalidKeyException
|
||||
{
|
||||
String canonicalDestination = Util.canonicalizeNumber(context, destination);
|
||||
VerifiedMessage.VerifiedState verifiedState = getVerifiedState(verifiedStatus);
|
||||
SignalServiceMessageSender messageSender = messageSenderFactory.create();
|
||||
VerifiedMessage verifiedMessage = new VerifiedMessage(canonicalDestination, new IdentityKey(identityKey, 0), verifiedState);
|
||||
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessage));
|
||||
}
|
||||
|
||||
private void sendFullUpdate() throws IOException, UntrustedIdentityException, InvalidNumberException {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
IdentityDatabase.IdentityReader reader = identityDatabase.readerFor(identityDatabase.getIdentities());
|
||||
List<VerifiedMessage> verifiedMessages = new LinkedList<>();
|
||||
|
||||
try {
|
||||
IdentityRecord identityRecord;
|
||||
|
||||
while (reader != null && (identityRecord = reader.getNext()) != null) {
|
||||
if (identityRecord.getVerifiedStatus() != VerifiedStatus.DEFAULT) {
|
||||
Recipient recipient = RecipientFactory.getRecipientForId(context, identityRecord.getRecipientId(), true);
|
||||
String destination = Util.canonicalizeNumber(context, recipient.getNumber());
|
||||
VerifiedMessage.VerifiedState verifiedState = getVerifiedState(identityRecord.getVerifiedStatus());
|
||||
|
||||
verifiedMessages.add(new VerifiedMessage(destination, identityRecord.getIdentityKey(), verifiedState));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (reader != null) reader.close();
|
||||
}
|
||||
|
||||
if (!verifiedMessages.isEmpty()) {
|
||||
SignalServiceMessageSender messageSender = messageSenderFactory.create();
|
||||
messageSender.sendMessage(SignalServiceSyncMessage.forVerified(verifiedMessages));
|
||||
}
|
||||
}
|
||||
|
||||
private VerifiedMessage.VerifiedState getVerifiedState(VerifiedStatus status) {
|
||||
VerifiedMessage.VerifiedState verifiedState;
|
||||
|
||||
switch (status) {
|
||||
case DEFAULT: verifiedState = VerifiedMessage.VerifiedState.DEFAULT; break;
|
||||
case VERIFIED: verifiedState = VerifiedMessage.VerifiedState.VERIFIED; break;
|
||||
case UNVERIFIED: verifiedState = VerifiedMessage.VerifiedState.UNVERIFIED; break;
|
||||
default: throw new AssertionError("Unknown status: " + verifiedStatus);
|
||||
}
|
||||
|
||||
return verifiedState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(Exception exception) {
|
||||
return exception instanceof PushNetworkException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingExpirationUpdateMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
@ -44,6 +45,7 @@ import org.thoughtcrime.securesms.sms.OutgoingEndSessionMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libsignal.DuplicateMessageException;
|
||||
@ -75,13 +77,12 @@ import org.whispersystems.signalservice.api.messages.multidevice.ReadMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.RequestMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
|
||||
public class PushDecryptJob extends ContextJob {
|
||||
|
||||
private static final long serialVersionUID = 2L;
|
||||
@ -165,9 +166,10 @@ public class PushDecryptJob extends ContextJob {
|
||||
} else if (content.getSyncMessage().isPresent()) {
|
||||
SignalServiceSyncMessage syncMessage = content.getSyncMessage().get();
|
||||
|
||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, envelope, syncMessage.getSent().get(), smsMessageId);
|
||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get());
|
||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(masterSecret, syncMessage.getRead().get(), envelope.getTimestamp());
|
||||
if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, envelope, syncMessage.getSent().get(), smsMessageId);
|
||||
else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get());
|
||||
else if (syncMessage.getRead().isPresent()) handleSynchronizeReadMessage(masterSecret, syncMessage.getRead().get(), envelope.getTimestamp());
|
||||
else if (syncMessage.getVerified().isPresent()) handleSynchronizeVerifiedMessage(masterSecret, syncMessage.getVerified().get());
|
||||
else Log.w(TAG, "Contains no known sync types...");
|
||||
} else if (content.getCallMessage().isPresent()) {
|
||||
Log.w(TAG, "Got call message...");
|
||||
@ -389,6 +391,14 @@ public class PushDecryptJob extends ContextJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeVerifiedMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull List<VerifiedMessage> verifiedMessages)
|
||||
{
|
||||
for (VerifiedMessage verifiedMessage : verifiedMessages) {
|
||||
IdentityUtil.processVerifiedMessage(context, masterSecret, verifiedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SignalServiceEnvelope envelope,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@ -430,6 +440,10 @@ public class PushDecryptJob extends ContextJob {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MultiDeviceContactUpdateJob(getContext()));
|
||||
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new MultiDeviceVerifiedUpdateJob(getContext()));
|
||||
}
|
||||
|
||||
if (message.isGroupsRequest()) {
|
||||
|
@ -219,11 +219,6 @@ public class MessageNotifier {
|
||||
if (isVisible) {
|
||||
List<MarkedMessageInfo> messageIds = threads.setRead(threadId, false);
|
||||
MarkReadReceiver.process(context, messageIds);
|
||||
|
||||
if (recipients != null && recipients.getPrimaryRecipient() != null) {
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.setSeen(recipients.getPrimaryRecipient().getRecipientId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextSecurePreferences.isNotificationsEnabled(context) ||
|
||||
|
@ -16,6 +16,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
@ -260,7 +261,7 @@ public class RegistrationService extends Service {
|
||||
|
||||
TextSecurePreferences.setWebsocketRegistered(this, true);
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(self.getRecipientId(), identityKey.getPublicKey(), true, System.currentTimeMillis(), true, true);
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(self.getRecipientId(), identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED, true, System.currentTimeMillis(), true);
|
||||
DirectoryHelper.refreshDirectory(this, accountManager, number);
|
||||
|
||||
DirectoryRefreshListener.schedule(this);
|
||||
|
@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule.SignalMessageSenderFactory;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
@ -69,7 +68,6 @@ import org.webrtc.SurfaceViewRenderer;
|
||||
import org.webrtc.VideoRenderer;
|
||||
import org.webrtc.VideoTrack;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
@ -339,12 +337,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUnseenIdentity(this.recipient)) {
|
||||
insertMissedCall(this.recipient, true);
|
||||
terminate();
|
||||
return;
|
||||
}
|
||||
|
||||
timeoutExecutor.schedule(new TimeoutRunnable(this.callId), 2, TimeUnit.MINUTES);
|
||||
|
||||
initializeVideo();
|
||||
@ -370,6 +362,7 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
||||
@Override
|
||||
public void onFailureContinue(Throwable error) {
|
||||
Log.w(TAG, error);
|
||||
insertMissedCall(recipient, true);
|
||||
terminate();
|
||||
}
|
||||
});
|
||||
@ -955,28 +948,6 @@ public class WebRtcCallService extends Service implements InjectableType, PeerCo
|
||||
else return result;
|
||||
}
|
||||
|
||||
private boolean isUnseenIdentity(@NonNull Recipient recipient) {
|
||||
Log.w(TAG, "Checking for unseen identity: " + recipient.getRecipientId());
|
||||
|
||||
Optional<IdentityRecord> identityRecord = DatabaseFactory.getIdentityDatabase(this).getIdentity(recipient.getRecipientId());
|
||||
|
||||
if (!identityRecord.isPresent()) {
|
||||
throw new AssertionError("Should have an identity record at this point.");
|
||||
}
|
||||
|
||||
if (identityRecord.get().isFirstUse()) {
|
||||
Log.w(TAG, "Identity is first use...");
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.w(TAG, "Last seen: " + identityRecord.get().getSeen() + " vs timestamp: " + identityRecord.get().getTimestamp());
|
||||
if (identityRecord.get().getSeen() >= identityRecord.get().getTimestamp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private long getCallId(Intent intent) {
|
||||
return intent.getLongExtra(EXTRA_CALL_ID, -1);
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
|
||||
public class IncomingIdentityDefaultMessage extends IncomingTextMessage {
|
||||
|
||||
public IncomingIdentityDefaultMessage(IncomingTextMessage base) {
|
||||
super(base, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
|
||||
public class IncomingIdentityVerifiedMessage extends IncomingTextMessage {
|
||||
|
||||
public IncomingIdentityVerifiedMessage(IncomingTextMessage base) {
|
||||
super(base, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityVerified() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -227,6 +227,14 @@ public class IncomingTextMessage implements Parcelable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isIdentityVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isIdentityDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
|
@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class OutgoingIdentityDefaultMessage extends OutgoingTextMessage {
|
||||
|
||||
public OutgoingIdentityDefaultMessage(Recipients recipients) {
|
||||
super(recipients, "", -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public OutgoingTextMessage withBody(String body) {
|
||||
return new OutgoingIdentityDefaultMessage(getRecipients());
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.thoughtcrime.securesms.sms;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
|
||||
public class OutgoingIdentityVerifiedMessage extends OutgoingTextMessage {
|
||||
|
||||
public OutgoingIdentityVerifiedMessage(Recipients recipients) {
|
||||
super(recipients, "", -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIdentityVerified() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutgoingTextMessage withBody(String body) {
|
||||
return new OutgoingIdentityVerifiedMessage(getRecipients());
|
||||
}
|
||||
}
|
@ -60,6 +60,14 @@ public class OutgoingTextMessage {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isIdentityVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isIdentityDefault() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static OutgoingTextMessage from(SmsMessageRecord record) {
|
||||
if (record.isSecure()) {
|
||||
return new OutgoingEncryptedMessage(record.getRecipients(), record.getBody().getBody(), record.getExpiresIn());
|
||||
|
@ -2,61 +2,59 @@ package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityUpdateMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingIdentityVerifiedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingIdentityDefaultMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingIdentityVerifiedMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.SignalProtocolAddress;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.libsignal.state.SessionStore;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceGroup;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.messages.multidevice.VerifiedMessage;
|
||||
import org.whispersystems.signalservice.api.util.InvalidNumberException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
|
||||
public class IdentityUtil {
|
||||
|
||||
private static final String TAG = IdentityUtil.class.getSimpleName();
|
||||
|
||||
@UiThread
|
||||
public static ListenableFuture<Optional<IdentityKey>> getRemoteIdentityKey(final Context context,
|
||||
final MasterSecret masterSecret,
|
||||
final Recipient recipient)
|
||||
{
|
||||
final SettableFuture<Optional<IdentityKey>> future = new SettableFuture<>();
|
||||
public static ListenableFuture<Optional<IdentityRecord>> getRemoteIdentityKey(final Context context, final Recipient recipient) {
|
||||
final SettableFuture<Optional<IdentityRecord>> future = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Recipient, Void, Optional<IdentityKey>>() {
|
||||
new AsyncTask<Recipient, Void, Optional<IdentityRecord>>() {
|
||||
@Override
|
||||
protected Optional<IdentityKey> doInBackground(Recipient... recipient) {
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(recipient[0].getNumber(), SignalServiceAddress.DEFAULT_DEVICE_ID);
|
||||
SessionRecord record = sessionStore.loadSession(axolotlAddress);
|
||||
|
||||
if (record == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
return Optional.fromNullable(record.getSessionState().getRemoteIdentityKey());
|
||||
protected Optional<IdentityRecord> doInBackground(Recipient... recipient) {
|
||||
return DatabaseFactory.getIdentityDatabase(context)
|
||||
.getIdentity(recipient[0].getRecipientId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Optional<IdentityKey> result) {
|
||||
protected void onPostExecute(Optional<IdentityRecord> result) {
|
||||
future.set(result);
|
||||
}
|
||||
}.execute(recipient);
|
||||
@ -64,6 +62,102 @@ public class IdentityUtil {
|
||||
return future;
|
||||
}
|
||||
|
||||
public static void markIdentityVerified(Context context, MasterSecretUnion masterSecret,
|
||||
Recipient recipient, boolean verified, boolean remote)
|
||||
{
|
||||
long time = System.currentTimeMillis();
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFor(context, recipient, true);
|
||||
GroupDatabase.Reader reader = groupDatabase.getGroups();
|
||||
|
||||
String number = recipient.getNumber();
|
||||
|
||||
try {
|
||||
number = Util.canonicalizeNumber(context, number);
|
||||
} catch (InvalidNumberException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
|
||||
GroupDatabase.GroupRecord groupRecord;
|
||||
|
||||
while ((groupRecord = reader.getNext()) != null) {
|
||||
if (groupRecord.getMembers().contains(number) && groupRecord.isActive()) {
|
||||
SignalServiceGroup group = new SignalServiceGroup(groupRecord.getId());
|
||||
|
||||
if (remote) {
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.of(group), 0);
|
||||
|
||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(incoming);
|
||||
} else {
|
||||
Recipients groupRecipients = RecipientFactory.getRecipientsFromString(context, GroupUtil.getEncodedId(group.getGroupId()), true);
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipients);
|
||||
OutgoingTextMessage outgoing ;
|
||||
|
||||
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipients);
|
||||
else outgoing = new OutgoingIdentityDefaultMessage(recipients);
|
||||
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context).insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
IncomingTextMessage incoming = new IncomingTextMessage(number, 1, time, null, Optional.<SignalServiceGroup>absent(), 0);
|
||||
|
||||
if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming);
|
||||
else incoming = new IncomingIdentityDefaultMessage(incoming);
|
||||
|
||||
smsDatabase.insertMessageInbox(incoming);
|
||||
} else {
|
||||
OutgoingTextMessage outgoing;
|
||||
|
||||
if (verified) outgoing = new OutgoingIdentityVerifiedMessage(recipients);
|
||||
else outgoing = new OutgoingIdentityDefaultMessage(recipients);
|
||||
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
|
||||
Log.w(TAG, "Inserting verified outbox...");
|
||||
DatabaseFactory.getEncryptingSmsDatabase(context)
|
||||
.insertMessageOutbox(masterSecret, threadId, outgoing, false, time, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void processVerifiedMessage(Context context, MasterSecretUnion masterSecret, VerifiedMessage verifiedMessage) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
|
||||
Recipient recipient = RecipientFactory.getRecipientsFromString(context, verifiedMessage.getDestination(), true).getPrimaryRecipient();
|
||||
Optional<IdentityRecord> identityRecord = identityDatabase.getIdentity(recipient.getRecipientId());
|
||||
|
||||
if (!identityRecord.isPresent() && verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT) {
|
||||
Log.w(TAG, "No existing record for default status");
|
||||
return;
|
||||
}
|
||||
|
||||
if (identityRecord.isPresent() &&
|
||||
identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey()) &&
|
||||
identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.DEFAULT &&
|
||||
verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.DEFAULT)
|
||||
{
|
||||
identityDatabase.setVerified(recipient.getRecipientId(), identityRecord.get().getIdentityKey(), IdentityDatabase.VerifiedStatus.DEFAULT);
|
||||
markIdentityVerified(context, masterSecret, recipient, false, true);
|
||||
}
|
||||
|
||||
if (verifiedMessage.getVerified() == VerifiedMessage.VerifiedState.VERIFIED &&
|
||||
(!identityRecord.isPresent() ||
|
||||
(identityRecord.isPresent() && !identityRecord.get().getIdentityKey().equals(verifiedMessage.getIdentityKey())) ||
|
||||
(identityRecord.isPresent() && identityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)))
|
||||
{
|
||||
identityDatabase.saveIdentity(recipient.getRecipientId(), verifiedMessage.getIdentityKey(),
|
||||
IdentityDatabase.VerifiedStatus.VERIFIED, false, System.currentTimeMillis(), true);
|
||||
markIdentityVerified(context, masterSecret, recipient, true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void markIdentityUpdate(Context context, Recipient recipient) {
|
||||
long time = System.currentTimeMillis();
|
||||
SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context);
|
||||
@ -98,4 +192,62 @@ public class IdentityUtil {
|
||||
MessageNotifier.updateNotification(context, null, insertResult.get().getThreadId());
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable String getUnverifiedBannerDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> unverified)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, unverified,
|
||||
R.string.IdentityUtil_unverified_banner_one,
|
||||
R.string.IdentityUtil_unverified_banner_two,
|
||||
R.string.IdentityUtil_unverified_banner_many);
|
||||
}
|
||||
|
||||
public static @Nullable String getUnverifiedSendDialogDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> unverified)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, unverified,
|
||||
R.string.IdentityUtil_unverified_dialog_one,
|
||||
R.string.IdentityUtil_unverified_dialog_two,
|
||||
R.string.IdentityUtil_unverified_dialog_many);
|
||||
}
|
||||
|
||||
public static @Nullable String getUntrustedSendDialogDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> untrusted)
|
||||
{
|
||||
return getPluralizedIdentityDescription(context, untrusted,
|
||||
R.string.IdentityUtil_untrusted_dialog_one,
|
||||
R.string.IdentityUtil_untrusted_dialog_two,
|
||||
R.string.IdentityUtil_untrusted_dialog_many);
|
||||
}
|
||||
|
||||
private static @Nullable String getPluralizedIdentityDescription(@NonNull Context context,
|
||||
@NonNull List<Recipient> recipients,
|
||||
@StringRes int resourceOne,
|
||||
@StringRes int resourceTwo,
|
||||
@StringRes int resourceMany)
|
||||
{
|
||||
if (recipients.isEmpty()) return null;
|
||||
|
||||
if (recipients.size() == 1) {
|
||||
String name = recipients.get(0).toShortString();
|
||||
return context.getString(resourceOne, name);
|
||||
} else {
|
||||
String firstName = recipients.get(0).toShortString();
|
||||
String secondName = recipients.get(1).toShortString();
|
||||
|
||||
if (recipients.size() == 2) {
|
||||
return context.getString(resourceTwo, firstName, secondName);
|
||||
} else {
|
||||
String nMore;
|
||||
|
||||
if (recipients.size() == 3) {
|
||||
nMore = context.getResources().getQuantityString(R.plurals.identity_others, 1);
|
||||
} else {
|
||||
nMore = context.getResources().getQuantityString(R.plurals.identity_others, recipients.size() - 2);
|
||||
}
|
||||
|
||||
return context.getString(resourceMany, firstName, secondName, nMore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,8 +32,9 @@ public class VerifySpan extends ClickableSpan {
|
||||
@Override
|
||||
public void onClick(View widget) {
|
||||
Intent intent = new Intent(context, VerifyIdentityActivity.class);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID, recipientId);
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_IDENTITY, new IdentityKeyParcelable(identityKey));
|
||||
intent.putExtra(VerifyIdentityActivity.RECIPIENT_ID_EXTRA, recipientId);
|
||||
intent.putExtra(VerifyIdentityActivity.IDENTITY_EXTRA, new IdentityKeyParcelable(identityKey));
|
||||
intent.putExtra(VerifyIdentityActivity.VERIFIED_EXTRA, false);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user