Loading src/com/android/server/telecom/CallsManager.java +227 −136 Original line number Original line Diff line number Diff line Loading @@ -1564,14 +1564,22 @@ public class CallsManager extends Call.ListenerBase CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( potentialPhoneAccounts -> { potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); Log.i(CallsManager.this, "make room for outgoing call stage"); boolean isPotentialInCallMMICode = if (isPotentialInCallMMICode(handle) && !isSelfManaged) { isPotentialInCallMMICode(handle) && !isSelfManaged; return CompletableFuture.completedFuture(finalCall); // 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, // If a call is being reused, then it has already passed the // then it has already passed the makeRoomForOutgoingCall check once and will // makeRoomForOutgoingCall check once and will fail the second time due to the // fail the second time due to the call transitioning into the CONNECTING state. // call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall if (isReusedCall) { && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) { 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(); Call foregroundCall = getForegroundCall(); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); if (foregroundCall.isSelfManaged()) { if (foregroundCall.isSelfManaged()) { Loading Loading @@ -3926,59 +3934,87 @@ public class CallsManager extends Call.ListenerBase } } @VisibleForTesting @VisibleForTesting public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) { if (hasMaximumLiveCalls(call)) { // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize // NOTE: If the amount of live calls changes beyond 1, this logic will probably // distraction. This does not affect live call count. // have to change. 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); Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + Log.i(this, "makeRoomForOutgoingEmergencyCall call = " + emergencyCall liveCall); + " livecall = " + liveCall); if (call == liveCall) { if (emergencyCall == liveCall) { // If the call is already the foreground call, then we are golden. // Not likely, but a good sanity check. // 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; return true; } } if (hasMaximumOutgoingCalls(call)) { if (hasMaximumOutgoingCalls(emergencyCall)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { if (!outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the emergencyCall.getAnalytics().setCallIsAdditional(true); // 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); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" + " emergency call."); + " emergency call."); return true; return true; } } if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // Sanity check: if there is an orphaned emergency call in the // state, just disconnect it since the user has explicitly started a new call. // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user call.getAnalytics().setCallIsAdditional(true); // has explicitly started a new call. emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); + " of new outgoing call."); return true; 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; return false; } } if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) { if (liveCall.getState() == CallState.AUDIO_PROCESSING) { call.getAnalytics().setCallIsAdditional(true); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.disconnect(0, "disconnecting audio processing call for emergency"); liveCall.disconnect("disconnecting audio processing call for emergency"); return true; return true; } } // If we have the max number of held managed calls and we're placing an emergency call, // 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. // we'll disconnect the ongoing call if it cannot be held. if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) { if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) { call.getAnalytics().setCallIsAdditional(true); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(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 " liveCall.disconnect("disconnecting to make room for emergency call " + call.getId()); + emergencyCall.getId()); return true; return true; } } Loading @@ -3994,7 +4030,7 @@ public class CallsManager extends Call.ListenerBase if (liveCallPhoneAccount == null && liveCall.isConference() && if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " + liveCallPhoneAccount); 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 // 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 // emergency call if possible. Disconnect the active call in favor of the emergency call // instead of trying to hold. // instead of trying to hold. if (isEmergency && liveCall.getTargetPhoneAccount() != null) { if (liveCall.getTargetPhoneAccount() != null) { PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( liveCall.getTargetPhoneAccount()); liveCall.getTargetPhoneAccount()); if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { Loading @@ -4015,6 +4051,88 @@ public class CallsManager extends Call.ListenerBase return true; 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 // 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 // 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. // 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. // The live call cannot be held so we're out of luck here. There's no room. return false; 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 Original line Diff line number Diff line Loading @@ -136,7 +136,8 @@ public class CallsManagerTest extends TelecomTestCase { ComponentName.unflattenFromString("com.baz/.Self"), "Self"); ComponentName.unflattenFromString("com.baz/.Self"), "Self"); private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION | PhoneAccount.CAPABILITY_CALL_PROVIDER) | PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) .setIsEnabled(true) .setIsEnabled(true) .build(); .build(); private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2") 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), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyLong(), anyString()); verify(ongoingCall).disconnect(anyLong(), anyString()); } } Loading @@ -1148,7 +1149,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); verify(ongoingCall).reject(anyBoolean(), any(), any()); } } Loading @@ -1163,7 +1164,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyString()); verify(ongoingCall).disconnect(anyString()); } } Loading @@ -1179,10 +1180,29 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); 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 * Verifies that changes to a {@link PhoneAccount}'s * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. * {@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 Original line Diff line number Diff line Loading @@ -1564,14 +1564,22 @@ public class CallsManager extends Call.ListenerBase CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync( potentialPhoneAccounts -> { potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); Log.i(CallsManager.this, "make room for outgoing call stage"); boolean isPotentialInCallMMICode = if (isPotentialInCallMMICode(handle) && !isSelfManaged) { isPotentialInCallMMICode(handle) && !isSelfManaged; return CompletableFuture.completedFuture(finalCall); // 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, // If a call is being reused, then it has already passed the // then it has already passed the makeRoomForOutgoingCall check once and will // makeRoomForOutgoingCall check once and will fail the second time due to the // fail the second time due to the call transitioning into the CONNECTING state. // call transitioning into the CONNECTING state. if (!isPotentialInCallMMICode && (!isReusedCall if (isReusedCall) { && !makeRoomForOutgoingCall(finalCall, finalCall.isEmergencyCall()))) { 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(); Call foregroundCall = getForegroundCall(); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall); if (foregroundCall.isSelfManaged()) { if (foregroundCall.isSelfManaged()) { Loading Loading @@ -3926,59 +3934,87 @@ public class CallsManager extends Call.ListenerBase } } @VisibleForTesting @VisibleForTesting public boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { public boolean makeRoomForOutgoingEmergencyCall(Call emergencyCall) { if (hasMaximumLiveCalls(call)) { // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize // NOTE: If the amount of live calls changes beyond 1, this logic will probably // distraction. This does not affect live call count. // have to change. 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); Call liveCall = getFirstCallWithState(LIVE_CALL_STATES); Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + Log.i(this, "makeRoomForOutgoingEmergencyCall call = " + emergencyCall liveCall); + " livecall = " + liveCall); if (call == liveCall) { if (emergencyCall == liveCall) { // If the call is already the foreground call, then we are golden. // Not likely, but a good sanity check. // 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; return true; } } if (hasMaximumOutgoingCalls(call)) { if (hasMaximumOutgoingCalls(emergencyCall)) { Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); if (isEmergency && !outgoingCall.isEmergencyCall()) { if (!outgoingCall.isEmergencyCall()) { // Disconnect the current outgoing call if it's not an emergency call. If the emergencyCall.getAnalytics().setCallIsAdditional(true); // 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); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" outgoingCall.disconnect("Disconnecting dialing call in favor of new dialing" + " emergency call."); + " emergency call."); return true; return true; } } if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} // Sanity check: if there is an orphaned emergency call in the // state, just disconnect it since the user has explicitly started a new call. // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user call.getAnalytics().setCallIsAdditional(true); // has explicitly started a new call. emergencyCall.getAnalytics().setCallIsAdditional(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.getAnalytics().setCallIsInterrupted(true); outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor" + " of new outgoing call."); + " of new outgoing call."); return true; 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; return false; } } if (liveCall.getState() == CallState.AUDIO_PROCESSING && isEmergency) { if (liveCall.getState() == CallState.AUDIO_PROCESSING) { call.getAnalytics().setCallIsAdditional(true); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.getAnalytics().setCallIsInterrupted(true); liveCall.disconnect(0, "disconnecting audio processing call for emergency"); liveCall.disconnect("disconnecting audio processing call for emergency"); return true; return true; } } // If we have the max number of held managed calls and we're placing an emergency call, // 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. // we'll disconnect the ongoing call if it cannot be held. if (hasMaximumManagedHoldingCalls(call) && isEmergency && !canHold(liveCall)) { if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) { call.getAnalytics().setCallIsAdditional(true); emergencyCall.getAnalytics().setCallIsAdditional(true); liveCall.getAnalytics().setCallIsInterrupted(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 " liveCall.disconnect("disconnecting to make room for emergency call " + call.getId()); + emergencyCall.getId()); return true; return true; } } Loading @@ -3994,7 +4030,7 @@ public class CallsManager extends Call.ListenerBase if (liveCallPhoneAccount == null && liveCall.isConference() && if (liveCallPhoneAccount == null && liveCall.isConference() && !liveCall.getChildCalls().isEmpty()) { !liveCall.getChildCalls().isEmpty()) { liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + Log.i(this, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " + liveCallPhoneAccount); 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 // 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 // emergency call if possible. Disconnect the active call in favor of the emergency call // instead of trying to hold. // instead of trying to hold. if (isEmergency && liveCall.getTargetPhoneAccount() != null) { if (liveCall.getTargetPhoneAccount() != null) { PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( PhoneAccount pa = mPhoneAccountRegistrar.getPhoneAccountUnchecked( liveCall.getTargetPhoneAccount()); liveCall.getTargetPhoneAccount()); if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) { Loading @@ -4015,6 +4051,88 @@ public class CallsManager extends Call.ListenerBase return true; 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 // 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 // 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. // 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. // The live call cannot be held so we're out of luck here. There's no room. return false; 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 Original line Diff line number Diff line Loading @@ -136,7 +136,8 @@ public class CallsManagerTest extends TelecomTestCase { ComponentName.unflattenFromString("com.baz/.Self"), "Self"); ComponentName.unflattenFromString("com.baz/.Self"), "Self"); private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1") .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION | PhoneAccount.CAPABILITY_CALL_PROVIDER) | PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) .setIsEnabled(true) .setIsEnabled(true) .build(); .build(); private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2") 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), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyLong(), anyString()); verify(ongoingCall).disconnect(anyLong(), anyString()); } } Loading @@ -1148,7 +1149,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); verify(ongoingCall).reject(anyBoolean(), any(), any()); } } Loading @@ -1163,7 +1164,7 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).disconnect(anyString()); verify(ongoingCall).disconnect(anyString()); } } Loading @@ -1179,10 +1180,29 @@ public class CallsManagerTest extends TelecomTestCase { newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), newEmergencyCall.setHandle(Uri.fromParts("tel", "5551213", null), TelecomManager.PRESENTATION_ALLOWED); TelecomManager.PRESENTATION_ALLOWED); assertTrue(mCallsManager.makeRoomForOutgoingCall(newEmergencyCall, true /*isEmergency*/)); assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall)); verify(ongoingCall).reject(anyBoolean(), any(), any()); 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 * Verifies that changes to a {@link PhoneAccount}'s * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call. Loading