mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-08 08:28:34 +00:00
Call handling state machine refactor.
This commit is contained in:
parent
b3f0a44f10
commit
5eaac6cb17
@ -198,7 +198,7 @@ public final class CallParticipantsState {
|
||||
} else {
|
||||
localRenderState = WebRtcLocalRenderState.SMALL_RECTANGLE;
|
||||
}
|
||||
} else if (callState != WebRtcViewModel.State.CALL_DISCONNECTED) {
|
||||
} else if (callState != WebRtcViewModel.State.CALL_INCOMING && callState != WebRtcViewModel.State.CALL_DISCONNECTED) {
|
||||
localRenderState = WebRtcLocalRenderState.LARGE;
|
||||
}
|
||||
} else if (callState == WebRtcViewModel.State.CALL_PRE_JOIN) {
|
||||
|
@ -22,7 +22,6 @@ import androidx.constraintlayout.widget.Guideline;
|
||||
import androidx.core.util.Consumer;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.transition.AutoTransition;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
@ -553,6 +552,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
Transition transition = new AutoTransition().setOrdering(TransitionSet.ORDERING_TOGETHER)
|
||||
.setDuration(TRANSITION_DURATION_MILLIS);
|
||||
|
||||
TransitionManager.endTransitions(parent);
|
||||
TransitionManager.beginDelayedTransition(parent, transition);
|
||||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
@ -570,6 +570,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private void fadeInNewUiState(@NonNull Set<View> previouslyVisibleViewSet, boolean useSmallMargins) {
|
||||
Transition transition = new AutoTransition().setDuration(TRANSITION_DURATION_MILLIS);
|
||||
|
||||
TransitionManager.endTransitions(parent);
|
||||
TransitionManager.beginDelayedTransition(parent, transition);
|
||||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
|
@ -116,4 +116,16 @@ public class CallParticipant {
|
||||
public int hashCode() {
|
||||
return Objects.hash(cameraState, recipient, identityKey, videoSink, videoEnabled, microphoneEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String toString() {
|
||||
return "CallParticipant{" +
|
||||
"cameraState=" + cameraState +
|
||||
", recipient=" + recipient.getId() +
|
||||
", identityKey=" + (identityKey == null ? "absent" : "present") +
|
||||
", videoSink=" + (videoSink.getEglBase() == null ? "not initialized" : "initialized") +
|
||||
", videoEnabled=" + videoEnabled +
|
||||
", microphoneEnabled=" + microphoneEnabled +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.events;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
@ -13,6 +14,8 @@ import java.util.List;
|
||||
public class WebRtcViewModel {
|
||||
|
||||
public enum State {
|
||||
IDLE,
|
||||
|
||||
// Normal states
|
||||
CALL_PRE_JOIN,
|
||||
CALL_INCOMING,
|
||||
@ -48,7 +51,7 @@ public class WebRtcViewModel {
|
||||
public WebRtcViewModel(@NonNull State state,
|
||||
@NonNull Recipient recipient,
|
||||
@NonNull CameraState localCameraState,
|
||||
@NonNull BroadcastVideoSink localSink,
|
||||
@Nullable BroadcastVideoSink localSink,
|
||||
boolean isBluetoothAvailable,
|
||||
boolean isMicrophoneEnabled,
|
||||
boolean isRemoteVideoOffer,
|
||||
@ -62,7 +65,7 @@ public class WebRtcViewModel {
|
||||
this.callConnectedTime = callConnectedTime;
|
||||
this.remoteParticipants = remoteParticipants;
|
||||
|
||||
localParticipant = CallParticipant.createLocal(localCameraState, localSink, isMicrophoneEnabled);
|
||||
localParticipant = CallParticipant.createLocal(localCameraState, localSink != null ? localSink : new BroadcastVideoSink(null), isMicrophoneEnabled);
|
||||
}
|
||||
|
||||
public @NonNull State getState() {
|
||||
@ -97,4 +100,15 @@ public class WebRtcViewModel {
|
||||
return remoteParticipants;
|
||||
}
|
||||
|
||||
@Override public @NonNull String toString() {
|
||||
return "WebRtcViewModel{" +
|
||||
"state=" + state +
|
||||
", recipient=" + recipient.getId() +
|
||||
", isBluetoothAvailable=" + isBluetoothAvailable +
|
||||
", isRemoteVideoOffer=" + isRemoteVideoOffer +
|
||||
", callConnectedTime=" + callConnectedTime +
|
||||
", localParticipant=" + localParticipant +
|
||||
", remoteParticipants=" + remoteParticipants +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
private final int cameraCount;
|
||||
@NonNull private CameraState.Direction activeDirection;
|
||||
private boolean enabled;
|
||||
private boolean isInitialized;
|
||||
|
||||
public Camera(@NonNull Context context,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@ -80,6 +81,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
capturer.initialize(SurfaceTextureHelper.create("WebRTC-SurfaceTextureHelper", eglBase.getEglBaseContext()),
|
||||
context,
|
||||
observer);
|
||||
isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,6 +125,7 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
public void dispose() {
|
||||
if (capturer != null) {
|
||||
capturer.dispose();
|
||||
isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,6 +145,10 @@ public class Camera implements CameraControl, CameraVideoCapturer.CameraSwitchHa
|
||||
return capturer;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
private @Nullable CameraVideoCapturer createVideoCapturer(@NonNull CameraEnumerator enumerator,
|
||||
@NonNull CameraState.Direction direction)
|
||||
{
|
||||
|
@ -1,8 +1,11 @@
|
||||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class CameraState {
|
||||
public class CameraState implements Parcelable {
|
||||
|
||||
public static final CameraState UNKNOWN = new CameraState(Direction.NONE, 0);
|
||||
|
||||
@ -14,6 +17,10 @@ public class CameraState {
|
||||
this.cameraCount = cameraCount;
|
||||
}
|
||||
|
||||
private CameraState(Parcel in) {
|
||||
this(Direction.valueOf(in.readString()), in.readInt());
|
||||
}
|
||||
|
||||
public int getCameraCount() {
|
||||
return cameraCount;
|
||||
}
|
||||
@ -31,6 +38,17 @@ public class CameraState {
|
||||
return "count: " + cameraCount + ", activeDirection: " + activeDirection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(activeDirection.name());
|
||||
dest.writeInt(cameraCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
FRONT, BACK, NONE, PENDING;
|
||||
|
||||
@ -49,4 +67,16 @@ public class CameraState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final Creator<CameraState> CREATOR = new Creator<CameraState>() {
|
||||
@Override
|
||||
public CameraState createFromParcel(Parcel in) {
|
||||
return new CameraState(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CameraState[] newArray(int size) {
|
||||
return new CameraState[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallId;
|
||||
import org.signal.ringrtc.Remote;
|
||||
@ -84,7 +86,7 @@ public final class RemotePeer implements Remote, Parcelable
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean callIdEquals(RemotePeer remotePeer) {
|
||||
public boolean callIdEquals(@Nullable RemotePeer remotePeer) {
|
||||
return remotePeer != null && this.callId.equals(remotePeer.callId);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
package org.thoughtcrime.securesms.ringrtc;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wrap turn server info so it can be sent via an intent.
|
||||
*/
|
||||
public class TurnServerInfoParcel implements Parcelable {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final List<String> urls;
|
||||
|
||||
public TurnServerInfoParcel(@NonNull TurnServerInfo turnServerInfo) {
|
||||
urls = new ArrayList<>(turnServerInfo.getUrls());
|
||||
username = turnServerInfo.getUsername();
|
||||
password = turnServerInfo.getPassword();
|
||||
}
|
||||
|
||||
private TurnServerInfoParcel(@NonNull Parcel in) {
|
||||
username = in.readString();
|
||||
password = in.readString();
|
||||
urls = in.createStringArrayList();
|
||||
}
|
||||
|
||||
public @Nullable String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public @Nullable String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public @NonNull List<String> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(username);
|
||||
dest.writeString(password);
|
||||
dest.writeStringList(urls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<TurnServerInfoParcel> CREATOR = new Creator<TurnServerInfoParcel>() {
|
||||
@Override
|
||||
public TurnServerInfoParcel createFromParcel(Parcel in) {
|
||||
return new TurnServerInfoParcel(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TurnServerInfoParcel[] newArray(int size) {
|
||||
return new TurnServerInfoParcel[size];
|
||||
}
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,249 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.IceCandidate;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
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.util.Util;
|
||||
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.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;
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
||||
|
||||
/**
|
||||
* Encapsulates the shared logic to manage an active 1:1 call. An active call is any call that is being setup
|
||||
* or ongoing. Other action processors delegate the appropriate action to it but it is not intended
|
||||
* to be the main processor for the system.
|
||||
*/
|
||||
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);
|
||||
}};
|
||||
|
||||
public ActiveCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
super(webRtcInteractor, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
|
||||
if (resultReceiver != null) {
|
||||
resultReceiver.send(1, null);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
boolean broadcast,
|
||||
@NonNull ArrayList<IceCandidateParcel> 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()));
|
||||
}
|
||||
|
||||
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forIceUpdates(iceUpdateMessages, true, destinationDeviceId);
|
||||
|
||||
webRtcInteractor.sendCallMessage(callMetadata.getRemotePeer(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidateParcels)
|
||||
{
|
||||
Log.i(tag, "handleReceivedIceCandidates(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()) + ", count: " + iceCandidateParcels.size());
|
||||
|
||||
LinkedList<IceCandidate> iceCandidates = new LinkedList<>();
|
||||
for (IceCandidateParcel parcel : iceCandidateParcels) {
|
||||
iceCandidates.add(parcel.getIceCandidate());
|
||||
}
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedIceCandidates(callMetadata.getCallId(), callMetadata.getRemoteDevice(), iceCandidates);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "receivedIceCandidates() failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
|
||||
Log.i(tag, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId());
|
||||
|
||||
CallParticipant oldParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant newParticipant = oldParticipant.withVideoEnabled(enable);
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.putParticipant(activePeer.getRecipient(), newParticipant)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleLocalHangup(): call_id: " + currentState.getCallInfoState().requireActivePeer().getCallId());
|
||||
|
||||
ApplicationDependencies.getSignalServiceAccountManager().cancelInFlightRequests();
|
||||
ApplicationDependencies.getSignalServiceMessageSender().cancelInFlightRequests();
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().hangup();
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return terminate(currentState, currentState.getCallInfoState().getActivePeer());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "hangup() failed: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleCallConcluded():");
|
||||
Log.i(tag, "delete remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode());
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.removeRemotePeer(remotePeer)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
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 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();
|
||||
}
|
||||
|
||||
if (activePeer.getState() == CallState.IDLE) {
|
||||
webRtcInteractor.stopForegroundService();
|
||||
}
|
||||
|
||||
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp());
|
||||
|
||||
return terminate(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull String action,
|
||||
@NonNull RemotePeer remotePeer)
|
||||
{
|
||||
Log.i(tag, "handleEndedRemote(): call_id: " + remotePeer.getCallId() + " action: " + action);
|
||||
|
||||
WebRtcViewModel.State state = currentState.getCallInfoState().getCallState();
|
||||
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
|
||||
boolean remotePeerIsActive = remotePeer.callIdEquals(activePeer);
|
||||
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 (action.equals(ACTION_ENDED_REMOTE_HANGUP)) {
|
||||
if (remotePeerIsActive) {
|
||||
state = outgoingBeforeAccept ? WebRtcViewModel.State.RECIPIENT_UNAVAILABLE : WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||
}
|
||||
|
||||
if (incomingBeforeAccept) {
|
||||
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp());
|
||||
}
|
||||
} else if (action.equals(ACTION_ENDED_REMOTE_BUSY) && remotePeerIsActive) {
|
||||
activePeer.receivedBusy();
|
||||
|
||||
OutgoingRinger ringer = new OutgoingRinger(context);
|
||||
ringer.start(OutgoingRinger.Type.BUSY);
|
||||
Util.runOnMainDelayed(ringer::stop, BUSY_TONE_LENGTH);
|
||||
} else if (action.equals(ACTION_ENDED_REMOTE_GLARE) && incomingBeforeAccept) {
|
||||
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp());
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(state)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(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);
|
||||
|
||||
if (remotePeer.callIdEquals(currentState.getCallInfoState().getActivePeer())) {
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
}
|
||||
|
||||
if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) {
|
||||
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp());
|
||||
}
|
||||
|
||||
return terminate(currentState, remotePeer);
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_CONNECTING;
|
||||
|
||||
/**
|
||||
* Encapsulates the logic to begin a 1:1 call from scratch. Other action processors
|
||||
* delegate the appropriate action to it but it is not intended to be the main processor for the system.
|
||||
*/
|
||||
public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor {
|
||||
|
||||
public BeginCallActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
super(webRtcInteractor, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull OfferMessage.Type offerType)
|
||||
{
|
||||
remotePeer.setCallStartTimestamp(System.currentTimeMillis());
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(new OutgoingCallActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callRecipient(remotePeer.getRecipient())
|
||||
.callState(WebRtcViewModel.State.CALL_OUTGOING)
|
||||
.putRemotePeer(remotePeer)
|
||||
.putParticipant(remotePeer.getRecipient(),
|
||||
CallParticipant.createRemote(
|
||||
remotePeer.getRecipient(),
|
||||
null,
|
||||
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
|
||||
false
|
||||
))
|
||||
.build();
|
||||
|
||||
CallManager.CallMediaType callMediaType = WebRtcUtil.getCallMediaTypeFromOfferType(offerType);
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().call(remotePeer, callMediaType, 1);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Unable to create outgoing call: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleStartIncomingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
remotePeer.answering();
|
||||
|
||||
Log.i(tag, "assign activePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode());
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, remotePeer);
|
||||
webRtcInteractor.retrieveTurnServers(remotePeer);
|
||||
|
||||
return currentState.builder()
|
||||
.actionProcessor(new IncomingCallActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callRecipient(remotePeer.getRecipient())
|
||||
.activePeer(remotePeer)
|
||||
.callState(WebRtcViewModel.State.CALL_INCOMING)
|
||||
.putParticipant(remotePeer.getRecipient(),
|
||||
CallParticipant.createRemote(
|
||||
remotePeer.getRecipient(),
|
||||
null,
|
||||
new BroadcastVideoSink(currentState.getVideoState().getEglBase()),
|
||||
false
|
||||
))
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||
|
||||
/**
|
||||
* Encapsulates the shared logic to setup a 1:1 call. Setup primarily includes retrieving turn servers and
|
||||
* transitioning to the connected state. Other action processors delegate the appropriate action to it but it is
|
||||
* not intended to be the main processor for the system.
|
||||
*/
|
||||
public class CallSetupActionProcessorDelegate extends WebRtcActionProcessor {
|
||||
|
||||
public CallSetupActionProcessorDelegate(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
super(webRtcInteractor, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleCallConnected(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
if (!remotePeer.callIdEquals(currentState.getCallInfoState().getActivePeer())) {
|
||||
Log.w(tag, "handleCallConnected(): Ignoring for inactive call.");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Log.i(tag, "handleCallConnected(): call_id: " + remotePeer.getCallId());
|
||||
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
|
||||
webRtcInteractor.startAudioCommunication(activePeer.getState() == CallState.REMOTE_RINGING);
|
||||
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||
|
||||
activePeer.connected();
|
||||
|
||||
if (currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
||||
} else {
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(new ConnectedCallActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_CONNECTED)
|
||||
.callConnectedTime(System.currentTimeMillis())
|
||||
.build();
|
||||
|
||||
webRtcInteractor.unregisterPowerButtonReceiver();
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_ESTABLISHED, activePeer);
|
||||
|
||||
try {
|
||||
CallManager callManager = webRtcInteractor.getCallManager();
|
||||
callManager.setCommunicationMode();
|
||||
callManager.setAudioEnable(currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
callManager.setVideoEnable(currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Enabling audio/video failed: ", e);
|
||||
}
|
||||
|
||||
if (currentState.getCallSetupState().isAcceptWithVideo()) {
|
||||
currentState = currentState.getActionProcessor().handleSetEnableVideo(currentState, true);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
|
||||
if (camera.isInitialized()) {
|
||||
camera.setEnabled(enable);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.enableVideoOnCreate(enable)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
|
||||
WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
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.locks.LockManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Handles action for a connected/ongoing call. At this point it's mostly responding
|
||||
* to user actions (local and remote) on video/mic and adjusting accordingly.
|
||||
*/
|
||||
public class ConnectedCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(ConnectedCallActionProcessor.class);
|
||||
|
||||
private final ActiveCallActionProcessorDelegate activeCallDelegate;
|
||||
|
||||
public ConnectedCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
activeCallDelegate = new ActiveCallActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
|
||||
return activeCallDelegate.handleIsInCallQuery(currentState, resultReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.i(TAG, "handleSetEnableVideo(): call_id: " + currentState.getCallInfoState().requireActivePeer().getCallId());
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().setVideoEnable(enable);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "setVideoEnable() failed: ", e);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(currentState.getVideoState().requireCamera().getCameraState())
|
||||
.build();
|
||||
|
||||
if (currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IN_VIDEO);
|
||||
} else {
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
}
|
||||
|
||||
WebRtcUtil.enableSpeakerPhoneIfNeeded(context, currentState.getLocalDeviceState().getCameraState().isEnabled());
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
currentState = currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().setAudioEnable(currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Enabling audio failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
return activeCallDelegate.handleRemoteVideoEnable(currentState, enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
boolean broadcast,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
|
||||
{
|
||||
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidateParcels)
|
||||
{
|
||||
return activeCallDelegate.handleReceivedIceCandidates(currentState, callMetadata, iceCandidateParcels);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||
return activeCallDelegate.handleLocalHangup(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleReceivedOfferWhileActive(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleCallConcluded(currentState, remotePeer);
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
|
||||
/**
|
||||
* Encapsulates the shared logic to deal with local device actions. Other action processors inherit
|
||||
* the behavior by extending it instead of delegating. It is not intended to be the main processor
|
||||
* for the system.
|
||||
*/
|
||||
public abstract class DeviceAwareActionProcessor extends WebRtcActionProcessor {
|
||||
|
||||
public DeviceAwareActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
super(webRtcInteractor, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleWiredHeadsetChange(@NonNull WebRtcServiceState currentState, boolean present) {
|
||||
Log.i(tag, "handleWiredHeadsetChange():");
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
if (present && androidAudioManager.isSpeakerphoneOn()) {
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
androidAudioManager.setBluetoothScoOn(false);
|
||||
} else if (!present && !androidAudioManager.isSpeakerphoneOn() && !androidAudioManager.isBluetoothScoOn() && currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
androidAudioManager.setSpeakerphoneOn(true);
|
||||
}
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleBluetoothChange(@NonNull WebRtcServiceState currentState, boolean available) {
|
||||
Log.i(tag, "handleBluetoothChange(): " + available);
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isBluetoothAvailable(available)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetSpeakerAudio(@NonNull WebRtcServiceState currentState, boolean isSpeaker) {
|
||||
Log.i(tag, "handleSetSpeakerAudio(): " + isSpeaker);
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
androidAudioManager.setSpeakerphoneOn(isSpeaker);
|
||||
|
||||
if (!currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
}
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetBluetoothAudio(@NonNull WebRtcServiceState currentState, boolean isBluetooth) {
|
||||
Log.i(tag, "handleSetBluetoothAudio(): " + isBluetooth);
|
||||
|
||||
webRtcInteractor.setWantsBluetoothConnection(isBluetooth);
|
||||
|
||||
if (!currentState.getLocalDeviceState().getCameraState().isEnabled()) {
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
}
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetCameraFlip(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleSetCameraFlip():");
|
||||
|
||||
if (currentState.getLocalDeviceState().getCameraState().isEnabled() && currentState.getVideoState().getCamera() != null) {
|
||||
currentState.getVideoState().getCamera().flip();
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(currentState.getVideoState().getCamera().getCameraState())
|
||||
.build();
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleCameraSwitchCompleted(@NonNull WebRtcServiceState currentState, @NonNull CameraState newCameraState) {
|
||||
Log.i(tag, "handleCameraSwitchCompleted():");
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(newCameraState)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
/**
|
||||
* Handles disconnecting state actions. This primairly entails dealing with final
|
||||
* clean up in the call concluded action, but also allows for transitioning into idle/setup
|
||||
* via beginning an outgoing or incoming call.
|
||||
*/
|
||||
public class DisconnectingCallActionProcessor extends WebRtcActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(DisconnectingCallActionProcessor.class);
|
||||
|
||||
public DisconnectingCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleStartIncomingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleStartIncomingCall():");
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(new IdleActionProcessor(webRtcInteractor))
|
||||
.build();
|
||||
return currentState.getActionProcessor().handleStartIncomingCall(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer, @NonNull OfferMessage.Type offerType) {
|
||||
Log.i(TAG, "handleOutgoingCall():");
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(new IdleActionProcessor(webRtcInteractor))
|
||||
.build();
|
||||
return currentState.getActionProcessor().handleOutgoingCall(currentState, remotePeer, offerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleCallConcluded():");
|
||||
Log.i(TAG, "delete remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode());
|
||||
return currentState.builder()
|
||||
.actionProcessor(new IdleActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.removeRemotePeer(remotePeer)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.webrtc.CapturerObserver;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Action handler for when the system is at rest. Mainly responsible
|
||||
* for starting pre-call state, starting an outgoing call, or receiving an
|
||||
* incoming call.
|
||||
*/
|
||||
public class IdleActionProcessor extends WebRtcActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(IdleActionProcessor.class);
|
||||
|
||||
private final BeginCallActionProcessorDelegate beginCallDelegate;
|
||||
|
||||
public IdleActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
beginCallDelegate = new BeginCallActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleStartIncomingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleStartIncomingCall():");
|
||||
|
||||
currentState = WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
return beginCallDelegate.handleStartIncomingCall(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull OfferMessage.Type offerType)
|
||||
{
|
||||
Log.i(TAG, "handleOutgoingCall():");
|
||||
|
||||
currentState = WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
return beginCallDelegate.handleOutgoingCall(currentState, remotePeer, offerType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handlePreJoinCall():");
|
||||
|
||||
WebRtcServiceState newState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
|
||||
|
||||
return newState.builder()
|
||||
.actionProcessor(new PreJoinActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
|
||||
.callRecipient(remotePeer.getRecipient())
|
||||
.build();
|
||||
}
|
||||
|
||||
private @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
BroadcastVideoSink sink = currentState.getVideoState().requireLocalSink();
|
||||
|
||||
if (camera.hasCapturer()) {
|
||||
camera.initCapturer(new CapturerObserver() {
|
||||
@Override
|
||||
public void onFrameCaptured(VideoFrame videoFrame) {
|
||||
sink.onFrame(videoFrame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCapturerStarted(boolean success) {}
|
||||
|
||||
@Override
|
||||
public void onCapturerStopped() {}
|
||||
});
|
||||
camera.setEnabled(true);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
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;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
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;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
|
||||
|
||||
/**
|
||||
* Responsible for setting up and managing the start of an incoming 1:1 call. Transitioned
|
||||
* to from idle or pre-join and can either move to a connected state (user picks up) or
|
||||
* a disconnected state (remote hangup, local hangup, etc.).
|
||||
*/
|
||||
public class IncomingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(IncomingCallActionProcessor.class);
|
||||
|
||||
private final ActiveCallActionProcessorDelegate activeCallDelegate;
|
||||
private final CallSetupActionProcessorDelegate callSetupDelegate;
|
||||
|
||||
public IncomingCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
activeCallDelegate = new ActiveCallActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
callSetupDelegate = new CallSetupActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
|
||||
return activeCallDelegate.handleIsInCallQuery(currentState, resultReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendAnswer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull WebRtcData.AnswerMetadata answerMetadata,
|
||||
boolean broadcast)
|
||||
{
|
||||
Log.i(TAG, "handleSendAnswer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
AnswerMessage answerMessage = new AnswerMessage(callMetadata.getCallId().longValue(), answerMetadata.getSdp(), answerMetadata.getOpaque());
|
||||
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forAnswer(answerMessage, true, destinationDeviceId);
|
||||
|
||||
webRtcInteractor.sendCallMessage(callMetadata.getRemotePeer(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleTurnServerUpdate(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull List<PeerConnection.IceServer> iceServers,
|
||||
boolean isAlwaysTurn)
|
||||
{
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
boolean hideIp = !activePeer.getRecipient().isSystemContact() || isAlwaysTurn;
|
||||
VideoState videoState = currentState.getVideoState();
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||
context,
|
||||
videoState.requireEglBase(),
|
||||
videoState.requireLocalSink(),
|
||||
callParticipant.getVideoSink(),
|
||||
videoState.requireCamera(),
|
||||
iceServers,
|
||||
hideIp,
|
||||
false);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Unable to proceed with call: ", e);
|
||||
}
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleAcceptCall(@NonNull WebRtcServiceState currentState, boolean answerWithVideo) {
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
|
||||
Log.i(TAG, "handleAcceptCall(): call_id: " + activePeer.getCallId());
|
||||
|
||||
DatabaseFactory.getSmsDatabase(context).insertReceivedCall(activePeer.getId());
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.acceptWithVideo(answerWithVideo)
|
||||
.build();
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().acceptCall(activePeer.getCallId());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "accept() failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleDenyCall(@NonNull WebRtcServiceState currentState) {
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
|
||||
if (activePeer.getState() != CallState.LOCAL_RINGING) {
|
||||
Log.w(TAG, "Can only deny from ringing!");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Log.i(TAG, "handleDenyCall():");
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().hangup();
|
||||
DatabaseFactory.getSmsDatabase(context).insertMissedCall(activePeer.getId(), System.currentTimeMillis());
|
||||
return terminate(currentState, activePeer);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "hangup() failed: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleLocalRinging(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleLocalRinging(): call_id: " + remotePeer.getCallId());
|
||||
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
Recipient recipient = remotePeer.getRecipient();
|
||||
|
||||
activePeer.localRinging();
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
|
||||
boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext(), recipient);
|
||||
if (shouldDisturbUserWithCall) {
|
||||
webRtcInteractor.startWebRtcCallActivityIfPossible();
|
||||
}
|
||||
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
if (shouldDisturbUserWithCall && TextSecurePreferences.isCallNotificationsEnabled(context)) {
|
||||
Uri ringtone = recipient.resolve().getCallRingtone();
|
||||
RecipientDatabase.VibrateState vibrateState = recipient.resolve().getCallVibrate();
|
||||
|
||||
if (ringtone == null) {
|
||||
ringtone = TextSecurePreferences.getCallNotificationRingtone(context);
|
||||
}
|
||||
|
||||
webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientDatabase.VibrateState.ENABLED || (vibrateState == RecipientDatabase.VibrateState.DEFAULT && TextSecurePreferences.isCallNotificationVibrateEnabled(context)));
|
||||
}
|
||||
|
||||
webRtcInteractor.registerPowerButtonReceiver();
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer);
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_INCOMING)
|
||||
.build();
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleScreenOffChange(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(TAG, "Silencing incoming ringer...");
|
||||
|
||||
webRtcInteractor.silenceIncomingRinger();
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
return activeCallDelegate.handleRemoteVideoEnable(currentState, enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleReceivedOfferWhileActive(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleCallConcluded(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
boolean broadcast,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
|
||||
{
|
||||
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidateParcels)
|
||||
{
|
||||
return activeCallDelegate.handleReceivedIceCandidates(currentState, callMetadata, iceCandidateParcels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleCallConnected(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return callSetupDelegate.handleCallConnected(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
return callSetupDelegate.handleSetEnableVideo(currentState, enable);
|
||||
}
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.media.AudioManager;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
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.logging.Log;
|
||||
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;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
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;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_OUTGOING_RINGING;
|
||||
|
||||
/**
|
||||
* Responsible for setting up and managing the start of an outgoing 1:1 call. Transitioned
|
||||
* to from idle or pre-join and can either move to a connected state (callee picks up) or
|
||||
* a disconnected state (remote hangup, local hangup, etc.).
|
||||
*/
|
||||
public class OutgoingCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(OutgoingCallActionProcessor.class);
|
||||
|
||||
private final ActiveCallActionProcessorDelegate activeCallDelegate;
|
||||
private final CallSetupActionProcessorDelegate callSetupDelegate;
|
||||
|
||||
public OutgoingCallActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
activeCallDelegate = new ActiveCallActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
callSetupDelegate = new CallSetupActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
|
||||
return activeCallDelegate.handleIsInCallQuery(currentState, resultReceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleStartOutgoingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleStartOutgoingCall():");
|
||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
remotePeer.dialing();
|
||||
|
||||
Log.i(TAG, "assign activePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode());
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
webRtcInteractor.startOutgoingRinger(OutgoingRinger.Type.RINGING);
|
||||
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_OUTGOING_RINGING, remotePeer);
|
||||
|
||||
DatabaseFactory.getSmsDatabase(context).insertOutgoingCall(remotePeer.getId());
|
||||
|
||||
webRtcInteractor.retrieveTurnServers(remotePeer);
|
||||
|
||||
return builder.changeCallInfoState()
|
||||
.activePeer(remotePeer)
|
||||
.callState(WebRtcViewModel.State.CALL_OUTGOING)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendOffer(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull OfferMetadata offerMetadata, boolean broadcast) {
|
||||
Log.i(TAG, "handleSendOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
OfferMessage offerMessage = new OfferMessage(callMetadata.getCallId().longValue(), offerMetadata.getSdp(), offerMetadata.getOfferType(), offerMetadata.getOpaque());
|
||||
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOffer(offerMessage, true, destinationDeviceId);
|
||||
|
||||
webRtcInteractor.sendCallMessage(callMetadata.getRemotePeer(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleTurnServerUpdate(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull List<PeerConnection.IceServer> iceServers,
|
||||
boolean isAlwaysTurn)
|
||||
{
|
||||
try {
|
||||
VideoState videoState = currentState.getVideoState();
|
||||
RemotePeer activePeer = currentState.getCallInfoState().requireActivePeer();
|
||||
CallParticipant callParticipant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
|
||||
webRtcInteractor.getCallManager().proceed(activePeer.getCallId(),
|
||||
context,
|
||||
videoState.requireEglBase(),
|
||||
videoState.requireLocalSink(),
|
||||
callParticipant.getVideoSink(),
|
||||
videoState.requireCamera(),
|
||||
iceServers,
|
||||
isAlwaysTurn,
|
||||
currentState.getCallSetupState().isEnableVideoOnCreate());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "Unable to proceed with call: ", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(currentState.getVideoState().requireCamera().getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleRemoteRinging(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleRemoteRinging(): call_id: " + remotePeer.getCallId());
|
||||
|
||||
currentState.getCallInfoState().requireActivePeer().remoteRinging();
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_RINGING)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedAnswer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull WebRtcData.AnswerMetadata answerMetadata,
|
||||
@NonNull WebRtcData.ReceivedAnswerMetadata receivedAnswerMetadata)
|
||||
{
|
||||
Log.i(TAG, "handleReceivedAnswer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
try {
|
||||
byte[] remoteIdentityKey = WebRtcUtil.getPublicKeyBytes(receivedAnswerMetadata.getRemoteIdentityKey());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(context).serialize());
|
||||
|
||||
webRtcInteractor.getCallManager().receivedAnswer(callMetadata.getCallId(), callMetadata.getRemoteDevice(), answerMetadata.getOpaque(), answerMetadata.getSdp(), receivedAnswerMetadata.isMultiRing(), remoteIdentityKey, localIdentityKey);
|
||||
} catch (CallException | InvalidKeyException e) {
|
||||
return callFailure(currentState, "receivedAnswer() failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedBusy(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata) {
|
||||
Log.i(TAG, "handleReceivedBusy(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedBusy(callMetadata.getCallId(), callMetadata.getRemoteDevice());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "receivedBusy() failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
return activeCallDelegate.handleRemoteVideoEnable(currentState, enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||
return activeCallDelegate.handleLocalHangup(currentState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleReceivedOfferWhileActive(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEndedRemote(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleEnded(currentState, action, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return activeCallDelegate.handleCallConcluded(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
boolean broadcast,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidates)
|
||||
{
|
||||
return activeCallDelegate.handleSendIceCandidates(currentState, callMetadata, broadcast, iceCandidates);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedIceCandidates(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull ArrayList<IceCandidateParcel> iceCandidateParcels)
|
||||
{
|
||||
return activeCallDelegate.handleReceivedIceCandidates(currentState, callMetadata, iceCandidateParcels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull WebRtcServiceState handleCallConnected(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
return callSetupDelegate.handleCallConnected(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
return callSetupDelegate.handleSetEnableVideo(currentState, enable);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
/**
|
||||
* Handles pre-join call actions. This serves as a more capable idle state as no
|
||||
* call has actually start so incoming and outgoing calls are allowed.
|
||||
*/
|
||||
public class PreJoinActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(PreJoinActionProcessor.class);
|
||||
|
||||
private final BeginCallActionProcessorDelegate beginCallDelegate;
|
||||
|
||||
public PreJoinActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
beginCallDelegate = new BeginCallActionProcessorDelegate(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleCancelPreJoinCall(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(TAG, "handleCancelPreJoinCall():");
|
||||
|
||||
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||
|
||||
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleStartIncomingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(TAG, "handleStartIncomingCall():");
|
||||
|
||||
currentState = WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState)
|
||||
.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_INCOMING)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.sendMessage(currentState);
|
||||
return beginCallDelegate.handleStartIncomingCall(currentState, remotePeer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull OfferMessage.Type offerType)
|
||||
{
|
||||
Log.i(TAG, "handleOutgoingCall():");
|
||||
currentState = WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
return beginCallDelegate.handleOutgoingCall(currentState, remotePeer, offerType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.w(TAG, "handleSetEnableVideo(): Changing for pre-join call.");
|
||||
|
||||
currentState.getVideoState().getCamera().setEnabled(enable);
|
||||
return currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.enableVideoOnCreate(enable)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(currentState.getVideoState().getCamera().getCameraState())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
return currentState.builder()
|
||||
.changeLocalDeviceState()
|
||||
.isMicrophoneEnabled(!muted)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,639 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallId;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||
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.OfferMetadata;
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedOfferMetadata;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.thoughtcrime.securesms.util.TelephonyUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.webrtc.PeerConnection;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.calls.BusyMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_ACCEPT_CALL;
|
||||
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_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_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_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_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_CAMERA_STATE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_ERROR;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_IS_ALWAYS_TURN;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_MUTE;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_RESULT_RECEIVER;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SPEAKER;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.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.getEnable;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceCandidates;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getIceServers;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcIntentParser.getOfferMessageType;
|
||||
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.
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
public abstract class WebRtcActionProcessor {
|
||||
|
||||
protected final Context context;
|
||||
protected final WebRtcInteractor webRtcInteractor;
|
||||
protected final String tag;
|
||||
|
||||
public WebRtcActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull String tag) {
|
||||
this.context = webRtcInteractor.getWebRtcCallService();
|
||||
this.webRtcInteractor = webRtcInteractor;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public @NonNull String getTag() {
|
||||
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, getRemotePeerFromMap(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), (Throwable) intent.getSerializableExtra(EXTRA_ERROR));
|
||||
|
||||
// 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, intent.getParcelableExtra(EXTRA_CAMERA_STATE));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleIsInCallQuery(@NonNull WebRtcServiceState currentState, @Nullable ResultReceiver resultReceiver) {
|
||||
if (resultReceiver != null) {
|
||||
resultReceiver.send(0, null);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//region Pre-Join
|
||||
|
||||
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handlePreJoinCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleCancelPreJoinCall(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleCancelPreJoinCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion Pre-Join
|
||||
|
||||
//region Outgoing Call
|
||||
|
||||
protected @NonNull WebRtcServiceState handleOutgoingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer, @NonNull OfferMessage.Type offerType) {
|
||||
Log.i(tag, "handleOutgoingCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleStartOutgoingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleStartOutgoingCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendOffer(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, @NonNull OfferMetadata offerMetadata, boolean broadcast) {
|
||||
Log.i(tag, "handleSendOffer not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleRemoteRinging(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleRemoteRinging not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedAnswer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull AnswerMetadata answerMetadata,
|
||||
@NonNull ReceivedAnswerMetadata receivedAnswerMetadata)
|
||||
{
|
||||
Log.i(tag, "handleReceivedAnswer not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedBusy(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata) {
|
||||
Log.i(tag, "handleReceivedBusy not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion Outgoing call
|
||||
|
||||
//region Incoming call
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOffer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull WebRtcData.CallMetadata callMetadata,
|
||||
@NonNull WebRtcData.OfferMetadata offerMetadata,
|
||||
@NonNull ReceivedOfferMetadata receivedOfferMetadata)
|
||||
{
|
||||
Log.i(tag, "handleReceivedOffer(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
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());
|
||||
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());
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Log.i(tag, "add remotePeer callId: " + callMetadata.getRemotePeer().getCallId() + " key: " + callMetadata.getRemotePeer().hashCode());
|
||||
|
||||
callMetadata.getRemotePeer().setCallStartTimestamp(receivedOfferMetadata.getServerReceivedTimestamp());
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.isRemoteVideoOffer(offerMetadata.getOfferType() == OfferMessage.Type.VIDEO_CALL)
|
||||
.commit()
|
||||
.changeCallInfoState()
|
||||
.putRemotePeer(callMetadata.getRemotePeer())
|
||||
.build();
|
||||
|
||||
long messageAgeSec = Math.max(receivedOfferMetadata.getServerDeliveredTimestamp() - receivedOfferMetadata.getServerReceivedTimestamp(), 0) / 1000;
|
||||
Log.i(tag, "messageAgeSec: " + messageAgeSec + ", serverReceivedTimestamp: " + receivedOfferMetadata.getServerReceivedTimestamp() + ", serverDeliveredTimestamp: " + receivedOfferMetadata.getServerDeliveredTimestamp());
|
||||
|
||||
try {
|
||||
byte[] remoteIdentityKey = WebRtcUtil.getPublicKeyBytes(receivedOfferMetadata.getRemoteIdentityKey());
|
||||
byte[] localIdentityKey = WebRtcUtil.getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(context).serialize());
|
||||
|
||||
webRtcInteractor.getCallManager().receivedOffer(callMetadata.getCallId(),
|
||||
callMetadata.getRemotePeer(),
|
||||
callMetadata.getRemoteDevice(),
|
||||
offerMetadata.getOpaque(),
|
||||
offerMetadata.getSdp(),
|
||||
messageAgeSec,
|
||||
WebRtcUtil.getCallMediaTypeFromOfferType(offerMetadata.getOfferType()),
|
||||
1,
|
||||
receivedOfferMetadata.isMultiRing(),
|
||||
true,
|
||||
remoteIdentityKey,
|
||||
localIdentityKey);
|
||||
} catch (CallException | InvalidKeyException e) {
|
||||
return callFailure(currentState, "Unable to process received offer: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOfferExpired(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer)
|
||||
{
|
||||
Log.i(tag, "handleReceivedOfferExpired(): call_id: " + remotePeer.getCallId());
|
||||
|
||||
webRtcInteractor.insertMissedCall(remotePeer, true, remotePeer.getCallStartTimestamp());
|
||||
|
||||
return terminate(currentState, remotePeer);
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleStartIncomingCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleStartIncomingCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleAcceptCall(@NonNull WebRtcServiceState currentState, boolean answerWithVideo) {
|
||||
Log.i(tag, "handleAcceptCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleLocalRinging(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleLocalRinging not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleDenyCall(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleDenyCall not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendAnswer(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull AnswerMetadata answerMetadata,
|
||||
boolean broadcast)
|
||||
{
|
||||
Log.i(tag, "handleSendAnswer not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion Incoming call
|
||||
|
||||
//region Active call
|
||||
|
||||
protected @NonNull WebRtcServiceState handleCallConnected(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleCallConnected not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOfferWhileActive(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleReceivedOfferWhileActive not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendBusy(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.CallMetadata callMetadata, boolean broadcast) {
|
||||
Log.i(tag, "handleSendBusy(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
BusyMessage busyMessage = new BusyMessage(callMetadata.getCallId().longValue());
|
||||
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forBusy(busyMessage, true, destinationDeviceId);
|
||||
|
||||
webRtcInteractor.sendCallMessage(callMetadata.getRemotePeer(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleCallConcluded(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleCallConcluded not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleRemoteVideoEnable(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.i(tag, "handleRemoteVideoEnable not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedHangup(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull HangupMetadata hangupMetadata)
|
||||
{
|
||||
Log.i(tag, "handleReceivedHangup(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedHangup(callMetadata.getCallId(), callMetadata.getRemoteDevice(), hangupMetadata.getCallHangupType(), hangupMetadata.getDeviceId());
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "receivedHangup() failed: ", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleLocalHangup(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleLocalHangup not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendHangup(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull CallMetadata callMetadata,
|
||||
@NonNull HangupMetadata hangupMetadata,
|
||||
boolean broadcast)
|
||||
{
|
||||
Log.i(tag, "handleSendHangup(): id: " + callMetadata.getCallId().format(callMetadata.getRemoteDevice()));
|
||||
|
||||
HangupMessage hangupMessage = new HangupMessage(callMetadata.getCallId().longValue(), hangupMetadata.getType(), hangupMetadata.getDeviceId(), hangupMetadata.isLegacy());
|
||||
Integer destinationDeviceId = broadcast ? null : callMetadata.getRemoteDevice();
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forHangup(hangupMessage, true, destinationDeviceId);
|
||||
|
||||
webRtcInteractor.sendCallMessage(callMetadata.getRemotePeer(), callMessage);
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleMessageSentSuccess(@NonNull WebRtcServiceState currentState, @NonNull CallId callId) {
|
||||
try {
|
||||
webRtcInteractor.getCallManager().messageSent(callId);
|
||||
} catch (CallException e) {
|
||||
return callFailure(currentState, "callManager.messageSent() failed: ", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleMessageSentError(@NonNull WebRtcServiceState currentState, @NonNull CallId callId, @Nullable Throwable error) {
|
||||
Log.w(tag, error);
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().messageSendFailure(callId);
|
||||
} catch (CallException e) {
|
||||
currentState = callFailure(currentState, "callManager.messageSendFailure() failed: ", e);
|
||||
}
|
||||
|
||||
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
|
||||
if (activePeer == null) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
if (error instanceof UntrustedIdentityException) {
|
||||
CallParticipant participant = Objects.requireNonNull(currentState.getCallInfoState().getRemoteParticipant(activePeer.getRecipient()));
|
||||
CallParticipant untrusted = participant.withIdentityKey(((UntrustedIdentityException) error).getIdentityKey());
|
||||
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.UNTRUSTED_IDENTITY)
|
||||
.putParticipant(activePeer.getRecipient(), untrusted)
|
||||
.commit();
|
||||
} else if (error instanceof UnregisteredUserException) {
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.NO_SUCH_USER)
|
||||
.commit();
|
||||
} else if (error instanceof IOException) {
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
|
||||
.commit();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
//endregion Active call
|
||||
|
||||
//region Call setup
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSendIceCandidates(@NonNull WebRtcServiceState currentState, @NonNull CallMetadata callMetadata, boolean broadcast, @NonNull ArrayList<IceCandidateParcel> 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 not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState handleTurnServerUpdate(@NonNull WebRtcServiceState currentState, @NonNull List<PeerConnection.IceServer> iceServers, boolean isAlwaysTurn) {
|
||||
Log.i(tag, "handleTurnServerUpdate not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion Call setup
|
||||
|
||||
//region Local device
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetEnableVideo(@NonNull WebRtcServiceState currentState, boolean enable) {
|
||||
Log.i(tag, "handleSetEnableVideo not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetMuteAudio(@NonNull WebRtcServiceState currentState, boolean muted) {
|
||||
Log.i(tag, "handleSetMuteAudio not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetSpeakerAudio(@NonNull WebRtcServiceState currentState, boolean isSpeaker) {
|
||||
Log.i(tag, "handleSetSpeakerAudio not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetBluetoothAudio(@NonNull WebRtcServiceState currentState, boolean isBluetooth) {
|
||||
Log.i(tag, "handleSetBluetoothAudio not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetCameraFlip(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleSetCameraFlip not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleScreenOffChange(@NonNull WebRtcServiceState currentState) {
|
||||
Log.i(tag, "handleScreenOffChange not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleBluetoothChange(@NonNull WebRtcServiceState currentState, boolean available) {
|
||||
Log.i(tag, "handleBluetoothChange not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleWiredHeadsetChange(@NonNull WebRtcServiceState currentState, boolean present) {
|
||||
Log.i(tag, "handleWiredHeadsetChange not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState handleCameraSwitchCompleted(@NonNull WebRtcServiceState currentState, @NonNull CameraState newCameraState) {
|
||||
Log.i(tag, "handleCameraSwitchCompleted not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion Local device
|
||||
|
||||
//region End call
|
||||
|
||||
protected @NonNull WebRtcServiceState handleEndedRemote(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleEndedRemote not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion End call
|
||||
|
||||
//region End call failure
|
||||
|
||||
protected @NonNull WebRtcServiceState handleEnded(@NonNull WebRtcServiceState currentState, @NonNull String action, @NonNull RemotePeer remotePeer) {
|
||||
Log.i(tag, "handleEnded not processed");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
//region Global call operations
|
||||
|
||||
public @NonNull WebRtcServiceState callFailure(@NonNull WebRtcServiceState currentState,
|
||||
@Nullable String message,
|
||||
@Nullable Throwable error)
|
||||
{
|
||||
Log.w(tag, "callFailure(): " + message, error);
|
||||
|
||||
WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
if (currentState.getCallInfoState().getActivePeer() != null) {
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED);
|
||||
}
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().reset();
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to reset call manager: ", e);
|
||||
}
|
||||
|
||||
currentState = builder.changeCallInfoState().clearPeerMap().build();
|
||||
return terminate(currentState, currentState.getCallInfoState().getActivePeer());
|
||||
}
|
||||
|
||||
public synchronized @NonNull WebRtcServiceState terminate(@NonNull WebRtcServiceState currentState, @Nullable RemotePeer remotePeer) {
|
||||
Log.i(tag, "terminate():");
|
||||
|
||||
RemotePeer activePeer = currentState.getCallInfoState().getActivePeer();
|
||||
|
||||
if (activePeer == null) {
|
||||
Log.i(tag, "skipping with no active peer");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (!activePeer.callIdEquals(remotePeer)) {
|
||||
Log.i(tag, "skipping remotePeer is not active peer");
|
||||
return currentState;
|
||||
}
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
webRtcInteractor.stopForegroundService();
|
||||
boolean playDisconnectSound = (activePeer.getState() == CallState.DIALING) ||
|
||||
(activePeer.getState() == CallState.REMOTE_RINGING) ||
|
||||
(activePeer.getState() == CallState.RECEIVED_BUSY) ||
|
||||
(activePeer.getState() == CallState.CONNECTED);
|
||||
webRtcInteractor.stopAudio(playDisconnectSound);
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
|
||||
return WebRtcVideoUtil.deinitializeVideo(currentState)
|
||||
.builder()
|
||||
.changeCallInfoState()
|
||||
.activePeer(null)
|
||||
.commit()
|
||||
.actionProcessor(currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED ? new DisconnectingCallActionProcessor(webRtcInteractor) : new IdleActionProcessor(webRtcInteractor))
|
||||
.terminate()
|
||||
.build();
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
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.ringrtc.RemotePeer;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
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_SERVER_DELIVERED_TIMESTAMP;
|
||||
import static org.thoughtcrime.securesms.service.WebRtcCallService.EXTRA_SERVER_RECEIVED_TIMESTAMP;
|
||||
|
||||
/**
|
||||
* Collection of classes to ease parsing data from intents and passing said data
|
||||
* around.
|
||||
*/
|
||||
public class WebRtcData {
|
||||
|
||||
/**
|
||||
* Low-level metadata Information about the call.
|
||||
*/
|
||||
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) {
|
||||
this.remotePeer = remotePeer;
|
||||
this.callId = callId;
|
||||
this.remoteDevice = remoteDevice;
|
||||
}
|
||||
|
||||
@NonNull RemotePeer getRemotePeer() {
|
||||
return remotePeer;
|
||||
}
|
||||
|
||||
@NonNull CallId getCallId() {
|
||||
return callId;
|
||||
}
|
||||
|
||||
int getRemoteDevice() {
|
||||
return remoteDevice;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for a call offer to be sent or received.
|
||||
*/
|
||||
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) {
|
||||
this.opaque = opaque;
|
||||
this.sdp = sdp;
|
||||
this.offerType = offerType;
|
||||
}
|
||||
|
||||
@Nullable byte[] getOpaque() {
|
||||
return opaque;
|
||||
}
|
||||
|
||||
@Nullable String getSdp() {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
@NonNull OfferMessage.Type getOfferType() {
|
||||
return offerType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional metadata for a received call.
|
||||
*/
|
||||
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) {
|
||||
this.remoteIdentityKey = remoteIdentityKey;
|
||||
this.serverReceivedTimestamp = serverReceivedTimestamp;
|
||||
this.serverDeliveredTimestamp = serverDeliveredTimestamp;
|
||||
this.isMultiRing = isMultiRing;
|
||||
}
|
||||
|
||||
@NonNull byte[] getRemoteIdentityKey() {
|
||||
return remoteIdentityKey;
|
||||
}
|
||||
|
||||
long getServerReceivedTimestamp() {
|
||||
return serverReceivedTimestamp;
|
||||
}
|
||||
|
||||
long getServerDeliveredTimestamp() {
|
||||
return serverDeliveredTimestamp;
|
||||
}
|
||||
|
||||
boolean isMultiRing() {
|
||||
return isMultiRing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for an answer to be sent or received.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@Nullable byte[] getOpaque() {
|
||||
return opaque;
|
||||
}
|
||||
|
||||
@Nullable String getSdp() {
|
||||
return sdp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional metadata for a received answer.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@NonNull byte[] getRemoteIdentityKey() {
|
||||
return remoteIdentityKey;
|
||||
}
|
||||
|
||||
boolean isMultiRing() {
|
||||
return isMultiRing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for a remote or local hangup.
|
||||
*/
|
||||
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) {
|
||||
this.type = type;
|
||||
this.isLegacy = isLegacy;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
@NonNull HangupMessage.Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@NonNull CallManager.HangupType getCallHangupType() {
|
||||
switch (type) {
|
||||
case ACCEPTED: return CallManager.HangupType.ACCEPTED;
|
||||
case BUSY: return CallManager.HangupType.BUSY;
|
||||
case NORMAL: return CallManager.HangupType.NORMAL;
|
||||
case DECLINED: return CallManager.HangupType.DECLINED;
|
||||
case NEED_PERMISSION: return CallManager.HangupType.NEED_PERMISSION;
|
||||
default: throw new IllegalArgumentException("Unexpected hangup type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLegacy() {
|
||||
return isLegacy;
|
||||
}
|
||||
|
||||
int getDeviceId() {
|
||||
return deviceId;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
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.thoughtcrime.securesms.logging.Log;
|
||||
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.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
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_ENABLE;
|
||||
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_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;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
int remotePeerKey = getRemotePeerKey(intent);
|
||||
RemotePeer remotePeer = currentState.getCallInfoState().getPeer(remotePeerKey);
|
||||
|
||||
if (remotePeer == null) {
|
||||
throw new AssertionError("No RemotePeer in map for key: " + remotePeerKey + "!");
|
||||
}
|
||||
|
||||
return remotePeer;
|
||||
}
|
||||
|
||||
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 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 @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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.ringrtc.CallManager;
|
||||
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.webrtc.audio.BluetoothStateManager;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
|
||||
/**
|
||||
* Serves as the bridge between the action processing framework as the WebRTC service. Attempts
|
||||
* to minimize direct access to various managers by providing a simple proxy to them. Due to the
|
||||
* heavy use of {@link CallManager} throughout, it was exempted from the rule.
|
||||
*/
|
||||
public class WebRtcInteractor {
|
||||
|
||||
@NonNull private final WebRtcCallService webRtcCallService;
|
||||
@NonNull private final CallManager callManager;
|
||||
@NonNull private final LockManager lockManager;
|
||||
@NonNull private final SignalAudioManager audioManager;
|
||||
@NonNull private final BluetoothStateManager bluetoothStateManager;
|
||||
@NonNull private final CameraEventListener cameraEventListener;
|
||||
|
||||
public WebRtcInteractor(@NonNull WebRtcCallService webRtcCallService,
|
||||
@NonNull CallManager callManager,
|
||||
@NonNull LockManager lockManager,
|
||||
@NonNull SignalAudioManager audioManager,
|
||||
@NonNull BluetoothStateManager bluetoothStateManager,
|
||||
@NonNull CameraEventListener cameraEventListener)
|
||||
{
|
||||
this.webRtcCallService = webRtcCallService;
|
||||
this.callManager = callManager;
|
||||
this.lockManager = lockManager;
|
||||
this.audioManager = audioManager;
|
||||
this.bluetoothStateManager = bluetoothStateManager;
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
}
|
||||
|
||||
@NonNull CameraEventListener getCameraEventListener() {
|
||||
return cameraEventListener;
|
||||
}
|
||||
|
||||
@NonNull CallManager getCallManager() {
|
||||
return callManager;
|
||||
}
|
||||
|
||||
@NonNull WebRtcCallService getWebRtcCallService() {
|
||||
return webRtcCallService;
|
||||
}
|
||||
|
||||
void setWantsBluetoothConnection(boolean enabled) {
|
||||
bluetoothStateManager.setWantsConnection(enabled);
|
||||
}
|
||||
|
||||
void updatePhoneState(@NonNull LockManager.PhoneState phoneState) {
|
||||
lockManager.updatePhoneState(phoneState);
|
||||
}
|
||||
|
||||
void sendMessage(@NonNull WebRtcServiceState state) {
|
||||
webRtcCallService.sendMessage(state);
|
||||
}
|
||||
|
||||
void sendCallMessage(@NonNull RemotePeer remotePeer, @NonNull SignalServiceCallMessage callMessage) {
|
||||
webRtcCallService.sendCallMessage(remotePeer, callMessage);
|
||||
}
|
||||
|
||||
void setCallInProgressNotification(int type, @NonNull RemotePeer remotePeer) {
|
||||
webRtcCallService.setCallInProgressNotification(type, remotePeer);
|
||||
}
|
||||
|
||||
void retrieveTurnServers(@NonNull RemotePeer remotePeer) {
|
||||
webRtcCallService.retrieveTurnServers(remotePeer);
|
||||
}
|
||||
|
||||
void stopForegroundService() {
|
||||
webRtcCallService.stopForeground(true);
|
||||
}
|
||||
|
||||
void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal, long timestamp) {
|
||||
webRtcCallService.insertMissedCall(remotePeer, signal, timestamp);
|
||||
}
|
||||
|
||||
void startWebRtcCallActivityIfPossible() {
|
||||
webRtcCallService.startCallCardActivityIfPossible();
|
||||
}
|
||||
|
||||
void registerPowerButtonReceiver() {
|
||||
webRtcCallService.registerPowerButtonReceiver();
|
||||
}
|
||||
|
||||
void unregisterPowerButtonReceiver() {
|
||||
webRtcCallService.unregisterPowerButtonReceiver();
|
||||
}
|
||||
|
||||
void silenceIncomingRinger() {
|
||||
audioManager.silenceIncomingRinger();
|
||||
}
|
||||
|
||||
void initializeAudioForCall() {
|
||||
audioManager.initializeAudioForCall();
|
||||
}
|
||||
|
||||
void startIncomingRinger(@Nullable Uri ringtoneUri, boolean vibrate) {
|
||||
audioManager.startIncomingRinger(ringtoneUri, vibrate);
|
||||
}
|
||||
|
||||
void startOutgoingRinger(@NonNull OutgoingRinger.Type type) {
|
||||
audioManager.startOutgoingRinger(type);
|
||||
}
|
||||
|
||||
void stopAudio(boolean playDisconnect) {
|
||||
audioManager.stop(playDisconnect);
|
||||
}
|
||||
|
||||
void startAudioCommunication(boolean preserveSpeakerphone) {
|
||||
audioManager.startCommunication(preserveSpeakerphone);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.whispersystems.libsignal.InvalidKeyException;
|
||||
import org.whispersystems.libsignal.ecc.Curve;
|
||||
import org.whispersystems.libsignal.ecc.DjbECPublicKey;
|
||||
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
/**
|
||||
* Calling specific helpers.
|
||||
*/
|
||||
public final class WebRtcUtil {
|
||||
|
||||
private WebRtcUtil() {}
|
||||
|
||||
public static @NonNull byte[] getPublicKeyBytes(@NonNull byte[] identityKey) throws InvalidKeyException {
|
||||
ECPublicKey key = Curve.decodePoint(identityKey, 0);
|
||||
|
||||
if (key instanceof DjbECPublicKey) {
|
||||
return ((DjbECPublicKey) key).getPublicKey();
|
||||
}
|
||||
throw new InvalidKeyException();
|
||||
}
|
||||
|
||||
public static @NonNull LockManager.PhoneState getInCallPhoneState(@NonNull Context context) {
|
||||
AudioManager audioManager = ServiceUtil.getAudioManager(context);
|
||||
if (audioManager.isSpeakerphoneOn() || audioManager.isBluetoothScoOn() || audioManager.isWiredHeadsetOn()) {
|
||||
return LockManager.PhoneState.IN_HANDS_FREE_CALL;
|
||||
} else {
|
||||
return LockManager.PhoneState.IN_CALL;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull CallManager.CallMediaType getCallMediaTypeFromOfferType(@NonNull OfferMessage.Type offerType) {
|
||||
return offerType == OfferMessage.Type.VIDEO_CALL ? CallManager.CallMediaType.VIDEO_CALL : CallManager.CallMediaType.AUDIO_CALL;
|
||||
}
|
||||
|
||||
public static void enableSpeakerPhoneIfNeeded(@NonNull Context context, boolean enable) {
|
||||
if (!enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
//noinspection deprecation
|
||||
boolean shouldEnable = !(androidAudioManager.isSpeakerphoneOn() || androidAudioManager.isBluetoothScoOn() || androidAudioManager.isWiredHeadsetOn());
|
||||
|
||||
if (shouldEnable) {
|
||||
androidAudioManager.setSpeakerphoneOn(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraEventListener;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.webrtc.EglBase;
|
||||
|
||||
/**
|
||||
* Helper for initializing, reinitializing, and deinitializing the camera and it's related
|
||||
* infrastructure.
|
||||
*/
|
||||
public final class WebRtcVideoUtil {
|
||||
|
||||
private WebRtcVideoUtil() {}
|
||||
|
||||
public static @NonNull WebRtcServiceState initializeVideo(@NonNull Context context,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull WebRtcServiceState currentState)
|
||||
{
|
||||
final WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
Util.runOnMainSync(() -> {
|
||||
EglBase eglBase = EglBase.create();
|
||||
BroadcastVideoSink localSink = new BroadcastVideoSink(eglBase);
|
||||
Camera camera = new Camera(context, cameraEventListener, eglBase, CameraState.Direction.FRONT);
|
||||
|
||||
builder.changeVideoState()
|
||||
.eglBase(eglBase)
|
||||
.localSink(localSink)
|
||||
.camera(camera)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.commit();
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static @NonNull WebRtcServiceState reinitializeCamera(@NonNull Context context,
|
||||
@NonNull CameraEventListener cameraEventListener,
|
||||
@NonNull WebRtcServiceState currentState)
|
||||
{
|
||||
final WebRtcServiceStateBuilder builder = currentState.builder();
|
||||
|
||||
Util.runOnMainSync(() -> {
|
||||
Camera camera = currentState.getVideoState().requireCamera();
|
||||
camera.setEnabled(false);
|
||||
camera.dispose();
|
||||
|
||||
camera = new Camera(context,
|
||||
cameraEventListener,
|
||||
currentState.getVideoState().requireEglBase(),
|
||||
currentState.getLocalDeviceState().getCameraState().getActiveDirection());
|
||||
|
||||
builder.changeVideoState()
|
||||
.camera(camera)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(camera.getCameraState())
|
||||
.commit();
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static @NonNull WebRtcServiceState deinitializeVideo(@NonNull WebRtcServiceState currentState) {
|
||||
Camera camera = currentState.getVideoState().getCamera();
|
||||
if (camera != null) {
|
||||
camera.dispose();
|
||||
}
|
||||
|
||||
EglBase eglBase = currentState.getVideoState().getEglBase();
|
||||
if (eglBase != null) {
|
||||
eglBase.release();
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.changeVideoState()
|
||||
.eglBase(null)
|
||||
.camera(null)
|
||||
.localSink(null)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.cameraState(CameraState.UNKNOWN)
|
||||
.build();
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* General state of ongoing calls.
|
||||
*/
|
||||
public class CallInfoState {
|
||||
|
||||
WebRtcViewModel.State callState;
|
||||
Recipient callRecipient;
|
||||
long callConnectedTime;
|
||||
Map<Recipient, CallParticipant> remoteParticipants;
|
||||
Map<Integer, RemotePeer> peerMap;
|
||||
RemotePeer activePeer;
|
||||
|
||||
public CallInfoState() {
|
||||
this(WebRtcViewModel.State.IDLE, Recipient.UNKNOWN, -1, Collections.emptyMap(), Collections.emptyMap(), null);
|
||||
}
|
||||
|
||||
public CallInfoState(@NonNull CallInfoState toCopy) {
|
||||
this(toCopy.callState, toCopy.callRecipient, toCopy.callConnectedTime, toCopy.remoteParticipants, toCopy.peerMap, toCopy.activePeer);
|
||||
}
|
||||
|
||||
public CallInfoState(@NonNull WebRtcViewModel.State callState,
|
||||
@NonNull Recipient callRecipient,
|
||||
long callConnectedTime,
|
||||
@NonNull Map<Recipient, CallParticipant> remoteParticipants,
|
||||
@NonNull Map<Integer, RemotePeer> peerMap,
|
||||
@Nullable RemotePeer activePeer)
|
||||
{
|
||||
this.callState = callState;
|
||||
this.callRecipient = callRecipient;
|
||||
this.callConnectedTime = callConnectedTime;
|
||||
this.remoteParticipants = new LinkedHashMap<>(remoteParticipants);
|
||||
this.peerMap = new HashMap<>(peerMap);
|
||||
this.activePeer = activePeer;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getCallRecipient() {
|
||||
return callRecipient;
|
||||
}
|
||||
|
||||
public long getCallConnectedTime() {
|
||||
return callConnectedTime;
|
||||
}
|
||||
|
||||
public @Nullable CallParticipant getRemoteParticipant(@NonNull Recipient recipient) {
|
||||
return remoteParticipants.get(recipient);
|
||||
}
|
||||
|
||||
public @NonNull ArrayList<CallParticipant> getRemoteCallParticipants() {
|
||||
return new ArrayList<>(remoteParticipants.values());
|
||||
}
|
||||
|
||||
public @NonNull WebRtcViewModel.State getCallState() {
|
||||
return callState;
|
||||
}
|
||||
|
||||
public @Nullable RemotePeer getPeer(int hashCode) {
|
||||
return peerMap.get(hashCode);
|
||||
}
|
||||
|
||||
public @Nullable RemotePeer getActivePeer() {
|
||||
return activePeer;
|
||||
}
|
||||
|
||||
public @NonNull RemotePeer requireActivePeer() {
|
||||
return Objects.requireNonNull(activePeer);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Information specific to setting up a call.
|
||||
*/
|
||||
public final class CallSetupState {
|
||||
boolean enableVideoOnCreate;
|
||||
boolean isRemoteVideoOffer;
|
||||
boolean acceptWithVideo;
|
||||
|
||||
public CallSetupState() {
|
||||
this(false, false, false);
|
||||
}
|
||||
|
||||
public CallSetupState(@NonNull CallSetupState toCopy) {
|
||||
this(toCopy.enableVideoOnCreate, toCopy.isRemoteVideoOffer, toCopy.acceptWithVideo);
|
||||
}
|
||||
|
||||
public CallSetupState(boolean enableVideoOnCreate, boolean isRemoteVideoOffer, boolean acceptWithVideo) {
|
||||
this.enableVideoOnCreate = enableVideoOnCreate;
|
||||
this.isRemoteVideoOffer = isRemoteVideoOffer;
|
||||
this.acceptWithVideo = acceptWithVideo;
|
||||
}
|
||||
|
||||
public boolean isEnableVideoOnCreate() {
|
||||
return enableVideoOnCreate;
|
||||
}
|
||||
|
||||
public boolean isRemoteVideoOffer() {
|
||||
return isRemoteVideoOffer;
|
||||
}
|
||||
|
||||
public boolean isAcceptWithVideo() {
|
||||
return acceptWithVideo;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
|
||||
/**
|
||||
* Local device specific state.
|
||||
*/
|
||||
public final class LocalDeviceState {
|
||||
CameraState cameraState;
|
||||
boolean microphoneEnabled;
|
||||
boolean bluetoothAvailable;
|
||||
|
||||
LocalDeviceState() {
|
||||
this(CameraState.UNKNOWN, true, false);
|
||||
}
|
||||
|
||||
LocalDeviceState(@NonNull LocalDeviceState toCopy) {
|
||||
this(toCopy.cameraState, toCopy.microphoneEnabled, toCopy.bluetoothAvailable);
|
||||
}
|
||||
|
||||
LocalDeviceState(@NonNull CameraState cameraState, boolean microphoneEnabled, boolean bluetoothAvailable) {
|
||||
this.cameraState = cameraState;
|
||||
this.microphoneEnabled = microphoneEnabled;
|
||||
this.bluetoothAvailable = bluetoothAvailable;
|
||||
}
|
||||
|
||||
public @NonNull CameraState getCameraState() {
|
||||
return cameraState;
|
||||
}
|
||||
|
||||
public boolean isMicrophoneEnabled() {
|
||||
return microphoneEnabled;
|
||||
}
|
||||
|
||||
public boolean isBluetoothAvailable() {
|
||||
return bluetoothAvailable;
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.webrtc.EglBase;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Local device video state and infrastructure.
|
||||
*/
|
||||
public final class VideoState {
|
||||
EglBase eglBase;
|
||||
BroadcastVideoSink localSink;
|
||||
Camera camera;
|
||||
|
||||
VideoState() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
VideoState(@NonNull VideoState toCopy) {
|
||||
this(toCopy.eglBase, toCopy.localSink, toCopy.camera);
|
||||
}
|
||||
|
||||
VideoState(@Nullable EglBase eglBase, @Nullable BroadcastVideoSink localSink, @Nullable Camera camera) {
|
||||
this.eglBase = eglBase;
|
||||
this.localSink = localSink;
|
||||
this.camera = camera;
|
||||
}
|
||||
|
||||
public @Nullable EglBase getEglBase() {
|
||||
return eglBase;
|
||||
}
|
||||
|
||||
public @NonNull EglBase requireEglBase() {
|
||||
return Objects.requireNonNull(eglBase);
|
||||
}
|
||||
|
||||
public @Nullable BroadcastVideoSink getLocalSink() {
|
||||
return localSink;
|
||||
}
|
||||
|
||||
public @NonNull BroadcastVideoSink requireLocalSink() {
|
||||
return Objects.requireNonNull(localSink);
|
||||
}
|
||||
|
||||
public @Nullable Camera getCamera() {
|
||||
return camera;
|
||||
}
|
||||
|
||||
public @NonNull Camera requireCamera() {
|
||||
return Objects.requireNonNull(camera);
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcActionProcessor;
|
||||
|
||||
/**
|
||||
* Represent the entire state of the call system.
|
||||
*/
|
||||
public final class WebRtcServiceState {
|
||||
|
||||
WebRtcActionProcessor actionProcessor;
|
||||
CallSetupState callSetupState;
|
||||
CallInfoState callInfoState;
|
||||
LocalDeviceState localDeviceState;
|
||||
VideoState videoState;
|
||||
|
||||
public WebRtcServiceState(@NonNull WebRtcActionProcessor actionProcessor) {
|
||||
this.actionProcessor = actionProcessor;
|
||||
this.callSetupState = new CallSetupState();
|
||||
this.callInfoState = new CallInfoState();
|
||||
this.localDeviceState = new LocalDeviceState();
|
||||
this.videoState = new VideoState();
|
||||
}
|
||||
|
||||
public WebRtcServiceState(@NonNull WebRtcServiceState toCopy) {
|
||||
this.actionProcessor = toCopy.actionProcessor;
|
||||
this.callSetupState = new CallSetupState(toCopy.callSetupState);
|
||||
this.callInfoState = new CallInfoState(toCopy.callInfoState);
|
||||
this.localDeviceState = new LocalDeviceState(toCopy.localDeviceState);
|
||||
this.videoState = new VideoState(toCopy.videoState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcActionProcessor getActionProcessor() {
|
||||
return actionProcessor;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupState getCallSetupState() {
|
||||
return callSetupState;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoState getCallInfoState() {
|
||||
return callInfoState;
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceState getLocalDeviceState() {
|
||||
return localDeviceState;
|
||||
}
|
||||
|
||||
public @NonNull VideoState getVideoState() {
|
||||
return videoState;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder builder() {
|
||||
return new WebRtcServiceStateBuilder(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.WebRtcActionProcessor;
|
||||
import org.webrtc.EglBase;
|
||||
|
||||
/**
|
||||
* Builder that creates a new {@link WebRtcServiceState} from an existing one and allows
|
||||
* changes to all normally immutable data.
|
||||
*/
|
||||
public class WebRtcServiceStateBuilder {
|
||||
private WebRtcServiceState toBuild;
|
||||
|
||||
public WebRtcServiceStateBuilder(@NonNull WebRtcServiceState webRtcServiceState) {
|
||||
toBuild = new WebRtcServiceState(webRtcServiceState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState build() {
|
||||
return toBuild;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder actionProcessor(@NonNull WebRtcActionProcessor actionHandler) {
|
||||
toBuild.actionProcessor = actionHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder changeCallSetupState() {
|
||||
return new CallSetupStateBuilder();
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder changeCallInfoState() {
|
||||
return new CallInfoStateBuilder();
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceStateBuilder changeLocalDeviceState() {
|
||||
return new LocalDeviceStateBuilder();
|
||||
}
|
||||
|
||||
public @NonNull VideoStateBuilder changeVideoState() {
|
||||
return new VideoStateBuilder();
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder terminate() {
|
||||
toBuild.callSetupState = new CallSetupState();
|
||||
toBuild.localDeviceState = new LocalDeviceState();
|
||||
toBuild.videoState = new VideoState();
|
||||
|
||||
CallInfoState newCallInfoState = new CallInfoState();
|
||||
newCallInfoState.peerMap.putAll(toBuild.callInfoState.peerMap);
|
||||
toBuild.callInfoState = newCallInfoState;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public class LocalDeviceStateBuilder {
|
||||
private LocalDeviceState toBuild;
|
||||
|
||||
public LocalDeviceStateBuilder() {
|
||||
toBuild = new LocalDeviceState(WebRtcServiceStateBuilder.this.toBuild.localDeviceState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder commit() {
|
||||
WebRtcServiceStateBuilder.this.toBuild.localDeviceState = toBuild;
|
||||
return WebRtcServiceStateBuilder.this;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState build() {
|
||||
commit();
|
||||
return WebRtcServiceStateBuilder.this.build();
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceStateBuilder cameraState(@NonNull CameraState cameraState) {
|
||||
toBuild.cameraState = cameraState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceStateBuilder isMicrophoneEnabled(boolean enabled) {
|
||||
toBuild.microphoneEnabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull LocalDeviceStateBuilder isBluetoothAvailable(boolean available) {
|
||||
toBuild.bluetoothAvailable = available;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class CallSetupStateBuilder {
|
||||
private CallSetupState toBuild;
|
||||
|
||||
public CallSetupStateBuilder() {
|
||||
toBuild = new CallSetupState(WebRtcServiceStateBuilder.this.toBuild.callSetupState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder commit() {
|
||||
WebRtcServiceStateBuilder.this.toBuild.callSetupState = toBuild;
|
||||
return WebRtcServiceStateBuilder.this;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState build() {
|
||||
commit();
|
||||
return WebRtcServiceStateBuilder.this.build();
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder enableVideoOnCreate(boolean enableVideoOnCreate) {
|
||||
toBuild.enableVideoOnCreate = enableVideoOnCreate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder isRemoteVideoOffer(boolean isRemoteVideoOffer) {
|
||||
toBuild.isRemoteVideoOffer = isRemoteVideoOffer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder acceptWithVideo(boolean acceptWithVideo) {
|
||||
toBuild.acceptWithVideo = acceptWithVideo;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class VideoStateBuilder {
|
||||
private VideoState toBuild;
|
||||
|
||||
public VideoStateBuilder() {
|
||||
toBuild = new VideoState(WebRtcServiceStateBuilder.this.toBuild.videoState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder commit() {
|
||||
WebRtcServiceStateBuilder.this.toBuild.videoState = toBuild;
|
||||
return WebRtcServiceStateBuilder.this;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState build() {
|
||||
commit();
|
||||
return WebRtcServiceStateBuilder.this.build();
|
||||
}
|
||||
|
||||
public @NonNull VideoStateBuilder eglBase(@Nullable EglBase eglBase) {
|
||||
toBuild.eglBase = eglBase;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull VideoStateBuilder localSink(@Nullable BroadcastVideoSink localSink) {
|
||||
toBuild.localSink = localSink;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull VideoStateBuilder camera(@Nullable Camera camera) {
|
||||
toBuild.camera = camera;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class CallInfoStateBuilder {
|
||||
private CallInfoState toBuild;
|
||||
|
||||
public CallInfoStateBuilder() {
|
||||
toBuild = new CallInfoState(WebRtcServiceStateBuilder.this.toBuild.callInfoState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder commit() {
|
||||
WebRtcServiceStateBuilder.this.toBuild.callInfoState = toBuild;
|
||||
return WebRtcServiceStateBuilder.this;
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState build() {
|
||||
commit();
|
||||
return WebRtcServiceStateBuilder.this.build();
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder callState(@NonNull WebRtcViewModel.State callState) {
|
||||
toBuild.callState = callState;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder callRecipient(@NonNull Recipient callRecipient) {
|
||||
toBuild.callRecipient = callRecipient;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder callConnectedTime(long callConnectedTime) {
|
||||
toBuild.callConnectedTime = callConnectedTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder putParticipant(@NonNull Recipient recipient, @NonNull CallParticipant callParticipant) {
|
||||
toBuild.remoteParticipants.put(recipient, callParticipant);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder putRemotePeer(@NonNull RemotePeer remotePeer) {
|
||||
toBuild.peerMap.put(remotePeer.hashCode(), remotePeer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder clearPeerMap() {
|
||||
toBuild.peerMap.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder removeRemotePeer(@NonNull RemotePeer remotePeer) {
|
||||
toBuild.peerMap.remove(remotePeer.hashCode());
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder activePeer(@Nullable RemotePeer activePeer) {
|
||||
toBuild.activePeer = activePeer;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user