Handle safety number changes in a group call context.

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

View File

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

View File

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

View File

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

View File

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

View File

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