From 55c69cd50afbf3047737cd4b7d8e5a467ef0b04f Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Tue, 13 Jul 2021 19:37:00 -0400 Subject: [PATCH] Add additional fallback logic for change dialog. --- .../securesms/BindableConversationItem.java | 1 + .../securesms/ConfirmIdentityDialog.java | 176 ------------------ .../conversation/ConversationFragment.java | 6 + .../conversation/ConversationItem.java | 16 +- .../ui/error/SafetyNumberChangeDialog.java | 17 +- .../error/SafetyNumberChangeRepository.java | 4 +- .../MessageDetailsActivity.java | 11 +- .../messagedetails/MessageDetailsAdapter.java | 17 +- .../messagedetails/RecipientViewHolder.java | 21 ++- app/src/main/res/values/strings.xml | 1 + 10 files changed, 60 insertions(+), 210 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java index 0da79b86d9..177eae7c09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/BindableConversationItem.java @@ -70,6 +70,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable, void onGroupMemberClicked(@NonNull RecipientId recipientId, @NonNull GroupId groupId); void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord); void onMessageWithRecaptchaNeededClicked(@NonNull MessageRecord messageRecord); + void onIncomingIdentityMismatchClicked(@NonNull RecipientId recipientId); void onRegisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onUnregisterVoiceNoteCallbacks(@NonNull Observer onPlaybackStartObserver); void onVoiceNotePause(@NonNull Uri uri); diff --git a/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java b/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java deleted file mode 100644 index 9931491dd3..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/ConfirmIdentityDialog.java +++ /dev/null @@ -1,176 +0,0 @@ -package org.thoughtcrime.securesms; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.DialogInterface; -import android.os.AsyncTask; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.method.LinkMovementMethod; -import android.widget.TextView; - -import androidx.appcompat.app.AlertDialog; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.crypto.ReentrantSessionLock; -import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore; -import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.MessageDatabase; -import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; -import org.thoughtcrime.securesms.database.model.MessageRecord; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.PushDecryptMessageJob; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.sms.MessageSender; -import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.VerifySpan; -import org.whispersystems.libsignal.SignalProtocolAddress; -import org.whispersystems.libsignal.util.guava.Optional; -import org.whispersystems.signalservice.api.SignalSessionLock; -import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos; - -import java.io.IOException; - -public class ConfirmIdentityDialog extends AlertDialog { - - @SuppressWarnings("unused") - private static final String TAG = Log.tag(ConfirmIdentityDialog.class); - - private OnClickListener callback; - - public ConfirmIdentityDialog(Context context, - MessageRecord messageRecord, - IdentityKeyMismatch mismatch) - { - super(context); - - Recipient recipient = Recipient.resolved(mismatch.getRecipientId(context)); - String name = recipient.getDisplayName(context); - String introduction = context.getString(R.string.ConfirmIdentityDialog_your_safety_number_with_s_has_changed, name, name); - SpannableString spannableString = new SpannableString(introduction + " " + - context.getString(R.string.ConfirmIdentityDialog_you_may_wish_to_verify_your_safety_number_with_this_contact)); - - spannableString.setSpan(new VerifySpan(context, mismatch), - introduction.length()+1, spannableString.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - setTitle(name); - setMessage(spannableString); - - setButton(AlertDialog.BUTTON_POSITIVE, context.getString(R.string.ConfirmIdentityDialog_accept), new AcceptListener(messageRecord, mismatch, recipient.getId())); - setButton(AlertDialog.BUTTON_NEGATIVE, context.getString(android.R.string.cancel), new CancelListener()); - } - - @Override - public void show() { - super.show(); - ((TextView)this.findViewById(android.R.id.message)) - .setMovementMethod(LinkMovementMethod.getInstance()); - } - - public void setCallback(OnClickListener callback) { - this.callback = callback; - } - - private class AcceptListener implements OnClickListener { - - private final MessageRecord messageRecord; - private final IdentityKeyMismatch mismatch; - private final RecipientId recipientId; - - private AcceptListener(MessageRecord messageRecord, IdentityKeyMismatch mismatch, RecipientId recipientId) { - this.messageRecord = messageRecord; - this.mismatch = mismatch; - this.recipientId = recipientId; - } - - @SuppressLint("StaticFieldLeak") - @Override - public void onClick(DialogInterface dialog, int which) { - new AsyncTask() - { - @Override - protected Void doInBackground(Void... params) { - try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { - SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(Recipient.resolved(recipientId).requireServiceId(), 1); - TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(getContext()); - - identityKeyStore.saveIdentity(mismatchAddress, mismatch.getIdentityKey(), true); - } - - processMessageRecord(messageRecord); - - return null; - } - - private void processMessageRecord(MessageRecord messageRecord) { - if (messageRecord.isOutgoing()) processOutgoingMessageRecord(messageRecord); - else processIncomingMessageRecord(messageRecord); - } - - private void processOutgoingMessageRecord(MessageRecord messageRecord) { - MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext()); - MessageDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(getContext()); - - if (messageRecord.isMms()) { - mmsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getRecipientId(getContext()), - mismatch.getIdentityKey()); - - if (messageRecord.getRecipient().isPushGroup()) { - MessageSender.resendGroupMessage(getContext(), messageRecord, Recipient.resolved(mismatch.getRecipientId(getContext())).getId()); - } else { - MessageSender.resend(getContext(), messageRecord); - } - } else { - smsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getRecipientId(getContext()), - mismatch.getIdentityKey()); - - MessageSender.resend(getContext(), messageRecord); - } - } - - private void processIncomingMessageRecord(MessageRecord messageRecord) { - try { - MessageDatabase smsDatabase = DatabaseFactory.getSmsDatabase(getContext()); - - smsDatabase.removeMismatchedIdentity(messageRecord.getId(), - mismatch.getRecipientId(getContext()), - mismatch.getIdentityKey()); - - boolean legacy = !messageRecord.isContentBundleKeyExchange(); - - SignalServiceEnvelope envelope = new SignalServiceEnvelope(SignalServiceProtos.Envelope.Type.PREKEY_BUNDLE_VALUE, - Optional.of(RecipientUtil.toSignalServiceAddress(getContext(), messageRecord.getIndividualRecipient())), - messageRecord.getRecipientDeviceId(), - messageRecord.getDateSent(), - legacy ? Base64.decode(messageRecord.getBody()) : null, - !legacy ? Base64.decode(messageRecord.getBody()) : null, - 0, - 0, - null); - - ApplicationDependencies.getJobManager().add(new PushDecryptMessageJob(getContext(), envelope, messageRecord.getId())); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - if (callback != null) callback.onClick(null, 0); - } - } - - private class CancelListener implements OnClickListener { - @Override - public void onClick(DialogInterface dialog, int which) { - if (callback != null) callback.onClick(null, 0); - } - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 251c35c844..f140d44088 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -92,6 +92,7 @@ import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationM import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.colors.ColorizerView; import org.thoughtcrime.securesms.conversation.ui.error.EnableCallNotificationSettingsDialog; +import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessageDatabase; import org.thoughtcrime.securesms.database.MmsDatabase; @@ -1585,6 +1586,11 @@ public class ConversationFragment extends LoggingFragment { RecaptchaProofBottomSheetFragment.show(getChildFragmentManager()); } + @Override + public void onIncomingIdentityMismatchClicked(@NonNull RecipientId recipientId) { + SafetyNumberChangeDialog.show(getParentFragmentManager(), recipientId); + } + @Override public void onVoiceNotePause(@NonNull Uri uri) { listener.onVoiceNotePause(uri); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index 84cd72f483..3f7c79bcb2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -65,7 +65,6 @@ import com.google.android.exoplayer2.source.MediaSource; import org.jetbrains.annotations.NotNull; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.BindableConversationItem; -import org.thoughtcrime.securesms.ConfirmIdentityDialog; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; @@ -121,7 +120,6 @@ import org.thoughtcrime.securesms.revealable.ViewOnceMessageView; import org.thoughtcrime.securesms.revealable.ViewOnceUtil; import org.thoughtcrime.securesms.stickers.StickerUrl; import org.thoughtcrime.securesms.util.DateUtils; -import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan; import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.thoughtcrime.securesms.util.Projection; @@ -1450,16 +1448,6 @@ public final class ConversationItem extends RelativeLayout implements BindableCo /// Event handlers - private void handleApproveIdentity() { - List mismatches = messageRecord.getIdentityKeyMismatches(); - - if (mismatches.size() != 1) { - throw new AssertionError("Identity mismatch count: " + mismatches.size()); - } - - new ConfirmIdentityDialog(context, messageRecord, mismatches.get(0)).show(); - } - private Spannable getLongMessageSpan(@NonNull MessageRecord messageRecord) { String message; Runnable action; @@ -1810,7 +1798,9 @@ public final class ConversationItem extends RelativeLayout implements BindableCo eventListener.onMessageWithRecaptchaNeededClicked(messageRecord); } } else if (!messageRecord.isOutgoing() && messageRecord.isIdentityMismatchFailure()) { - handleApproveIdentity(); + if (eventListener != null) { + eventListener.onIncomingIdentityMismatchClicked(messageRecord.getIndividualRecipient().getId()); + } } else if (messageRecord.isPendingInsecureSmsFallback()) { handleMessageApproval(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java index f8a0653cbd..acea05ee59 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeDialog.java @@ -46,11 +46,22 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa private static final String MESSAGE_TYPE_EXTRA = "message_type"; private static final String CONTINUE_TEXT_RESOURCE_EXTRA = "continue_text_resource"; private static final String CANCEL_TEXT_RESOURCE_EXTRA = "cancel_text_resource"; + private static final String SKIP_CALLBACKS_EXTRA = "skip_callbacks_extra"; private SafetyNumberChangeViewModel viewModel; private SafetyNumberChangeAdapter adapter; private View dialogView; + public static void show(@NonNull FragmentManager fragmentManager, @NonNull RecipientId recipientId) { + Bundle arguments = new Bundle(); + arguments.putStringArray(RECIPIENT_IDS_EXTRA, new String[] { recipientId.serialize() }); + arguments.putInt(CONTINUE_TEXT_RESOURCE_EXTRA, R.string.safety_number_change_dialog__accept); + arguments.putBoolean(SKIP_CALLBACKS_EXTRA, true); + SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog(); + fragment.setArguments(arguments); + fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG); + } + public static void show(@NonNull FragmentManager fragmentManager, @NonNull List identityRecords) { List ids = Stream.of(identityRecords) .filterNot(IdentityDatabase.IdentityRecord::isFirstUse) @@ -196,9 +207,11 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa private void handleSendAnyway(DialogInterface dialogInterface, int which) { Log.d(TAG, "handleSendAnyway"); + boolean skipCallbacks = requireArguments().getBoolean(SKIP_CALLBACKS_EXTRA, false); + Activity activity = getActivity(); Callback callback; - if (activity instanceof Callback) { + if (activity instanceof Callback && !skipCallbacks) { callback = (Callback) activity; } else { callback = null; @@ -241,7 +254,9 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa public interface Callback { void onSendAnywayAfterSafetyNumberChange(@NonNull List changedRecipients); + void onMessageResentAfterSafetyNumberChange(); + void onCanceled(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java index 53d997d5e2..5cc455e031 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/error/SafetyNumberChangeRepository.java @@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.SignalProtocolAddress; import org.whispersystems.signalservice.api.SignalSessionLock; +import org.whispersystems.signalservice.api.push.SignalServiceAddress; import java.util.Collection; import java.util.List; @@ -124,13 +125,14 @@ final class SafetyNumberChangeRepository { try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) { for (ChangedRecipient changedRecipient : changedRecipients) { - SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), 1); + SignalProtocolAddress mismatchAddress = new SignalProtocolAddress(changedRecipient.getRecipient().requireServiceId(), SignalServiceAddress.DEFAULT_DEVICE_ID); TextSecureIdentityKeyStore identityKeyStore = new TextSecureIdentityKeyStore(context); Log.d(TAG, "Saving identity for: " + changedRecipient.getRecipient().getId() + " " + changedRecipient.getIdentityRecord().getIdentityKey().hashCode()); TextSecureIdentityKeyStore.SaveResult result = identityKeyStore.saveIdentity(mismatchAddress, changedRecipient.getIdentityRecord().getIdentityKey(), true); Log.d(TAG, "Saving identity result: " + result); if (result == TextSecureIdentityKeyStore.SaveResult.NO_CHANGE) { Log.i(TAG, "Archiving sessions explicitly as they appear to be out of sync."); + SessionUtil.archiveSession(context, changedRecipient.getRecipient().getId(), SignalServiceAddress.DEFAULT_DEVICE_ID); SessionUtil.archiveSiblingSessions(context, mismatchAddress); DatabaseFactory.getSenderKeySharedDatabase(context).deleteAllFor(changedRecipient.getRecipient().getId()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsActivity.java index 0f21943c6a..29adf9e5ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsActivity.java @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.messagedetails; import android.content.Context; import android.content.Intent; -import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.MenuItem; import android.widget.FrameLayout; @@ -13,9 +12,9 @@ import androidx.recyclerview.widget.RecyclerView; import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.colors.ColorizerView; +import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog; import org.thoughtcrime.securesms.database.MmsSmsDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackController; @@ -26,9 +25,7 @@ import org.thoughtcrime.securesms.messagedetails.MessageDetailsViewModel.Factory import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.DynamicDarkActionBarTheme; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.WindowUtil; import java.util.ArrayList; import java.util.Collection; @@ -102,7 +99,7 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity { ColorizerView colorizerView = findViewById(R.id.message_details_colorizer); colorizer = new Colorizer(colorizerView); - adapter = new MessageDetailsAdapter(this, glideRequests, colorizer); + adapter = new MessageDetailsAdapter(this, glideRequests, colorizer, this::onErrorClicked); list.setAdapter(adapter); list.setItemAnimator(null); @@ -170,4 +167,8 @@ public final class MessageDetailsActivity extends PassphraseRequiredActivity { } return true; } + + private void onErrorClicked(@NonNull MessageRecord messageRecord) { + SafetyNumberChangeDialog.show(this, messageRecord); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsAdapter.java index 5975dd56f8..e37ce339c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messagedetails/MessageDetailsAdapter.java @@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.conversation.ConversationMessage; import org.thoughtcrime.securesms.conversation.colors.Colorizer; +import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.mms.GlideRequests; import java.util.List; @@ -22,15 +23,17 @@ final class MessageDetailsAdapter extends ListAdapter new ConfirmIdentityDialog(itemView.getContext(), data.getMessageRecord(), data.getKeyMismatchFailure()).show()); + conflictButton.setOnClickListener(unused -> callbacks.onErrorClicked(data.getMessageRecord())); } else if ((data.getNetworkFailure() != null && !data.getMessageRecord().isPending()) || (!data.getMessageRecord().getRecipient().isPushGroup() && data.getMessageRecord().isFailed())) { timestamp.setVisibility(View.GONE); error.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1adb18e3b0..db7d1a6098 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1924,6 +1924,7 @@ Safety Number Changes + Accept Send anyway Call anyway Join call