Move calling management out of service.

This commit is contained in:
Cody Henthorne
2021-03-31 14:30:30 -04:00
committed by Alex Hart
parent d8dead82b6
commit 1dc3cf7824
32 changed files with 1355 additions and 2226 deletions

View File

@@ -5,35 +5,28 @@ import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager.CallEvent;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.ringrtc.CallState;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_BUSY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_GLARE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_ACCEPTED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_BUSY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_DECLINED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION;
import static org.thoughtcrime.securesms.service.WebRtcCallService.BUSY_TONE_LENGTH;
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_CONNECTING;
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
@@ -46,13 +39,13 @@ import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUT
*/
public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
private static final Map<String, WebRtcViewModel.State> ENDED_ACTION_TO_STATE = new HashMap<String, WebRtcViewModel.State>() {{
put(ACTION_ENDED_REMOTE_HANGUP_ACCEPTED, WebRtcViewModel.State.CALL_ACCEPTED_ELSEWHERE);
put(ACTION_ENDED_REMOTE_HANGUP_BUSY, WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE);
put(ACTION_ENDED_REMOTE_HANGUP_DECLINED, WebRtcViewModel.State.CALL_DECLINED_ELSEWHERE);
put(ACTION_ENDED_REMOTE_BUSY, WebRtcViewModel.State.CALL_BUSY);
put(ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION, WebRtcViewModel.State.CALL_NEEDS_PERMISSION);
put(ACTION_ENDED_REMOTE_GLARE, WebRtcViewModel.State.CALL_DISCONNECTED);
private static final Map<CallEvent, WebRtcViewModel.State> ENDED_REMOTE_EVENT_TO_STATE = new HashMap<CallEvent, WebRtcViewModel.State>() {{
put(CallEvent.ENDED_REMOTE_HANGUP_ACCEPTED, WebRtcViewModel.State.CALL_ACCEPTED_ELSEWHERE);
put(CallEvent.ENDED_REMOTE_HANGUP_BUSY, WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE);
put(CallEvent.ENDED_REMOTE_HANGUP_DECLINED, WebRtcViewModel.State.CALL_DECLINED_ELSEWHERE);
put(CallEvent.ENDED_REMOTE_BUSY, WebRtcViewModel.State.CALL_BUSY);
put(CallEvent.ENDED_REMOTE_HANGUP_NEED_PERMISSION, WebRtcViewModel.State.CALL_NEEDS_PERMISSION);
put(CallEvent.ENDED_REMOTE_GLARE, WebRtcViewModel.State.CALL_DISCONNECTED);
}};
public ActiveCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
@@ -71,14 +64,13 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
boolean broadcast,
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
@NonNull List<byte[]> iceCandidates)
{
Log.i(tag, "handleSendIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
LinkedList<IceUpdateMessage> iceUpdateMessages = new LinkedList<>();
for (IceCandidateParcel parcel : iceCandidates) {
iceUpdateMessages.add(parcel.getIceUpdateMessage(callMetadata.getCallId()));
}
List<IceUpdateMessage> iceUpdateMessages = Stream.of(iceCandidates)
.map(c -> new IceUpdateMessage(callMetadata.getCallId().longValue(), c, null))
.toList();
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forIceUpdates(iceUpdateMessages, true, destinationDeviceId);
@@ -89,7 +81,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId());
@@ -118,7 +110,7 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return terminate(currentState, currentState.getCallInfoState().getActivePeer());
} catch (CallException e) {
@@ -142,36 +134,45 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
Log.i(tag, "handleReceivedOfferWhileActive(): call_id: " + remotePeer.getCallId());
switch (activePeer.getState()) {
case DIALING:
case REMOTE_RINGING: webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); break;
case REMOTE_RINGING:
webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer);
break;
case IDLE:
case ANSWERING: webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); break;
case LOCAL_RINGING: webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); break;
case CONNECTED: webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, activePeer); break;
default: throw new IllegalStateException();
case ANSWERING:
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer);
break;
case LOCAL_RINGING:
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer);
break;
case CONNECTED:
webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, activePeer);
break;
default:
throw new IllegalStateException();
}
if (activePeer.getState() == CallState.IDLE) {
webRtcInteractor.stopForegroundService();
}
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
return terminate(currentState, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState,
@NonNull String action,
@NonNull CallEvent endedRemoteEvent,
@NonNull RemotePeer remotePeer)
{
Log.i(tag, "handleEndedRemote(): call_id: " + remotePeer.getCallId() + " action: " + action);
Log.i(tag, "handleEndedRemote(): call_id: " + remotePeer.getCallId() + " action: " + endedRemoteEvent);
WebRtcViewModel.State state = currentState.getCallInfoState().getCallState();
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
@@ -179,26 +180,26 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
boolean outgoingBeforeAccept = remotePeer.getState() == CallState.DIALING || remotePeer.getState() == CallState.REMOTE_RINGING;
boolean incomingBeforeAccept = remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING;
if (remotePeerIsActive && ENDED_ACTION_TO_STATE.containsKey(action)) {
state = Objects.requireNonNull(ENDED_ACTION_TO_STATE.get(action));
if (remotePeerIsActive && ENDED_REMOTE_EVENT_TO_STATE.containsKey(endedRemoteEvent)) {
state = Objects.requireNonNull(ENDED_REMOTE_EVENT_TO_STATE.get(endedRemoteEvent));
}
if (action.equals(ACTION_ENDED_REMOTE_HANGUP)) {
if (endedRemoteEvent == CallEvent.ENDED_REMOTE_HANGUP) {
if (remotePeerIsActive) {
state = outgoingBeforeAccept ? WebRtcViewModel.State.RECIPIENT_UNAVAILABLE : WebRtcViewModel.State.CALL_DISCONNECTED;
}
if (incomingBeforeAccept) {
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
}
} else if (action.equals(ACTION_ENDED_REMOTE_BUSY) && remotePeerIsActive) {
} else if (endedRemoteEvent == CallEvent.ENDED_REMOTE_BUSY && remotePeerIsActive) {
activePeer.receivedBusy();
OutgoingRinger ringer = new OutgoingRinger(context);
ringer.start(OutgoingRinger.Type.BUSY);
ThreadUtil.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH);
} else if (action.equals(ACTION_ENDED_REMOTE_GLARE) && incomingBeforeAccept) {
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
ThreadUtil.runOnMainDelayed(ringer::stop, SignalCallManager.BUSY_TONE_LENGTH);
} else if (endedRemoteEvent == CallEvent.ENDED_REMOTE_GLARE && incomingBeforeAccept) {
webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
}
currentState = currentState.builder()
@@ -206,33 +207,33 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
.callState(state)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return terminate(currentState, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
Log.i(tag, "handleEnded(): call_id: " + remotePeer.getCallId() + " action: " + action);
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallEvent endedEvent, @NonNull RemotePeer remotePeer) {
Log.i(tag, "handleEnded(): call_id: " + remotePeer.getCallId() + " action: " + endedEvent);
if (remotePeer.callIdEquals(currentState.getCallInfoState().getActivePeer()) && !currentState.getCallInfoState().getCallState().isErrorState()) {
currentState = currentState.builder()
.changeCallInfoState()
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
.callState(endedEvent == CallEvent.ENDED_TIMEOUT ? WebRtcViewModel.State.RECIPIENT_UNAVAILABLE : WebRtcViewModel.State.NETWORK_FAILURE)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
}
if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) {
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
}
return terminate(currentState, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) {
protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) {
Log.i(tag, "handleSetupFailure(): call_id: " + callId);
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
@@ -253,10 +254,10 @@ public class ActiveCallActionProcessorDelegate extends WebRtcActionProcessor {
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
if (activePeer.getState() == CallState.ANSWERING || activePeer.getState() == CallState.LOCAL_RINGING) {
webRtcInteractor.insertMissedCall(activePeer, true, activePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
webRtcInteractor.insertMissedCall(activePeer, activePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
}
return terminate(currentState, activePeer);

View File

@@ -7,12 +7,12 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import java.util.ArrayList;
import java.util.List;
/**
* Handles action for a connected/ongoing call. At this point it's mostly responding
@@ -85,7 +85,7 @@ public class ConnectedCallActionProcessor extends DeviceAwareActionProcessor {
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
boolean broadcast,
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
@NonNull List<byte[]> iceCandidates)
{
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
}
@@ -96,13 +96,13 @@ public class ConnectedCallActionProcessor extends DeviceAwareActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer);
}
@Override

View File

@@ -33,7 +33,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor {
androidAudioManager.setSpeakerphoneOn(true);
}
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return currentState;
}
@@ -61,7 +61,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor {
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
}
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return currentState;
}
@@ -76,7 +76,7 @@ public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor {
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
}
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return currentState;
}

View File

@@ -26,8 +26,6 @@ import org.webrtc.VideoTrack;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import java.util.ArrayList;
import java.util.Collections;
@@ -54,7 +52,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
Log.i(tag, "In a group call, send busy back to 1:1 call offer.");
currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
@@ -183,36 +181,6 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
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():");
@@ -315,7 +283,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return terminateGroupCall(currentState);
}
@@ -336,7 +304,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
try {
if (groupCall != null) {

View File

@@ -84,7 +84,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
.cameraState(camera.getCameraState())
.build();
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getCallSetupState().isEnableVideoOnCreate());
return currentState;
}
@@ -157,7 +157,7 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor {
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return terminateGroupCall(currentState);
}

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.ringrtc.Camera;
@@ -120,7 +119,7 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor {
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return terminateGroupCall(currentState);
}
@@ -142,7 +141,7 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor {
.cameraState(camera.getCameraState())
.build();
WebRtcUtil.enableSpeakerPhoneIfNeeded(webRtcInteractor.getWebRtcCallService(), currentState.getCallSetupState().isEnableVideoOnCreate());
WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getCallSetupState().isEnableVideoOnCreate());
return currentState;
}

View File

@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.RecipientDatabase;
@@ -18,7 +19,6 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.ringrtc.CallState;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
@@ -29,7 +29,6 @@ import org.webrtc.PeerConnection;
import org.whispersystems.signalservice.api.messages.calls.AnswerMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -101,7 +100,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
}
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return currentState;
}
@@ -204,13 +203,13 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer);
}
@Override
@@ -227,7 +226,7 @@ public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
boolean broadcast,
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
@NonNull List<byte[]> iceCandidates)
{
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
}

View File

@@ -9,12 +9,12 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.thoughtcrime.securesms.components.webrtc.OrientationAwareVideoSink;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.events.CallParticipant;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
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.OfferMetadata;
@@ -23,13 +23,11 @@ import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
import org.thoughtcrime.securesms.util.NetworkUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
import org.webrtc.PeerConnection;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -73,7 +71,7 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
webRtcInteractor.initializeAudioForCall();
webRtcInteractor.startOutgoingRinger(OutgoingRinger.Type.RINGING);
webRtcInteractor.startOutgoingRinger();
webRtcInteractor.setWantsBluetoothConnection(true);
webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, remotePeer);
@@ -203,13 +201,13 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
}
@Override
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEndedRemote(currentState, endedRemoteEvent, remotePeer);
}
@Override
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) {
return activeCallDelegate.handleEnded(currentState, endedEvent, remotePeer);
}
@Override
@@ -224,9 +222,9 @@ public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
@Override
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
@NonNull WebRtcData.CallMetadata callMetadata,
@NonNull CallMetadata callMetadata,
boolean broadcast,
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
@NonNull List<byte[]> iceCandidates)
{
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
}

View File

@@ -41,7 +41,7 @@ public class PreJoinActionProcessor extends DeviceAwareActionProcessor {
.callState(WebRtcViewModel.State.CALL_INCOMING)
.build();
webRtcInteractor.sendMessage(currentState);
webRtcInteractor.postStateUpdate(currentState);
return beginCallDelegate.handleStartIncomingCall(currentState, remotePeer);
}

View File

@@ -0,0 +1,760 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.ResultReceiver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.annimon.stream.Stream;
import org.greenrobot.eventbus.EventBus;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.signal.ringrtc.HttpHeader;
import org.signal.ringrtc.Remote;
import org.signal.storageservice.protos.groups.GroupExternalCredential;
import org.signal.zkgroup.VerificationFailedException;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.WebRtcCallActivity;
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
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.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import org.webrtc.PeerConnection;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.libsignal.util.guava.Optional;
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.OfferMessage;
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE;
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING;
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NETWORK_FAILURE;
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NO_SUCH_USER;
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.UNTRUSTED_IDENTITY;
/**
* Entry point for all things calling. Lives for the life of the app instance and will spin up a foreground service when needed to
* handle "active" calls.
*/
public final class SignalCallManager implements CallManager.Observer, GroupCall.Observer, CameraEventListener, AppForegroundObserver.Listener {
private static final String TAG = Log.tag(SignalCallManager.class);
public static final int BUSY_TONE_LENGTH = 2000;
@Nullable private final CallManager callManager;
private final Context context;
private final SignalServiceMessageSender messageSender;
private final SignalServiceAccountManager accountManager;
private final ExecutorService serviceExecutor;
private final Executor networkExecutor;
private final LockManager lockManager;
private WebRtcServiceState serviceState;
public SignalCallManager(@NonNull Application application) {
this.context = application.getApplicationContext();
this.messageSender = ApplicationDependencies.getSignalServiceMessageSender();
this.accountManager = ApplicationDependencies.getSignalServiceAccountManager();
this.lockManager = new LockManager(this.context);
this.serviceExecutor = Executors.newSingleThreadExecutor();
this.networkExecutor = Executors.newSingleThreadExecutor();
CallManager callManager = null;
try {
callManager = CallManager.createCallManager(this);
} catch (CallException e) {
Log.w(TAG, "Unable to create CallManager", e);
}
this.callManager = callManager;
this.serviceState = new WebRtcServiceState(new IdleActionProcessor(new WebRtcInteractor(this.context,
this,
lockManager,
new SignalAudioManager(context),
this,
this,
this)));
}
@NonNull CallManager getRingRtcCallManager() {
//noinspection ConstantConditions
return callManager;
}
@NonNull LockManager getLockManager() {
return lockManager;
}
private void process(@NonNull ProcessAction action) {
if (callManager == null) {
Log.w(TAG, "Unable to process action, call manager is not initialized");
return;
}
serviceExecutor.execute(() -> {
Log.v(TAG, "Processing action, handler: " + serviceState.getActionProcessor().getTag());
WebRtcServiceState previous = serviceState;
serviceState = action.process(previous, previous.getActionProcessor());
if (previous != serviceState) {
if (serviceState.getCallInfoState().getCallState() != WebRtcViewModel.State.IDLE) {
postStateUpdate(serviceState);
}
}
});
}
public void startPreJoinCall(@NonNull Recipient recipient) {
process((s, p) -> p.handlePreJoinCall(s, new RemotePeer(recipient.getId())));
}
public void startOutgoingAudioCall(@NonNull Recipient recipient) {
process((s, p) -> p.handleOutgoingCall(s, new RemotePeer(recipient.getId()), OfferMessage.Type.AUDIO_CALL));
}
public void startOutgoingVideoCall(@NonNull Recipient recipient) {
process((s, p) -> p.handleOutgoingCall(s, new RemotePeer(recipient.getId()), OfferMessage.Type.VIDEO_CALL));
}
public void cancelPreJoin() {
process((s, p) -> p.handleCancelPreJoinCall(s));
}
public void updateRenderedResolutions() {
process((s, p) -> p.handleUpdateRenderedResolutions(s));
}
public void orientationChanged(int degrees) {
process((s, p) -> p.handleOrientationChanged(s, degrees));
}
public void setAudioSpeaker(boolean isSpeaker) {
process((s, p) -> p.handleSetSpeakerAudio(s, isSpeaker));
}
public void setAudioBluetooth(boolean isBluetooth) {
process((s, p) -> p.handleSetBluetoothAudio(s, isBluetooth));
}
public void setMuteAudio(boolean enabled) {
process((s, p) -> p.handleSetMuteAudio(s, enabled));
}
public void setMuteVideo(boolean enabled) {
process((s, p) -> p.handleSetEnableVideo(s, enabled));
}
public void flipCamera() {
process((s, p) -> p.handleSetCameraFlip(s));
}
public void acceptCall(boolean answerWithVideo) {
process((s, p) -> p.handleAcceptCall(s, answerWithVideo));
}
public void denyCall() {
process((s, p) -> p.handleDenyCall(s));
}
public void localHangup() {
process((s, p) -> p.handleLocalHangup(s));
}
public void requestUpdateGroupMembers() {
process((s, p) -> p.handleGroupRequestUpdateMembers(s));
}
public void groupApproveSafetyChange(@NonNull List<RecipientId> changedRecipients) {
process((s, p) -> p.handleGroupApproveSafetyNumberChange(s, changedRecipients));
}
public void isCallActive(@Nullable ResultReceiver resultReceiver) {
process((s, p) -> p.handleIsInCallQuery(s, resultReceiver));
}
public void wiredHeadsetChange(boolean available) {
process((s, p) -> p.handleWiredHeadsetChange(s, available));
}
public void networkChange(boolean available) {
process((s, p) -> p.handleNetworkChanged(s, available));
}
public void bandwidthModeUpdate() {
process((s, p) -> p.handleBandwidthModeUpdate(s));
}
public void screenOff() {
process((s, p) -> p.handleScreenOffChange(s));
}
public void bluetoothChange(boolean available) {
process((s, p) -> p.handleBluetoothChange(s, available));
}
public void postStateUpdate(@NonNull WebRtcServiceState state) {
EventBus.getDefault().postSticky(new WebRtcViewModel(state));
}
public void receivedOffer(@NonNull WebRtcData.CallMetadata callMetadata,
@NonNull WebRtcData.OfferMetadata offerMetadata,
@NonNull WebRtcData.ReceivedOfferMetadata receivedOfferMetadata)
{
process((s, p) -> p.handleReceivedOffer(s, callMetadata, offerMetadata, receivedOfferMetadata));
}
public void receivedAnswer(@NonNull WebRtcData.CallMetadata callMetadata,
@NonNull WebRtcData.AnswerMetadata answerMetadata,
@NonNull WebRtcData.ReceivedAnswerMetadata receivedAnswerMetadata)
{
process((s, p) -> p.handleReceivedAnswer(s, callMetadata, answerMetadata, receivedAnswerMetadata));
}
public void receivedIceCandidates(@NonNull WebRtcData.CallMetadata callMetadata, @NonNull List<byte[]> iceCandidates) {
process((s, p) -> p.handleReceivedIceCandidates(s, callMetadata, iceCandidates));
}
public void receivedCallHangup(@NonNull WebRtcData.CallMetadata callMetadata, @NonNull WebRtcData.HangupMetadata hangupMetadata) {
process((s, p) -> p.handleReceivedHangup(s, callMetadata, hangupMetadata));
}
public void receivedCallBusy(@NonNull WebRtcData.CallMetadata callMetadata) {
process((s, p) -> p.handleReceivedBusy(s, callMetadata));
}
public void receivedOpaqueMessage(@NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
process((s, p) -> p.handleReceivedOpaqueMessage(s, opaqueMessageMetadata));
}
public void peekGroupCall(@NonNull RecipientId id) {
if (callManager == null) {
Log.i(TAG, "Unable to peekGroupCall, call manager is null");
return;
}
networkExecutor.execute(() -> {
try {
Recipient group = Recipient.resolved(id);
GroupId.V2 groupId = group.requireGroupId().requireV2();
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(context, groupId);
List<GroupCall.GroupMemberInfo> members = Stream.of(GroupManager.getUuidCipherTexts(context, groupId))
.map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize()))
.toList();
callManager.peekGroupCall(BuildConfig.SIGNAL_SFU_URL, credential.getTokenBytes().toByteArray(), members, peekInfo -> {
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(group);
DatabaseFactory.getSmsDatabase(context)
.updatePreviousGroupCall(threadId,
peekInfo.getEraId(),
peekInfo.getJoinedMembers(),
WebRtcUtil.isCallFull(peekInfo));
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, true, 0, BubbleUtil.BubbleState.HIDDEN);
EventBus.getDefault().postSticky(new GroupCallPeekEvent(id, peekInfo.getEraId(), peekInfo.getDeviceCount(), peekInfo.getMaxDevices()));
});
} catch (IOException | VerificationFailedException | CallException e) {
Log.e(TAG, "error peeking from active conversation", e);
}
});
}
public boolean startCallCardActivityIfPossible() {
if (Build.VERSION.SDK_INT >= 29 && !ApplicationDependencies.getAppForegroundObserver().isForegrounded()) {
return false;
}
context.startActivity(new Intent(context, WebRtcCallActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
return true;
}
@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);
if (callManager == null) {
Log.w(TAG, "Unable to start call, call manager is not initialized");
return;
}
if (remote == null) {
return;
}
process((s, p) -> {
RemotePeer remotePeer = (RemotePeer) remote;
if (s.getCallInfoState().getPeer(remotePeer.hashCode()) == null) {
Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping.");
try {
callManager.drop(callId);
} catch (CallException e) {
s = p.callFailure(s, "callManager.drop() failed: ", e);
}
}
remotePeer.setCallId(callId);
if (isOutgoing) {
return p.handleStartOutgoingCall(s, remotePeer);
} else {
return p.handleStartIncomingCall(s, remotePeer);
}
});
}
@Override
public void onCallEvent(@Nullable Remote remote, @NonNull CallManager.CallEvent event) {
if (callManager == null) {
Log.w(TAG, "Unable to process call event, call manager is not initialized");
return;
}
if (!(remote instanceof RemotePeer)) {
return;
}
process((s, p) -> {
RemotePeer remotePeer = (RemotePeer) remote;
if (s.getCallInfoState().getPeer(remotePeer.hashCode()) == null) {
Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping.");
try {
callManager.drop(remotePeer.getCallId());
} catch (CallException e) {
return p.callFailure(s, "callManager.drop() failed: ", e);
}
return s;
}
Log.i(TAG, "onCallEvent(): call_id: " + remotePeer.getCallId() + ", state: " + remotePeer.getState() + ", event: " + event);
switch (event) {
case LOCAL_RINGING:
return p.handleLocalRinging(s, remotePeer);
case REMOTE_RINGING:
return p.handleRemoteRinging(s, remotePeer);
case RECONNECTING:
Log.i(TAG, "Reconnecting: NOT IMPLEMENTED");
break;
case RECONNECTED:
Log.i(TAG, "Reconnected: NOT IMPLEMENTED");
break;
case LOCAL_CONNECTED:
case REMOTE_CONNECTED:
return p.handleCallConnected(s, remotePeer);
case REMOTE_VIDEO_ENABLE:
return p.handleRemoteVideoEnable(s, true);
case REMOTE_VIDEO_DISABLE:
return p.handleRemoteVideoEnable(s, false);
case ENDED_REMOTE_HANGUP:
case ENDED_REMOTE_HANGUP_NEED_PERMISSION:
case ENDED_REMOTE_HANGUP_ACCEPTED:
case ENDED_REMOTE_HANGUP_BUSY:
case ENDED_REMOTE_HANGUP_DECLINED:
case ENDED_REMOTE_BUSY:
case ENDED_REMOTE_GLARE:
return p.handleEndedRemote(s, event, remotePeer);
case ENDED_TIMEOUT:
case ENDED_INTERNAL_FAILURE:
case ENDED_SIGNALING_FAILURE:
case ENDED_CONNECTION_FAILURE:
return p.handleEnded(s, event, remotePeer);
case RECEIVED_OFFER_EXPIRED:
return p.handleReceivedOfferExpired(s, remotePeer);
case RECEIVED_OFFER_WHILE_ACTIVE:
case RECEIVED_OFFER_WITH_GLARE:
return p.handleReceivedOfferWhileActive(s, remotePeer);
case ENDED_LOCAL_HANGUP:
case ENDED_APP_DROPPED_CALL:
case IGNORE_CALLS_FROM_NON_MULTIRING_CALLERS:
Log.i(TAG, "Ignoring event: " + event);
break;
default:
throw new AssertionError("Unexpected event: " + event.toString());
}
return s;
});
}
@Override
public void onCallConcluded(@Nullable Remote remote) {
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
Log.i(TAG, "onCallConcluded: call_id: " + remotePeer.getCallId());
process((s, p) -> p.handleCallConcluded(s, remotePeer));
}
@Override
public void onSendOffer(@NonNull CallId callId,
@Nullable Remote remote,
@NonNull Integer remoteDevice,
@NonNull Boolean broadcast,
@NonNull byte[] opaque,
@NonNull CallManager.CallMediaType callMediaType)
{
Log.i(TAG, "onSendOffer: id: " + callId.format(remoteDevice) + " type: " + callMediaType.name());
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
OfferMessage.Type offerType = WebRtcUtil.getOfferTypeFromCallMediaType(callMediaType);
WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice);
WebRtcData.OfferMetadata offerMetadata = new WebRtcData.OfferMetadata(opaque, null, offerType);
process((s, p) -> p.handleSendOffer(s, callMetadata, offerMetadata, broadcast));
}
@Override
public void onSendAnswer(@NonNull CallId callId,
@Nullable Remote remote,
@NonNull Integer remoteDevice,
@NonNull Boolean broadcast,
@NonNull byte[] opaque)
{
Log.i(TAG, "onSendAnswer: id: " + callId.format(remoteDevice));
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice);
WebRtcData.AnswerMetadata answerMetadata = new WebRtcData.AnswerMetadata(opaque, null);
process((s, p) -> p.handleSendAnswer(s, callMetadata, answerMetadata, broadcast));
}
@Override
public void onSendIceCandidates(@NonNull CallId callId,
@Nullable Remote remote,
@NonNull Integer remoteDevice,
@NonNull Boolean broadcast,
@NonNull List<byte[]> iceCandidates)
{
Log.i(TAG, "onSendIceCandidates: id: " + callId.format(remoteDevice));
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice);
process((s, p) -> p.handleSendIceCandidates(s, callMetadata, broadcast, iceCandidates));
}
@Override
public void onSendHangup(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast, @NonNull CallManager.HangupType hangupType, @NonNull Integer deviceId, @NonNull Boolean useLegacyHangupMessage) {
Log.i(TAG, "onSendHangup: id: " + callId.format(remoteDevice) + " type: " + hangupType.name() + " isLegacy: " + useLegacyHangupMessage);
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice);
WebRtcData.HangupMetadata hangupMetadata = new WebRtcData.HangupMetadata(WebRtcUtil.getHangupTypeFromCallHangupType(hangupType), useLegacyHangupMessage, deviceId);
process((s, p) -> p.handleSendHangup(s, callMetadata, hangupMetadata, broadcast));
}
@Override
public void onSendBusy(@NonNull CallId callId, @Nullable Remote remote, @NonNull Integer remoteDevice, @NonNull Boolean broadcast) {
Log.i(TAG, "onSendBusy: id: " + callId.format(remoteDevice));
if (!(remote instanceof RemotePeer)) {
return;
}
RemotePeer remotePeer = (RemotePeer) remote;
WebRtcData.CallMetadata callMetadata = new WebRtcData.CallMetadata(remotePeer, callId, remoteDevice);
process((s, p) -> p.handleSendBusy(s, callMetadata, broadcast));
}
@Override
public void onSendCallMessage(@NonNull final UUID uuid, @NonNull final byte[] bytes) {
Log.i(TAG, "onSendCallMessage():");
OpaqueMessage opaqueMessage = new OpaqueMessage(bytes);
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOpaque(opaqueMessage, true, null);
networkExecutor.execute(() -> {
Recipient recipient = Recipient.resolved(RecipientId.from(uuid, null));
if (recipient.isBlocked()) {
return;
}
try {
messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
UnidentifiedAccessUtil.getAccessFor(context, recipient),
callMessage);
} catch (UntrustedIdentityException e) {
Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e);
process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), UNTRUSTED_IDENTITY, Optional.fromNullable(e.getIdentityKey())));
} catch (IOException e) {
Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e);
process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), NETWORK_FAILURE, Optional.absent()));
}
});
}
@Override
public void onSendHttpRequest(long requestId, @NonNull String url, @NonNull CallManager.HttpMethod httpMethod, @Nullable List<HttpHeader> headers, @Nullable byte[] body) {
if (callManager == null) {
Log.w(TAG, "Unable to send http request, call manager is not initialized");
return;
}
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);
try {
if (response instanceof CallingResponse.Success) {
CallingResponse.Success success = (CallingResponse.Success) response;
callManager.receivedHttpResponse(requestId, success.getResponseStatus(), success.getResponseBody());
} else {
callManager.httpRequestFailed(requestId);
}
} catch (CallException e) {
Log.i(TAG, "Failed to process HTTP response/failure", e);
}
});
}
@Override
public void requestMembershipProof(@NonNull final GroupCall groupCall) {
Log.i(TAG, "requestMembershipProof():");
Recipient recipient = serviceState.getCallInfoState().getCallRecipient();
if (!recipient.isPushV2Group()) {
Log.i(TAG, "Request membership proof for non-group");
return;
}
GroupCall currentGroupCall = serviceState.getCallInfoState().getGroupCall();
if (currentGroupCall == null || currentGroupCall.hashCode() != groupCall.hashCode()) {
Log.i(TAG, "Skipping group membership proof request, requested group call does not match current group call");
return;
}
networkExecutor.execute(() -> {
try {
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(context, recipient.getGroupId().get().requireV2());
process((s, p) -> p.handleGroupRequestMembershipProof(s, groupCall.hashCode(), credential.getTokenBytes().toByteArray()));
} catch (IOException e) {
Log.w(TAG, "Unable to get group membership proof from service", e);
onEnded(groupCall, GroupCall.GroupCallEndReason.SFU_CLIENT_FAILED_TO_JOIN);
} catch (VerificationFailedException e) {
Log.w(TAG, "Unable to verify group membership proof", e);
onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED);
}
});
}
@Override
public void requestGroupMembers(@NonNull GroupCall groupCall) {
process((s, p) -> p.handleGroupRequestUpdateMembers(s));
}
@Override
public void onLocalDeviceStateChanged(@NonNull GroupCall groupCall) {
process((s, p) -> p.handleGroupLocalDeviceStateChanged(s));
}
@Override
public void onRemoteDeviceStatesChanged(@NonNull GroupCall groupCall) {
process((s, p) -> p.handleGroupRemoteDeviceStateChanged(s));
}
@Override
public void onPeekChanged(@NonNull GroupCall groupCall) {
process((s, p) -> p.handleGroupJoinedMembershipChanged(s));
}
@Override
public void onEnded(@NonNull GroupCall groupCall, @NonNull GroupCall.GroupCallEndReason groupCallEndReason) {
process((s, p) -> p.handleGroupCallEnded(s, groupCall.hashCode(), groupCallEndReason));
}
@Override
public void onCameraSwitchCompleted(@NonNull final CameraState newCameraState) {
process((s, p) -> p.handleCameraSwitchCompleted(s, newCameraState));
}
@Override
public void onForeground() {
process((s, p) -> {
WebRtcViewModel.State callState = s.getCallInfoState().getCallState();
if (callState == CALL_INCOMING && s.getCallInfoState().getGroupCallState() == IDLE) {
startCallCardActivityIfPossible();
}
ApplicationDependencies.getAppForegroundObserver().removeListener(this);
return s;
});
}
public void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) {
Pair<Long, Long> messageAndThreadId = DatabaseFactory.getSmsDatabase(context)
.insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer);
ApplicationDependencies.getMessageNotifier()
.updateNotification(context, messageAndThreadId.second(), signal);
}
public void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
networkExecutor.execute(() -> {
try {
TurnServerInfo turnServerInfo = accountManager.getTurnServerInfo();
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
for (String url : turnServerInfo.getUrls()) {
Log.i(TAG, "ice_server: " + url);
if (url.startsWith("turn")) {
iceServers.add(PeerConnection.IceServer.builder(url)
.setUsername(turnServerInfo.getUsername())
.setPassword(turnServerInfo.getPassword())
.createIceServer());
} else {
iceServers.add(PeerConnection.IceServer.builder(url).createIceServer());
}
}
process((s, p) -> p.handleTurnServerUpdate(s, iceServers, TextSecurePreferences.isTurnOnly(context)));
} catch (IOException e) {
Log.w(TAG, "Unable to retrieve turn servers: ", e);
process((s, p) -> p.handleSetupFailure(s, remotePeer.getCallId()));
}
});
}
public void sendGroupCallUpdateMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(GroupCallUpdateSendJob.create(recipient.getId(), groupCallEraId)));
}
public void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection<UUID> joinedMembers, boolean isCallFull) {
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getSmsDatabase(context).insertOrUpdateGroupCall(groupId,
Recipient.self().getId(),
System.currentTimeMillis(),
groupCallEraId,
joinedMembers,
isCallFull));
}
public void sendCallMessage(@NonNull final RemotePeer remotePeer,
@NonNull final SignalServiceCallMessage callMessage)
{
networkExecutor.execute(() -> {
Recipient recipient = Recipient.resolved(remotePeer.getId());
if (recipient.isBlocked()) {
return;
}
try {
messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(context, recipient),
UnidentifiedAccessUtil.getAccessFor(context, recipient),
callMessage);
process((s, p) -> p.handleMessageSentSuccess(s, remotePeer.getCallId()));
} catch (UntrustedIdentityException e) {
processSendMessageFailureWithChangeDetection(remotePeer,
(s, p) -> p.handleMessageSentError(s,
remotePeer.getCallId(),
UNTRUSTED_IDENTITY,
Optional.fromNullable(e.getIdentityKey())));
} catch (IOException e) {
processSendMessageFailureWithChangeDetection(remotePeer,
(s, p) -> p.handleMessageSentError(s,
remotePeer.getCallId(),
e instanceof UnregisteredUserException ? NO_SUCH_USER : NETWORK_FAILURE,
Optional.absent()));
}
});
}
private void processSendMessageFailureWithChangeDetection(@NonNull RemotePeer remotePeer,
@NonNull ProcessAction failureProcessAction)
{
process((s, p) -> {
RemotePeer activePeer = s.getCallInfoState().getActivePeer();
boolean stateChanged = activePeer == null ||
remotePeer.getState() != activePeer.getState() ||
!remotePeer.getCallId().equals(activePeer.getCallId());
if (stateChanged) {
return p.handleMessageSentSuccess(s, remotePeer.getCallId());
} else {
return failureProcessAction.process(s, p);
}
});
}
interface ProcessAction {
@NonNull WebRtcServiceState process(@NonNull WebRtcServiceState currentState, @NonNull WebRtcActionProcessor processor);
}
}

View File

@@ -1,7 +1,6 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.content.Context;
import android.content.Intent;
import android.os.ResultReceiver;
import androidx.annotation.NonNull;
@@ -10,6 +9,7 @@ import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.CallException;
import org.signal.ringrtc.CallId;
import org.signal.ringrtc.CallManager;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.components.sensors.Orientation;
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
@@ -21,10 +21,8 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.ringrtc.CallState;
import org.thoughtcrime.securesms.ringrtc.Camera;
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;
@@ -41,116 +39,23 @@ 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 java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ACCEPT_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_BANDWIDTH_MODE_UPDATE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_BLUETOOTH_CHANGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CALL_CONCLUDED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CALL_CONNECTED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CAMERA_SWITCH_COMPLETED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_DENY_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_CONNECTION_FAILURE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_INTERNAL_FAILURE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_BUSY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_GLARE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_ACCEPTED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_BUSY;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_DECLINED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_SIGNALING_FAILURE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ENDED_TIMEOUT;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_FLIP_CAMERA;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_APPROVE_SAFETY_CHANGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_ENDED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_CALL_PEEK;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_JOINED_MEMBERSHIP_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_LOCAL_DEVICE_STATE_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_MESSAGE_SENT_ERROR;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REMOTE_DEVICE_STATE_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_MEMBERSHIP_PROOF;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_GROUP_REQUEST_UPDATE_MEMBERS;
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;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_ERROR;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_SUCCESS;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_NETWORK_CHANGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ORIENTATION_CHANGED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_OUTGOING_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_PRE_JOIN_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_EXPIRED;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_WHILE_ACTIVE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_ANSWER;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVE_BUSY;
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;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_ANSWER;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SEND_BUSY;
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;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_ENABLE_VIDEO;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_SET_MUTE_AUDIO;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_START_INCOMING_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_START_OUTGOING_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_TURN_SERVER_UPDATE;
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_IS_ALWAYS_TURN;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MUTE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RECIPIENT_IDS;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RESULT_RECEIVER;
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
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.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;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getNullableRemotePeerFromMap;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOrientationDegrees;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeer;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemotePeerFromMap;
/**
* Base WebRTC action processor and core of the calling state machine. As actions (as intents)
* are sent to the service, they are passed to an instance of the current state's action processor.
* Based on the state of the system, the action processor will either handle the event or do nothing.
*
* <p>
* For example, the {@link OutgoingCallActionProcessor} responds to the the
* {@link #handleReceivedBusy(WebRtcServiceState, CallMetadata)} event but no others do.
*
* Processing of the actions occur in {@link #processAction(String, Intent, WebRtcServiceState)} and
* <p>
* Processing of the actions occur in by calls from {@link SignalCallManager} and
* result in atomic state updates that are returned to the caller. Part of the state change can be
* the replacement of the current action processor.
*/
@@ -161,7 +66,7 @@ public abstract class WebRtcActionProcessor {
protected final String tag;
public WebRtcActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
this.context = webRtcInteractor.getWebRtcCallService();
this.context = webRtcInteractor.getContext();
this.webRtcInteractor = webRtcInteractor;
this.tag = tag;
}
@@ -170,102 +75,6 @@ public abstract class WebRtcActionProcessor {
return tag;
}
public @NonNull WebRtcServiceState processAction(@NonNull String action, @NonNull Intent intent, @NonNull WebRtcServiceState currentState) {
switch (action) {
case ACTION_IS_IN_CALL_QUERY: return handleIsInCallQuery(currentState, intent.getParcelableExtra(EXTRA_RESULT_RECEIVER));
// Pre-Join Actions
case ACTION_PRE_JOIN_CALL: return handlePreJoinCall(currentState, getRemotePeer(intent));
case ACTION_CANCEL_PRE_JOIN_CALL: return handleCancelPreJoinCall(currentState);
// Outgoing Call Actions
case ACTION_OUTGOING_CALL: return handleOutgoingCall(currentState, getRemotePeer(intent), getOfferMessageType(intent));
case ACTION_START_OUTGOING_CALL: return handleStartOutgoingCall(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_SEND_OFFER: return handleSendOffer(currentState, CallMetadata.fromIntent(intent), OfferMetadata.fromIntent(intent), getBroadcastFlag(intent));
case ACTION_REMOTE_RINGING: return handleRemoteRinging(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_RECEIVE_ANSWER: return handleReceivedAnswer(currentState, CallMetadata.fromIntent(intent), AnswerMetadata.fromIntent(intent), ReceivedAnswerMetadata.fromIntent(intent));
case ACTION_RECEIVE_BUSY: return handleReceivedBusy(currentState, CallMetadata.fromIntent(intent));
// Incoming Call Actions
case ACTION_RECEIVE_OFFER: return handleReceivedOffer(currentState, CallMetadata.fromIntent(intent), OfferMetadata.fromIntent(intent), ReceivedOfferMetadata.fromIntent(intent));
case ACTION_RECEIVED_OFFER_EXPIRED: return handleReceivedOfferExpired(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_START_INCOMING_CALL: return handleStartIncomingCall(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_ACCEPT_CALL: return handleAcceptCall(currentState, intent.getBooleanExtra(EXTRA_ANSWER_WITH_VIDEO, false));
case ACTION_LOCAL_RINGING: return handleLocalRinging(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_DENY_CALL: return handleDenyCall(currentState);
case ACTION_SEND_ANSWER: return handleSendAnswer(currentState, CallMetadata.fromIntent(intent), AnswerMetadata.fromIntent(intent), getBroadcastFlag(intent));
// Active Call Actions
case ACTION_CALL_CONNECTED: return handleCallConnected(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_RECEIVED_OFFER_WHILE_ACTIVE: return handleReceivedOfferWhileActive(currentState, getRemotePeerFromMap(intent, currentState));
case ACTION_SEND_BUSY: return handleSendBusy(currentState, CallMetadata.fromIntent(intent), getBroadcastFlag(intent));
case ACTION_CALL_CONCLUDED: return handleCallConcluded(currentState, getNullableRemotePeerFromMap(intent, currentState));
case ACTION_REMOTE_VIDEO_ENABLE: return handleRemoteVideoEnable(currentState, getEnable(intent));
case ACTION_RECEIVE_HANGUP: return handleReceivedHangup(currentState, CallMetadata.fromIntent(intent), HangupMetadata.fromIntent(intent));
case ACTION_LOCAL_HANGUP: return handleLocalHangup(currentState);
case ACTION_SEND_HANGUP: return handleSendHangup(currentState, CallMetadata.fromIntent(intent), HangupMetadata.fromIntent(intent), getBroadcastFlag(intent));
case ACTION_MESSAGE_SENT_SUCCESS: return handleMessageSentSuccess(currentState, getCallId(intent));
case ACTION_MESSAGE_SENT_ERROR: return handleMessageSentError(currentState, getCallId(intent), getErrorCallState(intent), getErrorIdentityKey(intent));
// Call Setup Actions
case ACTION_RECEIVE_ICE_CANDIDATES: return handleReceivedIceCandidates(currentState, CallMetadata.fromIntent(intent), getIceCandidates(intent));
case ACTION_SEND_ICE_CANDIDATES: return handleSendIceCandidates(currentState, CallMetadata.fromIntent(intent), getBroadcastFlag(intent), getIceCandidates(intent));
case ACTION_TURN_SERVER_UPDATE: return handleTurnServerUpdate(currentState, getIceServers(intent), intent.getBooleanExtra(EXTRA_IS_ALWAYS_TURN, false));
// Local Device Actions
case ACTION_SET_ENABLE_VIDEO: return handleSetEnableVideo(currentState, getEnable(intent));
case ACTION_SET_MUTE_AUDIO: return handleSetMuteAudio(currentState, intent.getBooleanExtra(EXTRA_MUTE, false));
case ACTION_FLIP_CAMERA: return handleSetCameraFlip(currentState);
case ACTION_SCREEN_OFF: return handleScreenOffChange(currentState);
case ACTION_WIRED_HEADSET_CHANGE: return handleWiredHeadsetChange(currentState, getAvailable(intent));
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, getCameraState(intent));
case ACTION_NETWORK_CHANGE: return handleNetworkChanged(currentState, getAvailable(intent));
case ACTION_BANDWIDTH_MODE_UPDATE: return handleBandwidthModeUpdate(currentState);
case ACTION_ORIENTATION_CHANGED: return handleOrientationChanged(currentState, getOrientationDegrees(intent));
// End Call Actions
case ACTION_ENDED_REMOTE_HANGUP:
case ACTION_ENDED_REMOTE_HANGUP_ACCEPTED:
case ACTION_ENDED_REMOTE_HANGUP_BUSY:
case ACTION_ENDED_REMOTE_HANGUP_DECLINED:
case ACTION_ENDED_REMOTE_BUSY:
case ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION:
case ACTION_ENDED_REMOTE_GLARE: return handleEndedRemote(currentState, action, getRemotePeerFromMap(intent, currentState));
// End Call Failure Actions
case ACTION_ENDED_TIMEOUT:
case ACTION_ENDED_INTERNAL_FAILURE:
case ACTION_ENDED_SIGNALING_FAILURE:
case ACTION_ENDED_CONNECTION_FAILURE: return handleEnded(currentState, action, getRemotePeerFromMap(intent, currentState));
// 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, 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_PEEK: return handleGroupCallPeek(currentState, getRemotePeer(intent));
case ACTION_GROUP_MESSAGE_SENT_ERROR: return handleGroupMessageSentError(currentState, getRemotePeer(intent), getErrorCallState(intent), getErrorIdentityKey(intent));
case ACTION_GROUP_APPROVE_SAFETY_CHANGE: return handleGroupApproveSafetyNumberChange(currentState, RecipientId.fromSerializedList(intent.getStringExtra(EXTRA_RECIPIENT_IDS)));
case ACTION_HTTP_SUCCESS: return handleHttpSuccess(currentState, HttpData.fromIntent(intent));
case ACTION_HTTP_FAILURE: return handleHttpFailure(currentState, HttpData.fromIntent(intent));
case ACTION_SEND_OPAQUE_MESSAGE: return handleSendOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
case ACTION_RECEIVE_OPAQUE_MESSAGE: return handleReceivedOpaqueMessage(currentState, OpaqueMessageMetadata.fromIntent(intent));
}
return currentState;
}
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
if (resultReceiver != null) {
resultReceiver.send(0, null);
@@ -337,21 +146,21 @@ public abstract class WebRtcActionProcessor {
if (TelephonyUtil.isAnyPstnLineBusy(context)) {
Log.i(tag, "PSTN line is busy.");
currentState = currentState.getActionProcessor().handleSendBusy(currentState, callMetadata, true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
if (!RecipientUtil.isCallRequestAccepted(context.getApplicationContext(), callMetadata.getRemotePeer().getRecipient())) {
Log.w(tag, "Caller is untrusted.");
currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NEED_PERMISSION), true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
if (offerMetadata.getOpaque() == null) {
Log.w(tag, "Opaque data is required.");
currentState = currentState.getActionProcessor().handleSendHangup(currentState, callMetadata, WebRtcData.HangupMetadata.fromType(HangupMessage.Type.NORMAL), true);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), true, receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
webRtcInteractor.insertMissedCall(callMetadata.getRemotePeer(), receivedOfferMetadata.getServerReceivedTimestamp(), offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL);
return currentState;
}
@@ -397,7 +206,7 @@ public abstract class WebRtcActionProcessor {
{
Log.i(tag, "handleReceivedOfferExpired(): call_id: " + remotePeer.getCallId());
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
webRtcInteractor.insertMissedCall(remotePeer, remotePeer.getCallStartTimestamp(), currentState.getCallSetupState().isRemoteVideoOffer());
return terminate(currentState, remotePeer);
}
@@ -475,7 +284,7 @@ public abstract class WebRtcActionProcessor {
try {
webRtcInteractor.getCallManager().receivedHangup(callMetadata.getCallId(), callMetadata.getRemoteDevice(), hangupMetadata.getCallHangupType(), hangupMetadata.getDeviceId());
} catch (CallException e) {
} catch (CallException e) {
return callFailure(currentState, "receivedHangup() failed: ", e);
}
@@ -515,7 +324,8 @@ public abstract class WebRtcActionProcessor {
protected @NonNull WebRtcServiceState handleMessageSentError(@NonNull WebRtcServiceState currentState,
@NonNull CallId callId,
@NonNull WebRtcViewModel.State errorCallState,
@NonNull Optional<IdentityKey> identityKey) {
@NonNull Optional<IdentityKey> identityKey)
{
Log.w(tag, "handleMessageSentError():");
try {
@@ -552,18 +362,13 @@ public abstract class WebRtcActionProcessor {
//region Call setup
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast, @NonNull ArrayList<IceCandidateParcel> iceCandidates) {
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast, @NonNull List<byte[]> iceCandidates) {
Log.i(tag, "handleSendIceCandidates not processed");
return currentState;
}
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull ArrayList<IceCandidateParcel> iceCandidateParcels) {
Log.i(tag, "handleReceivedIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + ", count: " + iceCandidateParcels.size());
LinkedList<byte[]> iceCandidates = new LinkedList<>();
for (IceCandidateParcel parcel : iceCandidateParcels) {
iceCandidates.add(parcel.getIceCandidate());
}
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull List<byte[]> iceCandidates) {
Log.i(tag, "handleReceivedIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + ", count: " + iceCandidates.size());
try {
webRtcInteractor.getCallManager().receivedIceCandidates(callMetadata.getCallId(), callMetadata.getRemoteDevice(), iceCandidates);
@@ -659,7 +464,7 @@ public abstract class WebRtcActionProcessor {
//region End call
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedRemoteEvent, @NonNull RemotePeer remotePeer) {
Log.i(tag, "handleEndedRemote not processed");
return currentState;
}
@@ -668,7 +473,7 @@ public abstract class WebRtcActionProcessor {
//region End call failure
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull CallManager.CallEvent endedEvent, @NonNull RemotePeer remotePeer) {
Log.i(tag, "handleEnded not processed");
return currentState;
}
@@ -677,7 +482,7 @@ public abstract class WebRtcActionProcessor {
//region Local call failure
protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) {
protected @NonNull WebRtcServiceState handleSetupFailure(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) {
Log.i(tag, "handleSetupFailure not processed");
return currentState;
}
@@ -786,11 +591,6 @@ public abstract class WebRtcActionProcessor {
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupCallPeek(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
webRtcInteractor.peekGroupCall(remotePeer.getId());
return currentState;
}
protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState,
@NonNull RemotePeer remotePeer,
@NonNull WebRtcViewModel.State errorCallState,
@@ -809,30 +609,6 @@ public abstract class WebRtcActionProcessor {
//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");

View File

@@ -0,0 +1,304 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.TelephonyUtil;
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager;
import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import java.util.Objects;
/**
* Provide a foreground service for {@link SignalCallManager} to leverage to run in the background when necessary. Also
* provides devices listeners needed for during a call (i.e., bluetooth, power button).
*/
public final class WebRtcCallService extends Service implements BluetoothStateManager.BluetoothStateListener {
private static final String TAG = Log.tag(WebRtcCallService.class);
private static final String ACTION_UPDATE = "UPDATE";
private static final String ACTION_STOP = "STOP";
private static final String ACTION_DENY_CALL = "DENY_CALL";
private static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP";
private static final String ACTION_WANTS_BLUETOOTH = "WANTS_BLUETOOTH";
private static final String ACTION_CHANGE_POWER_BUTTON = "CHANGE_POWER_BUTTON";
private static final String EXTRA_UPDATE_TYPE = "UPDATE_TYPE";
private static final String EXTRA_RECIPIENT_ID = "RECIPIENT_ID";
private static final String EXTRA_ENABLED = "ENABLED";
private SignalCallManager callManager;
private WiredHeadsetStateReceiver wiredHeadsetStateReceiver;
private NetworkReceiver networkReceiver;
private PowerButtonReceiver powerButtonReceiver;
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
private PhoneStateListener hangUpRtcOnDeviceCallAnswered;
private BluetoothStateManager bluetoothStateManager;
public static void update(@NonNull Context context, int type, @NonNull RecipientId recipientId) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_UPDATE)
.putExtra(EXTRA_UPDATE_TYPE, type)
.putExtra(EXTRA_RECIPIENT_ID, recipientId);
ContextCompat.startForegroundService(context, intent);
}
public static void stop(@NonNull Context context) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_STOP);
context.startService(intent);
}
public static @NonNull Intent denyCallIntent(@NonNull Context context) {
return new Intent(context, WebRtcCallService.class).setAction(ACTION_DENY_CALL);
}
public static @NonNull Intent hangupIntent(@NonNull Context context) {
return new Intent(context, WebRtcCallService.class).setAction(ACTION_LOCAL_HANGUP);
}
public static void setWantsBluetoothConnection(@NonNull Context context, boolean enabled) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_WANTS_BLUETOOTH)
.putExtra(EXTRA_ENABLED, enabled);
context.startService(intent);
}
public static void changePowerButtonReceiver(@NonNull Context context, boolean register) {
Intent intent = new Intent(context, WebRtcCallService.class);
intent.setAction(ACTION_CHANGE_POWER_BUTTON)
.putExtra(EXTRA_ENABLED, register);
context.startService(intent);
}
@Override
public void onCreate() {
Log.v(TAG, "onCreate");
super.onCreate();
this.callManager = ApplicationDependencies.getSignalCallManager();
this.bluetoothStateManager = new BluetoothStateManager(this, this);
this.hangUpRtcOnDeviceCallAnswered = new HangUpRtcOnPstnCallAnsweredListener();
registerUncaughtExceptionHandler();
registerWiredHeadsetStateReceiver();
registerNetworkReceiver();
TelephonyUtil.getManager(this)
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE);
}
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
super.onDestroy();
if (uncaughtExceptionHandlerManager != null) {
uncaughtExceptionHandlerManager.unregister();
}
if (bluetoothStateManager != null) {
bluetoothStateManager.onDestroy();
}
if (wiredHeadsetStateReceiver != null) {
unregisterReceiver(wiredHeadsetStateReceiver);
wiredHeadsetStateReceiver = null;
}
unregisterNetworkReceiver();
unregisterNetworkReceiver();
TelephonyUtil.getManager(this)
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null || intent.getAction() == null) {
return START_NOT_STICKY;
}
Log.i(TAG, "action: " + intent.getAction());
switch (intent.getAction()) {
case ACTION_UPDATE:
setCallInProgressNotification(intent.getIntExtra(EXTRA_UPDATE_TYPE, 0),
Objects.requireNonNull(intent.getParcelableExtra(EXTRA_RECIPIENT_ID)));
return START_STICKY;
case ACTION_WANTS_BLUETOOTH:
if (bluetoothStateManager != null) {
bluetoothStateManager.setWantsConnection(intent.getBooleanExtra(EXTRA_ENABLED, false));
}
return START_STICKY;
case ACTION_CHANGE_POWER_BUTTON:
if (intent.getBooleanExtra(EXTRA_ENABLED, false)) {
registerPowerButtonReceiver();
} else {
unregisterPowerButtonReceiver();
}
return START_STICKY;
case ACTION_STOP:
stopSelf();
stopForeground(true);
return START_NOT_STICKY;
case ACTION_DENY_CALL:
callManager.denyCall();
return START_NOT_STICKY;
case ACTION_LOCAL_HANGUP:
callManager.localHangup();
return START_NOT_STICKY;
default:
throw new AssertionError("Unknown action: " + intent.getAction());
}
}
public void setCallInProgressNotification(int type, @NonNull RecipientId id) {
startForeground(CallNotificationBuilder.getNotificationId(type),
CallNotificationBuilder.getCallInProgressNotification(this, type, Recipient.resolved(id)));
}
private void registerUncaughtExceptionHandler() {
uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager();
uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(callManager.getLockManager()));
}
private void registerWiredHeadsetStateReceiver() {
wiredHeadsetStateReceiver = new WiredHeadsetStateReceiver();
String action;
if (Build.VERSION.SDK_INT >= 21) {
action = AudioManager.ACTION_HEADSET_PLUG;
} else {
action = Intent.ACTION_HEADSET_PLUG;
}
registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action));
}
private void registerNetworkReceiver() {
if (networkReceiver == null) {
networkReceiver = new NetworkReceiver();
registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private void unregisterNetworkReceiver() {
if (networkReceiver != null) {
unregisterReceiver(networkReceiver);
networkReceiver = null;
}
}
public void registerPowerButtonReceiver() {
if (powerButtonReceiver == null) {
powerButtonReceiver = new PowerButtonReceiver();
registerReceiver(powerButtonReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
}
public void unregisterPowerButtonReceiver() {
if (powerButtonReceiver != null) {
unregisterReceiver(powerButtonReceiver);
powerButtonReceiver = null;
}
}
@Override
public @Nullable IBinder onBind(Intent intent) {
return null;
}
@Override
public void onBluetoothStateChanged(boolean isAvailable) {
callManager.bluetoothChange(isAvailable);
}
private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, @NonNull String phoneNumber) {
super.onCallStateChanged(state, phoneNumber);
if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
hangup();
Log.i(TAG, "Device phone call ended Signal call.");
}
}
private void hangup() {
callManager.localHangup();
}
}
private static class WiredHeadsetStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
int state = intent.getIntExtra("state", -1);
ApplicationDependencies.getSignalCallManager().wiredHeadsetChange(state != 0);
}
}
private static class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
ApplicationDependencies.getSignalCallManager().networkChange(activeNetworkInfo != null && activeNetworkInfo.isConnected());
ApplicationDependencies.getSignalCallManager().bandwidthModeUpdate();
}
}
private static class PowerButtonReceiver extends BroadcastReceiver {
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
ApplicationDependencies.getSignalCallManager().screenOff();
}
}
}
private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler {
private final LockManager lockManager;
private ProximityLockRelease(@NonNull LockManager lockManager) {
this.lockManager = lockManager;
}
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable);
lockManager.updatePhoneState(LockManager.PhoneState.IDLE);
}
}
}

View File

@@ -1,53 +1,30 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.content.Intent;
import androidx.annotation.NonNull;
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;
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.getRecipientId;
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getRemoteDevice;
/**
* Collection of classes to ease parsing data from intents and passing said data
* around.
* Collection of classes to ease passing calling data around.
*/
public class WebRtcData {
/**
* Low-level metadata Information about the call.
*/
static class CallMetadata {
public static class CallMetadata {
private final @NonNull RemotePeer remotePeer;
private final @NonNull CallId callId;
private final int remoteDevice;
public static @NonNull CallMetadata fromIntent(@NonNull Intent intent) {
return new CallMetadata(WebRtcIntentParser.getRemotePeer(intent), WebRtcIntentParser.getCallId(intent), WebRtcIntentParser.getRemoteDevice(intent));
}
private CallMetadata(@NonNull RemotePeer remotePeer, @NonNull CallId callId, int remoteDevice) {
public CallMetadata(@NonNull RemotePeer remotePeer, @NonNull CallId callId, int remoteDevice) {
this.remotePeer = remotePeer;
this.callId = callId;
this.remoteDevice = remoteDevice;
@@ -69,18 +46,12 @@ public class WebRtcData {
/**
* Metadata for a call offer to be sent or received.
*/
static class OfferMetadata {
public static class OfferMetadata {
private final @Nullable byte[] opaque;
private final @Nullable String sdp;
private final @NonNull OfferMessage.Type offerType;
static @NonNull OfferMetadata fromIntent(@NonNull Intent intent) {
return new OfferMetadata(WebRtcIntentParser.getOfferOpaque(intent),
WebRtcIntentParser.getOfferSdp(intent),
WebRtcIntentParser.getOfferMessageType(intent));
}
private OfferMetadata(@Nullable byte[] opaque, @Nullable String sdp, @NonNull OfferMessage.Type offerType) {
public OfferMetadata(@Nullable byte[] opaque, @Nullable String sdp, @NonNull OfferMessage.Type offerType) {
this.opaque = opaque;
this.sdp = sdp;
this.offerType = offerType;
@@ -102,20 +73,13 @@ public class WebRtcData {
/**
* Additional metadata for a received call.
*/
static class ReceivedOfferMetadata {
public static class ReceivedOfferMetadata {
private final @NonNull byte[] remoteIdentityKey;
private final long serverReceivedTimestamp;
private final long serverDeliveredTimestamp;
private final boolean isMultiRing;
static @NonNull ReceivedOfferMetadata fromIntent(@NonNull Intent intent) {
return new ReceivedOfferMetadata(WebRtcIntentParser.getRemoteIdentityKey(intent),
intent.getLongExtra(EXTRA_SERVER_RECEIVED_TIMESTAMP, -1),
intent.getLongExtra(EXTRA_SERVER_DELIVERED_TIMESTAMP, -1),
WebRtcIntentParser.getMultiRingFlag(intent));
}
ReceivedOfferMetadata(@NonNull byte[] remoteIdentityKey, long serverReceivedTimestamp, long serverDeliveredTimestamp, boolean isMultiRing) {
public ReceivedOfferMetadata(@NonNull byte[] remoteIdentityKey, long serverReceivedTimestamp, long serverDeliveredTimestamp, boolean isMultiRing) {
this.remoteIdentityKey = remoteIdentityKey;
this.serverReceivedTimestamp = serverReceivedTimestamp;
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
@@ -142,17 +106,13 @@ public class WebRtcData {
/**
* Metadata for an answer to be sent or received.
*/
static class AnswerMetadata {
private final @Nullable byte[] opaque;
private final @Nullable String sdp;
public static class AnswerMetadata {
private final @Nullable byte[] opaque;
private final @Nullable String sdp;
static @NonNull AnswerMetadata fromIntent(@NonNull Intent intent) {
return new AnswerMetadata(WebRtcIntentParser.getAnswerOpaque(intent), WebRtcIntentParser.getAnswerSdp(intent));
}
private AnswerMetadata(@Nullable byte[] opaque, @Nullable String sdp) {
this.opaque = opaque;
this.sdp = sdp;
public AnswerMetadata(@Nullable byte[] opaque, @Nullable String sdp) {
this.opaque = opaque;
this.sdp = sdp;
}
@Nullable byte[] getOpaque() {
@@ -167,17 +127,13 @@ public class WebRtcData {
/**
* Additional metadata for a received answer.
*/
static class ReceivedAnswerMetadata {
public static class ReceivedAnswerMetadata {
private final @NonNull byte[] remoteIdentityKey;
private final boolean isMultiRing;
static @NonNull ReceivedAnswerMetadata fromIntent(@NonNull Intent intent) {
return new ReceivedAnswerMetadata(WebRtcIntentParser.getRemoteIdentityKey(intent), WebRtcIntentParser.getMultiRingFlag(intent));
}
ReceivedAnswerMetadata(@NonNull byte[] remoteIdentityKey, boolean isMultiRing) {
this.remoteIdentityKey = remoteIdentityKey;
this.isMultiRing = isMultiRing;
public ReceivedAnswerMetadata(@NonNull byte[] remoteIdentityKey, boolean isMultiRing) {
this.remoteIdentityKey = remoteIdentityKey;
this.isMultiRing = isMultiRing;
}
@NonNull byte[] getRemoteIdentityKey() {
@@ -192,22 +148,16 @@ public class WebRtcData {
/**
* Metadata for a remote or local hangup.
*/
static class HangupMetadata {
public static class HangupMetadata {
private final @NonNull HangupMessage.Type type;
private final boolean isLegacy;
private final int deviceId;
static @NonNull HangupMetadata fromIntent(@NonNull Intent intent) {
return new HangupMetadata(HangupMessage.Type.fromCode(intent.getStringExtra(EXTRA_HANGUP_TYPE)),
intent.getBooleanExtra(EXTRA_HANGUP_IS_LEGACY, true),
intent.getIntExtra(EXTRA_HANGUP_DEVICE_ID, 0));
}
static @NonNull HangupMetadata fromType(@NonNull HangupMessage.Type type) {
return new HangupMetadata(type, true, 0);
}
HangupMetadata(@NonNull HangupMessage.Type type, boolean isLegacy, int deviceId) {
public HangupMetadata(@NonNull HangupMessage.Type type, boolean isLegacy, int deviceId) {
this.type = type;
this.isLegacy = isLegacy;
this.deviceId = deviceId;
@@ -237,56 +187,16 @@ public class WebRtcData {
}
}
/**
* 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 {
public 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) {
public OpaqueMessageMetadata(@NonNull UUID uuid, @NonNull byte[] opaque, int remoteDeviceId, long messageAgeSeconds) {
this.uuid = uuid;
this.opaque = opaque;
this.remoteDeviceId = remoteDeviceId;

View File

@@ -1,216 +0,0 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.logging.Log;
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.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.ringrtc.TurnServerInfoParcel;
import org.thoughtcrime.securesms.service.WebRtcCallService;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
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_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;
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_ORIENTATION_DEGREES;
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.
*/
public final class WebRtcIntentParser {
private static final String TAG = Log.tag(WebRtcIntentParser.class);
private WebRtcIntentParser() {}
public static @NonNull CallId getCallId(@NonNull Intent intent) {
return new CallId(intent.getLongExtra(EXTRA_CALL_ID, -1));
}
public static int getRemoteDevice(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1);
}
public static @NonNull RemotePeer getRemotePeer(@NonNull Intent intent) {
RemotePeer remotePeer = intent.getParcelableExtra(WebRtcCallService.EXTRA_REMOTE_PEER);
if (remotePeer == null) {
throw new AssertionError("No RemotePeer in intent!");
}
return remotePeer;
}
public static @NonNull RemotePeer getRemotePeerFromMap(@NonNull Intent intent, @NonNull WebRtcServiceState currentState) {
RemotePeer remotePeer = getNullableRemotePeerFromMap(intent, currentState);
if (remotePeer == null) {
throw new AssertionError("No RemotePeer in map for key: " + getRemotePeerKey(intent) + "!");
}
return remotePeer;
}
public static @Nullable RemotePeer getNullableRemotePeerFromMap(@NonNull Intent intent, @NonNull WebRtcServiceState currentState) {
return currentState.getCallInfoState().getPeer(getRemotePeerKey(intent));
}
public static int getRemotePeerKey(@NonNull Intent intent) {
if (!intent.hasExtra(EXTRA_REMOTE_PEER_KEY)) {
throw new AssertionError("No RemotePeer key in intent!");
}
// The default of -1 should never be applied since the key exists.
return intent.getIntExtra(EXTRA_REMOTE_PEER_KEY, -1);
}
public static boolean getMultiRingFlag(@NonNull Intent intent) {
return intent.getBooleanExtra(EXTRA_MULTI_RING, false);
}
public static @NonNull byte[] getRemoteIdentityKey(@NonNull Intent intent) {
return Objects.requireNonNull(intent.getByteArrayExtra(EXTRA_REMOTE_IDENTITY_KEY));
}
public static @Nullable String getAnswerSdp(@NonNull Intent intent) {
return intent.getStringExtra(EXTRA_ANSWER_SDP);
}
public static @Nullable String getOfferSdp(@NonNull Intent intent) {
return intent.getStringExtra(EXTRA_OFFER_SDP);
}
public static @Nullable byte[] getAnswerOpaque(@NonNull Intent intent) {
return intent.getByteArrayExtra(EXTRA_ANSWER_OPAQUE);
}
public static @Nullable byte[] getOfferOpaque(@NonNull Intent intent) {
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);
}
public static boolean getAvailable(@NonNull Intent intent) {
return intent.getBooleanExtra(EXTRA_AVAILABLE, false);
}
public static int getOrientationDegrees(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_ORIENTATION_DEGREES, 0);
}
public static @NonNull ArrayList<IceCandidateParcel> getIceCandidates(@NonNull Intent intent) {
return Objects.requireNonNull(intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES));
}
public static @NonNull List<PeerConnection.IceServer> getIceServers(@NonNull Intent intent) {
TurnServerInfoParcel turnServerInfoParcel = Objects.requireNonNull(intent.getParcelableExtra(EXTRA_TURN_SERVER_INFO));
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer());
for (String url : turnServerInfoParcel.getUrls()) {
Log.i(TAG, "ice_server: " + url);
if (url.startsWith("turn")) {
iceServers.add(PeerConnection.IceServer.builder(url)
.setUsername(turnServerInfoParcel.getUsername())
.setPassword(turnServerInfoParcel.getPassword())
.createIceServer());
} else {
iceServers.add(PeerConnection.IceServer.builder(url).createIceServer());
}
}
return iceServers;
}
public static @NonNull OfferMessage.Type getOfferMessageType(@NonNull Intent intent) {
return OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE));
}
public static boolean getEnable(@NonNull Intent intent) {
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 = intent.getParcelableExtra(EXTRA_ERROR_IDENTITY_KEY);
if (identityKeyParcelable != null) {
return Optional.fromNullable(identityKeyParcelable.get());
}
return Optional.absent();
}
public static int getGroupCallHash(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_GROUP_CALL_HASH, 0);
}
public static @NonNull GroupCall.GroupCallEndReason getGroupCallEndReason(@NonNull Intent intent) {
int ordinal = intent.getIntExtra(EXTRA_GROUP_CALL_END_REASON, -1);
if (ordinal >= 0 && ordinal < GroupCall.GroupCallEndReason.values().length) {
return GroupCall.GroupCallEndReason.values()[ordinal];
}
return GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED;
}
public static @NonNull RecipientId getRecipientId(@NonNull Intent intent, @NonNull String name) {
return RecipientId.from(Objects.requireNonNull(intent.getStringExtra(name)));
}
}

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;
@@ -11,10 +12,8 @@ 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;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.util.AppForegroundObserver;
import org.thoughtcrime.securesms.webrtc.audio.BluetoothStateManager;
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
@@ -30,32 +29,33 @@ import java.util.UUID;
*/
public class WebRtcInteractor {
@NonNull private final WebRtcCallService webRtcCallService;
@NonNull private final CallManager callManager;
@NonNull private final Context context;
@NonNull private final SignalCallManager signalCallManager;
@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;
@NonNull private final AppForegroundObserver.Listener foregroundListener;
public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService,
@NonNull CallManager callManager,
public WebRtcInteractor(@NonNull Context context,
@NonNull SignalCallManager signalCallManager,
@NonNull LockManager lockManager,
@NonNull SignalAudioManager audioManager,
@NonNull BluetoothStateManager bluetoothStateManager,
@NonNull CameraEventListener cameraEventListener,
@NonNull GroupCall.Observer groupCallObserver,
@NonNull AppForegroundObserver.Listener foregroundListener)
{
this.webRtcCallService = webRtcCallService;
this.callManager = callManager;
this.lockManager = lockManager;
this.audioManager = audioManager;
this.bluetoothStateManager = bluetoothStateManager;
this.cameraEventListener = cameraEventListener;
this.groupCallObserver = groupCallObserver;
this.foregroundListener = foregroundListener;
this.context = context;
this.signalCallManager = signalCallManager;
this.lockManager = lockManager;
this.audioManager = audioManager;
this.cameraEventListener = cameraEventListener;
this.groupCallObserver = groupCallObserver;
this.foregroundListener = foregroundListener;
}
@NonNull Context getContext() {
return context;
}
@NonNull CameraEventListener getCameraEventListener() {
@@ -63,11 +63,7 @@ public class WebRtcInteractor {
}
@NonNull CallManager getCallManager() {
return callManager;
}
@NonNull WebRtcCallService getWebRtcCallService() {
return webRtcCallService;
return signalCallManager.getRingRtcCallManager();
}
@NonNull GroupCall.Observer getGroupCallObserver() {
@@ -79,63 +75,59 @@ public class WebRtcInteractor {
}
void setWantsBluetoothConnection(boolean enabled) {
bluetoothStateManager.setWantsConnection(enabled);
WebRtcCallService.setWantsBluetoothConnection(context, enabled);
}
void updatePhoneState(@NonNull LockManager.PhoneState phoneState) {
lockManager.updatePhoneState(phoneState);
}
void sendMessage(@NonNull WebRtcServiceState state) {
webRtcCallService.sendMessage(state);
void postStateUpdate(@NonNull WebRtcServiceState state) {
signalCallManager.postStateUpdate(state);
}
void sendCallMessage(@NonNull RemotePeer remotePeer, @NonNull SignalServiceCallMessage callMessage) {
webRtcCallService.sendCallMessage(remotePeer, callMessage);
}
void sendOpaqueCallMessage(@NonNull UUID uuid, @NonNull SignalServiceCallMessage callMessage) {
webRtcCallService.sendOpaqueCallMessage(uuid, callMessage);
signalCallManager.sendCallMessage(remotePeer, callMessage);
}
void sendGroupCallMessage(@NonNull Recipient recipient, @Nullable String groupCallEraId) {
webRtcCallService.sendGroupCallMessage(recipient, groupCallEraId);
signalCallManager.sendGroupCallUpdateMessage(recipient, groupCallEraId);
}
void updateGroupCallUpdateMessage(@NonNull RecipientId groupId, @Nullable String groupCallEraId, @NonNull Collection<UUID> joinedMembers, boolean isCallFull) {
webRtcCallService.updateGroupCallUpdateMessage(groupId, groupCallEraId, joinedMembers, isCallFull);
signalCallManager.updateGroupCallUpdateMessage(groupId, groupCallEraId, joinedMembers, isCallFull);
}
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
webRtcCallService.setCallInProgressNotification(type, remotePeer.getRecipient());
WebRtcCallService.update(context, type, remotePeer.getRecipient().getId());
}
void setCallInProgressNotification(int type, @NonNull Recipient recipient) {
webRtcCallService.setCallInProgressNotification(type, recipient);
WebRtcCallService.update(context, type, recipient.getId());
}
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
webRtcCallService.retrieveTurnServers(remotePeer);
signalCallManager.retrieveTurnServers(remotePeer);
}
void stopForegroundService() {
webRtcCallService.stopForeground(true);
WebRtcCallService.stop(context);
}
void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp, boolean isVideoOffer) {
webRtcCallService.insertMissedCall(remotePeer, signal, timestamp, isVideoOffer);
void insertMissedCall(@NonNull RemotePeer remotePeer, long timestamp, boolean isVideoOffer) {
signalCallManager.insertMissedCall(remotePeer, true, timestamp, isVideoOffer);
}
boolean startWebRtcCallActivityIfPossible() {
return webRtcCallService.startCallCardActivityIfPossible();
return signalCallManager.startCallCardActivityIfPossible();
}
void registerPowerButtonReceiver() {
webRtcCallService.registerPowerButtonReceiver();
WebRtcCallService.changePowerButtonReceiver(context, true);
}
void unregisterPowerButtonReceiver() {
webRtcCallService.unregisterPowerButtonReceiver();
WebRtcCallService.changePowerButtonReceiver(context, false);
}
void silenceIncomingRinger() {
@@ -150,8 +142,8 @@ public class WebRtcInteractor {
audioManager.startIncomingRinger(ringtoneUri, vibrate);
}
void startOutgoingRinger(@NonNull OutgoingRinger.Type type) {
audioManager.startOutgoingRinger(type);
void startOutgoingRinger() {
audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING);
}
void stopAudio(boolean playDisconnect) {
@@ -163,6 +155,6 @@ public class WebRtcInteractor {
}
void peekGroupCall(@NonNull RecipientId recipientId) {
webRtcCallService.peekGroupCall(recipientId);
signalCallManager.peekGroupCall(recipientId);
}
}

View File

@@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.webrtc.locks.LockManager;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
/**
@@ -42,6 +43,27 @@ public final class WebRtcUtil {
return offerType == OfferMessage.Type.VIDEO_CALL ? CallManager.CallMediaType.VIDEO_CALL : CallManager.CallMediaType.AUDIO_CALL;
}
public static @NonNull OfferMessage.Type getOfferTypeFromCallMediaType(@NonNull CallManager.CallMediaType callMediaType) {
return callMediaType == CallManager.CallMediaType.VIDEO_CALL ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL;
}
public static @NonNull HangupMessage.Type getHangupTypeFromCallHangupType(@NonNull CallManager.HangupType hangupType) {
switch (hangupType) {
case ACCEPTED:
return HangupMessage.Type.ACCEPTED;
case BUSY:
return HangupMessage.Type.BUSY;
case NORMAL:
return HangupMessage.Type.NORMAL;
case DECLINED:
return HangupMessage.Type.DECLINED;
case NEED_PERMISSION:
return HangupMessage.Type.NEED_PERMISSION;
default:
throw new IllegalArgumentException("Unexpected hangup type: " + hangupType);
}
}
public static void enableSpeakerPhoneIfNeeded(@NonNull Context context, boolean enable) {
if (!enable) {
return;
@@ -49,7 +71,7 @@ public final class WebRtcUtil {
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
//noinspection deprecation
boolean shouldEnable = !(androidAudioManager.isSpeakerphoneOn() || androidAudioManager.isBluetoothScoOn() || androidAudioManager.isWiredHeadsetOn());
boolean shouldEnable = !(androidAudioManager.isSpeakerphoneOn() || androidAudioManager.isBluetoothScoOn() || androidAudioManager.isWiredHeadsetOn());
if (shouldEnable) {
androidAudioManager.setSpeakerphoneOn(true);