Handle safety number changes in a group call context.

This commit is contained in:
Cody Henthorne
2020-12-04 15:24:18 -05:00
committed by Greyson Parrelli
parent 112782ccaf
commit 42d61518b3
17 changed files with 578 additions and 121 deletions

View File

@@ -1410,7 +1410,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
}
@Override
public void onSendAnywayAfterSafetyNumberChange() {
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
initializeIdentityRecords().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {

View File

@@ -4,7 +4,6 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.telecom.Call;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -13,6 +12,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData;
@@ -30,16 +30,18 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.Collection;
import java.util.List;
public final class SafetyNumberChangeDialog extends DialogFragment implements SafetyNumberChangeAdapter.Callbacks {
public static final String SAFETY_NUMBER_DIALOG = "SAFETY_NUMBER";
private static final String RECIPIENT_IDS_EXTRA = "recipient_ids";
private static final String MESSAGE_ID_EXTRA = "message_id";
private static final String MESSAGE_TYPE_EXTRA = "message_type";
private static final String IS_CALL_EXTRA = "is_call";
private static final String RECIPIENT_IDS_EXTRA = "recipient_ids";
private static final String MESSAGE_ID_EXTRA = "message_id";
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 SafetyNumberChangeViewModel viewModel;
private SafetyNumberChangeAdapter adapter;
@@ -54,6 +56,7 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
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);
@@ -69,6 +72,7 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
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(fragmentActivity.getSupportFragmentManager(), SAFETY_NUMBER_DIALOG);
@@ -77,7 +81,43 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
public static void showForCall(@NonNull FragmentManager fragmentManager, @NonNull RecipientId recipientId) {
Bundle arguments = new Bundle();
arguments.putStringArray(RECIPIENT_IDS_EXTRA, new String[] { recipientId.serialize() });
arguments.putBoolean(IS_CALL_EXTRA, true);
arguments.putInt(CONTINUE_TEXT_RESOURCE_EXTRA, R.string.safety_number_change_dialog__call_anyway);
SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog();
fragment.setArguments(arguments);
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
public static void showForGroupCall(@NonNull FragmentManager fragmentManager, @NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
List<String> ids = Stream.of(identityRecords)
.filterNot(IdentityDatabase.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__join_call);
SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog();
fragment.setArguments(arguments);
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
}
public static void showForDuringGroupCall(@NonNull FragmentManager fragmentManager, @NonNull Collection<RecipientId> recipientIds) {
Fragment previous = fragmentManager.findFragmentByTag(SAFETY_NUMBER_DIALOG);
if (previous != null) {
((SafetyNumberChangeDialog) previous).updateRecipients(recipientIds);
return;
}
List<String> ids = Stream.of(recipientIds)
.map(RecipientId::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__continue_call);
arguments.putInt(CANCEL_TEXT_RESOURCE_EXTRA, R.string.safety_number_change_dialog__leave_call);
SafetyNumberChangeDialog fragment = new SafetyNumberChangeDialog();
fragment.setArguments(arguments);
fragment.show(fragmentManager, SAFETY_NUMBER_DIALOG);
@@ -105,7 +145,8 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
@Override
public @NonNull Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
boolean isCall = requireArguments().getBoolean(IS_CALL_EXTRA, false);
int continueText = requireArguments().getInt(CONTINUE_TEXT_RESOURCE_EXTRA, android.R.string.ok);
int cancelText = requireArguments().getInt(CANCEL_TEXT_RESOURCE_EXTRA, android.R.string.cancel);
dialogView = LayoutInflater.from(requireActivity()).inflate(R.layout.safety_number_change_dialog, null);
@@ -115,13 +156,17 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
builder.setTitle(R.string.safety_number_change_dialog__safety_number_changes)
.setView(dialogView)
.setPositiveButton(isCall ? R.string.safety_number_change_dialog__call_anyway : R.string.safety_number_change_dialog__send_anyway, this::handleSendAnyway)
.setNegativeButton(android.R.string.cancel, this::handleCancel);
.setCancelable(false)
.setPositiveButton(continueText, this::handleSendAnyway)
.setNegativeButton(cancelText, this::handleCancel);
setCancelable(false);
return builder.create();
}
@Override public void onDestroyView() {
@Override
public void onDestroyView() {
dialogView = null;
super.onDestroyView();
}
@@ -134,6 +179,10 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
list.setLayoutManager(new LinearLayoutManager(requireContext()));
}
private void updateRecipients(Collection<RecipientId> recipientIds) {
viewModel.updateRecipients(recipientIds);
}
private void handleSendAnyway(DialogInterface dialogInterface, int which) {
Activity activity = getActivity();
Callback callback;
@@ -149,9 +198,9 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
@Override
public void onChanged(TrustAndVerifyResult result) {
if (callback != null) {
switch (result) {
switch (result.getResult()) {
case TRUST_AND_VERIFY:
callback.onSendAnywayAfterSafetyNumberChange();
callback.onSendAnywayAfterSafetyNumberChange(result.getChangedRecipients());
break;
case TRUST_VERIFY_AND_RESEND:
callback.onMessageResentAfterSafetyNumberChange();
@@ -177,7 +226,7 @@ public final class SafetyNumberChangeDialog extends DialogFragment implements Sa
}
public interface Callback {
void onSendAnywayAfterSafetyNumberChange();
void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients);
void onMessageResentAfterSafetyNumberChange();
void onCanceled();
}

View File

@@ -11,15 +11,12 @@ import androidx.lifecycle.MutableLiveData;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.crypto.storage.TextSecureIdentityKeyStore;
import org.thoughtcrime.securesms.database.Database;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.MmsDatabase;
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -29,6 +26,7 @@ import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.SignalProtocolAddress;
import java.util.Collection;
import java.util.List;
import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
@@ -43,12 +41,6 @@ final class SafetyNumberChangeRepository {
this.context = context.getApplicationContext();
}
@NonNull LiveData<SafetyNumberChangeState> getSafetyNumberChangeState(@NonNull List<RecipientId> recipientIds, @Nullable Long messageId, @Nullable String messageType) {
MutableLiveData<SafetyNumberChangeState> liveData = new MutableLiveData<>();
SignalExecutors.BOUNDED.execute(() -> liveData.postValue(getSafetyNumberChangeStateInternal(recipientIds, messageId, messageType)));
return liveData;
}
@NonNull LiveData<TrustAndVerifyResult> trustOrVerifyChangedRecipients(@NonNull List<ChangedRecipient> changedRecipients) {
MutableLiveData<TrustAndVerifyResult> liveData = new MutableLiveData<>();
SignalExecutors.BOUNDED.execute(() -> liveData.postValue(trustOrVerifyChangedRecipientsInternal(changedRecipients)));
@@ -62,7 +54,7 @@ final class SafetyNumberChangeRepository {
}
@WorkerThread
private @NonNull SafetyNumberChangeState getSafetyNumberChangeStateInternal(@NonNull List<RecipientId> recipientIds, @Nullable Long messageId, @Nullable String messageType) {
public @NonNull SafetyNumberChangeState getSafetyNumberChangeState(@NonNull Collection<RecipientId> recipientIds, @Nullable Long messageId, @Nullable String messageType) {
MessageRecord messageRecord = null;
if (messageId != null && messageType != null) {
messageRecord = getMessageRecord(messageId, messageType);
@@ -112,7 +104,7 @@ final class SafetyNumberChangeRepository {
}
}
return TrustAndVerifyResult.TRUST_AND_VERIFY;
return TrustAndVerifyResult.trustAndVerify(changedRecipients);
}
@WorkerThread
@@ -130,7 +122,7 @@ final class SafetyNumberChangeRepository {
processOutgoingMessageRecord(changedRecipients, messageRecord);
}
return TrustAndVerifyResult.TRUST_VERIFY_AND_RESEND;
return TrustAndVerifyResult.trustVerifyAndResend(changedRecipients, messageRecord);
}
@WorkerThread

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation.ui.error;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
@@ -10,22 +11,26 @@ import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.conversation.ui.error.SafetyNumberChangeRepository.SafetyNumberChangeState;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
public final class SafetyNumberChangeViewModel extends ViewModel {
private final SafetyNumberChangeRepository safetyNumberChangeRepository;
private final LiveData<SafetyNumberChangeState> safetyNumberChangeState;
private final SafetyNumberChangeRepository safetyNumberChangeRepository;
private final MutableLiveData<Collection<RecipientId>> recipientIds;
private final LiveData<SafetyNumberChangeState> safetyNumberChangeState;
private SafetyNumberChangeViewModel(@NonNull List<RecipientId> recipientIds,
@Nullable Long messageId,
@Nullable String messageType,
SafetyNumberChangeRepository safetyNumberChangeRepository)
@NonNull SafetyNumberChangeRepository safetyNumberChangeRepository)
{
this.safetyNumberChangeRepository = safetyNumberChangeRepository;
safetyNumberChangeState = this.safetyNumberChangeRepository.getSafetyNumberChangeState(recipientIds, messageId, messageType);
this.recipientIds = new MutableLiveData<>(recipientIds);
this.safetyNumberChangeState = LiveDataUtil.mapAsync(this.recipientIds, ids -> this.safetyNumberChangeRepository.getSafetyNumberChangeState(ids, messageId, messageType));
}
@NonNull LiveData<List<ChangedRecipient>> getChangedRecipients() {
@@ -41,6 +46,10 @@ public final class SafetyNumberChangeViewModel extends ViewModel {
}
}
void updateRecipients(Collection<RecipientId> recipientIds) {
this.recipientIds.setValue(recipientIds);
}
public static final class Factory implements ViewModelProvider.Factory {
private final List<RecipientId> recipientIds;
private final Long messageId;

View File

@@ -1,7 +1,53 @@
package org.thoughtcrime.securesms.conversation.ui.error;
public enum TrustAndVerifyResult {
TRUST_AND_VERIFY,
TRUST_VERIFY_AND_RESEND,
UNKNOWN
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.recipients.RecipientId;
import java.util.List;
/**
* Result of trust/verify after safety number change.
*/
public class TrustAndVerifyResult {
private final List<RecipientId> changedRecipients;
private final MessageRecord messageRecord;
private final Result result;
static TrustAndVerifyResult trustAndVerify(@NonNull List<ChangedRecipient> changedRecipients) {
return new TrustAndVerifyResult(changedRecipients, null, Result.TRUST_AND_VERIFY);
}
static TrustAndVerifyResult trustVerifyAndResend(@NonNull List<ChangedRecipient> changedRecipients, @NonNull MessageRecord messageRecord) {
return new TrustAndVerifyResult(changedRecipients, messageRecord, Result.TRUST_VERIFY_AND_RESEND);
}
TrustAndVerifyResult(@NonNull List<ChangedRecipient> changedRecipients, @Nullable MessageRecord messageRecord, @NonNull Result result) {
this.changedRecipients = Stream.of(changedRecipients).map(changedRecipient -> changedRecipient.getRecipient().getId()).toList();
this.messageRecord = messageRecord;
this.result = result;
}
public @NonNull List<RecipientId> getChangedRecipients() {
return changedRecipients;
}
public @Nullable MessageRecord getMessageRecord() {
return messageRecord;
}
public @NonNull Result getResult() {
return result;
}
public enum Result {
TRUST_AND_VERIFY,
TRUST_VERIFY_AND_RESEND,
UNKNOWN
}
}