Implement new Safety Number Changes bottom sheeet.

This commit is contained in:
Alex Hart
2022-07-11 12:54:30 -03:00
parent b0dc7fe6df
commit 7a0f4fafe2
42 changed files with 1787 additions and 121 deletions

View File

@@ -158,6 +158,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.revealable.ViewOnceMessageActivity;
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
import org.thoughtcrime.securesms.stickers.StickerLocator;
@@ -1874,7 +1875,8 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
@Override
public void onIncomingIdentityMismatchClicked(@NonNull RecipientId recipientId) {
SafetyNumberChangeDialog.show(getParentFragmentManager(), recipientId);
SafetyNumberBottomSheet.forRecipientId(recipientId)
.show(getParentFragmentManager());
}
@Override

View File

@@ -152,6 +152,7 @@ import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlayerView;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey;
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactShareEditActivity;
@@ -161,7 +162,6 @@ import org.thoughtcrime.securesms.conversation.ConversationGroupViewModel.GroupA
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
import org.thoughtcrime.securesms.conversation.drafts.DraftRepository;
import org.thoughtcrime.securesms.conversation.drafts.DraftViewModel;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog;
import org.thoughtcrime.securesms.conversation.ui.groupcall.GroupCallViewModel;
import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewModel;
import org.thoughtcrime.securesms.crypto.ReentrantSessionLock;
@@ -263,6 +263,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.recipients.ui.disappearingmessages.RecipientDisappearingMessagesActivity;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet;
import org.thoughtcrime.securesms.search.MessageResult;
import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -348,7 +349,7 @@ public class ConversationParentFragment extends Fragment
AttachmentKeyboard.Callback,
ConversationReactionOverlay.OnReactionSelectedListener,
ReactWithAnyEmojiBottomSheetDialogFragment.Callback,
SafetyNumberChangeDialog.Callback,
SafetyNumberBottomSheet.Callbacks,
ReactionsBottomSheetDialogFragment.Callback,
MediaKeyboard.MediaKeyboardListener,
EmojiEventListener,
@@ -1594,22 +1595,16 @@ public class ConversationParentFragment extends Fragment
private void handleRecentSafetyNumberChange() {
List<IdentityRecord> records = identityRecords.getUnverifiedRecords();
records.addAll(identityRecords.getUntrustedRecords());
SafetyNumberChangeDialog.show(getChildFragmentManager(), records);
SafetyNumberBottomSheet
.forIdentityRecordsAndDestination(
records,
new ContactSearchKey.RecipientSearchKey.KnownRecipient(recipient.getId())
)
.show(getChildFragmentManager());
}
@Override
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
Log.d(TAG, "onSendAnywayAfterSafetyNumberChange");
initializeIdentityRecords().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
sendMessage(null);
}
});
}
@Override
public void onMessageResentAfterSafetyNumberChange() {
public void onMessageResentAfterSafetyNumberChangeInBottomSheet() {
Log.d(TAG, "onMessageResentAfterSafetyNumberChange");
initializeIdentityRecords().addListener(new AssertedSuccessListener<Boolean>() {
@Override
@@ -3629,6 +3624,17 @@ public class ConversationParentFragment extends Fragment
material3OnScrollHelper.setColorImmediate();
}
@Override
public void sendAnywayAfterSafetyNumberChangedInBottomSheet(@NonNull List<? extends ContactSearchKey.RecipientSearchKey> destinations) {
Log.d(TAG, "onSendAnywayAfterSafetyNumberChange");
initializeIdentityRecords().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
sendMessage(null);
}
});
}
// Listeners
private final class DeleteCanceledVoiceNoteListener implements ListenableFuture.Listener<VoiceNoteDraft> {
@@ -3888,7 +3894,9 @@ public class ConversationParentFragment extends Fragment
@Override
public void onMessageWithErrorClicked(@NonNull MessageRecord messageRecord) {
if (messageRecord.isIdentityMismatchFailure()) {
SafetyNumberChangeDialog.show(requireContext(), getChildFragmentManager(), messageRecord);
SafetyNumberBottomSheet
.forMessageRecord(requireContext(), messageRecord)
.show(getChildFragmentManager());
} else if (messageRecord.hasFailedWithNetworkFailures()) {
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.conversation_activity__message_could_not_be_sent)

View File

@@ -34,12 +34,12 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchConfiguration
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
import org.thoughtcrime.securesms.contacts.paged.ContactSearchMediator
import org.thoughtcrime.securesms.contacts.paged.ContactSearchState
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeDialog
import org.thoughtcrime.securesms.database.model.IdentityRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseGroupStoryBottomSheet
import org.thoughtcrime.securesms.mediasend.v2.stories.ChooseStoryTypeBottomSheet
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
import org.thoughtcrime.securesms.sharing.MultiShareArgs
import org.thoughtcrime.securesms.sharing.ShareSelectionAdapter
import org.thoughtcrime.securesms.sharing.ShareSelectionMappingModel
@@ -74,7 +74,7 @@ import org.thoughtcrime.securesms.util.visible
*/
class MultiselectForwardFragment :
Fragment(R.layout.multiselect_forward_fragment),
SafetyNumberChangeDialog.Callback,
SafetyNumberBottomSheet.Callbacks,
ChooseStoryTypeBottomSheet.Callback,
WrapperDialogFragment.WrapperDialogFragmentCallback,
ChooseInitialMyStoryMembershipBottomSheetDialogFragment.Callback {
@@ -204,7 +204,7 @@ class MultiselectForwardFragment :
when (it.stage) {
MultiselectForwardState.Stage.Selection -> {}
MultiselectForwardState.Stage.FirstConfirmation -> displayFirstSendConfirmation()
is MultiselectForwardState.Stage.SafetyConfirmation -> displaySafetyNumberConfirmation(it.stage.identities)
is MultiselectForwardState.Stage.SafetyConfirmation -> displaySafetyNumberConfirmation(it.stage.identities, it.stage.selectedContacts)
MultiselectForwardState.Stage.LoadingIdentities -> {}
MultiselectForwardState.Stage.SendPending -> {
handler?.removeCallbacksAndMessages(null)
@@ -288,8 +288,10 @@ class MultiselectForwardFragment :
viewModel.send(addMessage.text.toString(), contactSearchMediator.getSelectedContacts())
}
private fun displaySafetyNumberConfirmation(identityRecords: List<IdentityRecord>) {
SafetyNumberChangeDialog.show(childFragmentManager, identityRecords)
private fun displaySafetyNumberConfirmation(identityRecords: List<IdentityRecord>, selectedContacts: List<ContactSearchKey>) {
SafetyNumberBottomSheet
.forIdentityRecordsAndDestinations(identityRecords, selectedContacts)
.show(childFragmentManager)
}
private fun dismissWithSuccess(@PluralsRes toastTextResId: Int) {
@@ -332,11 +334,11 @@ class MultiselectForwardFragment :
callback.exitFlow()
}
override fun onSendAnywayAfterSafetyNumberChange(changedRecipients: MutableList<RecipientId>) {
viewModel.confirmSafetySend(addMessage.text.toString(), contactSearchMediator.getSelectedContacts())
override fun sendAnywayAfterSafetyNumberChangedInBottomSheet(destinations: List<ContactSearchKey.RecipientSearchKey>) {
viewModel.confirmSafetySend(addMessage.text.toString(), destinations.toSet())
}
override fun onMessageResentAfterSafetyNumberChange() {
override fun onMessageResentAfterSafetyNumberChangeInBottomSheet() {
throw UnsupportedOperationException()
}

View File

@@ -13,7 +13,7 @@ data class MultiselectForwardState(
object Selection : Stage()
object FirstConfirmation : Stage()
object LoadingIdentities : Stage()
data class SafetyConfirmation(val identities: List<IdentityRecord>) : Stage()
data class SafetyConfirmation(val identities: List<IdentityRecord>, val selectedContacts: List<ContactSearchKey>) : Stage()
object SendPending : Stage()
object SomeFailed : Stage()
object AllFailed : Stage()

View File

@@ -44,7 +44,14 @@ class MultiselectForwardViewModel(
if (identityRecords.isEmpty()) {
performSend(additionalMessage, selectedContacts)
} else {
store.update { it.copy(stage = MultiselectForwardState.Stage.SafetyConfirmation(identityRecords)) }
store.update { state ->
state.copy(
stage = MultiselectForwardState.Stage.SafetyConfirmation(
identityRecords,
selectedContacts.filterIsInstance<ContactSearchKey.RecipientSearchKey>()
)
)
}
}
}
}

View File

@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.conversation.ui.error;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
@@ -28,9 +27,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
@@ -64,37 +61,6 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
public static void show(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityRecord> identityRecords) {
List<String> ids = Stream.of(identityRecords)
.filterNot(IdentityRecord::isFirstUse)
.map(record -> record.getRecipientId().serialize())
.distinct()
.toList();
Bundle arguments = new Bundle();
arguments.putStringArray(RECIPIENT_IDS_EXTRA, ids.toArray(new String[0]));
arguments.putInt(CONTINUE_TEXT_RESOURCE_EXTRA, R.string.safety_number_change_dialog__send_anyway);
SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog();
fragment.setArguments(arguments);
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
public static void show(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull MessageRecord messageRecord) {
List<String> ids = Stream.of(messageRecord.getIdentityKeyMismatches())
.map(mismatch -> mismatch.getRecipientId(context).serialize())
.distinct()
.toList();
Bundle arguments = new Bundle();
arguments.putStringArray(RECIPIENT_IDS_EXTRA, ids.toArray(new String[0]));
arguments.putLong(MESSAGE_ID_EXTRA, messageRecord.getId());
arguments.putString(MESSAGE_TYPE_EXTRA, messageRecord.isMms() ? MmsSmsDatabase.MMS_TRANSPORT : MmsSmsDatabase.SMS_TRANSPORT);
arguments.putInt(CONTINUE_TEXT_RESOURCE_EXTRA, R.string.safety_number_change_dialog__send_anyway);
SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog();
fragment.setArguments(arguments);
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
public static void showForCall(@NonNull FragmentManager fragmentManager, @NonNull RecipientId recipientId) {
Bundle arguments = new Bundle();
arguments.putStringArray(RECIPIENT_IDS_EXTRA, new String[] { recipientId.serialize() });
@@ -140,7 +106,7 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
private SafetyNumberChangeDialog() { }
private SafetyNumberChangeDialog() {}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

View File

@@ -22,10 +22,12 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.IdentityRecord;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.safety.SafetyNumberRecipient;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.SignalSessionLock;
@@ -35,17 +37,39 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
final class SafetyNumberChangeRepository {
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class SafetyNumberChangeRepository {
private static final String TAG = Log.tag(SafetyNumberChangeRepository.class);
private final Context context;
SafetyNumberChangeRepository(Context context) {
public SafetyNumberChangeRepository(Context context) {
this.context = context.getApplicationContext();
}
@NonNull
public Single<TrustAndVerifyResult> trustOrVerifyChangedRecipientsRx(@NonNull List<SafetyNumberRecipient> safetyNumberRecipients) {
Log.d(TAG, "Trust or verify changed recipients for: " + Util.join(safetyNumberRecipients, ","));
return Single.fromCallable(() -> trustOrVerifyChangedRecipientsInternal(fromSafetyNumberRecipients(safetyNumberRecipients)))
.subscribeOn(Schedulers.io());
}
@NonNull
public Single<TrustAndVerifyResult> trustOrVerifyChangedRecipientsAndResendRx(@NonNull List<SafetyNumberRecipient> safetyNumberRecipients, @NonNull MessageId messageId) {
Log.d(TAG, "Trust or verify changed recipients and resend message: " + messageId + " for: " + Util.join(safetyNumberRecipients, ","));
return Single.fromCallable(() -> {
MessageRecord messageRecord = messageId.isMms() ? SignalDatabase.mms().getMessageRecord(messageId.getId())
: SignalDatabase.sms().getMessageRecord(messageId.getId());
return trustOrVerifyChangedRecipientsAndResendInternal(fromSafetyNumberRecipients(safetyNumberRecipients), messageRecord);
}).subscribeOn(Schedulers.io());
}
@NonNull LiveData<TrustAndVerifyResult> trustOrVerifyChangedRecipients(@NonNull List<ChangedRecipient> changedRecipients) {
Log.d(TAG, "Trust or verify changed recipients for: " + Util.join(changedRecipients, ","));
MutableLiveData<TrustAndVerifyResult> liveData = new MutableLiveData<>();
@@ -78,6 +102,14 @@ final class SafetyNumberChangeRepository {
return new SafetyNumberChangeState(changedRecipients, messageRecord);
}
private @NonNull List<ChangedRecipient> fromSafetyNumberRecipients(@NonNull List<SafetyNumberRecipient> safetyNumberRecipients) {
return safetyNumberRecipients.stream().map(this::fromSafetyNumberRecipient).collect(Collectors.toList());
}
private @NonNull ChangedRecipient fromSafetyNumberRecipient(@NonNull SafetyNumberRecipient safetyNumberRecipient) {
return new ChangedRecipient(safetyNumberRecipient.getRecipient(), safetyNumberRecipient.getIdentityRecord());
}
@WorkerThread
private @Nullable MessageRecord getMessageRecord(Long messageId, String messageType) {
try {
@@ -99,7 +131,7 @@ final class SafetyNumberChangeRepository {
private TrustAndVerifyResult trustOrVerifyChangedRecipientsInternal(@NonNull List<ChangedRecipient> changedRecipients) {
SignalIdentityKeyStore identityStore = ApplicationDependencies.getProtocolStore().aci().identities();
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
for (ChangedRecipient changedRecipient : changedRecipients) {
IdentityRecord identityRecord = changedRecipient.getIdentityRecord();
@@ -126,7 +158,7 @@ final class SafetyNumberChangeRepository {
Log.d(TAG, "No changed recipients to process, will still process message record");
}
try(SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
try (SignalSessionLock.Lock unused = ReentrantSessionLock.INSTANCE.acquire()) {
for (ChangedRecipient changedRecipient : changedRecipients) {
SignalProtocolAddress mismatchAddress = changedRecipient.getRecipient().requireServiceId().toProtocolAddress(SignalServiceAddress.DEFAULT_DEVICE_ID);