kopia lustrzana https://github.com/ryukoposting/Signal-Android
Handle safety number changes in a group call context.
rodzic
112782ccaf
commit
42d61518b3
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -689,8 +689,4 @@ public class WebRtcCallView extends FrameLayout {
|
|||
void onShowParticipantsList();
|
||||
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
||||
}
|
||||
|
||||
public interface EventListener {
|
||||
void onPotentialLayoutChange();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Ładowanie…
Reference in New Issue