mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-20 00:29:11 +01:00
Move calling management out of service.
This commit is contained in:
committed by
Alex Hart
parent
d8dead82b6
commit
1dc3cf7824
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user