mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-22 01:40:07 +01:00
Add initial support for Group Calling.
This commit is contained in:
@@ -15,14 +15,19 @@ import android.telephony.TelephonyManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallId;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.CallManager.CallEvent;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.signal.ringrtc.HttpHeader;
|
||||
import org.signal.ringrtc.IceCandidate;
|
||||
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.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
@@ -30,8 +35,10 @@ 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.GroupManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
||||
@@ -55,12 +62,15 @@ import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -71,8 +81,9 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
BluetoothStateManager.BluetoothStateListener,
|
||||
CameraEventListener
|
||||
BluetoothStateManager.BluetoothStateListener,
|
||||
CameraEventListener,
|
||||
GroupCall.Observer
|
||||
{
|
||||
|
||||
private static final String TAG = WebRtcCallService.class.getSimpleName();
|
||||
@@ -107,6 +118,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
public static final String EXTRA_CAMERA_STATE = "camera_state";
|
||||
public static final String EXTRA_IS_ALWAYS_TURN = "is_always_turn";
|
||||
public static final String EXTRA_TURN_SERVER_INFO = "turn_server_info";
|
||||
public static final String EXTRA_GROUP_EXTERNAL_TOKEN = "group_external_token";
|
||||
public static final String EXTRA_HTTP_REQUEST_ID = "http_request_id";
|
||||
public static final String EXTRA_HTTP_RESPONSE_STATUS = "http_response_status";
|
||||
public static final String EXTRA_HTTP_RESPONSE_BODY = "http_response_body";
|
||||
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 ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN";
|
||||
public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL";
|
||||
@@ -158,6 +176,17 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
public static final String ACTION_CAMERA_SWITCH_COMPLETED = "CAMERA_FLIP_COMPLETE";
|
||||
public static final String ACTION_TURN_SERVER_UPDATE = "TURN_SERVER_UPDATE";
|
||||
public static final String ACTION_SETUP_FAILURE = "SETUP_FAILURE";
|
||||
public static final String ACTION_HTTP_SUCCESS = "HTTP_SUCCESS";
|
||||
public static final String ACTION_HTTP_FAILURE = "HTTP_FAILURE";
|
||||
public static final String ACTION_SEND_OPAQUE_MESSAGE = "SEND_OPAQUE_MESSAGE";
|
||||
public static final String ACTION_RECEIVE_OPAQUE_MESSAGE = "RECEIVE_OPAQUE_MESSAGE";
|
||||
|
||||
public static final String ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED = "GROUP_LOCAL_DEVICE_CHANGE";
|
||||
public static final String ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED = "GROUP_REMOTE_DEVICE_CHANGE";
|
||||
public static final String ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED = "GROUP_JOINED_MEMBERS_CHANGE";
|
||||
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 int BUSY_TONE_LENGTH = 2000;
|
||||
|
||||
@@ -215,7 +244,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
return false;
|
||||
}
|
||||
|
||||
webRtcInteractor = new WebRtcInteractor(this, callManager, lockManager, new SignalAudioManager(this), bluetoothStateManager, this);
|
||||
webRtcInteractor = new WebRtcInteractor(this,
|
||||
callManager,
|
||||
lockManager,
|
||||
new SignalAudioManager(this),
|
||||
bluetoothStateManager,
|
||||
this,
|
||||
this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -366,9 +401,9 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
});
|
||||
}
|
||||
|
||||
public void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
||||
public void setCallInProgressNotification(int type, @NonNull Recipient recipient) {
|
||||
startForeground(CallNotificationBuilder.getNotificationId(getApplicationContext(), type),
|
||||
CallNotificationBuilder.getCallInProgressNotification(this, type, remotePeer.getRecipient()));
|
||||
CallNotificationBuilder.getCallInProgressNotification(this, type, recipient));
|
||||
}
|
||||
|
||||
public void sendMessage() {
|
||||
@@ -377,6 +412,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
|
||||
public void sendMessage(@NonNull WebRtcServiceState state) {
|
||||
EventBus.getDefault().postSticky(new WebRtcViewModel(state.getCallInfoState().getCallState(),
|
||||
state.getCallInfoState().getGroupCallState(),
|
||||
state.getCallInfoState().getCallRecipient(),
|
||||
state.getLocalDeviceState().getCameraState(),
|
||||
state.getVideoState().getLocalSink(),
|
||||
@@ -612,6 +648,10 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
listenableFutureTask.addListener(new SendCallMessageListener<>(remotePeer));
|
||||
}
|
||||
|
||||
public void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage opaqueMessage) {
|
||||
sendMessage(new RemotePeer(RecipientId.from(uuid, null)), opaqueMessage);
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -871,12 +911,93 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] bytes) {
|
||||
public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] opaque) {
|
||||
Log.i(TAG, "onSendCallMessage:");
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
|
||||
intent.setAction(ACTION_SEND_OPAQUE_MESSAGE)
|
||||
.putExtra(EXTRA_UUID, uuid.toString())
|
||||
.putExtra(EXTRA_OPAQUE_MESSAGE, opaque);
|
||||
|
||||
startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendHttpRequest(long l, @NonNull String s, @NonNull CallManager.HttpMethod httpMethod, @Nullable List<HttpHeader> list, @Nullable byte[] bytes) {
|
||||
Log.i(TAG, "onSendHttpRequest:");
|
||||
public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull CallManager.HttpMethod httpMethod, @Nullable List<HttpHeader> headers, @Nullable byte[] body) {
|
||||
Log.i(TAG, "onSendHttpRequest(): request_id: " + requestId);
|
||||
networkExecutor.execute(() -> {
|
||||
List<Pair<String, String>> headerPairs;
|
||||
if (headers != null) {
|
||||
headerPairs = Stream.of(headers)
|
||||
.map(header -> new Pair<>(header.getName(), header.getValue()))
|
||||
.toList();
|
||||
} else {
|
||||
headerPairs = Collections.emptyList();
|
||||
}
|
||||
|
||||
CallingResponse response = messageSender.makeCallingRequest(requestId, url, httpMethod.name(), headerPairs, body);
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
|
||||
if (response instanceof CallingResponse.Success) {
|
||||
CallingResponse.Success success = (CallingResponse.Success) response;
|
||||
|
||||
intent.setAction(ACTION_HTTP_SUCCESS)
|
||||
.putExtra(EXTRA_HTTP_REQUEST_ID, success.getRequestId())
|
||||
.putExtra(EXTRA_HTTP_RESPONSE_STATUS, success.getResponseStatus())
|
||||
.putExtra(EXTRA_HTTP_RESPONSE_BODY, success.getResponseBody());
|
||||
} else {
|
||||
intent.setAction(ACTION_HTTP_FAILURE)
|
||||
.putExtra(EXTRA_HTTP_REQUEST_ID, response.getRequestId());
|
||||
}
|
||||
|
||||
startService(intent);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMembershipProof(@NonNull GroupCall groupCall) {
|
||||
Log.i(TAG, "requestMembershipProof():");
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(this, serviceState.getCallInfoState().getCallRecipient().getGroupId().get().requireV2());
|
||||
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
|
||||
intent.setAction(ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF)
|
||||
.putExtra(EXTRA_GROUP_EXTERNAL_TOKEN, credential.getTokenBytes().toByteArray());
|
||||
|
||||
startService(intent);
|
||||
} catch (IOException | VerificationFailedException e) {
|
||||
Log.w(TAG, "Unable to fetch group membership proof", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestGroupMembers(@NonNull GroupCall groupCall) {
|
||||
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_REQUEST_UPDATE_MEMBERS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocalDeviceStateChanged(@NonNull GroupCall groupCall) {
|
||||
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteDeviceStatesChanged(@NonNull GroupCall groupCall) {
|
||||
startService(new Intent(this, WebRtcCallService.class).setAction(ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoinedMembersChanged(@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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||
|
||||
Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId());
|
||||
|
||||
CallParticipant oldParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant oldParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||
CallParticipant newParticipant = oldParticipant.withVideoEnabled(enable);
|
||||
|
||||
return currentState.builder()
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.util.LongSparseArray;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.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.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.signalservice.api.messages.calls.OpaqueMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base group call action processor that handles general callbacks around call members
|
||||
* and call specific setup information that is the same for any group call state.
|
||||
*/
|
||||
public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||
public GroupActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
super(webRtcInteractor, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupRemoteDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupRemoteDeviceStateChanged():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
|
||||
|
||||
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.clearParticipantMap();
|
||||
|
||||
LongSparseArray<GroupCall.RemoteDeviceState> remoteDevices = groupCall.getRemoteDeviceStates();
|
||||
|
||||
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();
|
||||
if (videoTrack != null) {
|
||||
videoSink = (callParticipant != null && callParticipant.getVideoSink().getEglBase() != null) ? callParticipant.getVideoSink()
|
||||
: new BroadcastVideoSink(currentState.getVideoState().requireEglBase());
|
||||
videoTrack.addSink(videoSink);
|
||||
} else {
|
||||
videoSink = new BroadcastVideoSink(null);
|
||||
}
|
||||
|
||||
builder.putParticipant(callParticipantId,
|
||||
CallParticipant.createRemote(recipient,
|
||||
null,
|
||||
videoSink,
|
||||
true));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
|
||||
Log.i(tag, "handleGroupRequestMembershipProof():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
try {
|
||||
groupCall.setMembershipProof(groupMembershipToken);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to set group membership proof", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupRequestUpdateMembers(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupRequestUpdateMembers():");
|
||||
|
||||
Recipient group = currentState.getCallInfoState().getCallRecipient();
|
||||
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()))
|
||||
.toList();
|
||||
|
||||
try {
|
||||
groupCall.setGroupMembers(new ArrayList<>(members));
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable set group members", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
|
||||
Map<CallParticipantId, CallParticipant> participants = currentState.getCallInfoState().getRemoteCallParticipantsMap();
|
||||
|
||||
ArrayList<GroupCall.RenderedResolution> renderedResolutions = 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));
|
||||
}
|
||||
|
||||
try {
|
||||
currentState.getCallInfoState().requireGroupCall().setRenderedResolutions(renderedResolutions);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to set rendered resolutions", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
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) {
|
||||
return groupCallFailure(currentState, "Unable to process received http response", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.HttpData httpData) {
|
||||
try {
|
||||
webRtcInteractor.getCallManager().httpRequestFailed(httpData.getRequestId());
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to process received http response", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleSendOpaqueMessage():");
|
||||
|
||||
OpaqueMessage opaqueMessage = new OpaqueMessage(opaqueMessageMetadata.getOpaque());
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOpaque(opaqueMessage, true, null);
|
||||
|
||||
webRtcInteractor.sendOpaqueCallMessage(opaqueMessageMetadata.getUuid(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage():");
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedCallMessage(opaqueMessageMetadata.getUuid(),
|
||||
opaqueMessageMetadata.getRemoteDeviceId(),
|
||||
1,
|
||||
opaqueMessageMetadata.getOpaque(),
|
||||
opaqueMessageMetadata.getMessageAgeSeconds());
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to receive opaque message", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState groupCallFailure(@NonNull WebRtcServiceState currentState, @NonNull String message, @NonNull Throwable error) {
|
||||
Log.w(tag, "groupCallFailure(): " + message, error);
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
try {
|
||||
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
|
||||
if (groupCall != null) {
|
||||
groupCall.disconnect();
|
||||
}
|
||||
webRtcInteractor.getCallManager().reset();
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to reset call manager: ", e);
|
||||
}
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
webRtcInteractor.stopForegroundService();
|
||||
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||
webRtcInteractor.stopAudio(playDisconnectSound);
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
|
||||
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||
|
||||
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
|
||||
/**
|
||||
* Process actions for when the call has at least once been connected and joined.
|
||||
*/
|
||||
public class GroupConnectedActionProcessor extends GroupActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(GroupConnectedActionProcessor.class);
|
||||
|
||||
public GroupConnectedActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.i(TAG, "handleSetEnableVideo():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoMuted(!enable);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable set video muted", e);
|
||||
}
|
||||
camera.setEnabled(enable);
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
|
||||
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
try {
|
||||
currentState.getCallInfoState().requireGroupCall().setOutgoingAudioMuted(muted);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to set audio muted", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||
|
||||
/**
|
||||
* Process actions to go from lobby to a joined call.
|
||||
*/
|
||||
public class GroupJoiningActionProcessor extends GroupActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(GroupJoiningActionProcessor.class);
|
||||
|
||||
private final CallSetupActionProcessorDelegate callSetupDelegate;
|
||||
|
||||
public GroupJoiningActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
callSetupDelegate = new CallSetupActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||
|
||||
Log.i(tag, "local device changed: " + device.getConnectionState() + " " + device.getJoinState());
|
||||
|
||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
switch (device.getConnectionState()) {
|
||||
case NOT_CONNECTED:
|
||||
case RECONNECTING:
|
||||
builder.changeCallInfoState()
|
||||
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||
.commit();
|
||||
break;
|
||||
case CONNECTING:
|
||||
case CONNECTED:
|
||||
if (device.getJoinState() == GroupCall.JoinState.JOINED) {
|
||||
|
||||
webRtcInteractor.startAudioCommunication(true);
|
||||
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||
|
||||
if (currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
||||
} else {
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
}
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, currentState.getCallInfoState().getCallRecipient());
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoMuted(!currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
} catch (CallException e) {
|
||||
Log.e(tag, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_CONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED)
|
||||
.callConnectedTime(System.currentTimeMillis())
|
||||
.commit()
|
||||
.actionProcessor(new GroupConnectedActionProcessor(webRtcInteractor))
|
||||
.build();
|
||||
} else if (device.getJoinState() == GroupCall.JoinState.JOINING) {
|
||||
builder.changeCallInfoState()
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||
.commit();
|
||||
} else {
|
||||
builder.changeCallInfoState()
|
||||
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||
.commit();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoMuted(!enable);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to set video muted", e);
|
||||
}
|
||||
camera.setEnabled(enable);
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
|
||||
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
try {
|
||||
currentState.getCallInfoState().requireGroupCall().setOutgoingAudioMuted(muted);
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to set audio muted", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
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.util.ServiceUtil;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
||||
|
||||
/**
|
||||
* Process actions while the user is in the pre-join lobby for the call.
|
||||
*/
|
||||
public class GroupPreJoinActionProcessor extends GroupActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(GroupPreJoinActionProcessor.class);
|
||||
|
||||
public GroupPreJoinActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handlePreJoinCall():");
|
||||
|
||||
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
|
||||
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
|
||||
currentState.getVideoState().requireEglBase(),
|
||||
webRtcInteractor.getGroupCallObserver());
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingAudioMuted(true);
|
||||
groupCall.setOutgoingVideoMuted(true);
|
||||
|
||||
Log.i(TAG, "Connecting to group call: " + currentState.getCallInfoState().getCallRecipient().getId());
|
||||
groupCall.connect();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to connect to group call", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.groupCall(groupCall)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCancelPreJoinCall(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(TAG, "handleCancelPreJoinCall():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
try {
|
||||
groupCall.disconnect();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
|
||||
}
|
||||
|
||||
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||
|
||||
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
GroupCall.LocalDeviceState device = groupCall.getLocalDeviceState();
|
||||
|
||||
Log.i(tag, "local device changed: " + device.getConnectionState() + " " + device.getJoinState());
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.groupCallState(WebRtcUtil.groupCallStateForConnection(device.getConnectionState()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupJoinedMembershipChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupJoinedMembershipChanged():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
|
||||
List<Recipient> callParticipants = Stream.of(groupCall.getJoinedGroupMembers())
|
||||
.map(uuid -> Recipient.externalPush(context, uuid, null, false))
|
||||
.toList();
|
||||
|
||||
WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder()
|
||||
.changeCallInfoState();
|
||||
|
||||
for (Recipient recipient : callParticipants) {
|
||||
builder.putParticipant(recipient, CallParticipant.createRemote(recipient, null, new BroadcastVideoSink(null), false));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull OfferMessage.Type offerType)
|
||||
{
|
||||
Log.i(TAG, "handleOutgoingCall():");
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().requireGroupCall();
|
||||
|
||||
currentState = WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, currentState.getCallInfoState().getCallRecipient());
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoSource(currentState.getVideoState().requireLocalSink(), currentState.getVideoState().requireCamera());
|
||||
groupCall.setOutgoingVideoMuted(!currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
groupCall.setBandwidthMode(GroupCall.BandwidthMode.NORMAL);
|
||||
|
||||
groupCall.join();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to join group call", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.actionProcessor(new GroupJoiningActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_OUTGOING)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.i(TAG, "handleSetEnableVideo(): Changing for pre-join group call. enable: " + enable);
|
||||
|
||||
currentState.getVideoState().requireCamera().setEnabled(enable);
|
||||
return currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.enableVideoOnCreate(enable)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(currentState.getVideoState().requireCamera().getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
Log.i(TAG, "handleSetMuteAudio(): Changing for pre-join group call. muted: " + muted);
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,6 @@ import org.webrtc.CapturerObserver;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Action handler for when the system is at rest. Mainly responsible
|
||||
* for starting pre-call state, starting an outgoing call, or receiving an
|
||||
@@ -52,14 +50,21 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
||||
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handlePreJoinCall():");
|
||||
|
||||
WebRtcServiceState newState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
|
||||
boolean isGroupCall = remotePeer.getRecipient().isPushV2Group();
|
||||
WebRtcActionProcessor processor = isGroupCall ? new GroupPreJoinActionProcessor(webRtcInteractor)
|
||||
: new PreJoinActionProcessor(webRtcInteractor);
|
||||
|
||||
return newState.builder()
|
||||
.actionProcessor(new PreJoinActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
|
||||
.callRecipient(remotePeer.getRecipient())
|
||||
.build();
|
||||
currentState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
|
||||
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(processor)
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
|
||||
.callRecipient(remotePeer.getRecipient())
|
||||
.build();
|
||||
|
||||
return isGroupCall ? currentState.getActionProcessor().handlePreJoinCall(currentState, remotePeer)
|
||||
: currentState;
|
||||
}
|
||||
|
||||
private @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
|
||||
|
||||
@@ -80,7 +80,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
boolean hideIp = !activePeer.getRecipient().isSystemContact() || isAlwaysTurn;
|
||||
VideoState videoState = currentState.getVideoState();
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||
|
||||
@@ -107,7 +107,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
try {
|
||||
VideoState videoState = currentState.getVideoState();
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||
|
||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||
context,
|
||||
|
||||
@@ -19,6 +19,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.HttpData;
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.OfferMetadata;
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedOfferMetadata;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
@@ -57,6 +58,14 @@ 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_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;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_HTTP_FAILURE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_HTTP_SUCCESS;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_IS_IN_CALL_QUERY;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_HANGUP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING;
|
||||
@@ -71,6 +80,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIV
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_HANGUP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_ICE_CANDIDATES;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_OFFER;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_OPAQUE_MESSAGE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_RINGING;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_REMOTE_VIDEO_ENABLE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SCREEN_OFF;
|
||||
@@ -79,6 +89,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_B
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_HANGUP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_ICE_CANDIDATES;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_OFFER;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_OPAQUE_MESSAGE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SETUP_FAILURE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_BLUETOOTH;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_AUDIO_SPEAKER;
|
||||
@@ -90,20 +101,22 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_TURN_S
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_WITH_VIDEO;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BLUETOOTH;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CAMERA_STATE;
|
||||
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_RESULT_RECEIVER;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.OpaqueMessageMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedAnswerMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getAvailable;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getBroadcastFlag;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCallId;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getCameraState;
|
||||
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.getGroupMembershipToken;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceCandidates;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType;
|
||||
@@ -189,7 +202,7 @@ public abstract class WebRtcActionProcessor {
|
||||
case ACTION_SET_AUDIO_SPEAKER: return handleSetSpeakerAudio(currentState, intent.getBooleanExtra(EXTRA_SPEAKER, false));
|
||||
case ACTION_SET_AUDIO_BLUETOOTH: return handleSetBluetoothAudio(currentState, intent.getBooleanExtra(EXTRA_BLUETOOTH, false));
|
||||
case ACTION_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent));
|
||||
case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, intent.getParcelableExtra(EXTRA_CAMERA_STATE));
|
||||
case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent));
|
||||
|
||||
// End Call Actions
|
||||
case ACTION_ENDED_REMOTE_HANGUP:
|
||||
@@ -208,6 +221,20 @@ public abstract class WebRtcActionProcessor {
|
||||
|
||||
// Local Call Failure Actions
|
||||
case ACTION_SETUP_FAILURE: return handleSetupFailure(currentState, getCallId(intent));
|
||||
|
||||
// Group Calling
|
||||
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_UPDATE_MEMBERS: return handleGroupRequestUpdateMembers(currentState);
|
||||
case ACTION_GROUP_UPDATE_RENDERED_RESOLUTIONS: return handleUpdateRenderedResolutions(currentState);
|
||||
|
||||
case ACTION_HTTP_SUCCESS: return handleHttpSuccess(currentState, HttpData.fromIntent(intent));
|
||||
case ACTION_HTTP_FAILURE: return handleHttpFailure(currentState, HttpData.fromIntent(intent));
|
||||
|
||||
case ACTION_SEND_OPAQUE_MESSAGE: return handleSendOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
|
||||
case ACTION_RECEIVE_OPAQUE_MESSAGE: return handleReceivedOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
|
||||
}
|
||||
|
||||
return currentState;
|
||||
@@ -275,8 +302,8 @@ public abstract class WebRtcActionProcessor {
|
||||
//region Incoming call
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull WebRtcData.OfferMetadata offerMetadata,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull OfferMetadata offerMetadata,
|
||||
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
|
||||
{
|
||||
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
@@ -386,7 +413,7 @@ public abstract class WebRtcActionProcessor {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendBusy(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast) {
|
||||
protected @NonNull WebRtcServiceState handleSendBusy(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast) {
|
||||
Log.i(tag, "handleSendBusy(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
BusyMessage busyMessage = new BusyMessage(callMetadata.getCallId().longValue());
|
||||
@@ -473,7 +500,7 @@ public abstract class WebRtcActionProcessor {
|
||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
if (errorCallState == WebRtcViewModel.State.UNTRUSTED_IDENTITY) {
|
||||
CallParticipant participant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant participant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteCallParticipant(activePeer.getRecipient()));
|
||||
CallParticipant untrusted = participant.withIdentityKey(identityKey.get());
|
||||
|
||||
builder.changeCallInfoState()
|
||||
@@ -648,4 +675,68 @@ public abstract class WebRtcActionProcessor {
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Group Calling
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupLocalDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupLocalDeviceStateChanged not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupRemoteDeviceStateChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupRemoteDeviceStateChanged not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupJoinedMembershipChanged(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupJoinedMembershipChanged not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupRequestMembershipProof(@NonNull WebRtcServiceState currentState, @NonNull byte[] groupMembershipToken) {
|
||||
Log.i(tag, "handleGroupRequestMembershipProof not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupRequestUpdateMembers(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleGroupRequestUpdateMembers not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleUpdateRenderedResolutions(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleUpdateRenderedResolutions not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
protected @NonNull WebRtcServiceState handleHttpSuccess(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedHttpResponse(httpData.getRequestId(), httpData.getStatus(), httpData.getBody() != null ? httpData.getBody() : new byte[0]);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Unable to process received http response", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleHttpFailure(@NonNull WebRtcServiceState currentState, @NonNull HttpData httpData) {
|
||||
try {
|
||||
webRtcInteractor.getCallManager().httpRequestFailed(httpData.getRequestId());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Unable to process received http response", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleSendOpaqueMessage not processed");
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage not processed");
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,18 @@ 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_HANGUP_DEVICE_ID;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_IS_LEGACY;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HANGUP_TYPE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_REQUEST_ID;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_RESPONSE_BODY;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_HTTP_RESPONSE_STATUS;
|
||||
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.getRemoteDevice;
|
||||
|
||||
/**
|
||||
* Collection of classes to ease parsing data from intents and passing said data
|
||||
@@ -224,4 +231,77 @@ public class WebRtcData {
|
||||
return deviceId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http response data.
|
||||
*/
|
||||
static class HttpData {
|
||||
private final long requestId;
|
||||
private final int status;
|
||||
private final byte[] body;
|
||||
|
||||
static @NonNull HttpData fromIntent(@NonNull Intent intent) {
|
||||
return new HttpData(intent.getLongExtra(EXTRA_HTTP_REQUEST_ID, -1),
|
||||
intent.getIntExtra(EXTRA_HTTP_RESPONSE_STATUS, -1),
|
||||
intent.getByteArrayExtra(EXTRA_HTTP_RESPONSE_BODY));
|
||||
}
|
||||
|
||||
HttpData(long requestId, int status, @Nullable byte[] body) {
|
||||
this.requestId = requestId;
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
long getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Nullable byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An opaque calling message.
|
||||
*/
|
||||
static class OpaqueMessageMetadata {
|
||||
private final UUID uuid;
|
||||
private final byte[] opaque;
|
||||
private final int remoteDeviceId;
|
||||
private final long messageAgeSeconds;
|
||||
|
||||
static @NonNull OpaqueMessageMetadata fromIntent(@NonNull Intent intent) {
|
||||
return new OpaqueMessageMetadata(WebRtcIntentParser.getUuid(intent),
|
||||
WebRtcIntentParser.getOpaque(intent),
|
||||
getRemoteDevice(intent),
|
||||
intent.getLongExtra(EXTRA_MESSAGE_AGE_SECONDS, 0));
|
||||
}
|
||||
|
||||
OpaqueMessageMetadata(@NonNull UUID uuid, @NonNull byte[] opaque, int remoteDeviceId, long messageAgeSeconds) {
|
||||
this.uuid = uuid;
|
||||
this.opaque = opaque;
|
||||
this.remoteDeviceId = remoteDeviceId;
|
||||
this.messageAgeSeconds = messageAgeSeconds;
|
||||
}
|
||||
|
||||
@NonNull UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@NonNull byte[] getOpaque() {
|
||||
return opaque;
|
||||
}
|
||||
|
||||
int getRemoteDeviceId() {
|
||||
return remoteDeviceId;
|
||||
}
|
||||
|
||||
long getMessageAgeSeconds() {
|
||||
return messageAgeSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.signal.ringrtc.CallId;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel;
|
||||
@@ -18,29 +19,35 @@ import org.webrtc.PeerConnection;
|
||||
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.util.UuidUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_OPAQUE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ANSWER_SDP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_AVAILABLE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_BROADCAST;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CALL_ID;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_CAMERA_STATE;
|
||||
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_EXTERNAL_TOKEN;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ICE_CANDIDATES;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MULTI_RING;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_OPAQUE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_SDP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OFFER_TYPE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_OPAQUE_MESSAGE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_DEVICE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_IDENTITY_KEY;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_REMOTE_PEER_KEY;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_TURN_SERVER_INFO;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_UUID;
|
||||
|
||||
/**
|
||||
* Helper to parse the various attributes out of intents passed to the service.
|
||||
@@ -111,6 +118,14 @@ public final class WebRtcIntentParser {
|
||||
return intent.getByteArrayExtra(EXTRA_OFFER_OPAQUE);
|
||||
}
|
||||
|
||||
public static @NonNull byte[] getOpaque(@NonNull Intent intent) {
|
||||
return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_OPAQUE_MESSAGE));
|
||||
}
|
||||
|
||||
public static @NonNull UUID getUuid(@NonNull Intent intent) {
|
||||
return UuidUtil.parseOrThrow(intent.getStringExtra(EXTRA_UUID));
|
||||
}
|
||||
|
||||
public static boolean getBroadcastFlag(@NonNull Intent intent) {
|
||||
return intent.getBooleanExtra(EXTRA_BROADCAST, false);
|
||||
}
|
||||
@@ -149,10 +164,17 @@ public final class WebRtcIntentParser {
|
||||
return intent.getBooleanExtra(EXTRA_ENABLE, false);
|
||||
}
|
||||
|
||||
public static @NonNull byte[] getGroupMembershipToken(@NonNull Intent intent) {
|
||||
return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_GROUP_EXTERNAL_TOKEN));
|
||||
}
|
||||
|
||||
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);
|
||||
if (identityKeyParcelable != null) {
|
||||
|
||||
@@ -6,6 +6,9 @@ import androidx.annotation.NonNull;
|
||||
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;
|
||||
@@ -16,6 +19,8 @@ import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Serves as the bridge between the action processing framework as the WebRTC service. Attempts
|
||||
* to minimize direct access to various managers by providing a simple proxy to them. Due to the
|
||||
@@ -23,19 +28,21 @@ import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMess
|
||||
*/
|
||||
public class WebRtcInteractor {
|
||||
|
||||
@NonNull private final WebRtcCallService webRtcCallService;
|
||||
@NonNull private final CallManager callManager;
|
||||
@NonNull private final LockManager lockManager;
|
||||
@NonNull private final SignalAudioManager audioManager;
|
||||
@NonNull private final BluetoothStateManager bluetoothStateManager;
|
||||
@NonNull private final CameraEventListener cameraEventListener;
|
||||
@NonNull private final WebRtcCallService webRtcCallService;
|
||||
@NonNull private final CallManager callManager;
|
||||
@NonNull private final LockManager lockManager;
|
||||
@NonNull private final SignalAudioManager audioManager;
|
||||
@NonNull private final BluetoothStateManager bluetoothStateManager;
|
||||
@NonNull private final CameraEventListener cameraEventListener;
|
||||
@NonNull private final GroupCall.Observer groupCallObserver;
|
||||
|
||||
public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService,
|
||||
@NonNull CallManager callManager,
|
||||
@NonNull LockManager lockManager,
|
||||
@NonNull SignalAudioManager audioManager,
|
||||
@NonNull BluetoothStateManager bluetoothStateManager,
|
||||
@NonNull CameraEventListener cameraEventListener)
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull GroupCall.Observer groupCallObserver)
|
||||
{
|
||||
this.webRtcCallService = webRtcCallService;
|
||||
this.callManager = callManager;
|
||||
@@ -43,6 +50,7 @@ public class WebRtcInteractor {
|
||||
this.audioManager = audioManager;
|
||||
this.bluetoothStateManager = bluetoothStateManager;
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
this.groupCallObserver = groupCallObserver;
|
||||
}
|
||||
|
||||
@NonNull CameraEventListener getCameraEventListener() {
|
||||
@@ -57,6 +65,10 @@ public class WebRtcInteractor {
|
||||
return webRtcCallService;
|
||||
}
|
||||
|
||||
@NonNull GroupCall.Observer getGroupCallObserver() {
|
||||
return groupCallObserver;
|
||||
}
|
||||
|
||||
void setWantsBluetoothConnection(boolean enabled) {
|
||||
bluetoothStateManager.setWantsConnection(enabled);
|
||||
}
|
||||
@@ -73,8 +85,16 @@ public class WebRtcInteractor {
|
||||
webRtcCallService.sendCallMessage(remotePeer, callMessage);
|
||||
}
|
||||
|
||||
void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage callMessage) {
|
||||
webRtcCallService.sendOpaqueCallMessage(uuid, callMessage);
|
||||
}
|
||||
|
||||
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
||||
webRtcCallService.setCallInProgressNotification(type, remotePeer);
|
||||
webRtcCallService.setCallInProgressNotification(type, remotePeer.getRecipient());
|
||||
}
|
||||
|
||||
void setCallInProgressNotification(int type, @NonNull Recipient recipient) {
|
||||
webRtcCallService.setCallInProgressNotification(type, recipient);
|
||||
}
|
||||
|
||||
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import android.media.AudioManager;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
@@ -56,4 +58,17 @@ public final class WebRtcUtil {
|
||||
androidAudioManager.setSpeakerphoneOn(true);
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull WebRtcViewModel.GroupCallState groupCallStateForConnection(@NonNull GroupCall.ConnectionState connectionState) {
|
||||
switch (connectionState) {
|
||||
case CONNECTING:
|
||||
return WebRtcViewModel.GroupCallState.CONNECTING;
|
||||
case CONNECTED:
|
||||
return WebRtcViewModel.GroupCallState.CONNECTED;
|
||||
case RECONNECTING:
|
||||
return WebRtcViewModel.GroupCallState.RECONNECTING;
|
||||
default:
|
||||
return WebRtcViewModel.GroupCallState.DISCONNECTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
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.ringrtc.RemotePeer;
|
||||
@@ -12,6 +14,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -20,27 +23,31 @@ import java.util.Objects;
|
||||
*/
|
||||
public class CallInfoState {
|
||||
|
||||
WebRtcViewModel.State callState;
|
||||
Recipient callRecipient;
|
||||
long callConnectedTime;
|
||||
Map<Recipient, CallParticipant> remoteParticipants;
|
||||
Map<Integer, RemotePeer> peerMap;
|
||||
RemotePeer activePeer;
|
||||
WebRtcViewModel.State callState;
|
||||
Recipient callRecipient;
|
||||
long callConnectedTime;
|
||||
Map<CallParticipantId, CallParticipant> remoteParticipants;
|
||||
Map<Integer, RemotePeer> peerMap;
|
||||
RemotePeer activePeer;
|
||||
GroupCall groupCall;
|
||||
WebRtcViewModel.GroupCallState groupState;
|
||||
|
||||
public CallInfoState() {
|
||||
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null, null, WebRtcViewModel.GroupCallState.IDLE);
|
||||
}
|
||||
|
||||
public CallInfoState(@NonNull CallInfoState toCopy) {
|
||||
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer);
|
||||
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer, toCopy.groupCall, toCopy.groupState);
|
||||
}
|
||||
|
||||
public CallInfoState(@NonNull WebRtcViewModel.State callState,
|
||||
@NonNull Recipient callRecipient,
|
||||
long callConnectedTime,
|
||||
@NonNull Map<Recipient, CallParticipant> remoteParticipants,
|
||||
@NonNull Map<CallParticipantId, CallParticipant> remoteParticipants,
|
||||
@NonNull Map<Integer, RemotePeer> peerMap,
|
||||
@Nullable RemotePeer activePeer)
|
||||
@Nullable RemotePeer activePeer,
|
||||
@Nullable GroupCall groupCall,
|
||||
@NonNull WebRtcViewModel.GroupCallState groupState)
|
||||
{
|
||||
this.callState = callState;
|
||||
this.callRecipient = callRecipient;
|
||||
@@ -48,6 +55,8 @@ public class CallInfoState {
|
||||
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
|
||||
this.peerMap = new HashMap<>(peerMap);
|
||||
this.activePeer = activePeer;
|
||||
this.groupCall = groupCall;
|
||||
this.groupState = groupState;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getCallRecipient() {
|
||||
@@ -58,11 +67,19 @@ public class CallInfoState {
|
||||
return callConnectedTime;
|
||||
}
|
||||
|
||||
public @Nullable CallParticipant getRemoteParticipant(@NonNull Recipient recipient) {
|
||||
return remoteParticipants.get(recipient);
|
||||
public @NonNull Map<CallParticipantId, CallParticipant> getRemoteCallParticipantsMap() {
|
||||
return new LinkedHashMap<>(remoteParticipants);
|
||||
}
|
||||
|
||||
public @NonNull ArrayList<CallParticipant> getRemoteCallParticipants() {
|
||||
public @Nullable CallParticipant getRemoteCallParticipant(@NonNull Recipient recipient) {
|
||||
return getRemoteCallParticipant(new CallParticipantId(recipient));
|
||||
}
|
||||
|
||||
public @Nullable CallParticipant getRemoteCallParticipant(@NonNull CallParticipantId callParticipantId) {
|
||||
return remoteParticipants.get(callParticipantId);
|
||||
}
|
||||
|
||||
public @NonNull List<CallParticipant> getRemoteCallParticipants() {
|
||||
return new ArrayList<>(remoteParticipants.values());
|
||||
}
|
||||
|
||||
@@ -81,4 +98,16 @@ public class CallInfoState {
|
||||
public @NonNull RemotePeer requireActivePeer() {
|
||||
return Objects.requireNonNull(activePeer);
|
||||
}
|
||||
|
||||
public @Nullable GroupCall getGroupCall() {
|
||||
return groupCall;
|
||||
}
|
||||
|
||||
public @NonNull GroupCall requireGroupCall() {
|
||||
return Objects.requireNonNull(groupCall);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcViewModel.GroupCallState getGroupCallState() {
|
||||
return groupState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
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.ringrtc.Camera;
|
||||
@@ -192,8 +194,18 @@ public class WebRtcServiceStateBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder putParticipant(@NonNull CallParticipantId callParticipantId, @NonNull CallParticipant callParticipant) {
|
||||
toBuild.remoteParticipants.put(callParticipantId, callParticipant);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder putParticipant(@NonNull Recipient recipient, @NonNull CallParticipant callParticipant) {
|
||||
toBuild.remoteParticipants.put(recipient, callParticipant);
|
||||
toBuild.remoteParticipants.put(new CallParticipantId(recipient), callParticipant);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder clearParticipantMap() {
|
||||
toBuild.remoteParticipants.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -216,5 +228,15 @@ public class WebRtcServiceStateBuilder {
|
||||
toBuild.activePeer = activePeer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder groupCall(@Nullable GroupCall groupCall) {
|
||||
toBuild.groupCall = groupCall;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder groupCallState(@Nullable WebRtcViewModel.GroupCallState groupState) {
|
||||
toBuild.groupState = groupState;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user