Loading src/com/android/server/telecom/CallsManager.java +227 −136 Original line number Diff line number Diff line Loading @@ -1564,14 +1564,22 @@ public class CallsManager extends Call.ListenerBase CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged; // Do not support any more live calls. Our options are to move a call to hold, // disconnect a call, or cancel this call altogether. If a call is being reused, // then it has already passed the makeRoomForOutgoingCall check once and will // fail the second time due to the call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) { if (isPotentialInCallMMICode(handle) && !isSelfManaged) { return CompletableFuture.completedFuture(finalCall); } // If a call is being reused, then it has already passed the // makeRoomForOutgoingCall check once and will fail the second time due to the // call transitioning into the CONNECTING state. if (isReusedCall) { return CompletableFuture.completedFuture(finalCall); } // If we can not supportany more active calls, our options are to move a call // to hold, disconnect a call, or cancel this call altogether. boolean isRoomForCall = finalCall.isEmergencyCall() ? makeRoomForOutgoingEmergencyCall(finalCall) : makeRoomForOutgoingCall(finalCall); if (!isRoomForCall) { Call foregroundCall = getForegroundCall(); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); if (foregroundCall.isSelfManaged()) { Loading Loading @@ -3926,59 +3934,87 @@ public class CallsManager extends Call.ListenerBase } @VisibleForTesting public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { if (hasMaximumLiveCalls(call)) { // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) { // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize // distraction. This does not affect live call count. if (hasRingingOrSimulatedRingingCall()) { Call ringingCall = getRingingOrSimulatedRingingCall(); ringingCall.getAnalytics().setCallIsAdditional(true); ringingCall.getAnalytics().setCallIsInterrupted(true); if (ringingCall.getState() == CallState.SIMULATED_RINGING) { if (!ringingCall.hasGoneActiveBefore()) { // If this is an incoming call that is currently in SIMULATED_RINGING only // after a call screen, disconnect to make room and mark as missed, since // the user didn't get a chance to accept/reject. ringingCall.disconnect("emergency call dialed during simulated ringing " + "after screen."); } else { // If this is a simulated ringing call after being active and put in // AUDIO_PROCESSING state again, disconnect normally. ringingCall.reject(false, null, "emergency call dialed during simulated " + "ringing."); } } else { // normal incoming ringing call. // Hang up the ringing call to make room for the emergency call and mark as missed, // since the user did not reject. ringingCall.setOverrideDisconnectCauseCode( new DisconnectCause(DisconnectCause.MISSED)); ringingCall.reject(false, null, "emergency call dialed during ringing."); } } // There is already room! if (!hasMaximumLiveCalls(emergencyCall)) return true; Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + liveCall); Log.i(this, "makeRoomForOutgoingEmergencyCall call = " + emergencyCall + " livecall = " + liveCall); if (call == liveCall) { // If the call is already the foreground call, then we are golden. // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT // state since the call was already populated into the list. if (emergencyCall == liveCall) { // Not likely, but a good sanity check. return true; } if (hasMaximumOutgoingCalls(call)) { if (hasMaximumOutgoingCalls(emergencyCall)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the // user tries to make two outgoing calls to different emergency call numbers, // we will try to connect the first outgoing call. call.getAnalytics().setCallIsAdditional(true); if (!outgoingCall.isEmergencyCall()) { emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" + " emergency call."); return true; } if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // state, just disconnect it since the user has explicitly started a new call. call.getAnalytics().setCallIsAdditional(true); // Sanity check: if there is an orphaned emergency call in the // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user // has explicitly started a new call. emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); return true; } // If the user tries to make two outgoing calls to different emergency call numbers, // we will try to connect the first outgoing call and reject the second. return false; } if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) { call.getAnalytics().setCallIsAdditional(true); if (liveCall.getState() == CallState.AUDIO_PROCESSING) { emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.disconnect(0, "disconnecting audio processing call for emergency"); liveCall.disconnect("disconnecting audio processing call for emergency"); return true; } // If we have the max number of held managed calls and we're placing an emergency call, // we'll disconnect the ongoing call if it cannot be held. if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) { call.getAnalytics().setCallIsAdditional(true); if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) { emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); // Disconnect the active call instead of the holding call because it is historically // easier to do, rather than disconnect a held call. liveCall.disconnect("disconnecting to make room for emergency call " + call.getId()); + emergencyCall.getId()); return true; } Loading @@ -3994,7 +4030,7 @@ public class CallsManager extends Call.ListenerBase if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " + liveCallPhoneAccount); } Loading @@ -4003,7 +4039,7 @@ public class CallsManager extends Call.ListenerBase // will not be that one and we do not want multiple PhoneAccounts active during an // emergency call if possible. Disconnect the active call in favor of the emergency call // instead of trying to hold. if (isEmergency && liveCall.getTargetPhoneAccount() != null) { if (liveCall.getTargetPhoneAccount() != null) { PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( liveCall.getTargetPhoneAccount()); if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { Loading @@ -4015,6 +4051,88 @@ public class CallsManager extends Call.ListenerBase return true; } // First thing, if we are trying to make an emergency call with the same package name as // the live call, then allow it so that the connection service can make its own decision // about how to handle the new call relative to the current one. // By default, for telephony, it will try to hold the existing call before placing the new // emergency call except for if the carrier does not support holding calls for emergency. // In this case, telephony will disconnect the call. if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount, emergencyCall.getTargetPhoneAccount())) { Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccount matches."); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); return true; } else if (emergencyCall.getTargetPhoneAccount() == null) { // Without a phone account, we can't say reliably that the call will fail. // If the user chooses the same phone account as the live call, then it's // still possible that the call can be made (like with CDMA calls not supporting // hold but they still support adding a call by going immediately into conference // mode). Return true here and we'll run this code again after user chooses an // account. return true; } // Hold the live call if possible before attempting the new outgoing emergency call. if (canHold(liveCall)) { Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call."); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.hold("calling " + emergencyCall.getId()); return true; } // The live call cannot be held so we're out of luck here. There's no room. return false; } private boolean makeRoomForOutgoingCall(Call call) { // Already room! if (!hasMaximumLiveCalls(call)) return true; // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + liveCall); if (call == liveCall) { // If the call is already the foreground call, then we are golden. // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT // state since the call was already populated into the list. return true; } if (hasMaximumOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // state, just disconnect it since the user has explicitly started a new call. call.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); return true; } return false; } // TODO: Remove once b/23035408 has been corrected. // If the live call is a conference, it will not have a target phone account set. This // means the check to see if the live call has the same target phone account as the new // call will not cause us to bail early. As a result, we'll end up holding the // ongoing conference call. However, the ConnectionService is already doing that. This // has caused problems with some carriers. As a workaround until b/23035408 is // corrected, we will try and get the target phone account for one of the conference's // children and use that instead. PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount(); if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + liveCallPhoneAccount); } // First thing, if we are trying to make a call with the same phone account as the live // call, then allow it so that the connection service can make its own decision about // how to handle the new call relative to the current one. Loading Loading @@ -4045,33 +4163,6 @@ public class CallsManager extends Call.ListenerBase // The live call cannot be held so we're out of luck here. There's no room. return false; } else if (hasRingingOrSimulatedRingingCall() && isEmergency) { Call ringingCall = getRingingOrSimulatedRingingCall(); ringingCall.getAnalytics().setCallIsAdditional(true); ringingCall.getAnalytics().setCallIsInterrupted(true); if (ringingCall.getState() == CallState.SIMULATED_RINGING) { if (!ringingCall.hasGoneActiveBefore()) { // If this is an incoming call that is currently in SIMULATED_RINGING only // after a call screen, disconnect to make room and mark as missed, since // the user didn't get a chance to accept/reject. ringingCall.disconnect("emergency call dialed during simulated ringing " + "after screen."); } else { // If this is a simulated ringing call after being active and put in // AUDIO_PROCESSING state again, disconnect normally. ringingCall.reject(false, null, "emergency call dialed during simulated " + "ringing."); } } else { // normal incoming ringing call. // Hang up the ringing call to make room for the emergency call and mark as missed, // since the user did not reject. ringingCall.setOverrideDisconnectCauseCode( new DisconnectCause(DisconnectCause.MISSED)); ringingCall.reject(false, null, "emergency call dialed during ringing."); } return true; } return true; } /** Loading tests/src/com/android/server/telecom/tests/CallsManagerTest.java +25 −5 Original line number Diff line number Diff line Loading @@ -136,7 +136,8 @@ public class CallsManagerTest extends TelecomTestCase { ComponentName.unflattenFromString("com.baz/.Self"), "Self"); private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION | PhoneAccount.CAPABILITY_CALL_PROVIDER) | PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) .setIsEnabled(true) .build(); private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2") Loading Loading @@ -1133,7 +1134,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyLong(), anyString()); } Loading @@ -1148,7 +1149,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); } Loading @@ -1163,7 +1164,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyString()); } Loading @@ -1179,10 +1180,29 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); } @SmallTest @Test public void testMakeRoomForEmergencyCallDuringActiveAndRingingCallDisconnectRinging() { when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE)) .thenReturn(SIM_1_ACCOUNT); Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE); doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD); Call ringingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING); Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW); when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any())) .thenReturn(true); newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ringingCall).reject(anyBoolean(), any(), any()); } /** * Verifies that changes to a {@link PhoneAccount}'s * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. Loading Loading
src/com/android/server/telecom/CallsManager.java +227 −136 Original line number Diff line number Diff line Loading @@ -1564,14 +1564,22 @@ public class CallsManager extends Call.ListenerBase CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged; // Do not support any more live calls. Our options are to move a call to hold, // disconnect a call, or cancel this call altogether. If a call is being reused, // then it has already passed the makeRoomForOutgoingCall check once and will // fail the second time due to the call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) { if (isPotentialInCallMMICode(handle) && !isSelfManaged) { return CompletableFuture.completedFuture(finalCall); } // If a call is being reused, then it has already passed the // makeRoomForOutgoingCall check once and will fail the second time due to the // call transitioning into the CONNECTING state. if (isReusedCall) { return CompletableFuture.completedFuture(finalCall); } // If we can not supportany more active calls, our options are to move a call // to hold, disconnect a call, or cancel this call altogether. boolean isRoomForCall = finalCall.isEmergencyCall() ? makeRoomForOutgoingEmergencyCall(finalCall) : makeRoomForOutgoingCall(finalCall); if (!isRoomForCall) { Call foregroundCall = getForegroundCall(); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); if (foregroundCall.isSelfManaged()) { Loading Loading @@ -3926,59 +3934,87 @@ public class CallsManager extends Call.ListenerBase } @VisibleForTesting public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { if (hasMaximumLiveCalls(call)) { // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) { // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize // distraction. This does not affect live call count. if (hasRingingOrSimulatedRingingCall()) { Call ringingCall = getRingingOrSimulatedRingingCall(); ringingCall.getAnalytics().setCallIsAdditional(true); ringingCall.getAnalytics().setCallIsInterrupted(true); if (ringingCall.getState() == CallState.SIMULATED_RINGING) { if (!ringingCall.hasGoneActiveBefore()) { // If this is an incoming call that is currently in SIMULATED_RINGING only // after a call screen, disconnect to make room and mark as missed, since // the user didn't get a chance to accept/reject. ringingCall.disconnect("emergency call dialed during simulated ringing " + "after screen."); } else { // If this is a simulated ringing call after being active and put in // AUDIO_PROCESSING state again, disconnect normally. ringingCall.reject(false, null, "emergency call dialed during simulated " + "ringing."); } } else { // normal incoming ringing call. // Hang up the ringing call to make room for the emergency call and mark as missed, // since the user did not reject. ringingCall.setOverrideDisconnectCauseCode( new DisconnectCause(DisconnectCause.MISSED)); ringingCall.reject(false, null, "emergency call dialed during ringing."); } } // There is already room! if (!hasMaximumLiveCalls(emergencyCall)) return true; Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + liveCall); Log.i(this, "makeRoomForOutgoingEmergencyCall call = " + emergencyCall + " livecall = " + liveCall); if (call == liveCall) { // If the call is already the foreground call, then we are golden. // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT // state since the call was already populated into the list. if (emergencyCall == liveCall) { // Not likely, but a good sanity check. return true; } if (hasMaximumOutgoingCalls(call)) { if (hasMaximumOutgoingCalls(emergencyCall)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the // user tries to make two outgoing calls to different emergency call numbers, // we will try to connect the first outgoing call. call.getAnalytics().setCallIsAdditional(true); if (!outgoingCall.isEmergencyCall()) { emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" + " emergency call."); return true; } if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // state, just disconnect it since the user has explicitly started a new call. call.getAnalytics().setCallIsAdditional(true); // Sanity check: if there is an orphaned emergency call in the // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user // has explicitly started a new call. emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); return true; } // If the user tries to make two outgoing calls to different emergency call numbers, // we will try to connect the first outgoing call and reject the second. return false; } if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) { call.getAnalytics().setCallIsAdditional(true); if (liveCall.getState() == CallState.AUDIO_PROCESSING) { emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.disconnect(0, "disconnecting audio processing call for emergency"); liveCall.disconnect("disconnecting audio processing call for emergency"); return true; } // If we have the max number of held managed calls and we're placing an emergency call, // we'll disconnect the ongoing call if it cannot be held. if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) { call.getAnalytics().setCallIsAdditional(true); if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) { emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); // Disconnect the active call instead of the holding call because it is historically // easier to do, rather than disconnect a held call. liveCall.disconnect("disconnecting to make room for emergency call " + call.getId()); + emergencyCall.getId()); return true; } Loading @@ -3994,7 +4030,7 @@ public class CallsManager extends Call.ListenerBase if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " + liveCallPhoneAccount); } Loading @@ -4003,7 +4039,7 @@ public class CallsManager extends Call.ListenerBase // will not be that one and we do not want multiple PhoneAccounts active during an // emergency call if possible. Disconnect the active call in favor of the emergency call // instead of trying to hold. if (isEmergency && liveCall.getTargetPhoneAccount() != null) { if (liveCall.getTargetPhoneAccount() != null) { PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( liveCall.getTargetPhoneAccount()); if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { Loading @@ -4015,6 +4051,88 @@ public class CallsManager extends Call.ListenerBase return true; } // First thing, if we are trying to make an emergency call with the same package name as // the live call, then allow it so that the connection service can make its own decision // about how to handle the new call relative to the current one. // By default, for telephony, it will try to hold the existing call before placing the new // emergency call except for if the carrier does not support holding calls for emergency. // In this case, telephony will disconnect the call. if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount, emergencyCall.getTargetPhoneAccount())) { Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccount matches."); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); return true; } else if (emergencyCall.getTargetPhoneAccount() == null) { // Without a phone account, we can't say reliably that the call will fail. // If the user chooses the same phone account as the live call, then it's // still possible that the call can be made (like with CDMA calls not supporting // hold but they still support adding a call by going immediately into conference // mode). Return true here and we'll run this code again after user chooses an // account. return true; } // Hold the live call if possible before attempting the new outgoing emergency call. if (canHold(liveCall)) { Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call."); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.hold("calling " + emergencyCall.getId()); return true; } // The live call cannot be held so we're out of luck here. There's no room. return false; } private boolean makeRoomForOutgoingCall(Call call) { // Already room! if (!hasMaximumLiveCalls(call)) return true; // NOTE: If the amount of live calls changes beyond 1, this logic will probably // have to change. Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + liveCall); if (call == liveCall) { // If the call is already the foreground call, then we are golden. // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT // state since the call was already populated into the list. return true; } if (hasMaximumOutgoingCalls(call)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // state, just disconnect it since the user has explicitly started a new call. call.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); return true; } return false; } // TODO: Remove once b/23035408 has been corrected. // If the live call is a conference, it will not have a target phone account set. This // means the check to see if the live call has the same target phone account as the new // call will not cause us to bail early. As a result, we'll end up holding the // ongoing conference call. However, the ConnectionService is already doing that. This // has caused problems with some carriers. As a workaround until b/23035408 is // corrected, we will try and get the target phone account for one of the conference's // children and use that instead. PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount(); if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + liveCallPhoneAccount); } // First thing, if we are trying to make a call with the same phone account as the live // call, then allow it so that the connection service can make its own decision about // how to handle the new call relative to the current one. Loading Loading @@ -4045,33 +4163,6 @@ public class CallsManager extends Call.ListenerBase // The live call cannot be held so we're out of luck here. There's no room. return false; } else if (hasRingingOrSimulatedRingingCall() && isEmergency) { Call ringingCall = getRingingOrSimulatedRingingCall(); ringingCall.getAnalytics().setCallIsAdditional(true); ringingCall.getAnalytics().setCallIsInterrupted(true); if (ringingCall.getState() == CallState.SIMULATED_RINGING) { if (!ringingCall.hasGoneActiveBefore()) { // If this is an incoming call that is currently in SIMULATED_RINGING only // after a call screen, disconnect to make room and mark as missed, since // the user didn't get a chance to accept/reject. ringingCall.disconnect("emergency call dialed during simulated ringing " + "after screen."); } else { // If this is a simulated ringing call after being active and put in // AUDIO_PROCESSING state again, disconnect normally. ringingCall.reject(false, null, "emergency call dialed during simulated " + "ringing."); } } else { // normal incoming ringing call. // Hang up the ringing call to make room for the emergency call and mark as missed, // since the user did not reject. ringingCall.setOverrideDisconnectCauseCode( new DisconnectCause(DisconnectCause.MISSED)); ringingCall.reject(false, null, "emergency call dialed during ringing."); } return true; } return true; } /** Loading
tests/src/com/android/server/telecom/tests/CallsManagerTest.java +25 −5 Original line number Diff line number Diff line Loading @@ -136,7 +136,8 @@ public class CallsManagerTest extends TelecomTestCase { ComponentName.unflattenFromString("com.baz/.Self"), "Self"); private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION | PhoneAccount.CAPABILITY_CALL_PROVIDER) | PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) .setIsEnabled(true) .build(); private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2") Loading Loading @@ -1133,7 +1134,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyLong(), anyString()); } Loading @@ -1148,7 +1149,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); } Loading @@ -1163,7 +1164,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyString()); } Loading @@ -1179,10 +1180,29 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); } @SmallTest @Test public void testMakeRoomForEmergencyCallDuringActiveAndRingingCallDisconnectRinging() { when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE)) .thenReturn(SIM_1_ACCOUNT); Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE); doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD); Call ringingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING); Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW); when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any())) .thenReturn(true); newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ringingCall).reject(anyBoolean(), any(), any()); } /** * Verifies that changes to a {@link PhoneAccount}'s * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. Loading