Add additional Group Calling features.

This commit is contained in:
Cody Henthorne
2020-11-20 15:42:46 -05:00
committed by GitHub
parent 8c1737e597
commit b90a74d26a
61 changed files with 1193 additions and 134 deletions

View File

@@ -29,13 +29,16 @@ import org.signal.ringrtc.Remote;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.zkgroup.VerificationFailedException;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -47,12 +50,15 @@ import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel;
import org.thoughtcrime.securesms.service.webrtc.IdleActionProcessor;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData;
import org.thoughtcrime.securesms.service.webrtc.WebRtcInteractor;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.util.FutureTaskListener;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager;
import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager;
@@ -125,6 +131,11 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
public static final String EXTRA_OPAQUE_MESSAGE = "opaque";
public static final String EXTRA_UUID = "uuid";
public static final String EXTRA_MESSAGE_AGE_SECONDS = "message_age_seconds";
public static final String EXTRA_GROUP_CALL_END_REASON = "group_call_end_reason";
public static final String EXTRA_GROUP_CALL_HASH = "group_call_hash";
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 ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN";
public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL";
@@ -187,6 +198,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
public static final String ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF = "GROUP_REQUEST_MEMBERSHIP_PROOF";
public static final String ACTION_GROUP_REQUEST_UPDATE_MEMBERS = "GROUP_REQUEST_UPDATE_MEMBERS";
public static final String ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS = "GROUP_UPDATE_RENDERED_RESOLUTIONS";
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 int BUSY_TONE_LENGTH = 2000;
@@ -652,6 +665,38 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
sendMessage(new RemotePeer(RecipientId.from(uuid, null)), opaqueMessage);
}
public void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId)));
peekGroupCall(new WebRtcData.GroupCallUpdateMetadata(Recipient.self().getId(), recipient.getId(), groupCallEraId, System.currentTimeMillis()));
}
public void peekGroupCall(@NonNull WebRtcData.GroupCallUpdateMetadata groupCallUpdateMetadata) {
networkExecutor.execute(() -> {
try {
Recipient group = Recipient.resolved(groupCallUpdateMetadata.getGroupRecipientId());
GroupId.V2 groupId = group.requireGroupId().requireV2();
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(this, groupId);
List<GroupCall.GroupMemberInfo> members = Stream.of(GroupManager.getUuidCipherTexts(this, groupId))
.map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize()))
.toList();
callManager.peekGroupCall(BuildConfig.SIGNAL_SFU_URL, credential.getTokenBytes().toByteArray(), members, peekInfo -> {
DatabaseFactory.getSmsDatabase(this).insertOrUpdateGroupCall(group.getId(),
groupCallUpdateMetadata.getSender(),
groupCallUpdateMetadata.getServerReceivedTimestamp(),
groupCallUpdateMetadata.getGroupCallEraId(),
peekInfo.getEraId(),
peekInfo.getJoinedMembers());
});
} catch (IOException | VerificationFailedException | CallException e) {
Log.e(TAG, "error peeking", e);
}
});
}
@Override
public void onStartCall(@Nullable Remote remote, @NonNull CallId callId, @NonNull Boolean isOutgoing, @Nullable CallManager.CallMediaType callMediaType) {
Log.i(TAG, "onStartCall(): callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType);
@@ -967,11 +1012,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF)
.putExtra(EXTRA_GROUP_EXTERNAL_TOKEN, credential.getTokenBytes().toByteArray());
.putExtra(EXTRA_GROUP_EXTERNAL_TOKEN, credential.getTokenBytes().toByteArray())
.putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode());
startService(intent);
} catch (IOException | VerificationFailedException e) {
Log.w(TAG, "Unable to fetch group membership proof", e);
onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED);
}
});
}
@@ -992,12 +1039,18 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
}
@Override
public void onJoinedMembersChanged(@NonNull GroupCall groupCall) {
public void onPeekChanged(@NonNull GroupCall groupCall) {
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED));
}
@Override
public void onEnded(@NonNull GroupCall groupCall, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
Log.i(TAG, "onEnded: " + groupCallEndReason);
Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(ACTION_GROUP_CALL_ENDED)
.putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode())
.putExtra(EXTRA_GROUP_CALL_END_REASON, groupCallEndReason.ordinal());
startService(intent);
}
}

View File

@@ -44,6 +44,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
remotePeer.getRecipient(),
null,
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
true,
false
))
.build();
@@ -82,6 +83,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
remotePeer.getRecipient(),
null,
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
true,
false
))
.build();

View File

@@ -48,14 +48,14 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
LongSparseArray<GroupCall.RemoteDeviceState> remoteDevices = groupCall.getRemoteDeviceStates();
for(int i = 0; i < remoteDevices.size(); i++) {
for (int i = 0; i < remoteDevices.size(); i++) {
GroupCall.RemoteDeviceState device = remoteDevices.get(remoteDevices.keyAt(i));
Recipient recipient = Recipient.externalPush(context, device.getUserId(), null, false);
CallParticipantId callParticipantId = new CallParticipantId(device.getDemuxId(), recipient.getId());
CallParticipant callParticipant = participants.get(callParticipantId);
BroadcastVideoSink videoSink;
VideoTrack videoTrack = device.getVideoTrack();
VideoTrack videoTrack = device.getVideoTrack();
if (videoTrack != null) {
videoSink = (callParticipant != null && callParticipant.getVideoSink().getEglBase() != null) ? callParticipant.getVideoSink()
: new BroadcastVideoSink(currentState.getVideoState().requireEglBase());
@@ -68,17 +68,23 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
CallParticipant.createRemote(recipient,
null,
videoSink,
true));
Boolean.FALSE.equals(device.getAudioMuted()),
Boolean.FALSE.equals(device.getVideoMuted())));
}
return builder.build();
}
@Override
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull byte[] groupMembershipToken) {
Log.i(tag, "handleGroupRequestMembershipProof():");
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
if (groupCall == null || groupCall.hashCode() != groupCallHash) {
return currentState;
}
try {
groupCall.setMembershipProof(groupMembershipToken);
} catch (CallException e) {
@@ -96,7 +102,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
List<GroupCall.GroupMemberInfo> members = Stream.of(GroupManager.getUuidCipherTexts(context, group.requireGroupId().requireV2()))
.map(e -> new GroupCall.GroupMemberInfo(e.getKey(), e.getValue().serialize()))
.map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize()))
.toList();
try {
@@ -109,17 +115,20 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
ArrayList<GroupCall.RenderedResolution> renderedResolutions = new ArrayList<>(participants.size());
ArrayList<GroupCall.VideoRequest> resolutionRequests = new ArrayList<>(participants.size());
for (Map.Entry<CallParticipantId, CallParticipant> entry : participants.entrySet()) {
BroadcastVideoSink.RequestedSize maxSize = entry.getValue().getVideoSink().getMaxRequestingSize();
renderedResolutions.add(new GroupCall.RenderedResolution(entry.getKey().getDemuxId(), maxSize.getWidth(), maxSize.getHeight(), null));
BroadcastVideoSink videoSink = entry.getValue().getVideoSink();
BroadcastVideoSink.RequestedSize maxSize = videoSink.getMaxRequestingSize();
resolutionRequests.add(new GroupCall.VideoRequest(entry.getKey().getDemuxId(), maxSize.getWidth(), maxSize.getHeight(), null));
videoSink.newSizeRequested();
}
try {
currentState.getCallInfoState().requireGroupCall().setRenderedResolutions(renderedResolutions);
currentState.getCallInfoState().requireGroupCall().requestVideo(resolutionRequests);
} catch (CallException e) {
return groupCallFailure(currentState, "Unable to set rendered resolutions", e);
}
@@ -127,7 +136,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
try {
webRtcInteractor.getCallManager().receivedHttpResponse(httpData.getRequestId(), httpData.getStatus(), httpData.getBody() != null ? httpData.getBody() : new byte[0]);
} catch (CallException e) {
@@ -136,7 +145,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
try {
webRtcInteractor.getCallManager().httpRequestFailed(httpData.getRequestId());
} catch (CallException e) {
@@ -174,9 +183,43 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
return currentState;
}
@Override
protected @NonNull WebRtcServiceState handleGroupCallEnded(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
Log.i(tag, "handleGroupCallEnded(): reason: " + groupCallEndReason);
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
if (groupCall == null || groupCall.hashCode() != groupCallHash) {
return currentState;
}
try {
groupCall.disconnect();
} catch (CallException e) {
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
}
currentState = currentState.builder()
.changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
return terminateGroupCall(currentState);
}
public @NonNull WebRtcServiceState groupCallFailure(@NonNull WebRtcServiceState currentState, @NonNull String message, @NonNull Throwable error) {
Log.w(tag, "groupCallFailure(): " + message, error);
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
if (recipient != null && currentState.getCallInfoState().getGroupCallState().isConnected()) {
webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall));
}
currentState = currentState.builder()
.changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
@@ -186,7 +229,6 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
webRtcInteractor.sendMessage(currentState);
try {
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
if (groupCall != null) {
groupCall.disconnect();
}

View File

@@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.GroupCall;
import org.signal.ringrtc.PeekInfo;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.ringrtc.Camera;
@@ -20,6 +21,32 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
super(webRtcInteractor, TAG);
}
@Override
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
GroupCall.ConnectionState connectionState = device.getConnectionState();
GroupCall.JoinState joinState = device.getJoinState();
Log.i(tag, "local device changed: " + connectionState + " " + joinState);
WebRtcViewModel.GroupCallState groupCallState = WebRtcUtil.groupCallStateForConnection(connectionState);
if (connectionState == GroupCall.ConnectionState.CONNECTED || connectionState == GroupCall.ConnectionState.CONNECTING) {
if (joinState == GroupCall.JoinState.JOINED) {
groupCallState = WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
} else if (joinState == GroupCall.JoinState.JOINING) {
groupCallState = WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING;
}
}
return currentState.builder().changeCallInfoState()
.groupCallState(groupCallState)
.build();
}
@Override
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
Log.i(TAG, "handleSetEnableVideo():");
@@ -58,16 +85,43 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
.build();
}
@Override
protected @NonNull WebRtcServiceState handleGroupJoinedMembershipChanged(@NonNull WebRtcServiceState currentState) {
Log.i(tag, "handleGroupJoinedMembershipChanged():");
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
PeekInfo peekInfo = groupCall.getPeekInfo();
if (peekInfo == null) {
return currentState;
}
if (currentState.getCallSetupState().hasSentJoinedMessage()) {
return currentState;
}
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), WebRtcUtil.getGroupCallEraId(groupCall));
return currentState.builder()
.changeCallSetupState()
.sentJoinedMessage(true)
.build();
}
@Override
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
Log.i(TAG, "handleLocalHangup():");
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
try {
groupCall.disconnect();
} catch (CallException e) {
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
}
webRtcInteractor.sendGroupCallMessage(currentState.getCallInfoState().getCallRecipient(), WebRtcUtil.getGroupCallEraId(groupCall));
currentState = currentState.builder()
.changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)

View File

@@ -8,6 +8,8 @@ import com.annimon.stream.Stream;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.GroupCall;
import org.signal.ringrtc.PeekInfo;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
@@ -40,6 +42,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
BuildConfig.SIGNAL_SFU_URL,
currentState.getVideoState().requireEglBase(),
webRtcInteractor.getGroupCallObserver());
@@ -96,8 +99,14 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
Log.i(tag, "handleGroupJoinedMembershipChanged():");
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
PeekInfo peekInfo = groupCall.getPeekInfo();
List<Recipient> callParticipants = Stream.of(groupCall.getJoinedGroupMembers())
if (peekInfo == null) {
Log.i(tag, "No peek info available");
return currentState;
}
List<Recipient> callParticipants = Stream.of(peekInfo.getJoinedMembers())
.map(uuid -> Recipient.externalPush(context, uuid, null, false))
.toList();
@@ -105,7 +114,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
.changeCallInfoState();
for (Recipient recipient : callParticipants) {
builder.putParticipant(recipient, CallParticipant.createRemote(recipient, null, new BroadcastVideoSink(null), false));
builder.putParticipant(recipient, CallParticipant.createRemote(recipient, null, new BroadcastVideoSink(null), true, true));
}
return builder.build();

View File

@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
@@ -19,6 +20,7 @@ import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.CallMetadata;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.GroupCallUpdateMetadata;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.HttpData;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.OfferMetadata;
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedOfferMetadata;
@@ -58,6 +60,8 @@ 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_CALL_ENDED;
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_REMOTE_DEVICE_STATE_CHANGED;
@@ -116,6 +120,8 @@ import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCa
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getEnable;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorCallState;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getErrorIdentityKey;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupCallEndReason;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupCallHash;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getGroupMembershipToken;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceCandidates;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers;
@@ -227,9 +233,11 @@ public abstract class WebRtcActionProcessor {
case ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED: return handleGroupLocalDeviceStateChanged(currentState);
case ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED: return handleGroupRemoteDeviceStateChanged(currentState);
case ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED: return handleGroupJoinedMembershipChanged(currentState);
case ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF: return handleGroupRequestMembershipProof(currentState, getGroupMembershipToken(intent));
case ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF: return handleGroupRequestMembershipProof(currentState, getGroupCallHash(intent), getGroupMembershipToken(intent));
case ACTION_GROUP_REQUEST_UPDATE_MEMBERS: return handleGroupRequestUpdateMembers(currentState);
case ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS: return handleUpdateRenderedResolutions(currentState);
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_HTTP_SUCCESS: return handleHttpSuccess(currentState, HttpData.fromIntent(intent));
case ACTION_HTTP_FAILURE: return handleHttpFailure(currentState, HttpData.fromIntent(intent));
@@ -694,7 +702,7 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull byte[] groupMembershipToken) {
Log.i(tag, "handleGroupRequestMembershipProof not processed");
return currentState;
}
@@ -709,6 +717,16 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupCallEnded(@NonNull WebRtcServiceState currentState, int groupCallHash, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
Log.i(tag, "handleGroupCallEnded not processed");
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupCallUpdateMessage(@NonNull WebRtcServiceState currentState, @NonNull GroupCallUpdateMetadata groupCallUpdateMetadata) {
webRtcInteractor.peekGroupCall(groupCallUpdateMetadata);
return currentState;
}
//endregion
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {

View File

@@ -7,12 +7,16 @@ import androidx.annotation.Nullable;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import java.util.UUID;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_ERA_ID;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_GROUP;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_UPDATE_SENDER;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_DEVICE_ID;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_IS_LEGACY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_TYPE;
@@ -22,6 +26,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_RE
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MESSAGE_AGE_SECONDS;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_DELIVERED_TIMESTAMP;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRecipientId;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemoteDevice;
/**
@@ -304,4 +309,44 @@ public class WebRtcData {
return messageAgeSeconds;
}
}
/**
* Metadata associated with a group call update message.
*/
public static class GroupCallUpdateMetadata {
private final RecipientId sender;
private final RecipientId groupRecipientId;
private final String groupCallEraId;
private final long serverReceivedTimestamp;
static @NonNull GroupCallUpdateMetadata fromIntent(@NonNull Intent intent) {
return new GroupCallUpdateMetadata(getRecipientId(intent, EXTRA_GROUP_CALL_UPDATE_SENDER),
getRecipientId(intent, EXTRA_GROUP_CALL_UPDATE_GROUP),
intent.getStringExtra(EXTRA_GROUP_CALL_ERA_ID),
intent.getLongExtra(EXTRA_SERVER_RECEIVED_TIMESTAMP, 0));
}
public GroupCallUpdateMetadata(@NonNull RecipientId sender, @NonNull RecipientId groupRecipientId, @Nullable String groupCallEraId, long serverReceivedTimestamp) {
this.sender = sender;
this.groupRecipientId = groupRecipientId;
this.groupCallEraId = groupCallEraId;
this.serverReceivedTimestamp = serverReceivedTimestamp;
}
public @NonNull RecipientId getSender() {
return sender;
}
public @NonNull RecipientId getGroupRecipientId() {
return groupRecipientId;
}
public @Nullable String getGroupCallEraId() {
return groupCallEraId;
}
public long getServerReceivedTimestamp() {
return serverReceivedTimestamp;
}
}
}

View File

@@ -6,9 +6,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
@@ -36,6 +38,8 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CAMERA_
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ENABLE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_CALL_STATE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR_IDENTITY_KEY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_END_REASON;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_CALL_HASH;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_GROUP_EXTERNAL_TOKEN;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ICE_CANDIDATES;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MULTI_RING;
@@ -174,15 +178,34 @@ public final class WebRtcIntentParser {
public static @NonNull CameraState getCameraState(@NonNull Intent intent) {
return Objects.requireNonNull(intent.getParcelableExtra(EXTRA_CAMERA_STATE));
}
public static @NonNull WebRtcViewModel.State getErrorCallState(@NonNull Intent intent) {
return (WebRtcViewModel.State) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_ERROR_CALL_STATE));
}
public static @NonNull Optional<IdentityKey> getErrorIdentityKey(@NonNull Intent intent) {
IdentityKeyParcelable identityKeyParcelable = (IdentityKeyParcelable) intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY);
IdentityKeyParcelable identityKeyParcelable = intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY);
if (identityKeyParcelable != null) {
return Optional.fromNullable(identityKeyParcelable.get());
}
return Optional.absent();
}
public static int getGroupCallHash(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_GROUP_CALL_HASH, 0);
}
public static @NonNull GroupCall.GroupCallEndReason getGroupCallEndReason(@NonNull Intent intent) {
int ordinal = intent.getIntExtra(EXTRA_GROUP_CALL_END_REASON, -1);
if (ordinal >= 0 && ordinal < GroupCall.GroupCallEndReason.values().length) {
return GroupCall.GroupCallEndReason.values()[ordinal];
}
return GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED;
}
public static @NonNull RecipientId getRecipientId(@NonNull Intent intent, @NonNull String name) {
return RecipientId.from(Objects.requireNonNull(intent.getStringExtra(name)));
}
}

View File

@@ -8,7 +8,6 @@ import androidx.annotation.Nullable;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.WebRtcCallService;
@@ -89,6 +88,10 @@ public class WebRtcInteractor {
webRtcCallService.sendOpaqueCallMessage(uuid, callMessage);
}
void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
webRtcCallService.sendGroupCallMessage(recipient, groupCallEraId);
}
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
webRtcCallService.setCallInProgressNotification(type, remotePeer.getRecipient());
}
@@ -144,4 +147,8 @@ public class WebRtcInteractor {
void startAudioCommunication(boolean preserveSpeakerphone) {
audioManager.startCommunication(preserveSpeakerphone);
}
void peekGroupCall(@NonNull WebRtcData.GroupCallUpdateMetadata groupCallUpdateMetadata) {
webRtcCallService.peekGroupCall(groupCallUpdateMetadata);
}
}

View File

@@ -4,9 +4,11 @@ import android.content.Context;
import android.media.AudioManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.signal.ringrtc.PeekInfo;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
@@ -66,4 +68,13 @@ public final class WebRtcUtil {
return WebRtcViewModel.GroupCallState.DISCONNECTED;
}
}
public static @Nullable String getGroupCallEraId(@Nullable GroupCall groupCall) {
if (groupCall == null) {
return null;
}
PeekInfo peekInfo = groupCall.getPeekInfo();
return peekInfo != null ? peekInfo.getEraId() : null;
}
}

View File

@@ -9,19 +9,21 @@ public final class CallSetupState {
boolean enableVideoOnCreate;
boolean isRemoteVideoOffer;
boolean acceptWithVideo;
boolean sentJoinedMessage;
public CallSetupState() {
this(false, false, false);
this(false, false, false, false);
}
public CallSetupState(@NonNull CallSetupState toCopy) {
this(toCopy.enableVideoOnCreate, toCopy.isRemoteVideoOffer, toCopy.acceptWithVideo);
this(toCopy.enableVideoOnCreate, toCopy.isRemoteVideoOffer, toCopy.acceptWithVideo, toCopy.sentJoinedMessage);
}
public CallSetupState(boolean enableVideoOnCreate, boolean isRemoteVideoOffer, boolean acceptWithVideo) {
public CallSetupState(boolean enableVideoOnCreate, boolean isRemoteVideoOffer, boolean acceptWithVideo, boolean sentJoinedMessage) {
this.enableVideoOnCreate = enableVideoOnCreate;
this.isRemoteVideoOffer = isRemoteVideoOffer;
this.acceptWithVideo = acceptWithVideo;
this.sentJoinedMessage = sentJoinedMessage;
}
public boolean isEnableVideoOnCreate() {
@@ -35,4 +37,8 @@ public final class CallSetupState {
public boolean isAcceptWithVideo() {
return acceptWithVideo;
}
public boolean hasSentJoinedMessage() {
return sentJoinedMessage;
}
}

View File

@@ -127,6 +127,11 @@ public class WebRtcServiceStateBuilder {
toBuild.acceptWithVideo = acceptWithVideo;
return this;
}
public @NonNull CallSetupStateBuilder sentJoinedMessage(boolean sentJoinedMessage) {
toBuild.sentJoinedMessage = sentJoinedMessage;
return this;
}
}
public class VideoStateBuilder {