Handle safety number changes in a group call context.

fork-5.53.8
Cody Henthorne 2020-12-04 15:24:18 -05:00 zatwierdzone przez Greyson Parrelli
rodzic 112782ccaf
commit 42d61518b3
17 zmienionych plików z 578 dodań i 121 usunięć

Wyświetl plik

@ -43,6 +43,7 @@ import org.greenrobot.eventbus.ThreadMode;
import org.thoughtcrime.securesms.components.TooltipPopup;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow;
import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState;
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
import org.thoughtcrime.securesms.components.webrtc.WebRtcAudioOutput;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallView;
import org.thoughtcrime.securesms.components.webrtc.WebRtcCallViewModel;
@ -53,21 +54,25 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.messagerequests.CalleeMustAcceptMessageRequestActivity;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import java.util.List;
public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumberChangeDialog.Callback {
private static final String TAG = WebRtcCallActivity.class.getSimpleName();
private static final int STANDARD_DELAY_FINISH = 1000;
private static final int STANDARD_DELAY_FINISH = 1000;
public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION";
public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION";
@ -138,9 +143,11 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
EventBus.getDefault().unregister(this);
}
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
finish();
if (!viewModel.isCallingStarted()) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
finish();
}
}
}
@ -151,11 +158,13 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
EventBus.getDefault().unregister(this);
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
startService(intent);
if (!viewModel.isCallingStarted()) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
startService(intent);
}
}
}
@ -231,6 +240,7 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
viewModel.getCallTime().observe(this, this::handleCallTime);
viewModel.getCallParticipantsState().observe(this, callScreen::updateCallParticipants);
viewModel.getCallParticipantListUpdate().observe(this, participantUpdateWindow::addCallParticipantListUpdate);
viewModel.getSafetyNumberChangeEvent().observe(this, this::handleSafetyNumberChangeEvent);
callScreen.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
@ -245,30 +255,35 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
}
private void handleViewModelEvent(@NonNull WebRtcCallViewModel.Event event) {
if (event instanceof WebRtcCallViewModel.Event.StartCall) {
startCall(((WebRtcCallViewModel.Event.StartCall)event).isVideoCall());
return;
} else if (event instanceof WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) {
SafetyNumberChangeDialog.showForGroupCall(getSupportFragmentManager(), ((WebRtcCallViewModel.Event.ShowGroupCallSafetyNumberChange) event).getIdentityRecords());
return;
}
if (isInPipMode()) {
return;
}
switch (event) {
case SHOW_VIDEO_TOOLTIP:
if (videoTooltip == null) {
videoTooltip = TooltipPopup.forTarget(callScreen.getVideoTooltipTarget())
.setBackgroundTint(ContextCompat.getColor(this, R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(this, R.color.core_white))
.setText(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video)
.setOnDismissListener(() -> viewModel.onDismissedVideoTooltip())
.show(TooltipPopup.POSITION_ABOVE);
return;
}
break;
case DISMISS_VIDEO_TOOLTIP:
if (videoTooltip != null) {
videoTooltip.dismiss();
videoTooltip = null;
}
break;
default:
throw new IllegalArgumentException("Unknown event: " + event);
if (event instanceof WebRtcCallViewModel.Event.ShowVideoTooltip) {
if (videoTooltip == null) {
videoTooltip = TooltipPopup.forTarget(callScreen.getVideoTooltipTarget())
.setBackgroundTint(ContextCompat.getColor(this, R.color.core_ultramarine))
.setTextColor(ContextCompat.getColor(this, R.color.core_white))
.setText(R.string.WebRtcCallActivity__tap_here_to_turn_on_your_video)
.setOnDismissListener(() -> viewModel.onDismissedVideoTooltip())
.show(TooltipPopup.POSITION_ABOVE);
return;
}
} else if (event instanceof WebRtcCallViewModel.Event.DismissVideoTooltip) {
if (videoTooltip != null) {
videoTooltip.dismiss();
videoTooltip = null;
}
} else {
throw new IllegalArgumentException("Unknown event: " + event);
}
}
@ -479,14 +494,28 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
SafetyNumberChangeDialog.showForCall(getSupportFragmentManager(), recipient.getId());
}
@Override
public void onSendAnywayAfterSafetyNumberChange() {
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId()))
.putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, OfferMessage.Type.AUDIO_CALL.getCode());
public void handleSafetyNumberChangeEvent(@NonNull WebRtcCallViewModel.SafetyNumberChangeEvent safetyNumberChangeEvent) {
if (Util.hasItems(safetyNumberChangeEvent.getRecipientIds())) {
if (safetyNumberChangeEvent.isInPipMode()) {
GroupCallSafetyNumberChangeNotificationUtil.showNotification(this, viewModel.getRecipient().get());
} else {
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(this, viewModel.getRecipient().get());
SafetyNumberChangeDialog.showForDuringGroupCall(getSupportFragmentManager(), safetyNumberChangeEvent.getRecipientIds());
}
}
}
startService(intent);
@Override
public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> changedRecipients) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state.getGroupCallState().isConnected()) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE)
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
startService(intent);
} else {
startCall(state.getLocalParticipant().isVideoEnabled());
}
}
@Override
@ -494,7 +523,19 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
@Override
public void onCanceled() {
handleTerminate(viewModel.getRecipient().get(), HangupMessage.Type.NORMAL);
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getGroupCallState().isNotIdle()) {
if (state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
startService(intent);
finish();
} else {
handleEndCall();
}
} else {
handleTerminate(viewModel.getRecipient().get(), HangupMessage.Type.NORMAL);
}
}
private boolean isSystemPipEnabledAndAvailable() {
@ -550,19 +591,23 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe
}
}
private void startCall(boolean isVideoCall) {
enableVideoIfAvailable = isVideoCall;
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId()))
.putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, (isVideoCall ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL).getCode());
startService(intent);
MessageSender.onMessageSent();
}
private final class ControlsListener implements WebRtcCallView.ControlsListener {
@Override
public void onStartCall(boolean isVideoCall) {
enableVideoIfAvailable = isVideoCall;
Intent intent = new Intent(WebRtcCallActivity.this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_OUTGOING_CALL)
.putExtra(WebRtcCallService.EXTRA_REMOTE_PEER, new RemotePeer(viewModel.getRecipient().getId()))
.putExtra(WebRtcCallService.EXTRA_OFFER_TYPE, (isVideoCall ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL).getCode());
startService(intent);
MessageSender.onMessageSent();
viewModel.startCall(isVideoCall);
}
@Override

Wyświetl plik

@ -0,0 +1,47 @@
package org.thoughtcrime.securesms.components.webrtc;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.notifications.NotificationChannels;
import org.thoughtcrime.securesms.recipients.Recipient;
/**
* Utility for showing and hiding safety number change notifications during a group call.
*/
public final class GroupCallSafetyNumberChangeNotificationUtil {
public static final String GROUP_CALLING_NOTIFICATION_TAG = "group_calling";
private GroupCallSafetyNumberChangeNotificationUtil() {
}
public static void showNotification(@NonNull Context context, @NonNull Recipient recipient) {
Intent contentIntent = new Intent(context, WebRtcCallActivity.class);
contentIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, contentIntent, 0);
Notification safetyNumberChangeNotification = new NotificationCompat.Builder(context, NotificationChannels.CALLS)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(recipient.getDisplayName(context))
.setContentText(context.getString(R.string.GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed))
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed)))
.setContentIntent(pendingIntent)
.build();
NotificationManagerCompat.from(context).notify(GROUP_CALLING_NOTIFICATION_TAG, recipient.hashCode(), safetyNumberChangeNotification);
}
public static void cancelNotification(@NonNull Context context, @NonNull Recipient recipient) {
NotificationManagerCompat.from(context).cancel(GROUP_CALLING_NOTIFICATION_TAG, recipient.hashCode());
}
}

Wyświetl plik

@ -1,19 +1,35 @@
package org.thoughtcrime.securesms.components.webrtc;
import android.content.Context;
import android.media.AudioManager;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import androidx.core.util.Consumer;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.util.Collections;
import java.util.List;
class WebRtcCallRepository {
private final Context context;
private final AudioManager audioManager;
WebRtcCallRepository() {
WebRtcCallRepository(@NonNull Context context) {
this.context = context;
this.audioManager = ServiceUtil.getAudioManager(ApplicationDependencies.getApplication());
}
WebRtcAudioOutput getAudioOutput() {
@NonNull WebRtcAudioOutput getAudioOutput() {
if (audioManager.isBluetoothScoOn()) {
return WebRtcAudioOutput.HEADSET;
} else if (audioManager.isSpeakerphoneOn()) {
@ -22,4 +38,20 @@ class WebRtcCallRepository {
return WebRtcAudioOutput.HANDSET;
}
}
@WorkerThread
void getIdentityRecords(@NonNull Recipient recipient, @NonNull Consumer<IdentityRecordList> consumer) {
SignalExecutors.BOUNDED.execute(() -> {
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(context);
List<Recipient> recipients;
if (recipient.isGroup()) {
recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
} else {
recipients = Collections.singletonList(recipient);
}
consumer.accept(identityDatabase.getIdentities(recipients));
});
}
}

Wyświetl plik

@ -689,8 +689,4 @@ public class WebRtcCallView extends FrameLayout {
void onShowParticipantsList();
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
}
public interface EventListener {
void onPotentialLayoutChange();
}
}

Wyświetl plik

@ -12,15 +12,19 @@ import androidx.lifecycle.ViewModel;
import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.database.IdentityDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -35,6 +39,8 @@ public class WebRtcCallViewModel extends ViewModel {
private final MutableLiveData<LiveRecipient> liveRecipient = new MutableLiveData<>(Recipient.UNKNOWN.live());
private final MutableLiveData<CallParticipantsState> participantsState = new MutableLiveData<>(CallParticipantsState.STARTING_STATE);
private final SingleLiveEvent<CallParticipantListUpdate> callParticipantListUpdate = new SingleLiveEvent<>();
private final MutableLiveData<Collection<RecipientId>> identityChangedRecipients = new MutableLiveData<>(Collections.emptyList());
private final LiveData<SafetyNumberChangeEvent> safetyNumberChangeEvent = LiveDataUtil.combineLatest(isInPipMode, identityChangedRecipients, SafetyNumberChangeEvent::new);
private boolean canDisplayTooltipIfNeeded = true;
private boolean hasEnabledLocalVideo = false;
@ -44,8 +50,9 @@ public class WebRtcCallViewModel extends ViewModel {
private Runnable elapsedTimeRunnable = this::handleTick;
private boolean canEnterPipMode = false;
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
private boolean callingStarted = false;
private final WebRtcCallRepository repository = new WebRtcCallRepository();
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
public LiveData<Boolean> getMicrophoneEnabled() {
return Transformations.distinctUntilChanged(microphoneEnabled);
@ -79,6 +86,10 @@ public class WebRtcCallViewModel extends ViewModel {
return callParticipantListUpdate;
}
public LiveData<SafetyNumberChangeEvent> getSafetyNumberChangeEvent() {
return safetyNumberChangeEvent;
}
public boolean canEnterPipMode() {
return canEnterPipMode;
}
@ -87,6 +98,10 @@ public class WebRtcCallViewModel extends ViewModel {
return answerWithVideoAvailable;
}
public boolean isCallingStarted() {
return callingStarted;
}
@MainThread
public void setIsInPipMode(boolean isInPipMode) {
this.isInPipMode.setValue(isInPipMode);
@ -123,6 +138,8 @@ public class WebRtcCallViewModel extends ViewModel {
}
previousParticipantsList = webRtcViewModel.getRemoteParticipants();
identityChangedRecipients.setValue(webRtcViewModel.getIdentityChangedParticipants());
}
updateWebRtcControls(webRtcViewModel.getState(),
@ -146,13 +163,13 @@ public class WebRtcCallViewModel extends ViewModel {
if (localParticipant.getCameraState().isEnabled()) {
canDisplayTooltipIfNeeded = false;
hasEnabledLocalVideo = true;
events.setValue(Event.DISMISS_VIDEO_TOOLTIP);
events.setValue(new Event.DismissVideoTooltip());
}
// If remote video is enabled and we a) haven't shown our video and b) have not dismissed the popup
if (canDisplayTooltipIfNeeded && webRtcViewModel.isRemoteVideoEnabled() && !hasEnabledLocalVideo) {
canDisplayTooltipIfNeeded = false;
events.setValue(Event.SHOW_VIDEO_TOOLTIP);
events.setValue(new Event.ShowVideoTooltip());
}
}
@ -259,8 +276,74 @@ public class WebRtcCallViewModel extends ViewModel {
cancelTimer();
}
public enum Event {
SHOW_VIDEO_TOOLTIP,
DISMISS_VIDEO_TOOLTIP
public void startCall(boolean isVideoCall) {
callingStarted = true;
Recipient recipient = getRecipient().get();
if (recipient.isGroup()) {
repository.getIdentityRecords(recipient, identityRecords -> {
if (identityRecords.isUntrusted(false) || identityRecords.isUnverified(false)) {
List<IdentityDatabase.IdentityRecord> records = identityRecords.getUnverifiedRecords();
records.addAll(identityRecords.getUntrustedRecords());
events.postValue(new Event.ShowGroupCallSafetyNumberChange(records));
} else {
events.postValue(new Event.StartCall(isVideoCall));
}
});
} else {
events.postValue(new Event.StartCall(isVideoCall));
}
}
public static abstract class Event {
private Event() {
}
public static class ShowVideoTooltip extends Event {
}
public static class DismissVideoTooltip extends Event {
}
public static class StartCall extends Event {
private final boolean isVideoCall;
public StartCall(boolean isVideoCall) {
this.isVideoCall = isVideoCall;
}
public boolean isVideoCall() {
return isVideoCall;
}
}
public static class ShowGroupCallSafetyNumberChange extends Event {
private final List<IdentityDatabase.IdentityRecord> identityRecords;
public ShowGroupCallSafetyNumberChange(@NonNull List<IdentityDatabase.IdentityRecord> identityRecords) {
this.identityRecords = identityRecords;
}
public @NonNull List<IdentityDatabase.IdentityRecord> getIdentityRecords() {
return identityRecords;
}
}
}
public static class SafetyNumberChangeEvent {
private final boolean isInPipMode;
private final Collection<RecipientId> recipientIds;
private SafetyNumberChangeEvent(boolean isInPipMode, @NonNull Collection<RecipientId> recipientIds) {
this.isInPipMode = isInPipMode;
this.recipientIds = recipientIds;
}
public boolean isInPipMode() {
return isInPipMode;
}
public @NonNull Collection<RecipientId> getRecipientIds() {
return recipientIds;
}
}
}

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -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
}
}

Wyświetl plik

@ -7,9 +7,11 @@ import com.annimon.stream.Stream;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import java.util.List;
import java.util.Set;
public class WebRtcViewModel {
@ -91,6 +93,7 @@ public class WebRtcViewModel {
private final CallParticipant localParticipant;
private final List<CallParticipant> remoteParticipants;
private final Set<RecipientId> identityChangedRecipients;
public WebRtcViewModel(@NonNull State state,
@NonNull GroupCallState groupState,
@ -101,15 +104,17 @@ public class WebRtcViewModel {
boolean isMicrophoneEnabled,
boolean isRemoteVideoOffer,
long callConnectedTime,
@NonNull List<CallParticipant> remoteParticipants)
@NonNull List<CallParticipant> remoteParticipants,
@NonNull Set<RecipientId> identityChangedRecipients)
{
this.state = state;
this.groupState = groupState;
this.recipient = recipient;
this.isBluetoothAvailable = isBluetoothAvailable;
this.isRemoteVideoOffer = isRemoteVideoOffer;
this.callConnectedTime = callConnectedTime;
this.remoteParticipants = remoteParticipants;
this.state = state;
this.groupState = groupState;
this.recipient = recipient;
this.isBluetoothAvailable = isBluetoothAvailable;
this.isRemoteVideoOffer = isRemoteVideoOffer;
this.callConnectedTime = callConnectedTime;
this.remoteParticipants = remoteParticipants;
this.identityChangedRecipients = identityChangedRecipients;
localParticipant = CallParticipant.createLocal(localCameraState, localSink != null ? localSink : new BroadcastVideoSink(null), isMicrophoneEnabled);
}
@ -150,7 +155,12 @@ public class WebRtcViewModel {
return remoteParticipants;
}
@Override public @NonNull String toString() {
public @NonNull Set<RecipientId> getIdentityChangedParticipants() {
return identityChangedRecipients;
}
@Override
public @NonNull String toString() {
return "WebRtcViewModel{" +
"state=" + state +
", recipient=" + recipient.getId() +
@ -159,6 +169,7 @@ public class WebRtcViewModel {
", callConnectedTime=" + callConnectedTime +
", localParticipant=" + localParticipant +
", remoteParticipants=" + remoteParticipants +
", identityChangedRecipients=" + identityChangedRecipients +
'}';
}
}

Wyświetl plik

@ -138,6 +138,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
public static final String EXTRA_GROUP_CALL_UPDATE_SENDER = "group_call_update_sender";
public static final String EXTRA_GROUP_CALL_UPDATE_GROUP = "group_call_update_group";
public static final String EXTRA_GROUP_CALL_ERA_ID = "era_id";
public static final String EXTRA_RECIPIENT_IDS = "recipient_ids";
public static final String ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN";
public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL";
@ -203,6 +204,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
public static final String ACTION_GROUP_CALL_ENDED = "GROUP_CALL_ENDED";
public static final String ACTION_GROUP_CALL_UPDATE_MESSAGE = "GROUP_CALL_UPDATE_MESSAGE";
public static final String ACTION_GROUP_CALL_PEEK = "GROUP_CALL_PEEK";
public static final String ACTION_GROUP_MESSAGE_SENT_ERROR = "GROUP_MESSAGE_SENT_ERROR";
public static final String ACTION_GROUP_APPROVE_SAFETY_CHANGE = "GROUP_APPROVE_SAFETY_CHANGE";
public static final int BUSY_TONE_LENGTH = 2000;
@ -436,7 +439,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
state.getLocalDeviceState().isMicrophoneEnabled(),
state.getCallSetupState().isRemoteVideoOffer(),
state.getCallInfoState().getCallConnectedTime(),
state.getCallInfoState().getRemoteCallParticipants()));
state.getCallInfoState().getRemoteCallParticipants(),
state.getCallInfoState().getIdentityChangedRecipients()));
}
private @NonNull ListenableFutureTask<Boolean> sendMessage(@NonNull final RemotePeer remotePeer,
@ -669,7 +673,36 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
}
public void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage opaqueMessage) {
sendMessage(new RemotePeer(RecipientId.from(uuid, null)), opaqueMessage);
RecipientId recipientId = RecipientId.from(uuid, null);
ListenableFutureTask<Boolean> listenableFutureTask = sendMessage(new RemotePeer(recipientId), opaqueMessage);
listenableFutureTask.addListener(new FutureTaskListener<Boolean>() {
@Override
public void onSuccess(Boolean result) {
// intentionally left blank
}
@Override
public void onFailure(ExecutionException exception) {
Throwable error = exception.getCause();
Log.i(TAG, "sendOpaqueCallMessage onFailure: ", error);
Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class);
intent.setAction(ACTION_GROUP_MESSAGE_SENT_ERROR);
WebRtcViewModel.State state = WebRtcViewModel.State.NETWORK_FAILURE;
if (error instanceof UntrustedIdentityException) {
intent.putExtra(EXTRA_ERROR_IDENTITY_KEY, new IdentityKeyParcelable(((UntrustedIdentityException) error).getIdentityKey()));
state = WebRtcViewModel.State.UNTRUSTED_IDENTITY;
}
intent.putExtra(EXTRA_ERROR_CALL_STATE, state);
intent.putExtra(EXTRA_REMOTE_PEER, new RemotePeer(recipientId));
startService(intent);
}
});
}
public void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
@ -739,13 +772,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
}
public void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection<UUID> joinedMembers, boolean isCallFull) {
DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(groupId,
Recipient.self().getId(),
System.currentTimeMillis(),
null,
groupCallEraId,
joinedMembers,
isCallFull);
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(groupId,
Recipient.self().getId(),
System.currentTimeMillis(),
null,
groupCallEraId,
joinedMembers,
isCallFull));
}
@Override

Wyświetl plik

@ -9,16 +9,21 @@ import com.annimon.stream.Stream;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import org.webrtc.VideoTrack;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
@ -201,6 +206,47 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
return currentState;
}
@Override
protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState,
@NonNull RemotePeer remotePeer,
@NonNull WebRtcViewModel.State errorCallState,
@NonNull Optional<IdentityKey> identityKey)
{
Log.w(tag, "handleGroupMessageSentError(): error: " + errorCallState);
if (errorCallState == WebRtcViewModel.State.UNTRUSTED_IDENTITY) {
return currentState.builder()
.changeCallInfoState()
.addIdentityChangedRecipient(remotePeer.getId())
.build();
}
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupApproveSafetyNumberChange(@NonNull WebRtcServiceState currentState,
@NonNull List<RecipientId> recipientIds)
{
Log.i(tag, "handleGroupApproveSafetyNumberChange():");
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
if (groupCall != null) {
currentState = currentState.builder()
.changeCallInfoState()
.removeIdentityChangedRecipients(recipientIds)
.build();
try {
groupCall.resendMediaKeys();
} catch (CallException e) {
return groupCallFailure(currentState, "Unable to resend media keys", e);
}
}
return currentState;
}
@Override
protected @NonNull WebRtcServiceState handleGroupCallEnded(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
Log.i(tag, "handleGroupCallEnded(): reason: " + groupCallEndReason);
@ -269,6 +315,8 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
WebRtcVideoUtil.deinitializeVideo(currentState);
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient());
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
}
}

Wyświetl plik

@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.ringrtc.CallState;
import org.thoughtcrime.securesms.ringrtc.CameraState;
@ -60,11 +61,13 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_SIGNALING_FAILURE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_TIMEOUT;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_FLIP_CAMERA;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_ENDED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_PEEK;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_UPDATE_MESSAGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_MESSAGE_SENT_ERROR;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS;
@ -108,6 +111,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BLUETOOTH;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_IS_ALWAYS_TURN;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MUTE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RECIPIENT_IDS;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RESULT_RECEIVER;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
@ -240,6 +244,8 @@ public abstract class WebRtcActionProcessor {
case ACTION_GROUP_CALL_ENDED: return handleGroupCallEnded(currentState, getGroupCallHash(intent), getGroupCallEndReason(intent));
case ACTION_GROUP_CALL_UPDATE_MESSAGE: return handleGroupCallUpdateMessage(currentState, GroupCallUpdateMetadata.fromIntent(intent));
case ACTION_GROUP_CALL_PEEK: return handleGroupCallPeek(currentState, getRemotePeer(intent));
case ACTION_GROUP_MESSAGE_SENT_ERROR: return handleGroupMessageSentError(currentState, getRemotePeer(intent), getErrorCallState(intent), getErrorIdentityKey(intent));
case ACTION_GROUP_APPROVE_SAFETY_CHANGE: return handleGroupApproveSafetyNumberChange(currentState, RecipientId.fromSerializedList(intent.getStringExtra(EXTRA_RECIPIENT_IDS)));
case ACTION_HTTP_SUCCESS: return handleHttpSuccess(currentState, HttpData.fromIntent(intent));
case ACTION_HTTP_FAILURE: return handleHttpFailure(currentState, HttpData.fromIntent(intent));
@ -734,6 +740,22 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState,
@NonNull RemotePeer remotePeer,
@NonNull WebRtcViewModel.State errorCallState,
@NonNull Optional<IdentityKey> identityKey)
{
Log.i(tag, "handleGroupMessageSentError not processed");
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupApproveSafetyNumberChange(@NonNull WebRtcServiceState currentState,
@NonNull List<RecipientId> recipientIds)
{
Log.i(tag, "handleGroupApproveSafetyNumberChange not processed");
return currentState;
}
//endregion
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {

Wyświetl plik

@ -8,15 +8,18 @@ import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* General state of ongoing calls.
@ -31,13 +34,30 @@ public class CallInfoState {
RemotePeer activePeer;
GroupCall groupCall;
WebRtcViewModel.GroupCallState groupState;
Set<RecipientId> identityChangedRecipients;
public CallInfoState() {
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null, null, WebRtcViewModel.GroupCallState.IDLE);
this(WebRtcViewModel.State.IDLE,
Recipient.UNKNOWN,
-1,
Collections.emptyMap(),
Collections.emptyMap(),
null,
null,
WebRtcViewModel.GroupCallState.IDLE,
Collections.emptySet());
}
public CallInfoState(@NonNull CallInfoState toCopy) {
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer, toCopy.groupCall, toCopy.groupState);
this(toCopy.callState,
toCopy.callRecipient,
toCopy.callConnectedTime,
toCopy.remoteParticipants,
toCopy.peerMap,
toCopy.activePeer,
toCopy.groupCall,
toCopy.groupState,
toCopy.identityChangedRecipients);
}
public CallInfoState(@NonNull WebRtcViewModel.State callState,
@ -47,16 +67,18 @@ public class CallInfoState {
@NonNull Map<Integer, RemotePeer> peerMap,
@Nullable RemotePeer activePeer,
@Nullable GroupCall groupCall,
@NonNull WebRtcViewModel.GroupCallState groupState)
@NonNull WebRtcViewModel.GroupCallState groupState,
@NonNull Set<RecipientId> identityChangedRecipients)
{
this.callState = callState;
this.callRecipient = callRecipient;
this.callConnectedTime = callConnectedTime;
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
this.peerMap = new HashMap<>(peerMap);
this.activePeer = activePeer;
this.groupCall = groupCall;
this.groupState = groupState;
this.callState = callState;
this.callRecipient = callRecipient;
this.callConnectedTime = callConnectedTime;
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
this.peerMap = new HashMap<>(peerMap);
this.activePeer = activePeer;
this.groupCall = groupCall;
this.groupState = groupState;
this.identityChangedRecipients = new HashSet<>(identityChangedRecipients);
}
public @NonNull Recipient getCallRecipient() {
@ -110,4 +132,8 @@ public class CallInfoState {
public @NonNull WebRtcViewModel.GroupCallState getGroupCallState() {
return groupState;
}
public @NonNull Set<RecipientId> getIdentityChangedRecipients() {
return identityChangedRecipients;
}
}

Wyświetl plik

@ -9,12 +9,15 @@ import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.CallParticipantId;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.Camera;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.WebRtcActionProcessor;
import org.webrtc.EglBase;
import java.util.Collection;
/**
* Builder that creates a new {@link WebRtcServiceState} from an existing one and allows
* changes to all normally immutable data.
@ -243,5 +246,15 @@ public class WebRtcServiceStateBuilder {
toBuild.groupState = groupState;
return this;
}
public @NonNull CallInfoStateBuilder addIdentityChangedRecipient(@NonNull RecipientId id) {
toBuild.identityChangedRecipients.add(id);
return this;
}
public @NonNull CallInfoStateBuilder removeIdentityChangedRecipients(@NonNull Collection<RecipientId> ids) {
toBuild.identityChangedRecipients.removeAll(ids);
return this;
}
}
}

Wyświetl plik

@ -1797,6 +1797,8 @@
<string name="WebRtcCallActivity__declined_on_a_linked_device">Declined on a linked device.</string>
<string name="WebRtcCallActivity__busy_on_a_linked_device">Busy on a linked device.</string>
<string name="GroupCallSafetyNumberChangeNotification__someone_has_joined_this_call_with_a_safety_number_that_has_changed">Someone has joined this call with a safety number that has changed.</string>
<!-- WebRtcCallScreen -->
<string name="WebRtcCallScreen_new_safety_numbers">The safety number for your conversation with %1$s has changed. This could either mean that someone is trying to intercept your communication, or that %2$s simply re-installed Signal.</string>
<string name="WebRtcCallScreen_you_may_wish_to_verify_this_contact">You may wish to verify your safety number with this contact.</string>
@ -1958,6 +1960,9 @@
<string name="safety_number_change_dialog__safety_number_changes">Safety Number Changes</string>
<string name="safety_number_change_dialog__send_anyway">Send anyway</string>
<string name="safety_number_change_dialog__call_anyway">Call anyway</string>
<string name="safety_number_change_dialog__join_call">Join call</string>
<string name="safety_number_change_dialog__continue_call">Continue call</string>
<string name="safety_number_change_dialog__leave_call">Leave call</string>
<string name="safety_number_change_dialog__the_following_people_may_have_reinstalled_or_changed_devices">The following people may have reinstalled or changed devices. Verify your safety number with them to ensure privacy.</string>
<string name="safety_number_change_dialog__view">View</string>
<string name="safety_number_change_dialog__previous_verified">Previous verified</string>