Loading res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -423,4 +423,7 @@ <!-- In-call screen: error message shown when the user attempts to place a call, but the live call cannot be held. --> <string name="callFailed_unholdable_call">Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call.</string> <!-- In-call screen: error message shown when the user attempts to dial an MMI code, but there is an ongoing call on a different phone account. --> <string name="callFailed_reject_mmi">This MMI code is not available for calls across multiple accounts.</string> </resources> src/com/android/server/telecom/CallsManager.java +40 −13 Original line number Diff line number Diff line Loading @@ -278,7 +278,7 @@ public class CallsManager extends Call.ListenerBase * {@link #getNumCallsWithState(int, Call, PhoneAccountHandle, int...)} to indicate both managed * and self-managed calls should be included. */ private static final int CALL_FILTER_ALL = 3; public static final int CALL_FILTER_ALL = 3; private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION = "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"; Loading Loading @@ -796,7 +796,7 @@ public class CallsManager extends Call.ListenerBase mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager() ? mContext.getSystemService(BlockedNumbersManager.class) : null; mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this, mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this, mContext, new CallSequencingController(this, mContext, mClockProxy, mAnomalyReporter, mTimeoutsAdapter, mMetricsController, mFeatureFlags), mCallAudioManager, mFeatureFlags); Loading Loading @@ -2187,7 +2187,15 @@ public class CallsManager extends Call.ListenerBase potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) { boolean shouldAllowMmiCode = mCallSequencingAdapter .shouldAllowMmiCode(finalCall); if (shouldAllowMmiCode) { return CompletableFuture.completedFuture(true); } else { Log.i(this, "Rejecting the MMI code because there is an " + "ongoing call on a different phone account."); return CompletableFuture.completedFuture(false); } } // If a call is being reused, then it has already passed the // makeRoomForOutgoingCall check once and will fail the second time due to the Loading Loading @@ -5095,14 +5103,40 @@ public class CallsManager extends Call.ListenerBase * ({@link #CALL_FILTER_ALL}). * @param excludeCall Where {@code non-null}, this call is excluded from the count. * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle} * are excluded from the count. * are included in the count. * @param states The list of {@link CallState}s to include in the count. * @return Count of calls matching criteria. */ @VisibleForTesting public int getNumCallsWithState(final int callFilter, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states); // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } @VisibleForTesting public int getNumCallsWithStateWithoutHandle(final int callFilter, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states); // If a phone account handle was specified, only consider calls not associated with that // phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> !phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } private Stream<Call> getCallsWithState(final int callFilter, Call excludeCall, int... states) { Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet()); Stream<Call> callsStream = mCalls.stream() Loading @@ -5120,15 +5154,8 @@ public class CallsManager extends Call.ListenerBase callsStream = callsStream.filter(call -> call != excludeCall); } // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); return callsStream; } return (int) callsStream.count(); } /** * Determines the number of calls (visible to the calling user) matching the specified criteria. * This is an overloaded method which is being used in a security patch to fix up the call Loading @@ -5145,7 +5172,7 @@ public class CallsManager extends Call.ListenerBase * {@link UserHandle}. * @param hasCrossUserAccess indicates if calling user has the INTERACT_ACROSS_USERS permission. * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle} * are excluded from the count. * are included in the count. * @param states The list of {@link CallState}s to include in the count. * @return Count of calls matching criteria. */ Loading src/com/android/server/telecom/callsequencing/CallSequencingController.java +14 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.server.telecom.callsequencing; import static android.Manifest.permission.CALL_PRIVILEGED; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_MSG; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import static com.android.server.telecom.CallsManager.OUTGOING_CALL_STATES; import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall; Loading Loading @@ -906,6 +908,18 @@ public class CallSequencingController { }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock())); } public boolean hasMmiCodeRestriction(Call call) { if (mCallsManager.getNumCallsWithStateWithoutHandle( CALL_FILTER_ALL, call, call.getTargetPhoneAccount(), ONGOING_CALL_STATES) > 0) { // Set disconnect cause so that error will be printed out when call is disconnected. CharSequence msg = mContext.getText(R.string.callFailed_reject_mmi); call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, msg, msg, "Rejected MMI code due to an ongoing call on another phone account.")); return true; } return false; } private void showErrorDialogForMaxOutgoingCall(Call call) { call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS); int stringId = R.string.callFailed_too_many_calls; Loading src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java +21 −2 Original line number Diff line number Diff line Loading @@ -16,21 +16,26 @@ package com.android.server.telecom.callsequencing; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.OutcomeReceiver; import android.telecom.CallAttributes; import android.telecom.CallException; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.Log; import com.android.server.telecom.Call; import com.android.server.telecom.CallAudioManager; import com.android.server.telecom.CallState; import com.android.server.telecom.CallsManager; import com.android.server.telecom.LoggedHandlerExecutor; import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction; import com.android.server.telecom.flags.FeatureFlags; import com.android.server.telecom.R; import java.util.concurrent.CompletableFuture; Loading @@ -41,16 +46,18 @@ import java.util.concurrent.CompletableFuture; public class CallsManagerCallSequencingAdapter { private final CallsManager mCallsManager; private final Context mContext; private final CallSequencingController mSequencingController; private final CallAudioManager mCallAudioManager; private final Handler mHandler; private final FeatureFlags mFeatureFlags; private final boolean mIsCallSequencingEnabled; public CallsManagerCallSequencingAdapter(CallsManager callsManager, public CallsManagerCallSequencingAdapter(CallsManager callsManager, Context context, CallSequencingController sequencingController, CallAudioManager callAudioManager, FeatureFlags featureFlags) { mCallsManager = callsManager; mContext = context; mSequencingController = sequencingController; mCallAudioManager = callAudioManager; mHandler = sequencingController.getHandler(); Loading Loading @@ -278,6 +285,18 @@ public class CallsManagerCallSequencingAdapter { } } /** * Tries to see if there are any ongoing calls on another phone account when an MMI code is * detected to determine whether it should be allowed. For DSDA purposes, we will not allow any * MMI codes when there's a call on a different phone account. * @param call The call to ignore and the associated phone account to exclude when getting the * total call count. * @return {@code true} if the MMI code should be allowed, {@code false} otherwise. */ public boolean shouldAllowMmiCode(Call call) { return !mIsCallSequencingEnabled || !mSequencingController.hasMmiCodeRestriction(call); } public Handler getHandler() { return mHandler; } Loading tests/src/com/android/server/telecom/tests/CallSequencingTests.java +28 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.telecom.tests; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall; import static junit.framework.Assert.assertNotNull; Loading @@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.junit.Assert.assertTrue; Loading @@ -46,6 +49,7 @@ import android.os.UserHandle; import android.telecom.CallAttributes; import android.telecom.CallException; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.CarrierConfigManager; Loading Loading @@ -574,6 +578,30 @@ public class CallSequencingTests extends TelecomTestCase { .processDisconnectCallAndCleanup(eq(mActiveCall), eq(previousState)); } @Test @SmallTest public void testMmiCodeRestrictionReject() { // Verify that when calls are detected across other phone accounts, // that the MMI code is rejected. when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1); when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall, mHandle1, ONGOING_CALL_STATES)).thenReturn(1); assertTrue(mController.hasMmiCodeRestriction(mNewCall)); verify(mNewCall).setOverrideDisconnectCauseCode(any(DisconnectCause.class)); } @Test @SmallTest public void testMmiCodeRestrictionAllow() { // Verify that when no calls are detected across other phone accounts, // that the MMI code is allowed. when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1); when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall, mHandle1, ONGOING_CALL_STATES)).thenReturn(0); assertFalse(mController.hasMmiCodeRestriction(mNewCall)); verify(mNewCall, times(0)).setOverrideDisconnectCauseCode(any(DisconnectCause.class)); } /* Helpers */ private void setPhoneAccounts(Call call1, Call call2, boolean useSamePhoneAccount) { when(call1.getTargetPhoneAccount()).thenReturn(mHandle1); Loading Loading
res/values/strings.xml +3 −0 Original line number Diff line number Diff line Loading @@ -423,4 +423,7 @@ <!-- In-call screen: error message shown when the user attempts to place a call, but the live call cannot be held. --> <string name="callFailed_unholdable_call">Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call.</string> <!-- In-call screen: error message shown when the user attempts to dial an MMI code, but there is an ongoing call on a different phone account. --> <string name="callFailed_reject_mmi">This MMI code is not available for calls across multiple accounts.</string> </resources>
src/com/android/server/telecom/CallsManager.java +40 −13 Original line number Diff line number Diff line Loading @@ -278,7 +278,7 @@ public class CallsManager extends Call.ListenerBase * {@link #getNumCallsWithState(int, Call, PhoneAccountHandle, int...)} to indicate both managed * and self-managed calls should be included. */ private static final int CALL_FILTER_ALL = 3; public static final int CALL_FILTER_ALL = 3; private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION = "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"; Loading Loading @@ -796,7 +796,7 @@ public class CallsManager extends Call.ListenerBase mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager() ? mContext.getSystemService(BlockedNumbersManager.class) : null; mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this, mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this, mContext, new CallSequencingController(this, mContext, mClockProxy, mAnomalyReporter, mTimeoutsAdapter, mMetricsController, mFeatureFlags), mCallAudioManager, mFeatureFlags); Loading Loading @@ -2187,7 +2187,15 @@ public class CallsManager extends Call.ListenerBase potentialPhoneAccounts -> { Log.i(CallsManager.this, "make room for outgoing call stage"); if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) { boolean shouldAllowMmiCode = mCallSequencingAdapter .shouldAllowMmiCode(finalCall); if (shouldAllowMmiCode) { return CompletableFuture.completedFuture(true); } else { Log.i(this, "Rejecting the MMI code because there is an " + "ongoing call on a different phone account."); return CompletableFuture.completedFuture(false); } } // If a call is being reused, then it has already passed the // makeRoomForOutgoingCall check once and will fail the second time due to the Loading Loading @@ -5095,14 +5103,40 @@ public class CallsManager extends Call.ListenerBase * ({@link #CALL_FILTER_ALL}). * @param excludeCall Where {@code non-null}, this call is excluded from the count. * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle} * are excluded from the count. * are included in the count. * @param states The list of {@link CallState}s to include in the count. * @return Count of calls matching criteria. */ @VisibleForTesting public int getNumCallsWithState(final int callFilter, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states); // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } @VisibleForTesting public int getNumCallsWithStateWithoutHandle(final int callFilter, Call excludeCall, PhoneAccountHandle phoneAccountHandle, int... states) { Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states); // If a phone account handle was specified, only consider calls not associated with that // phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> !phoneAccountHandle.equals(call.getTargetPhoneAccount())); } return (int) callsStream.count(); } private Stream<Call> getCallsWithState(final int callFilter, Call excludeCall, int... states) { Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet()); Stream<Call> callsStream = mCalls.stream() Loading @@ -5120,15 +5154,8 @@ public class CallsManager extends Call.ListenerBase callsStream = callsStream.filter(call -> call != excludeCall); } // If a phone account handle was specified, only consider calls for that phone account. if (phoneAccountHandle != null) { callsStream = callsStream.filter( call -> phoneAccountHandle.equals(call.getTargetPhoneAccount())); return callsStream; } return (int) callsStream.count(); } /** * Determines the number of calls (visible to the calling user) matching the specified criteria. * This is an overloaded method which is being used in a security patch to fix up the call Loading @@ -5145,7 +5172,7 @@ public class CallsManager extends Call.ListenerBase * {@link UserHandle}. * @param hasCrossUserAccess indicates if calling user has the INTERACT_ACROSS_USERS permission. * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle} * are excluded from the count. * are included in the count. * @param states The list of {@link CallState}s to include in the count. * @return Count of calls matching criteria. */ Loading
src/com/android/server/telecom/callsequencing/CallSequencingController.java +14 −0 Original line number Diff line number Diff line Loading @@ -18,10 +18,12 @@ package com.android.server.telecom.callsequencing; import static android.Manifest.permission.CALL_PRIVILEGED; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_MSG; import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import static com.android.server.telecom.CallsManager.OUTGOING_CALL_STATES; import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall; Loading Loading @@ -906,6 +908,18 @@ public class CallSequencingController { }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock())); } public boolean hasMmiCodeRestriction(Call call) { if (mCallsManager.getNumCallsWithStateWithoutHandle( CALL_FILTER_ALL, call, call.getTargetPhoneAccount(), ONGOING_CALL_STATES) > 0) { // Set disconnect cause so that error will be printed out when call is disconnected. CharSequence msg = mContext.getText(R.string.callFailed_reject_mmi); call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, msg, msg, "Rejected MMI code due to an ongoing call on another phone account.")); return true; } return false; } private void showErrorDialogForMaxOutgoingCall(Call call) { call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS); int stringId = R.string.callFailed_too_many_calls; Loading
src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java +21 −2 Original line number Diff line number Diff line Loading @@ -16,21 +16,26 @@ package com.android.server.telecom.callsequencing; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.OutcomeReceiver; import android.telecom.CallAttributes; import android.telecom.CallException; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.Log; import com.android.server.telecom.Call; import com.android.server.telecom.CallAudioManager; import com.android.server.telecom.CallState; import com.android.server.telecom.CallsManager; import com.android.server.telecom.LoggedHandlerExecutor; import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction; import com.android.server.telecom.flags.FeatureFlags; import com.android.server.telecom.R; import java.util.concurrent.CompletableFuture; Loading @@ -41,16 +46,18 @@ import java.util.concurrent.CompletableFuture; public class CallsManagerCallSequencingAdapter { private final CallsManager mCallsManager; private final Context mContext; private final CallSequencingController mSequencingController; private final CallAudioManager mCallAudioManager; private final Handler mHandler; private final FeatureFlags mFeatureFlags; private final boolean mIsCallSequencingEnabled; public CallsManagerCallSequencingAdapter(CallsManager callsManager, public CallsManagerCallSequencingAdapter(CallsManager callsManager, Context context, CallSequencingController sequencingController, CallAudioManager callAudioManager, FeatureFlags featureFlags) { mCallsManager = callsManager; mContext = context; mSequencingController = sequencingController; mCallAudioManager = callAudioManager; mHandler = sequencingController.getHandler(); Loading Loading @@ -278,6 +285,18 @@ public class CallsManagerCallSequencingAdapter { } } /** * Tries to see if there are any ongoing calls on another phone account when an MMI code is * detected to determine whether it should be allowed. For DSDA purposes, we will not allow any * MMI codes when there's a call on a different phone account. * @param call The call to ignore and the associated phone account to exclude when getting the * total call count. * @return {@code true} if the MMI code should be allowed, {@code false} otherwise. */ public boolean shouldAllowMmiCode(Call call) { return !mIsCallSequencingEnabled || !mSequencingController.hasMmiCodeRestriction(call); } public Handler getHandler() { return mHandler; } Loading
tests/src/com/android/server/telecom/tests/CallSequencingTests.java +28 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.telecom.tests; import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL; import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES; import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall; import static junit.framework.Assert.assertNotNull; Loading @@ -30,6 +32,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.junit.Assert.assertTrue; Loading @@ -46,6 +49,7 @@ import android.os.UserHandle; import android.telecom.CallAttributes; import android.telecom.CallException; import android.telecom.Connection; import android.telecom.DisconnectCause; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.CarrierConfigManager; Loading Loading @@ -574,6 +578,30 @@ public class CallSequencingTests extends TelecomTestCase { .processDisconnectCallAndCleanup(eq(mActiveCall), eq(previousState)); } @Test @SmallTest public void testMmiCodeRestrictionReject() { // Verify that when calls are detected across other phone accounts, // that the MMI code is rejected. when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1); when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall, mHandle1, ONGOING_CALL_STATES)).thenReturn(1); assertTrue(mController.hasMmiCodeRestriction(mNewCall)); verify(mNewCall).setOverrideDisconnectCauseCode(any(DisconnectCause.class)); } @Test @SmallTest public void testMmiCodeRestrictionAllow() { // Verify that when no calls are detected across other phone accounts, // that the MMI code is allowed. when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1); when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall, mHandle1, ONGOING_CALL_STATES)).thenReturn(0); assertFalse(mController.hasMmiCodeRestriction(mNewCall)); verify(mNewCall, times(0)).setOverrideDisconnectCauseCode(any(DisconnectCause.class)); } /* Helpers */ private void setPhoneAccounts(Call call1, Call call2, boolean useSamePhoneAccount) { when(call1.getTargetPhoneAccount()).thenReturn(mHandle1); Loading