diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index 4a95288e9..24dac2be5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -64,7 +64,6 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe private static final String TAG = WebRtcCallActivity.class.getSimpleName(); private static final int STANDARD_DELAY_FINISH = 1000; - public static final int BUSY_SIGNAL_DELAY_FINISH = 5500; public static final String ANSWER_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".ANSWER_ACTION"; public static final String DENY_ACTION = WebRtcCallActivity.class.getCanonicalName() + ".DENY_ACTION"; @@ -408,8 +407,7 @@ public class WebRtcCallActivity extends AppCompatActivity implements SafetyNumbe EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); callScreen.setRecipient(event.getRecipient()); callScreen.setStatus(getString(R.string.RedPhone_busy)); - - delayedFinish(BUSY_SIGNAL_DELAY_FINISH); + delayedFinish(WebRtcCallService.BUSY_TONE_LENGTH); } private void handleCallConnected(@NonNull WebRtcViewModel event) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/CallState.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/CallState.java index 8bbf6f993..1f1037e0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/CallState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/CallState.java @@ -10,10 +10,10 @@ public enum CallState { /** Idle, setting up objects */ IDLE, - /** Dialing. Outgoing call is signaling the remote peer */ + /** Dialing. Outgoing call is signaling the remote peer */ DIALING, - /** Answering. Incoming call is responding to remote peer */ + /** Answering. Incoming call is responding to remote peer */ ANSWERING, /** Remote ringing. Outgoing call, ICE negotiation is complete */ @@ -25,10 +25,9 @@ public enum CallState { /** Connected. Incoming/Outgoing call, the call is connected */ CONNECTED, - /** Terminated. Incoming/Outgoing call, the call is terminated */ + /** Terminated. Incoming/Outgoing call, the call is terminated */ TERMINATED, - /** Busy. Outgoing call received a busy notification */ + /** Busy. Outgoing call received a busy notification */ RECEIVED_BUSY; - } diff --git a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/RemotePeer.java b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/RemotePeer.java index 9106fb108..dedf1580d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ringrtc/RemotePeer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ringrtc/RemotePeer.java @@ -40,6 +40,10 @@ public final class RemotePeer implements Remote, Parcelable return callId; } + public void setCallId(@NonNull CallId callId) { + this.callId = callId; + } + public @NonNull CallState getState() { return callState; } @@ -73,21 +77,19 @@ public final class RemotePeer implements Remote, Parcelable return remotePeer != null && this.callId.equals(remotePeer.callId); } - public void dialing(@NonNull CallId callId) { + public void dialing() { if (callState != CallState.IDLE) { throw new IllegalStateException("Cannot transition to DIALING from state: " + callState); } - this.callId = callId; this.callState = CallState.DIALING; } - public void answering(@NonNull CallId callId) { + public void answering() { if (callState != CallState.IDLE) { throw new IllegalStateException("Cannot transition to ANSWERING from state: " + callState); } - this.callId = callId; this.callState = CallState.ANSWERING; } @@ -99,14 +101,6 @@ public final class RemotePeer implements Remote, Parcelable this.callState = CallState.REMOTE_RINGING; } - public void receivedBusy() { - if (callState != CallState.DIALING) { - Log.w(TAG, "RECEIVED_BUSY from unexpected state: " + callState); - } - - this.callState = CallState.RECEIVED_BUSY; - } - public void localRinging() { if (callState != CallState.ANSWERING) { throw new IllegalStateException("Cannot transition to LOCAL_RINGING from state: " + callState); @@ -123,6 +117,14 @@ public final class RemotePeer implements Remote, Parcelable this.callState = CallState.CONNECTED; } + public void receivedBusy() { + if (callState != CallState.DIALING) { + Log.w(TAG, "RECEIVED_BUSY from unexpected state: " + callState); + } + + this.callState = CallState.RECEIVED_BUSY; + } + @Override public int describeContents() { return 0; diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java index ddad832e0..e0e69fd32 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java @@ -12,6 +12,7 @@ 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; @@ -47,6 +48,7 @@ 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.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder; import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver; import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager; @@ -68,7 +70,6 @@ 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.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException; import java.io.IOException; @@ -102,6 +103,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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_OFFER_OPAQUE = "offer_opaque"; public static final String EXTRA_OFFER_SDP = "offer_sdp"; @@ -161,6 +163,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer, public static final String ACTION_ENDED_RX_OFFER_WHILE_ACTIVE = "ENDED_RX_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 remoteVideoEnabled = false; @@ -181,12 +185,14 @@ public class WebRtcCallService extends Service implements CallManager.Observer, private IncomingPstnCallReceiver callReceiver; private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; - @Nullable private CallManager callManager; - @Nullable private RemotePeer activePeer; - @Nullable private TextureViewRenderer localRenderer; - @Nullable private TextureViewRenderer remoteRenderer; - @Nullable private EglBase eglBase; - @Nullable private Camera camera; + @Nullable private CallManager callManager; + @Nullable private RemotePeer activePeer; + @Nullable private RemotePeer busyPeer; + @Nullable private SparseArray peerMap; + @Nullable private TextureViewRenderer localRenderer; + @Nullable private TextureViewRenderer remoteRenderer; + @Nullable private EglBase eglBase; + @Nullable private Camera camera; private final ExecutorService serviceExecutor = Executors.newSingleThreadExecutor(); private final ExecutorService networkExecutor = Executors.newSingleThreadExecutor(); @@ -257,7 +263,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, else if (intent.getAction().equals(ACTION_ENDED_RX_OFFER_EXPIRED)) handleEndedReceivedOfferExpired(intent); else if (intent.getAction().equals(ACTION_ENDED_RX_OFFER_WHILE_ACTIVE)) handleEndedReceivedOfferWhileActive(intent); else if (intent.getAction().equals(ACTION_CALL_CONCLUDED)) handleCallConcluded(intent); - }); return START_NOT_STICKY; @@ -322,15 +327,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } } - - // Initializers - 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)); @@ -340,7 +343,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } catch (CallException e) { callFailure("Unable to create Call Manager: ", e); } - } private void registerIncomingPstnCallReceiver() { @@ -383,8 +385,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } } - // Handlers - private void handleReceivedOffer(Intent intent) { CallId callId = getCallId(intent); RemotePeer remotePeer = getRemotePeer(intent); @@ -399,7 +399,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, Log.i(TAG, "handleReceivedOffer(): id: " + callId.format(remoteDevice)); if (TelephonyUtil.isAnyPstnLineBusy(this)) { - Log.i(TAG, "handleReceivedOffer(): PSTN line is busy."); + Log.i(TAG, "PSTN line is busy."); intent.putExtra(EXTRA_BROADCAST, true); handleSendBusy(intent); insertMissedCall(remotePeer, true); @@ -407,7 +407,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } if (remotePeer.getRecipient() == null || !RecipientUtil.isCallRequestAccepted(getApplicationContext(), remotePeer.getRecipient())) { - Log.i(TAG, "handleReceivedOffer(): Caller is untrusted."); + Log.w(TAG, "Caller is untrusted."); intent.putExtra(EXTRA_BROADCAST, true); intent.putExtra(EXTRA_HANGUP_TYPE, HangupMessage.Type.NEED_PERMISSION.getCode()); handleSendHangup(intent); @@ -415,12 +415,15 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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, "handleReceivedOffer(): messageAgeSec: " + messageAgeSec + ", serverReceivedTimestamp: " + serverReceivedTimestamp + ", serverDeliveredTimestamp: " + serverDeliveredTimestamp); + Log.i(TAG, "messageAgeSec: " + messageAgeSec + ", serverReceivedTimestamp: " + serverReceivedTimestamp + ", serverDeliveredTimestamp: " + serverDeliveredTimestamp); try { callManager.receivedOffer(callId, remotePeer, remoteDevice, opaque, sdp, messageAgeSec, callType, 1, isMultiRing, true); @@ -430,15 +433,19 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } 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?"); } - Log.i(TAG, "handleOutgoingCall():"); EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); + peerMap.append(remotePeer.hashCode(), remotePeer); + Log.i(TAG, "add remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode()); + initializeVideo(); OfferMessage.Type offerType = OfferMessage.Type.fromCode(intent.getStringExtra(EXTRA_OFFER_TYPE)); @@ -480,6 +487,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, try { callManager.hangup(); DatabaseFactory.getSmsDatabase(this).insertMissedCall(activePeer.getId()); + terminate(activePeer); } catch (CallException e) { callFailure("hangup() failed: ", e); } @@ -591,7 +599,19 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleStartOutgoingCall(Intent intent) { - Log.i(TAG, "handleStartOutgoingCall(): callId: " + activePeer.getCallId()); + 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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); lockManager.updatePhoneState(getInCallPhoneState()); @@ -609,9 +629,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, boolean isAlwaysTurn = TextSecurePreferences.isTurnOnly(WebRtcCallService.this); - LinkedList deviceList = new LinkedList(); - deviceList.add(SignalServiceAddress.DEFAULT_DEVICE_ID); - try { callManager.proceed(activePeer.getCallId(), WebRtcCallService.this, @@ -635,11 +652,19 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleStartIncomingCall(Intent intent) { - if (activePeer.getState() != CallState.ANSWERING) { - throw new IllegalStateException("StartIncoming while non-ANSWERING"); + Log.i(TAG, "handleStartIncomingCall():"); + + if (activePeer != null) { + throw new IllegalStateException("handleStartIncomingCall(): activePeer already set"); } - Log.i(TAG, "handleStartIncomingCall(): callId: " + activePeer.getCallId()); + 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(); @@ -648,12 +673,9 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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; - LinkedList deviceList = new LinkedList<>(); - try { callManager.proceed(activePeer.getCallId(), WebRtcCallService.this, @@ -677,7 +699,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleAcceptCall(Intent intent) { - if (activePeer != null && activePeer.getState() != CallState.LOCAL_RINGING) { Log.w(TAG, "handleAcceptCall(): Ignoring for inactive call."); return; @@ -705,7 +726,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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)); + Log.i(TAG, "handleSendOffer(): id: " + callId.format(remoteDevice)); OfferMessage offerMessage = new OfferMessage(callId.longValue(), sdp, offerType, opaque); Integer destinationDeviceId = broadcast ? null : remoteDevice; @@ -722,7 +743,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, byte[] opaque = intent.getByteArrayExtra(EXTRA_ANSWER_OPAQUE); String sdp = intent.getStringExtra(EXTRA_ANSWER_SDP); - Log.i(TAG, "handleSendAnswer: id: " + callId.format(remoteDevice)); + Log.i(TAG, "handleSendAnswer(): id: " + callId.format(remoteDevice)); AnswerMessage answerMessage = new AnswerMessage(callId.longValue(), sdp, opaque); Integer destinationDeviceId = broadcast ? null : remoteDevice; @@ -738,7 +759,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); ArrayList iceCandidates = intent.getParcelableArrayListExtra(EXTRA_ICE_CANDIDATES); - Log.i(TAG, "handleSendIceCandidates: id: " + callId.format(remoteDevice)); + Log.i(TAG, "handleSendIceCandidates(): id: " + callId.format(remoteDevice)); LinkedList iceUpdateMessages = new LinkedList(); for (IceCandidateParcel parcel : iceCandidates) { @@ -760,7 +781,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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)); + Log.i(TAG, "handleSendHangup(): id: " + callId.format(remoteDevice)); HangupMessage hangupMessage = new HangupMessage(callId.longValue(), type, deviceId, isLegacy); Integer destinationDeviceId = broadcast ? null : remoteDevice; @@ -775,7 +796,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, Integer remoteDevice = intent.getIntExtra(EXTRA_REMOTE_DEVICE, -1); boolean broadcast = intent.getBooleanExtra(EXTRA_BROADCAST, false); - Log.i(TAG, "handleSendBusy: id: " + callId.format(remoteDevice)); + Log.i(TAG, "handleSendBusy(): id: " + callId.format(remoteDevice)); BusyMessage busyMessage = new BusyMessage(callId.longValue()); Integer destinationDeviceId = broadcast ? null : remoteDevice; @@ -805,7 +826,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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()); + Log.i(TAG, "handleReceivedIceCandidates(): id: " + callId.format(remoteDevice) + ", count: " + iceCandidateParcels.size()); LinkedList iceCandidates = new LinkedList(); for (IceCandidateParcel parcel : iceCandidateParcels) { @@ -849,16 +870,16 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleLocalRinging(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); + RemotePeer remotePeer = getRemotePeerFromMap(intent); Recipient recipient = remotePeer.getRecipient(); - Log.i(TAG, "handleLocalRinging(): call_id: " + remotePeer.getCallId()); - 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); @@ -883,30 +904,29 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleRemoteRinging(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); - Recipient recipient = remotePeer.getRecipient(); - - Log.i(TAG, "handleRemoteRinging(): call_id: " + remotePeer.getCallId()); + 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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } private void handleCallConnected(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); - - Log.i(TAG, "handleCallConnected: call_id: " + remotePeer.getCallId()); + 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); @@ -947,7 +967,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer, return; } - Log.i(TAG, "handleRemoteVideoEnable: call_id: " + activePeer.getCallId()); + Log.i(TAG, "handleRemoteVideoEnable(): call_id: " + activePeer.getCallId()); remoteVideoEnabled = enable; sendMessage(WebRtcViewModel.State.CALL_CONNECTED, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); @@ -1007,46 +1027,49 @@ public class WebRtcCallService extends Service implements CallManager.Observer, private void handleLocalHangup(Intent intent) { if (activePeer == null) { + if (busyPeer != null) { + sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, busyPeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); + busyPeer = null; + } + Log.w(TAG, "handleLocalHangup(): Ignoring for inactive call."); return; } Log.i(TAG, "handleLocalHangup(): call_id: " + activePeer.getCallId()); - if (activePeer.getState() == CallState.RECEIVED_BUSY) { - sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); - terminate(); - } else { - accountManager.cancelInFlightRequests(); - messageSender.cancelInFlightRequests(); + accountManager.cancelInFlightRequests(); + messageSender.cancelInFlightRequests(); + try { + callManager.hangup(); sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); - - try { - callManager.hangup(); - } catch (CallException e) { - callFailure("hangup() failed: ", e); - } + terminate(activePeer); + } catch (CallException e) { + callFailure("hangup() failed: ", e); } } private void handleEndedReceivedOfferExpired(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); + RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedReceivedOfferExpired(): call_id: " + remotePeer.getCallId()); + insertMissedCall(remotePeer, true); + + terminate(remotePeer); } private void handleEndedReceivedOfferWhileActive(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); - - Log.i(TAG, "handleEndedReceivedOfferWhileActive(): call_id: " + remotePeer.getCallId()); + RemotePeer remotePeer = getRemotePeerFromMap(intent); if (activePeer == null) { - Log.w(TAG, "handleEndedReceivedOfferWhileActive(): ignoring call with null activePeer"); + Log.w(TAG, "handleEndedReceivedOfferWhileActive(): Ignoring for inactive call."); return; } + Log.i(TAG, "handleEndedReceivedOfferWhileActive(): call_id: " + remotePeer.getCallId()); + switch (activePeer.getState()) { case DIALING: case REMOTE_RINGING: setCallInProgressNotification(TYPE_OUTGOING_RINGING, activePeer); break; @@ -1062,10 +1085,12 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } insertMissedCall(remotePeer, true); + + terminate(remotePeer); } private void handleEndedRemoteHangup(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); + RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteHangup(): call_id: " + remotePeer.getCallId()); @@ -1082,80 +1107,102 @@ public class WebRtcCallService extends Service implements CallManager.Observer, if (incomingBeforeAccept) { insertMissedCall(remotePeer, true); } + + terminate(remotePeer); } private void handleEndedRemoteHangupAccepted(Intent intent) { - RemotePeer remotePeer = getRemotePeer(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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } + + terminate(remotePeer); } private void handleEndedRemoteHangupBusy(Intent intent) { - RemotePeer remotePeer = getRemotePeer(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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } + + terminate(remotePeer); } private void handleEndedRemoteHangupDeclined(Intent intent) { - RemotePeer remotePeer = getRemotePeer(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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } - } - private void delayedBusyFinish(CallId callId) { - if (activePeer != null && callId.equals(activePeer.getCallId())) { - Log.i(TAG, "delayedBusyFinish(): calling terminate()"); - terminate(); - } + terminate(remotePeer); } private void handleEndedRemoteBusy(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); - CallId callId = remotePeer.getCallId(); + RemotePeer remotePeer = getRemotePeerFromMap(intent); - Log.i(TAG, "handleEndedRemoteBusy(): call_id: " + callId); + Log.i(TAG, "handleEndedRemoteBusy(): call_id: " + remotePeer.getCallId()); - if (!remotePeer.callIdEquals(activePeer)) { - Log.w(TAG, "handleEndedRemoteBusy(): Ignoring for inactive call."); - return; + 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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } - activePeer.receivedBusy(); - sendMessage(WebRtcViewModel.State.CALL_BUSY, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); - - audioManager.startOutgoingRinger(OutgoingRinger.Type.BUSY); - Util.runOnMainDelayed(() -> { - delayedBusyFinish(callId); - }, WebRtcCallActivity.BUSY_SIGNAL_DELAY_FINISH); + terminate(remotePeer); } private void handleEndedRemoteNeedPermission(Intent intent) { - RemotePeer remotePeer = getRemotePeer(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, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } + + terminate(remotePeer); } private void handleEndedRemoteGlare(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); + RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedRemoteGlare(): call_id: " + remotePeer.getCallId()); - handleEndedRemoteBusy(intent); + + if (remotePeer.callIdEquals(activePeer)) { + sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, remotePeer, localCameraState, remoteVideoEnabled, 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 = getRemotePeer(intent); + RemotePeer remotePeer = getRemotePeerFromMap(intent); Log.i(TAG, "handleEndedFailure(): call_id: " + remotePeer.getCallId()); + if (remotePeer.callIdEquals(activePeer)) { sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } @@ -1163,6 +1210,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer, if (remotePeer.getState() == CallState.ANSWERING || remotePeer.getState() == CallState.LOCAL_RINGING) { insertMissedCall(remotePeer, true); } + + terminate(remotePeer); } private void handleEndedTimeout(Intent intent) { @@ -1190,22 +1239,15 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } private void handleCallConcluded(Intent intent) { - RemotePeer remotePeer = getRemotePeer(intent); + Log.i(TAG, "handleCallConcluded():"); - Log.i(TAG, "handleCallConcluded(): call_id: " + remotePeer.getCallId()); - if (!remotePeer.callIdEquals(activePeer)) { - Log.w(TAG, "handleCallConcluded(): Ignoring for inactive call."); - return; - } + RemotePeer remotePeer = getRemotePeerFromMap(intent); - boolean terminateAlreadyScheduled = activePeer.getState() == CallState.RECEIVED_BUSY; - if (!terminateAlreadyScheduled) { - terminate(); - } + Log.i(TAG, "delete remotePeer callId: " + remotePeer.getCallId() + " key: " + remotePeer.hashCode()); + + peerMap.delete(remotePeer.hashCode()); } - /// Helper Methods - private boolean isIdle() { return activePeer == null; } @@ -1222,7 +1264,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, camera = new Camera(WebRtcCallService.this, WebRtcCallService.this, eglBase); localCameraState = camera.getCameraState(); - }); } @@ -1231,11 +1272,16 @@ public class WebRtcCallService extends Service implements CallManager.Observer, CallNotificationBuilder.getCallInProgressNotification(this, type, remotePeer.getRecipient())); } - private synchronized void terminate() { - Log.i(TAG, "terminate()"); + private synchronized void terminate(RemotePeer remotePeer) { + Log.i(TAG, "terminate():"); if (activePeer == null) { - Log.i(TAG, "terminate(): skipping with no active peer"); + Log.i(TAG, "skipping with no active peer"); + return; + } + + if (!remotePeer.callIdEquals(activePeer)) { + Log.i(TAG, "skipping remotePeer is not active peer"); return; } @@ -1264,11 +1310,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } this.localCameraState = CameraState.UNKNOWN; - this.activePeer = null; this.microphoneEnabled = true; this.remoteVideoEnabled = false; this.enableVideoOnCreate = false; + Log.i(TAG, "clear activePeer callId: " + activePeer.getCallId() + " key: " + activePeer.hashCode()); + this.activePeer = null; + lockManager.updatePhoneState(LockManager.PhoneState.IDLE); } @@ -1356,8 +1404,29 @@ public class WebRtcCallService extends Service implements CallManager.Observer, 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, message, error); + Log.w(TAG, "callFailure(): " + message, error); if (activePeer != null) { sendMessage(WebRtcViewModel.State.CALL_DISCONNECTED, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); @@ -1373,7 +1442,9 @@ public class WebRtcCallService extends Service implements CallManager.Observer, Log.w(TAG, "No call manager, not reseting. Error message: " + message , error); } - terminate(); + terminate(activePeer); + + peerMap.clear(); } private static @NonNull CallManager.CallMediaType getCallMediaTypeFromOfferType(@NonNull OfferMessage.Type offerType) { @@ -1645,7 +1716,6 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } else if (error instanceof IOException) { sendMessage(WebRtcViewModel.State.NETWORK_FAILURE, activePeer, localCameraState, remoteVideoEnabled, bluetoothAvailable, microphoneEnabled, isRemoteVideoOffer); } - } } @@ -1663,34 +1733,32 @@ public class WebRtcCallService extends Service implements CallManager.Observer, } } - // CallManager observer callbacks - @Override public void onStartCall(Remote remote, CallId callId, Boolean isOutgoing, CallManager.CallMediaType callMediaType) { - Log.i(TAG, "onStartCall: callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType); - - if (activePeer != null) { - throw new IllegalStateException("activePeer already set for START_OUTGOING_CALL"); - } + Log.i(TAG, "onStartCall(): callId: " + callId + ", outgoing: " + isOutgoing + ", type: " + callMediaType); if (remote instanceof RemotePeer) { - activePeer = (RemotePeer)remote; + 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); - AudioManager audioManager = ServiceUtil.getAudioManager(this); - audioManager.setSpeakerphoneOn(false); - if (isOutgoing) { - intent.setAction(ACTION_START_OUTGOING_CALL); - activePeer.dialing(callId); + intent.setAction(ACTION_START_OUTGOING_CALL); } else { - intent.setAction(ACTION_START_INCOMING_CALL); - activePeer.answering(callId); + intent.setAction(ACTION_START_INCOMING_CALL); } - intent.putExtra(EXTRA_REMOTE_PEER, activePeer) - .putExtra(EXTRA_CALL_ID, callId.longValue()); + intent.putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); startService(intent); } else { @@ -1701,11 +1769,15 @@ public class WebRtcCallService extends Service implements CallManager.Observer, @Override public void onCallEvent(Remote remote, CallEvent event) { if (remote instanceof RemotePeer) { - RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); + 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() + ", event: " + event); - intent.putExtra(EXTRA_REMOTE_PEER, remotePeer); + 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: @@ -1790,11 +1862,13 @@ public class WebRtcCallService extends Service implements CallManager.Observer, public void onCallConcluded(Remote remote) { if (remote instanceof RemotePeer) { RemotePeer remotePeer = (RemotePeer)remote; - Intent intent = new Intent(this, WebRtcCallService.class); Log.i(TAG, "onCallConcluded: call_id: " + remotePeer.getCallId()); + + Intent intent = new Intent(this, WebRtcCallService.class); intent.setAction(ACTION_CALL_CONCLUDED) - .putExtra(EXTRA_REMOTE_PEER, remotePeer); + .putExtra(EXTRA_REMOTE_PEER_KEY, remotePeer.hashCode()); + startService(intent); } else { throw new AssertionError("Received remote is not instanceof RemotePeer");