mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 08:39:22 +01:00
Add additional Group Calling features.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user