package org.thoughtcrime.securesms.service; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.IBinder; import android.os.ResultReceiver; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.SparseArray; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.greenrobot.eventbus.EventBus; import org.signal.ringrtc.CallException; import org.signal.ringrtc.CallId; import org.signal.ringrtc.CallManager; import org.signal.ringrtc.CallManager.CallEvent; import org.signal.ringrtc.IceCandidate; import org.signal.ringrtc.Remote; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.WebRtcCallActivity; import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.crypto.IdentityKeyUtil; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; 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.notifications.DoNotDisturbUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.ringrtc.CallState; import org.thoughtcrime.securesms.ringrtc.Camera; import org.thoughtcrime.securesms.ringrtc.CameraEventListener; import org.thoughtcrime.securesms.ringrtc.CameraState; import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.util.FutureTaskListener; import org.thoughtcrime.securesms.util.ListenableFutureTask; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TelephonyUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver; import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager; 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.webrtc.CapturerObserver; import org.webrtc.EglBase; import org.webrtc.PeerConnection; import org.webrtc.VideoFrame; 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.libsignal.util.Pair; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.calls.AnswerMessage; import org.whispersystems.signalservice.api.messages.calls.BusyMessage; import org.whispersystems.signalservice.api.messages.calls.HangupMessage; import org.whispersystems.signalservice.api.messages.calls.IceUpdateMessage; import org.whispersystems.signalservice.api.messages.calls.OfferMessage; import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage; import org.whispersystems.signalservice.api.messages.calls.TurnServerInfo; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; 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; public class WebRtcCallService extends Service implements CallManager.Observer, BluetoothStateManager.BluetoothStateListener, CameraEventListener { private static final String TAG = WebRtcCallService.class.getSimpleName(); public static final String EXTRA_MUTE = "mute_value"; public static final String EXTRA_AVAILABLE = "enabled_value"; public static final String EXTRA_SERVER_RECEIVED_TIMESTAMP = "server_received_timestamp"; public static final String EXTRA_SERVER_DELIVERED_TIMESTAMP = "server_delivered_timestamp"; public static final String EXTRA_CALL_ID = "call_id"; public static final String EXTRA_RESULT_RECEIVER = "result_receiver"; public static final String EXTRA_SPEAKER = "audio_speaker"; public static final String EXTRA_BLUETOOTH = "audio_bluetooth"; public static final String EXTRA_REMOTE_PEER = "remote_peer"; public static final String EXTRA_REMOTE_PEER_KEY = "remote_peer_key"; public static final String EXTRA_REMOTE_DEVICE = "remote_device"; public static final String EXTRA_REMOTE_IDENTITY_KEY = "remote_identity_key"; public static final String EXTRA_OFFER_OPAQUE = "offer_opaque"; public static final String EXTRA_OFFER_SDP = "offer_sdp"; public static final String EXTRA_OFFER_TYPE = "offer_type"; public static final String EXTRA_MULTI_RING = "multi_ring"; public static final String EXTRA_HANGUP_TYPE = "hangup_type"; public static final String EXTRA_HANGUP_IS_LEGACY = "hangup_is_legacy"; public static final String EXTRA_HANGUP_DEVICE_ID = "hangup_device_id"; public static final String EXTRA_ANSWER_OPAQUE = "answer_opaque"; public static final String EXTRA_ANSWER_SDP = "answer_sdp"; public static final String EXTRA_ICE_CANDIDATES = "ice_candidates"; public static final String EXTRA_ENABLE = "enable_value"; public static final String EXTRA_BROADCAST = "broadcast"; public static final String EXTRA_ANSWER_WITH_VIDEO = "enable_video"; public static final String ACTION_PRE_JOIN_CALL = "CALL_PRE_JOIN"; public static final String ACTION_CANCEL_PRE_JOIN_CALL = "CANCEL_PRE_JOIN_CALL"; public static final String ACTION_OUTGOING_CALL = "CALL_OUTGOING"; public static final String ACTION_DENY_CALL = "DENY_CALL"; public static final String ACTION_LOCAL_HANGUP = "LOCAL_HANGUP"; public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO"; public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA"; public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE"; public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE"; public static final String ACTION_SCREEN_OFF = "SCREEN_OFF"; public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"; public static final String ACTION_SET_AUDIO_SPEAKER = "SET_AUDIO_SPEAKER"; public static final String ACTION_SET_AUDIO_BLUETOOTH = "SET_AUDIO_BLUETOOTH"; public static final String ACTION_CALL_CONNECTED = "CALL_CONNECTED"; public static final String ACTION_START_OUTGOING_CALL = "START_OUTGOING_CALL"; public static final String ACTION_START_INCOMING_CALL = "START_INCOMING_CALL"; public static final String ACTION_LOCAL_RINGING = "LOCAL_RINGING"; public static final String ACTION_REMOTE_RINGING = "REMOTE_RINGING"; public static final String ACTION_ACCEPT_CALL = "ACCEPT_CALL"; public static final String ACTION_SEND_OFFER = "SEND_OFFER"; public static final String ACTION_SEND_ANSWER = "SEND_ANSWER"; public static final String ACTION_SEND_ICE_CANDIDATES = "SEND_ICE_CANDIDATES"; public static final String ACTION_SEND_HANGUP = "SEND_HANGUP"; public static final String ACTION_SEND_BUSY = "SEND_BUSY"; public static final String ACTION_RECEIVE_OFFER = "RECEIVE_OFFER"; public static final String ACTION_RECEIVE_ANSWER = "RECEIVE_ANSWER"; public static final String ACTION_RECEIVE_ICE_CANDIDATES = "RECEIVE_ICE_CANDIDATES"; public static final String ACTION_RECEIVE_HANGUP = "RECEIVE_HANGUP"; public static final String ACTION_RECEIVE_BUSY = "RECEIVE_BUSY"; public static final String ACTION_REMOTE_VIDEO_ENABLE = "REMOTE_VIDEO_ENABLE"; public static final String ACTION_SET_ENABLE_VIDEO = "SET_ENABLE_VIDEO"; public static final String ACTION_ENDED_REMOTE_HANGUP = "ENDED_REMOTE_HANGUP"; public static final String ACTION_ENDED_REMOTE_HANGUP_ACCEPTED = "ENDED_REMOTE_HANGUP_ACCEPTED"; public static final String ACTION_ENDED_REMOTE_HANGUP_DECLINED = "ENDED_REMOTE_HANGUP_DECLINED"; public static final String ACTION_ENDED_REMOTE_HANGUP_BUSY = "ENDED_REMOTE_HANGUP_BUSY"; public static final String ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION = "ENDED_REMOTE_HANGUP_NEED_PERMISSION"; public static final String ACTION_ENDED_REMOTE_BUSY = "ENDED_REMOTE_BUSY"; public static final String ACTION_ENDED_REMOTE_GLARE = "ENDED_REMOTE_GLARE"; public static final String ACTION_ENDED_TIMEOUT = "ENDED_TIMEOUT"; public static final String ACTION_ENDED_INTERNAL_FAILURE = "ENDED_INTERNAL_FAILURE"; public static final String ACTION_ENDED_SIGNALING_FAILURE = "ENDED_SIGNALING_FAILURE"; public static final String ACTION_ENDED_CONNECTION_FAILURE = "ENDED_CONNECTION_FAILURE"; public static final String ACTION_RECEIVED_OFFER_EXPIRED = "RECEIVED_OFFER_EXPIRED"; public static final String ACTION_RECEIVED_OFFER_WHILE_ACTIVE = "RECEIVED_OFFER_WHILE_ACTIVE"; public static final String ACTION_CALL_CONCLUDED = "CALL_CONCLUDED"; public static final int BUSY_TONE_LENGTH = 2000; private CameraState localCameraState = CameraState.UNKNOWN; private boolean microphoneEnabled = true; private boolean bluetoothAvailable = false; private boolean enableVideoOnCreate = false; private boolean isRemoteVideoOffer = false; private boolean acceptWithVideo = false; private long callConnectedTime = -1; private SignalServiceMessageSender messageSender; private SignalServiceAccountManager accountManager; private SignalAudioManager audioManager; private BluetoothStateManager bluetoothStateManager; private WiredHeadsetStateReceiver wiredHeadsetStateReceiver; private PowerButtonReceiver powerButtonReceiver; private LockManager lockManager; private IncomingPstnCallReceiver callReceiver; private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; @Nullable private CallManager callManager; @Nullable private RemotePeer activePeer; @Nullable private RemotePeer busyPeer; @Nullable private RemotePeer preJoinPeer; @Nullable private SparseArray peerMap; @Nullable private EglBase eglBase; @Nullable private BroadcastVideoSink localSink; @Nullable private Camera camera; private final Map remoteParticipantMap = new LinkedHashMap<>(); private final ExecutorService serviceExecutor = Executors.newSingleThreadExecutor(); private final ExecutorService networkExecutor = Executors.newSingleThreadExecutor(); private final PhoneStateListener hangUpRtcOnDeviceCallAnswered = new HangUpRtcOnPstnCallAnsweredListener(); @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate"); initializeResources(); registerIncomingPstnCallReceiver(); registerUncaughtExceptionHandler(); registerWiredHeadsetStateReceiver(); TelephonyUtil.getManager(this) .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE); } @Override public int onStartCommand(final Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand..."); if (intent == null || intent.getAction() == null) return START_NOT_STICKY; serviceExecutor.execute(() -> { if (intent.getAction().equals(ACTION_RECEIVE_OFFER)) handleReceivedOffer(intent); else if (intent.getAction().equals(ACTION_RECEIVE_BUSY)) handleReceivedBusy(intent); else if (intent.getAction().equals(ACTION_PRE_JOIN_CALL)) handlePreJoinCall(intent); else if (intent.getAction().equals(ACTION_CANCEL_PRE_JOIN_CALL)) handleCancelPreJoinCall(); else if (intent.getAction().equals(ACTION_OUTGOING_CALL) && isIdle()) handleOutgoingCall(intent); else if (intent.getAction().equals(ACTION_DENY_CALL)) handleDenyCall(intent); else if (intent.getAction().equals(ACTION_LOCAL_HANGUP)) handleLocalHangup(intent); else if (intent.getAction().equals(ACTION_SET_MUTE_AUDIO)) handleSetMuteAudio(intent); else if (intent.getAction().equals(ACTION_FLIP_CAMERA)) handleSetCameraFlip(intent); else if (intent.getAction().equals(ACTION_BLUETOOTH_CHANGE)) handleBluetoothChange(intent); else if (intent.getAction().equals(ACTION_WIRED_HEADSET_CHANGE)) handleWiredHeadsetChange(intent); else if (intent.getAction().equals(ACTION_SCREEN_OFF)) handleScreenOffChange(intent); else if (intent.getAction().equals(ACTION_CALL_CONNECTED)) handleCallConnected(intent); else if (intent.getAction().equals(ACTION_IS_IN_CALL_QUERY)) handleIsInCallQuery(intent); else if (intent.getAction().equals(ACTION_SET_AUDIO_SPEAKER)) handleSetSpeakerAudio(intent); else if (intent.getAction().equals(ACTION_SET_AUDIO_BLUETOOTH)) handleSetBluetoothAudio(intent); else if (intent.getAction().equals(ACTION_START_OUTGOING_CALL)) handleStartOutgoingCall(intent); else if (intent.getAction().equals(ACTION_START_INCOMING_CALL)) handleStartIncomingCall(intent); else if (intent.getAction().equals(ACTION_ACCEPT_CALL)) handleAcceptCall(intent); else if (intent.getAction().equals(ACTION_LOCAL_RINGING)) handleLocalRinging(intent); else if (intent.getAction().equals(ACTION_REMOTE_RINGING)) handleRemoteRinging(intent); else if (intent.getAction().equals(ACTION_SEND_OFFER)) handleSendOffer(intent); else if (intent.getAction().equals(ACTION_SEND_ANSWER)) handleSendAnswer(intent); else if (intent.getAction().equals(ACTION_SEND_ICE_CANDIDATES)) handleSendIceCandidates(intent); else if (intent.getAction().equals(ACTION_SEND_HANGUP)) handleSendHangup(intent); else if (intent.getAction().equals(ACTION_SEND_BUSY)) handleSendBusy(intent); else if (intent.getAction().equals(ACTION_RECEIVE_ANSWER)) handleReceivedAnswer(intent); else if (intent.getAction().equals(ACTION_RECEIVE_ICE_CANDIDATES)) handleReceivedIceCandidates(intent); else if (intent.getAction().equals(ACTION_RECEIVE_HANGUP)) handleReceivedHangup(intent); else if (intent.getAction().equals(ACTION_REMOTE_VIDEO_ENABLE)) handleRemoteVideoEnable(intent); else if (intent.getAction().equals(ACTION_SET_ENABLE_VIDEO)) handleSetEnableVideo(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_HANGUP)) handleEndedRemoteHangup(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_HANGUP_ACCEPTED)) handleEndedRemoteHangupAccepted(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_HANGUP_BUSY)) handleEndedRemoteHangupBusy(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_HANGUP_DECLINED)) handleEndedRemoteHangupDeclined(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_BUSY)) handleEndedRemoteBusy(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION)) handleEndedRemoteNeedPermission(intent); else if (intent.getAction().equals(ACTION_ENDED_REMOTE_GLARE)) handleEndedRemoteGlare(intent); else if (intent.getAction().equals(ACTION_ENDED_TIMEOUT)) handleEndedTimeout(intent); else if (intent.getAction().equals(ACTION_ENDED_INTERNAL_FAILURE)) handleEndedInternalFailure(intent); else if (intent.getAction().equals(ACTION_ENDED_SIGNALING_FAILURE)) handleEndedSignalingFailure(intent); else if (intent.getAction().equals(ACTION_ENDED_CONNECTION_FAILURE)) handleEndedConnectionFailure(intent); else if (intent.getAction().equals(ACTION_RECEIVED_OFFER_EXPIRED)) handleReceivedOfferExpired(intent); else if (intent.getAction().equals(ACTION_RECEIVED_OFFER_WHILE_ACTIVE)) handleReceivedOfferWhileActive(intent); else if (intent.getAction().equals(ACTION_CALL_CONCLUDED)) handleCallConcluded(intent); }); return START_NOT_STICKY; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy"); if (callManager != null) { try { callManager.close(); } catch (CallException e) { Log.w(TAG, "Unable to close call manager: ", e); } callManager = null; } if (callReceiver != null) { unregisterReceiver(callReceiver); } if (uncaughtExceptionHandlerManager != null) { uncaughtExceptionHandlerManager.unregister(); } if (bluetoothStateManager != null) { bluetoothStateManager.onDestroy(); } if (wiredHeadsetStateReceiver != null) { unregisterReceiver(wiredHeadsetStateReceiver); wiredHeadsetStateReceiver = null; } if (powerButtonReceiver != null) { unregisterReceiver(powerButtonReceiver); powerButtonReceiver = null; } TelephonyUtil.getManager(this) .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE); } @Override public void onBluetoothStateChanged(boolean isAvailable) { Log.i(TAG, "onBluetoothStateChanged: " + isAvailable); Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_BLUETOOTH_CHANGE); intent.putExtra(EXTRA_AVAILABLE, isAvailable); startService(intent); } @Override public void onCameraSwitchCompleted(@NonNull CameraState newCameraState) { localCameraState = newCameraState; if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else if (preJoinPeer != null) { sendMessage(WebRtcViewModel.State.CALL_PRE_JOIN, preJoinPeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void initializeResources() { this.messageSender = ApplicationDependencies.getSignalServiceMessageSender(); this.accountManager = ApplicationDependencies.getSignalServiceAccountManager(); this.lockManager = new LockManager(this); this.audioManager = new SignalAudioManager(this); this.bluetoothStateManager = new BluetoothStateManager(this, this); this.peerMap = new SparseArray<>(); this.messageSender.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); this.accountManager.setSoTimeoutMillis(TimeUnit.SECONDS.toMillis(10)); try { this.callManager = CallManager.createCallManager(this); } catch (CallException e) { callFailure("Unable to create Call Manager: ", e); } } private void registerIncomingPstnCallReceiver() { callReceiver = new IncomingPstnCallReceiver(); registerReceiver(callReceiver, new IntentFilter("android.intent.action.PHONE_STATE")); } private void registerUncaughtExceptionHandler() { uncaughtExceptionHandlerManager = new UncaughtExceptionHandlerManager(); uncaughtExceptionHandlerManager.registerHandler(new ProximityLockRelease(lockManager)); } private void registerWiredHeadsetStateReceiver() { wiredHeadsetStateReceiver = new WiredHeadsetStateReceiver(); String action; if (Build.VERSION.SDK_INT >= 21) { action = AudioManager.ACTION_HEADSET_PLUG; } else { action = Intent.ACTION_HEADSET_PLUG; } registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action)); } private void registerPowerButtonReceiver() { if (powerButtonReceiver == null) { powerButtonReceiver = new PowerButtonReceiver(); registerReceiver(powerButtonReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF)); } } private void unregisterPowerButtonReceiver() { if (powerButtonReceiver != null) { unregisterReceiver(powerButtonReceiver); powerButtonReceiver = null; } } private void handleReceivedOffer(Intent intent) { CallId callId = getCallId(intent); RemotePeer remotePeer = getRemotePeer(intent); byte[] remoteIdentityKey = intent.getByteArrayExtra(EXTRA_REMOTE_IDENTITY_KEY); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); byte[] opaque = intent.getByteArrayExtra(EXTRA_OFFER_OPAQUE); String sdp = intent.getStringExtra(EXTRA_OFFER_SDP); long serverReceivedTimestamp = intent.getLongExtra(EXTRA_SERVER_RECEIVED_TIMESTAMP, -1); long serverDeliveredTimestamp = intent.getLongExtra(EXTRA_SERVER_DELIVERED_TIMESTAMP, -1); OfferMessage.Type offerType = OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE)); boolean isMultiRing = intent.getBooleanExtra(EXTRA_MULTI_RING, false); Log.i(TAG, "handleReceivedOffer(): id: " + callId.format(remoteDevice)); if (TelephonyUtil.isAnyPstnLineBusy(this)) { Log.i(TAG, "PSTN line is busy."); intent.putExtra(EXTRA_BROADCAST, true); handleSendBusy(intent); insertMissedCall(remotePeer, true); return; } if (remotePeer.getRecipient() == null || !RecipientUtil.isCallRequestAccepted(getApplicationContext(), remotePeer.getRecipient())) { Log.w(TAG, "Caller is untrusted."); intent.putExtra(EXTRA_BROADCAST, true); intent.putExtra(EXTRA_HANGUP_TYPE, HangupMessage.Type.NEED_PERMISSION.getCode()); handleSendHangup(intent); insertMissedCall(remotePeer, true); return; } peerMap.append(remotePeer.hashCode(), remotePeer); Log.i(TAG, "add remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode()); isRemoteVideoOffer = offerType == OfferMessage.Type.VIDEO_CALL; CallManager.CallMediaType callType = getCallMediaTypeFromOfferType(offerType); long messageAgeSec = Math.max(serverDeliveredTimestamp - serverReceivedTimestamp, 0) / 1000; Log.i(TAG, "messageAgeSec: " + messageAgeSec + ", serverReceivedTimestamp: " + serverReceivedTimestamp + ", serverDeliveredTimestamp: " + serverDeliveredTimestamp); try { remoteIdentityKey = getPublicKeyBytes(remoteIdentityKey); byte[] localIdentityKey = getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(this).serialize()); callManager.receivedOffer(callId, remotePeer, remoteDevice, opaque, sdp, messageAgeSec, callType, 1, isMultiRing, true, remoteIdentityKey, localIdentityKey); } catch (CallException | InvalidKeyException e) { callFailure("Unable to process received offer: ", e); } } private void handlePreJoinCall(Intent intent) { Log.i(TAG, "handlePreJoinCall():"); RemotePeer remotePeer = getRemotePeer(intent); if (remotePeer.getState() != CallState.IDLE) { throw new IllegalStateException("Dialing from non-idle?"); } preJoinPeer = remotePeer; initializeVideo(); localCameraState = initializeVanityCamera(); EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); sendMessage(WebRtcViewModel.State.CALL_PRE_JOIN, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, false); } private @NonNull CameraState initializeVanityCamera() { if (camera == null || localSink == null) { return CameraState.UNKNOWN; } if (camera.hasCapturer()) { camera.initCapturer(new CapturerObserver() { @Override public void onFrameCaptured(VideoFrame videoFrame) { localSink.onFrame(videoFrame); } @Override public void onCapturerStarted(boolean success) {} @Override public void onCapturerStopped() {} }); camera.setEnabled(true); } return camera.getCameraState(); } private void handleCancelPreJoinCall() { cleanupVideo(); preJoinPeer = null; } private void handleOutgoingCall(Intent intent) { Log.i(TAG, "handleOutgoingCall():"); RemotePeer remotePeer = getRemotePeer(intent); if (remotePeer.getState() != CallState.IDLE) { throw new IllegalStateException("Dialing from non-idle?"); } preJoinPeer = null; EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); peerMap.append(remotePeer.hashCode(), remotePeer); Log.i(TAG, "add remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode()); initializeVideo(); remoteParticipantMap.put(remotePeer.getRecipient(), CallParticipant.createRemote( remotePeer.getRecipient(), null, new BroadcastVideoSink(eglBase), false )); OfferMessage.Type offerType = OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE)); CallManager.CallMediaType callMediaType = getCallMediaTypeFromOfferType(offerType); try { callManager.call(remotePeer, callMediaType, 1); } catch (CallException e) { callFailure("Unable to create outgoing call: ", e); } } private void handleIsInCallQuery(Intent intent) { ResultReceiver resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER); if (resultReceiver != null) { resultReceiver.send(activePeer != null ? 1 : 0, null); } } private void insertMissedCall(@NonNull RemotePeer remotePeer, boolean signal) { Pair messageAndThreadId = DatabaseFactory.getSmsDatabase(this).insertMissedCall(remotePeer.getId()); ApplicationDependencies.getMessageNotifier().updateNotification(this, messageAndThreadId.second(), signal); } private void handleDenyCall(Intent intent) { if (activePeer == null) { Log.i(TAG, "handleDenyCall(): Ignoring for inactive call."); return; } if (activePeer.getState() != CallState.LOCAL_RINGING) { Log.w(TAG, "Can only deny from ringing!"); return; } Log.i(TAG, "handleDenyCall():"); try { callManager.hangup(); DatabaseFactory.getSmsDatabase(this).insertMissedCall(activePeer.getId()); terminate(activePeer); } catch (CallException e) { callFailure("hangup() failed: ", e); } } private void handleSetSpeakerAudio(Intent intent) { boolean isSpeaker = intent.getBooleanExtra(EXTRA_SPEAKER, false); AudioManager audioManager = ServiceUtil.getAudioManager(this); bluetoothStateManager.setWantsConnection(false); audioManager.setSpeakerphoneOn(isSpeaker); if (!localCameraState.isEnabled()) { lockManager.updatePhoneState(getInCallPhoneState()); } if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void handleSetBluetoothAudio(Intent intent) { boolean isBluetooth = intent.getBooleanExtra(EXTRA_BLUETOOTH, false); bluetoothStateManager.setWantsConnection(isBluetooth); if (!localCameraState.isEnabled()) { lockManager.updatePhoneState(getInCallPhoneState()); } if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void handleSetMuteAudio(Intent intent) { boolean muted = intent.getBooleanExtra(EXTRA_MUTE, false); microphoneEnabled = !muted; if (activePeer == null) { Log.w(TAG, "handleSetMuteAudio(): Ignoring for inactive call."); return; } if (activePeer.getState() == CallState.CONNECTED) { try { callManager.setAudioEnable(microphoneEnabled); } catch (CallException e) { callFailure("Enabling audio failed: ", e); } } if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void handleSetCameraFlip(Intent intent) { Log.i(TAG, "handleSetCameraFlip()..."); if (localCameraState.isEnabled() && camera != null) { camera.flip(); localCameraState = camera.getCameraState(); if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else if (preJoinPeer != null) { sendMessage(WebRtcViewModel.State.CALL_PRE_JOIN, preJoinPeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } } private void handleBluetoothChange(Intent intent) { bluetoothAvailable = intent.getBooleanExtra(EXTRA_AVAILABLE, false); if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void handleWiredHeadsetChange(Intent intent) { Log.i(TAG, "handleWiredHeadsetChange..."); if ((activePeer != null) && (activePeer.getState() == CallState.CONNECTED || activePeer.getState() == CallState.DIALING || activePeer.getState() == CallState.RECEIVED_BUSY || activePeer.getState() == CallState.REMOTE_RINGING)) { AudioManager audioManager = ServiceUtil.getAudioManager(this); boolean present = intent.getBooleanExtra(EXTRA_AVAILABLE, false); if (present && audioManager.isSpeakerphoneOn()) { audioManager.setSpeakerphoneOn(false); audioManager.setBluetoothScoOn(false); } else if (!present && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && localCameraState.isEnabled()) { audioManager.setSpeakerphoneOn(true); } sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } private void handleScreenOffChange(Intent intent) { if ((activePeer != null) && (activePeer.getState() == CallState.ANSWERING || activePeer.getState() == CallState.LOCAL_RINGING)) { Log.i(TAG, "Silencing incoming ringer..."); audioManager.silenceIncomingRinger(); } } private void handleStartOutgoingCall(Intent intent) { Log.i(TAG, "handleStartOutgoingCall():"); if (activePeer != null) { throw new IllegalStateException("handleStartOutgoingCall(): activePeer already set"); } RemotePeer remotePeer = getRemotePeerFromMap(intent); activePeer = remotePeer; activePeer.dialing(); Log.i(TAG, "assign activePeer callId: " + activePeer.getCallId() + " key: " + activePeer.hashCode()); AudioManager androidAudioManager = ServiceUtil.getAudioManager(this); androidAudioManager.setSpeakerphoneOn(false); sendMessage(WebRtcViewModel.State.CALL_OUTGOING, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); lockManager.updatePhoneState(getInCallPhoneState()); audioManager.initializeAudioForCall(); audioManager.startOutgoingRinger(OutgoingRinger.Type.RINGING); bluetoothStateManager.setWantsConnection(true); setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); DatabaseFactory.getSmsDatabase(this).insertOutgoingCall(activePeer.getId()); retrieveTurnServers().addListener(new SuccessOnlyListener>(this.activePeer.getState(), this.activePeer.getCallId()) { @Override public void onSuccessContinue(List iceServers) { boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); try { callManager.proceed(activePeer.getCallId(), WebRtcCallService.this, eglBase, localSink, remoteParticipantMap.get(activePeer.getRecipient()).getVideoSink(), camera, iceServers, isAlwaysTurn, enableVideoOnCreate); } catch (CallException e) { callFailure("Unable to proceed with call: ", e); } localCameraState = camera.getCameraState(); if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } }); } private void handleStartIncomingCall(Intent intent) { Log.i(TAG, "handleStartIncomingCall():"); if (activePeer != null) { throw new IllegalStateException("handleStartIncomingCall(): activePeer already set"); } RemotePeer remotePeer = getRemotePeerFromMap(intent); activePeer = remotePeer; activePeer.answering(); Log.i(TAG, "assign activePeer callId: " + activePeer.getCallId() + " key: " + activePeer.hashCode()); AudioManager androidAudioManager = ServiceUtil.getAudioManager(this); androidAudioManager.setSpeakerphoneOn(false); initializeVideo(); remoteParticipantMap.put(remotePeer.getRecipient(), CallParticipant.createRemote( remotePeer.getRecipient(), null, new BroadcastVideoSink(eglBase), false )); setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); retrieveTurnServers().addListener(new SuccessOnlyListener>(this.activePeer.getState(), this.activePeer.getCallId()) { @Override public void onSuccessContinue(List iceServers) { boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); boolean hideIp = !activePeer.getRecipient().isSystemContact() || isAlwaysTurn; try { callManager.proceed(activePeer.getCallId(), WebRtcCallService.this, eglBase, localSink, remoteParticipantMap.get(activePeer.getRecipient()).getVideoSink(), camera, iceServers, hideIp, false); } catch (CallException e) { callFailure("Unable to proceed with call: ", e); } lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING); if (activePeer != null) { sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } }); } private void handleAcceptCall(Intent intent) { if (activePeer != null && activePeer.getState() != CallState.LOCAL_RINGING) { Log.w(TAG, "handleAcceptCall(): Ignoring for inactive call."); return; } Log.i(TAG, "handleAcceptCall(): call_id: " + activePeer.getCallId()); DatabaseFactory.getSmsDatabase(this).insertReceivedCall(activePeer.getId()); acceptWithVideo = intent.getBooleanExtra(EXTRA_ANSWER_WITH_VIDEO, false); try { callManager.acceptCall(activePeer.getCallId()); } catch (CallException e) { callFailure("accept() failed: ", e); } } private void handleSendOffer(Intent intent) { RemotePeer remotePeer = getRemotePeer(intent); CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); byte[] opaque = intent.getByteArrayExtra(EXTRA_OFFER_OPAQUE); String sdp = intent.getStringExtra(EXTRA_OFFER_SDP); OfferMessage.Type offerType = OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE)); Log.i(TAG, "handleSendOffer(): id: " + callId.format(remoteDevice)); OfferMessage offerMessage = new OfferMessage(callId.longValue(), sdp, offerType, opaque); Integer destinationDeviceId = broadcast ? null : remoteDevice; SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOffer(offerMessage, true, destinationDeviceId); sendCallMessage(remotePeer, callMessage); } private void handleSendAnswer(Intent intent) { RemotePeer remotePeer = getRemotePeer(intent); CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); byte[] opaque = intent.getByteArrayExtra(EXTRA_ANSWER_OPAQUE); String sdp = intent.getStringExtra(EXTRA_ANSWER_SDP); Log.i(TAG, "handleSendAnswer(): id: " + callId.format(remoteDevice)); AnswerMessage answerMessage = new AnswerMessage(callId.longValue(), sdp, opaque); Integer destinationDeviceId = broadcast ? null : remoteDevice; SignalServiceCallMessage callMessage = SignalServiceCallMessage.forAnswer(answerMessage, true, destinationDeviceId); sendCallMessage(remotePeer, callMessage); } private void handleSendIceCandidates(Intent intent) { RemotePeer remotePeer = getRemotePeer(intent); CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); ArrayList iceCandidates = intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES); Log.i(TAG, "handleSendIceCandidates(): id: " + callId.format(remoteDevice)); LinkedList iceUpdateMessages = new LinkedList(); for (IceCandidateParcel parcel : iceCandidates) { iceUpdateMessages.add(parcel.getIceUpdateMessage(callId)); } Integer destinationDeviceId = broadcast ? null : remoteDevice; SignalServiceCallMessage callMessage = SignalServiceCallMessage.forIceUpdates(iceUpdateMessages, true, destinationDeviceId); sendCallMessage(remotePeer, callMessage); } private void handleSendHangup(Intent intent) { RemotePeer remotePeer = getRemotePeer(intent); CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); HangupMessage.Type type = HangupMessage.Type.fromCode(intent.getStringExtra(EXTRA_HANGUP_TYPE)); boolean isLegacy = intent.getBooleanExtra(EXTRA_HANGUP_IS_LEGACY, true); int deviceId = intent.getIntExtra(EXTRA_HANGUP_DEVICE_ID, 0); Log.i(TAG, "handleSendHangup(): id: " + callId.format(remoteDevice)); HangupMessage hangupMessage = new HangupMessage(callId.longValue(), type, deviceId, isLegacy); Integer destinationDeviceId = broadcast ? null : remoteDevice; SignalServiceCallMessage callMessage = SignalServiceCallMessage.forHangup(hangupMessage, true, destinationDeviceId); sendCallMessage(remotePeer, callMessage); } private void handleSendBusy(Intent intent) { RemotePeer remotePeer = getRemotePeer(intent); CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); Log.i(TAG, "handleSendBusy(): id: " + callId.format(remoteDevice)); BusyMessage busyMessage = new BusyMessage(callId.longValue()); Integer destinationDeviceId = broadcast ? null : remoteDevice; SignalServiceCallMessage callMessage = SignalServiceCallMessage.forBusy(busyMessage, true, destinationDeviceId); sendCallMessage(remotePeer, callMessage); } private void handleReceivedAnswer(Intent intent) { CallId callId = getCallId(intent); byte[] remoteIdentityKey = intent.getByteArrayExtra(EXTRA_REMOTE_IDENTITY_KEY); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); byte[] opaque = intent.getByteArrayExtra(EXTRA_ANSWER_OPAQUE); String sdp = intent.getStringExtra(EXTRA_ANSWER_SDP); boolean isMultiRing = intent.getBooleanExtra(EXTRA_MULTI_RING, false); Log.i(TAG, "handleReceivedAnswer(): id: " + callId.format(remoteDevice)); try { remoteIdentityKey = getPublicKeyBytes(remoteIdentityKey); byte[] localIdentityKey = getPublicKeyBytes(IdentityKeyUtil.getIdentityKey(this).serialize()); callManager.receivedAnswer(callId, remoteDevice, opaque, sdp, isMultiRing, remoteIdentityKey, localIdentityKey); } catch (CallException | InvalidKeyException e) { callFailure("receivedAnswer() failed: ", e); } } private void handleReceivedIceCandidates(Intent intent) { CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); ArrayList iceCandidateParcels = intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES); Log.i(TAG, "handleReceivedIceCandidates(): id: " + callId.format(remoteDevice) + ", count: " + iceCandidateParcels.size()); LinkedList iceCandidates = new LinkedList(); for (IceCandidateParcel parcel : iceCandidateParcels) { iceCandidates.add(parcel.getIceCandidate()); } try { callManager.receivedIceCandidates(callId, remoteDevice, iceCandidates); } catch (CallException e) { callFailure("receivedIceCandidates() failed: ", e); } } private void handleReceivedHangup(Intent intent) { CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); HangupMessage.Type hangupType = HangupMessage.Type.fromCode(intent.getStringExtra(EXTRA_HANGUP_TYPE)); Integer deviceId = intent.getIntExtra(EXTRA_HANGUP_DEVICE_ID, 0); CallManager.HangupType callHangupType = getCallHangupTypeFromHangupType(hangupType); Log.i(TAG, "handleReceivedHangup(): id: " + callId.format(remoteDevice)); try { callManager.receivedHangup(callId, remoteDevice, callHangupType, deviceId); } catch (CallException e) { callFailure("receivedHangup() failed: ", e); } } private void handleReceivedBusy(Intent intent) { CallId callId = getCallId(intent); Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); Log.i(TAG, "handleReceivedBusy(): id: " + callId.format(remoteDevice)); try { callManager.receivedBusy(callId, remoteDevice); } catch (CallException e) { callFailure("receivedBusy() failed: ", e); } } private void handleLocalRinging(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Recipient recipient = remotePeer.getRecipient(); if (!remotePeer.callIdEquals(activePeer)) { Log.w(TAG, "handleLocalRinging(): Ignoring for inactive call."); return; } Log.i(TAG, "handleLocalRinging(): call_id: " + remotePeer.getCallId()); activePeer.localRinging(); lockManager.updatePhoneState(LockManager.PhoneState.INTERACTIVE); sendMessage(WebRtcViewModel.State.CALL_INCOMING, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(getApplicationContext(), recipient); if (shouldDisturbUserWithCall) { startCallCardActivityIfPossible(); } audioManager.initializeAudioForCall(); if (shouldDisturbUserWithCall && TextSecurePreferences.isCallNotificationsEnabled(this)) { Uri ringtone = recipient.resolve().getCallRingtone(); VibrateState vibrateState = recipient.resolve().getCallVibrate(); if (ringtone == null) ringtone = TextSecurePreferences.getCallNotificationRingtone(this); audioManager.startIncomingRinger(ringtone, vibrateState == VibrateState.ENABLED || (vibrateState == VibrateState.DEFAULT && TextSecurePreferences.isCallNotificationVibrateEnabled(this))); } registerPowerButtonReceiver(); setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); } private void handleRemoteRinging(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); if (!remotePeer.callIdEquals(activePeer)) { Log.w(TAG, "handleRemoteRinging(): Ignoring for inactive call."); return; } Log.i(TAG, "handleRemoteRinging(): call_id: " + remotePeer.getCallId()); activePeer.remoteRinging(); sendMessage(WebRtcViewModel.State.CALL_RINGING, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } private void handleCallConnected(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); if (!remotePeer.callIdEquals(activePeer)) { Log.w(TAG, "handleCallConnected(): Ignoring for inactive call."); return; } Log.i(TAG, "handleCallConnected(): call_id: " + remotePeer.getCallId()); audioManager.startCommunication(activePeer.getState() == CallState.REMOTE_RINGING); bluetoothStateManager.setWantsConnection(true); activePeer.connected(); if (localCameraState.isEnabled()) { lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); } else { lockManager.updatePhoneState(getInCallPhoneState()); } callConnectedTime = System.currentTimeMillis(); sendMessage(WebRtcViewModel.State.CALL_CONNECTED, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); unregisterPowerButtonReceiver(); setCallInProgressNotification(TYPE_ESTABLISHED, activePeer); try { callManager.setCommunicationMode(); callManager.setAudioEnable(microphoneEnabled); callManager.setVideoEnable(localCameraState.isEnabled()); } catch (CallException e) { callFailure("Enabling audio/video failed: ", e); } if (acceptWithVideo) { handleSetEnableVideo(new Intent().putExtra(EXTRA_ENABLE, true)); } } private void handleRemoteVideoEnable(Intent intent) { Boolean enable = intent.getBooleanExtra(EXTRA_ENABLE, false); if (activePeer == null) { Log.w(TAG, "handleRemoteVideoEnable(): Ignoring for inactive call."); return; } Log.i(TAG, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId()); CallParticipant oldParticipant = Objects.requireNonNull(remoteParticipantMap.get(activePeer.getRecipient())); CallParticipant newParticipant = oldParticipant.withVideoEnabled(enable); remoteParticipantMap.put(activePeer.getRecipient(), newParticipant); sendMessage(WebRtcViewModel.State.CALL_CONNECTED, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } private void handleSetEnableVideo(Intent intent) { boolean enable = intent.getBooleanExtra(EXTRA_ENABLE, false); AudioManager audioManager = ServiceUtil.getAudioManager(this); if (activePeer == null) { if (preJoinPeer != null) { Log.w(TAG, "handleSetEnableVideo(): Changing for pre-join call."); camera.setEnabled(enable); enableVideoOnCreate = enable; localCameraState = camera.getCameraState(); sendMessage(WebRtcViewModel.State.CALL_PRE_JOIN, preJoinPeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else { Log.w(TAG, "handleSetEnableVideo(): Ignoring for inactive call."); } return; } Log.i(TAG, "handleSetEnableVideo(): call_id: " + activePeer.getCallId()); if (activePeer.getState() != CallState.CONNECTED) { enableVideoOnCreate = enable; if (enableVideoOnCreate && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && !audioManager.isWiredHeadsetOn()) { audioManager.setSpeakerphoneOn(true); } return; } try { callManager.setVideoEnable(enable); } catch (CallException e) { callFailure("setVideoEnable() failed: ", e); return; } localCameraState = camera.getCameraState(); if (localCameraState.isEnabled()) { lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO); } else { lockManager.updatePhoneState(getInCallPhoneState()); } if (localCameraState.isEnabled() && !audioManager.isSpeakerphoneOn() && !audioManager.isBluetoothScoOn() && !audioManager.isWiredHeadsetOn()) { audioManager.setSpeakerphoneOn(true); } sendMessage(viewModelStateFor(activePeer), activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } private void handleLocalHangup(Intent intent) { if (activePeer == null) { if (busyPeer != null) { sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, busyPeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); busyPeer = null; } Log.w(TAG, "handleLocalHangup(): Ignoring for inactive call."); return; } Log.i(TAG, "handleLocalHangup(): call_id: " + activePeer.getCallId()); accountManager.cancelInFlightRequests(); messageSender.cancelInFlightRequests(); try { callManager.hangup(); sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); terminate(activePeer); } catch (CallException e) { callFailure("hangup() failed: ", e); } } private void handleReceivedOfferExpired(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleReceivedOfferExpired(): call_id: " + remotePeer.getCallId()); insertMissedCall(remotePeer, true); terminate(remotePeer); } private void handleReceivedOfferWhileActive(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); if (activePeer == null) { Log.w(TAG, "handleReceivedOfferWhileActive(): Ignoring for inactive call."); return; } Log.i(TAG, "handleReceivedOfferWhileActive(): call_id: " + remotePeer.getCallId()); switch (activePeer.getState()) { case DIALING: case REMOTE_RINGING: setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); break; case IDLE: setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); break; case ANSWERING: setCallInProgressNotification(TYPE_INCOMING_CONNECTING, activePeer); break; case LOCAL_RINGING: setCallInProgressNotification(TYPE_INCOMING_RINGING, activePeer); break; case CONNECTED: setCallInProgressNotification(TYPE_ESTABLISHED, activePeer); break; default: throw new IllegalStateException(); } if (activePeer.getState() == CallState.IDLE) { stopForeground(true); } insertMissedCall(remotePeer, true); terminate(remotePeer); } private void handleEndedRemoteHangup(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteHangup(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { boolean outgoingBeforeAccept = remotePeer.getState() == CallState.DIALING || remotePeer.getState() == CallState.REMOTE_RINGING; if (outgoingBeforeAccept) { sendMessage(WebRtcViewModel.State.RECIPIENT_UNAVAILABLE, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else { sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } boolean incomingBeforeAccept = remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING; if (incomingBeforeAccept) { insertMissedCall(remotePeer, true); } terminate(remotePeer); } private void handleEndedRemoteHangupAccepted(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteHangupAccepted(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.CALL_ACCEPTED_ELSEWHERE, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } terminate(remotePeer); } private void handleEndedRemoteHangupBusy(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteHangupBusy(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.CALL_ONGOING_ELSEWHERE, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } terminate(remotePeer); } private void handleEndedRemoteHangupDeclined(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteHangupDeclined(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.CALL_DECLINED_ELSEWHERE, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } terminate(remotePeer); } private void handleEndedRemoteBusy(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteBusy(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { activePeer.receivedBusy(); busyPeer = activePeer; OutgoingRinger ringer = new OutgoingRinger(this); ringer.start(OutgoingRinger.Type.BUSY); Util.runOnMainDelayed(() -> { ringer.stop(); busyPeer = null; }, BUSY_TONE_LENGTH); sendMessage(WebRtcViewModel.State.CALL_BUSY, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } terminate(remotePeer); } private void handleEndedRemoteNeedPermission(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteNeedPermission(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.CALL_NEEDS_PERMISSION, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } terminate(remotePeer); } private void handleEndedRemoteGlare(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteGlare(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, remotePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } boolean incomingBeforeAccept = remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING; if (incomingBeforeAccept) { insertMissedCall(remotePeer, true); } terminate(remotePeer); } private void handleEndedFailure(Intent intent) { RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedFailure(): call_id: " + remotePeer.getCallId()); if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) { insertMissedCall(remotePeer, true); } terminate(remotePeer); } private void handleEndedTimeout(Intent intent) { Log.i(TAG, "handleEndedTimeout():"); handleEndedFailure(intent); } private void handleEndedInternalFailure(Intent intent) { Log.i(TAG, "handleEndedInternalFailure():"); handleEndedFailure(intent); } private void handleEndedSignalingFailure(Intent intent) { Log.i(TAG, "handleEndedSignalingFailure():"); handleEndedFailure(intent); } private void handleEndedConnectionFailure(Intent intent) { Log.i(TAG, "handleEndedConnectionFailure():"); handleEndedFailure(intent); } private void handleCallConcluded(Intent intent) { Log.i(TAG, "handleCallConcluded():"); RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "delete remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode()); peerMap.delete(remotePeer.hashCode()); } private boolean isIdle() { return activePeer == null; } private static byte[] getPublicKeyBytes(byte[] identityKey) throws InvalidKeyException { ECPublicKey key = Curve.decodePoint(identityKey, 0); if (key instanceof DjbECPublicKey) { return ((DjbECPublicKey) key).getPublicKey(); } throw new InvalidKeyException(); } private void initializeVideo() { Util.runOnMainSync(() -> { if (eglBase == null) { eglBase = EglBase.create(); localSink = new BroadcastVideoSink(eglBase); } if (camera != null) { camera.setEnabled(false); camera.dispose(); } camera = new Camera(WebRtcCallService.this, WebRtcCallService.this, eglBase, localCameraState.getActiveDirection()); localCameraState = camera.getCameraState(); }); } private void cleanupVideo() { if (camera != null) { camera.dispose(); camera = null; } if (eglBase != null) { eglBase.release(); eglBase = null; } localCameraState = CameraState.UNKNOWN; } private void setCallInProgressNotification(int type, RemotePeer remotePeer) { startForeground(CallNotificationBuilder.getNotificationId(getApplicationContext(), type), CallNotificationBuilder.getCallInProgressNotification(this, type, remotePeer.getRecipient())); } private synchronized void terminate(RemotePeer remotePeer) { Log.i(TAG, "terminate():"); if (activePeer == null) { Log.i(TAG, "skipping with no active peer"); return; } if (!remotePeer.callIdEquals(activePeer)) { Log.i(TAG, "skipping remotePeer is not active peer"); return; } lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING); stopForeground(true); boolean playDisconnectSound = (activePeer.getState() == CallState.DIALING) || (activePeer.getState() == CallState.REMOTE_RINGING) || (activePeer.getState() == CallState.RECEIVED_BUSY) || (activePeer.getState() == CallState.CONNECTED); audioManager.stop(playDisconnectSound); bluetoothStateManager.setWantsConnection(false); cleanupVideo(); this.microphoneEnabled = true; this.enableVideoOnCreate = false; Log.i(TAG, "clear activePeer callId: " + activePeer.getCallId() + " key: " + activePeer.hashCode()); this.activePeer = null; remoteParticipantMap.clear(); lockManager.updatePhoneState(LockManager.PhoneState.IDLE); } private void sendMessage(@NonNull WebRtcViewModel.State state, @NonNull RemotePeer remotePeer, @NonNull CameraState localCameraState, boolean bluetoothAvailable, boolean microphoneEnabled, boolean isRemoteVideoOffer) { EventBus.getDefault().postSticky(new WebRtcViewModel(state, remotePeer.getRecipient(), localCameraState, localSink, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer, callConnectedTime, new ArrayList<>(remoteParticipantMap.values()))); } private ListenableFutureTask sendMessage(@NonNull final RemotePeer remotePeer, @NonNull final SignalServiceCallMessage callMessage) { Callable callable = new Callable() { @Override public Boolean call() throws Exception { Recipient recipient = remotePeer.getRecipient(); messageSender.sendCallMessage(RecipientUtil.toSignalServiceAddress(WebRtcCallService.this, recipient), UnidentifiedAccessUtil.getAccessFor(WebRtcCallService.this, recipient), callMessage); return true; } }; ListenableFutureTask listenableFutureTask = new ListenableFutureTask<>(callable, null, serviceExecutor); networkExecutor.execute(listenableFutureTask); return listenableFutureTask; } private void startCallCardActivityIfPossible() { if (Build.VERSION.SDK_INT >= 29 && !ApplicationContext.getInstance(getApplicationContext()).isAppVisible()) { return; } Intent activityIntent = new Intent(); activityIntent.setClass(this, WebRtcCallActivity.class); activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); this.startActivity(activityIntent); } private static @NonNull CallId getCallId(Intent intent) { return new CallId(intent.getLongExtra(EXTRA_CALL_ID, -1)); } private static @NonNull RemotePeer getRemotePeer(Intent intent) { RemotePeer remotePeer = intent.getParcelableExtra(EXTRA_REMOTE_PEER); if (remotePeer == null) throw new AssertionError("No RemotePeer in intent!"); return remotePeer; } private static @NonNull int getRemotePeerKey(Intent intent) { if (!intent.getExtras().containsKey(EXTRA_REMOTE_PEER_KEY)) { throw new AssertionError("No RemotePeer key in intent!"); } // The default of -1 should never be applied since the key exists. int remotePeerKey = intent.getIntExtra(EXTRA_REMOTE_PEER_KEY, -1); return remotePeerKey; } private @NonNull RemotePeer getRemotePeerFromMap(Intent intent) { int remotePeerKey = getRemotePeerKey(intent); RemotePeer remotePeer = peerMap.get(remotePeerKey); if (remotePeer == null) { throw new AssertionError("No RemotePeer in map for key: " + remotePeerKey + "!"); } return remotePeer; } private void callFailure(String message, Throwable error) { Log.w(TAG, "callFailure(): " + message, error); if (activePeer != null) { sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } if (callManager != null) { try { callManager.reset(); } catch (CallException e) { Log.w(TAG, "Unable to reset call manager: ", e); } } else { Log.w(TAG, "No call manager, not reseting. Error message: " + message , error); } terminate(activePeer); peerMap.clear(); } private static @NonNull CallManager.CallMediaType getCallMediaTypeFromOfferType(@NonNull OfferMessage.Type offerType) { return offerType == OfferMessage.Type.VIDEO_CALL ? CallManager.CallMediaType.VIDEO_CALL : CallManager.CallMediaType.AUDIO_CALL; } private static @NonNull OfferMessage.Type getOfferTypeFromCallMediaType(@NonNull CallManager.CallMediaType mediaType) { return mediaType == CallManager.CallMediaType.VIDEO_CALL ? OfferMessage.Type.VIDEO_CALL : OfferMessage.Type.AUDIO_CALL; } private static @NonNull CallManager.HangupType getCallHangupTypeFromHangupType(@NonNull HangupMessage.Type hangupType) { switch (hangupType) { 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: " + hangupType); } } private static @NonNull HangupMessage.Type getHangupTypeFromCallHangupType(@NonNull CallManager.HangupType hangupType) { switch (hangupType) { case ACCEPTED: return HangupMessage.Type.ACCEPTED; case BUSY: return HangupMessage.Type.BUSY; case NORMAL: return HangupMessage.Type.NORMAL; case DECLINED: return HangupMessage.Type.DECLINED; case NEED_PERMISSION: return HangupMessage.Type.NEED_PERMISSION; default: throw new IllegalArgumentException("Unexpected hangup type: " + hangupType); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } private WebRtcViewModel.State viewModelStateFor(@NonNull RemotePeer remotePeer) { switch (remotePeer.getState()) { case CONNECTED: return WebRtcViewModel.State.CALL_CONNECTED; case DIALING: return WebRtcViewModel.State.CALL_OUTGOING; case REMOTE_RINGING: return WebRtcViewModel.State.CALL_RINGING; case LOCAL_RINGING: return WebRtcViewModel.State.CALL_INCOMING; case ANSWERING: return WebRtcViewModel.State.CALL_INCOMING; case IDLE: return WebRtcViewModel.State.CALL_DISCONNECTED; } return WebRtcViewModel.State.CALL_DISCONNECTED; } private static class WiredHeadsetStateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra("state", -1); Intent serviceIntent = new Intent(context, WebRtcCallService.class); serviceIntent.setAction(WebRtcCallService.ACTION_WIRED_HEADSET_CHANGE); serviceIntent.putExtra(WebRtcCallService.EXTRA_AVAILABLE, state != 0); context.startService(serviceIntent); } } private static class PowerButtonReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { Intent serviceIntent = new Intent(context, WebRtcCallService.class); serviceIntent.setAction(WebRtcCallService.ACTION_SCREEN_OFF); context.startService(serviceIntent); } } } private static class ProximityLockRelease implements Thread.UncaughtExceptionHandler { private final LockManager lockManager; private ProximityLockRelease(LockManager lockManager) { this.lockManager = lockManager; } @Override public void uncaughtException(Thread thread, Throwable throwable) { Log.i(TAG, "Uncaught exception - releasing proximity lock", throwable); lockManager.updatePhoneState(LockManager.PhoneState.IDLE); } } public static void isCallActive(@NonNull Context context, @NonNull ResultReceiver resultReceiver) { Intent intent = new Intent(context, WebRtcCallService.class); intent.setAction(ACTION_IS_IN_CALL_QUERY); intent.putExtra(EXTRA_RESULT_RECEIVER, resultReceiver); context.startService(intent); } private class HangUpRtcOnPstnCallAnsweredListener extends PhoneStateListener { @Override public void onCallStateChanged(int state, String phoneNumber) { super.onCallStateChanged(state, phoneNumber); if (state == TelephonyManager.CALL_STATE_OFFHOOK) { hangup(); Log.i(TAG, "Device phone call ended Signal call."); } } private void hangup() { Intent intent = new Intent(WebRtcCallService.this, WebRtcCallService.class); intent.setAction(ACTION_LOCAL_HANGUP); startService(intent); } } private ListenableFutureTask> retrieveTurnServers() { Callable> callable = () -> { LinkedList results = new LinkedList<>(); results.add(new PeerConnection.IceServer("stun:stun1.l.google.com:19302")); try { TurnServerInfo turnServerInfo = accountManager.getTurnServerInfo(); for (String url : turnServerInfo.getUrls()) { Log.i(TAG, "ice_server: " + url); if (url.startsWith("turn")) { results.add(new PeerConnection.IceServer(url, turnServerInfo.getUsername(), turnServerInfo.getPassword())); } else { results.add(new PeerConnection.IceServer(url)); } } } catch (IOException e) { Log.w(TAG, e); } return results; }; ListenableFutureTask> futureTask = new ListenableFutureTask<>(callable, null, serviceExecutor); networkExecutor.execute(futureTask); return futureTask; } private abstract class StateAwareListener implements FutureTaskListener { private final CallState expectedState; private final CallId expectedCallId; StateAwareListener(CallState expectedState, CallId expectedCallId) { this.expectedState = expectedState; this.expectedCallId = expectedCallId; } public CallId getCallId() { return this.expectedCallId; } @Override public void onSuccess(V result) { if (!isConsistentState()) { Log.i(TAG, "State has changed since request, skipping success callback..."); onStateChangeContinue(); } else { onSuccessContinue(result); } } @Override public void onFailure(ExecutionException throwable) { if (!isConsistentState()) { Log.w(TAG, throwable); Log.w(TAG, "State has changed since request, skipping failure callback..."); onStateChangeContinue(); } else { onFailureContinue(throwable.getCause()); } } public void onStateChangeContinue() {} private boolean isConsistentState() { return activePeer != null && expectedState == activePeer.getState() && expectedCallId.equals(activePeer.getCallId()); } public abstract void onSuccessContinue(V result); public abstract void onFailureContinue(Throwable throwable); } private abstract class FailureListener extends StateAwareListener { FailureListener(CallState expectedState, CallId expectedCallId) { super(expectedState, expectedCallId); } @Override public void onSuccessContinue(V result) {} } private abstract class SuccessOnlyListener extends StateAwareListener { SuccessOnlyListener(CallState expectedState, CallId expectedCallId) { super(expectedState, expectedCallId); } @Override public void onFailureContinue(Throwable throwable) { Log.w(TAG, throwable); throw new AssertionError(throwable); } } private class SendCallMessageListener extends StateAwareListener { SendCallMessageListener(RemotePeer expectedRemotePeer) { super(expectedRemotePeer.getState(), expectedRemotePeer.getCallId()); } @Override public void onSuccessContinue(V result) { if (callManager != null) { try { callManager.messageSent(getCallId()); } catch (CallException e) { callFailure("callManager.messageSent() failed: ", e); } } } @Override public void onStateChangeContinue() { if (callManager != null) { try { callManager.messageSent(getCallId()); } catch (CallException e) { callFailure("callManager.messageSent() failed: ", e); } } } @Override public void onFailureContinue(Throwable error) { Log.w(TAG, error); if (callManager != null) { try { callManager.messageSendFailure(getCallId()); } catch (CallException e) { callFailure("callManager.messageSendFailure() failed: ", e); } } if (activePeer == null) { return; } if (error instanceof UntrustedIdentityException) { CallParticipant participant = Objects.requireNonNull(remoteParticipantMap.get(activePeer.getRecipient())); CallParticipant untrusted = participant.withIdentityKey(((UntrustedIdentityException) error).getIdentityKey()); remoteParticipantMap.put(activePeer.getRecipient(), untrusted); sendMessage(WebRtcViewModel.State.UNTRUSTED_IDENTITY, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else if (error instanceof UnregisteredUserException) { sendMessage(WebRtcViewModel.State.NO_SUCH_USER, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } else if (error instanceof IOException) { sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, activePeer, localCameraState, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } } } private void sendCallMessage(RemotePeer remotePeer, SignalServiceCallMessage callMessage) { ListenableFutureTask listenableFutureTask = sendMessage(remotePeer, callMessage); listenableFutureTask.addListener(new SendCallMessageListener<>(remotePeer)); } private LockManager.PhoneState getInCallPhoneState() { AudioManager audioManager = ServiceUtil.getAudioManager(this); if (audioManager.isSpeakerphoneOn() || audioManager.isBluetoothScoOn() || audioManager.isWiredHeadsetOn()) { return LockManager.PhoneState.IN_HANDS_FREE_CALL; } else { return LockManager.PhoneState.IN_CALL; } } @Override public void onStartCall(Remote remote, CallId callId, Boolean isOutgoing, CallManager.CallMediaType callMediaType) { Log.i(TAG, "onStartCall(): callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer) remote; if (peerMap.get(remotePeer.hashCode()) == null) { Log.w(TAG, "remotePeer not found in map with key: " + remotePeer.hashCode() + "! Dropping."); try { callManager.drop(callId); } catch (CallException e) { callFailure("callManager.drop() failed: ", e); } } remotePeer.setCallId(callId); Intent intent = new Intent(this, WebRtcCallService.class); if (isOutgoing) { intent.setAction(ACTION_START_OUTGOING_CALL); } else { intent.setAction(ACTION_START_INCOMING_CALL); } intent.putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onCallEvent(Remote remote, CallEvent event) { if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer) remote; if (peerMap.get(remotePeer.hashCode()) == null) { throw new AssertionError("remotePeer not found in map!"); } Log.i(TAG, "onCallEvent(): call_id: " + remotePeer.getCallId() + ", state: " + remotePeer.getState() + ", event: " + event); Intent intent = new Intent(this, WebRtcCallService.class); intent.putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); switch (event) { case LOCAL_RINGING: intent.setAction(ACTION_LOCAL_RINGING); break; case REMOTE_RINGING: intent.setAction(ACTION_REMOTE_RINGING); break; case RECONNECTING: Log.i(TAG, "Reconnecting: NOT IMPLEMENTED"); break; case RECONNECTED: Log.i(TAG, "Reconnected: NOT IMPLEMENTED"); break; case LOCAL_CONNECTED: case REMOTE_CONNECTED: intent.setAction(ACTION_CALL_CONNECTED); break; case REMOTE_VIDEO_ENABLE: intent.setAction(ACTION_REMOTE_VIDEO_ENABLE) .putExtra(EXTRA_ENABLE, true); break; case REMOTE_VIDEO_DISABLE: intent.setAction(ACTION_REMOTE_VIDEO_ENABLE) .putExtra(EXTRA_ENABLE, false); break; case ENDED_REMOTE_HANGUP: intent.setAction(ACTION_ENDED_REMOTE_HANGUP); break; case ENDED_REMOTE_HANGUP_NEED_PERMISSION: intent.setAction(ACTION_ENDED_REMOTE_HANGUP_NEED_PERMISSION); break; case ENDED_REMOTE_HANGUP_ACCEPTED: intent.setAction(ACTION_ENDED_REMOTE_HANGUP_ACCEPTED); break; case ENDED_REMOTE_HANGUP_BUSY: intent.setAction(ACTION_ENDED_REMOTE_HANGUP_BUSY); break; case ENDED_REMOTE_HANGUP_DECLINED: intent.setAction(ACTION_ENDED_REMOTE_HANGUP_DECLINED); break; case ENDED_REMOTE_BUSY: intent.setAction(ACTION_ENDED_REMOTE_BUSY); break; case ENDED_REMOTE_GLARE: intent.setAction(ACTION_ENDED_REMOTE_GLARE); break; case ENDED_TIMEOUT: intent.setAction(ACTION_ENDED_TIMEOUT); break; case ENDED_INTERNAL_FAILURE: intent.setAction(ACTION_ENDED_INTERNAL_FAILURE); break; case ENDED_SIGNALING_FAILURE: intent.setAction(ACTION_ENDED_SIGNALING_FAILURE); break; case ENDED_CONNECTION_FAILURE: intent.setAction(ACTION_ENDED_CONNECTION_FAILURE); break; case RECEIVED_OFFER_EXPIRED: intent.setAction(ACTION_RECEIVED_OFFER_EXPIRED); break; case RECEIVED_OFFER_WHILE_ACTIVE: case RECEIVED_OFFER_WITH_GLARE: intent.setAction(ACTION_RECEIVED_OFFER_WHILE_ACTIVE); break; case ENDED_LOCAL_HANGUP: case ENDED_APP_DROPPED_CALL: case IGNORE_CALLS_FROM_NON_MULTIRING_CALLERS: Log.i(TAG, "Ignoring event: " + event); return; default: throw new AssertionError("Unexpected event: " + event.toString()); } startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onCallConcluded(Remote remote) { if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; Log.i(TAG, "onCallConcluded: call_id: " + remotePeer.getCallId()); Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_CALL_CONCLUDED) .putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onSendOffer(CallId callId, Remote remote, Integer remoteDevice, Boolean broadcast, byte[] opaque, String sdp, CallManager.CallMediaType callMediaType) { Log.i(TAG, "onSendOffer: id: " + callId.format(remoteDevice) + " type: " + callMediaType.name()); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; String offerType = getOfferTypeFromCallMediaType(callMediaType).getCode(); Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_SEND_OFFER) .putExtra(EXTRA_CALL_ID, callId.longValue()) .putExtra(EXTRA_REMOTE_PEER, remotePeer) .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) .putExtra(EXTRA_BROADCAST, broadcast) .putExtra(EXTRA_OFFER_OPAQUE, opaque) .putExtra(EXTRA_OFFER_SDP, sdp) .putExtra(EXTRA_OFFER_TYPE, offerType); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onSendAnswer(CallId callId, Remote remote, Integer remoteDevice, Boolean broadcast, byte[] opaque, String sdp) { Log.i(TAG, "onSendAnswer: id: " + callId.format(remoteDevice)); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_SEND_ANSWER) .putExtra(EXTRA_CALL_ID, callId.longValue()) .putExtra(EXTRA_REMOTE_PEER, remotePeer) .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) .putExtra(EXTRA_BROADCAST, broadcast) .putExtra(EXTRA_ANSWER_OPAQUE, opaque) .putExtra(EXTRA_ANSWER_SDP, sdp); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onSendIceCandidates(CallId callId, Remote remote, Integer remoteDevice, Boolean broadcast, List iceCandidates) { Log.i(TAG, "onSendIceCandidates: id: " + callId.format(remoteDevice)); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; Intent intent = new Intent(this, WebRtcCallService.class); ArrayList iceCandidateParcels = new ArrayList<>(iceCandidates.size()); for (IceCandidate iceCandidate : iceCandidates) { iceCandidateParcels.add(new IceCandidateParcel(iceCandidate)); } intent.setAction(ACTION_SEND_ICE_CANDIDATES) .putExtra(EXTRA_CALL_ID, callId.longValue()) .putExtra(EXTRA_REMOTE_PEER, remotePeer) .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) .putExtra(EXTRA_BROADCAST, broadcast) .putParcelableArrayListExtra(EXTRA_ICE_CANDIDATES, iceCandidateParcels); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onSendHangup(CallId callId, Remote remote, Integer remoteDevice, Boolean broadcast, CallManager.HangupType hangupType, Integer deviceId, Boolean useLegacyHangupMessage) { Log.i(TAG, "onSendHangup: id: " + callId.format(remoteDevice) + " type: " + hangupType.name() + " isLegacy: " + useLegacyHangupMessage); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_SEND_HANGUP) .putExtra(EXTRA_CALL_ID, callId.longValue()) .putExtra(EXTRA_REMOTE_PEER, remotePeer) .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) .putExtra(EXTRA_BROADCAST, broadcast) .putExtra(EXTRA_HANGUP_DEVICE_ID, deviceId.intValue()) .putExtra(EXTRA_HANGUP_IS_LEGACY, useLegacyHangupMessage.booleanValue()) .putExtra(EXTRA_HANGUP_TYPE, getHangupTypeFromCallHangupType(hangupType).getCode()); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } @Override public void onSendBusy(CallId callId, Remote remote, Integer remoteDevice, Boolean broadcast) { Log.i(TAG, "onSendBusy: id: " + callId.format(remoteDevice)); if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_SEND_BUSY) .putExtra(EXTRA_CALL_ID, callId.longValue()) .putExtra(EXTRA_REMOTE_PEER, remotePeer) .putExtra(EXTRA_REMOTE_DEVICE, remoteDevice) .putExtra(EXTRA_BROADCAST, broadcast); startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer"); } } }