Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aba2f0ae authored by Pranav Madapurmath's avatar Pranav Madapurmath
Browse files

DSDA: Deflect MMI codes

Reject MMI codes when there's an ongoing call on a different phone
account. Instead, display an error message to the user explaining that
the MMI code is not available when there's already another call in place on another phone account.

Bug: 389787292
Test: atest CallSequencingTests
Test: atest atest CtsTelecomCujTestCases:CallSequencingMmiTest
Test: Manual verification in Dialer UI that error message is shown when dialing incall MMI across phone accounts.
Flag: com.android.server.telecom.flags.enable_call_sequencing
Change-Id: I93b805882da9afabb8afb7657912e703f991c795
parent 780199d1
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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>
+40 −13
Original line number Diff line number Diff line
@@ -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";
@@ -790,7 +790,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);
@@ -2181,7 +2181,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
@@ -5089,14 +5097,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()
@@ -5114,15 +5148,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
@@ -5139,7 +5166,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.
     */
+14 −0
Original line number Diff line number Diff line
@@ -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;

@@ -894,6 +896,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;
+21 −2
Original line number Diff line number Diff line
@@ -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;

@@ -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();
@@ -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;
    }
+28 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -559,6 +563,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);