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

Commit 3171e5f5 authored by Chinmay Dhodapkar's avatar Chinmay Dhodapkar Committed by Automerger Merge Worker
Browse files

Merge "block call forwarding MMI codes from apps" into udc-dev am: 9393d8c4

parents 01d0710d 9393d8c4
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -182,9 +182,10 @@ public class CallIntentProcessor {
        boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
                initiatingUser.getIdentifier());


        NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
                isPrivilegedDialer, defaultDialerCache);
                isPrivilegedDialer, defaultDialerCache, new MmiUtils());

        // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
        NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
+7 −35
Original line number Diff line number Diff line
@@ -477,6 +477,7 @@ public class CallsManager extends Call.ListenerBase

    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();

    private final MmiUtils mMmiUtils = new MmiUtils();
    /**
     * Listener to PhoneAccountRegistrar events.
     */
@@ -1863,7 +1864,7 @@ public class CallsManager extends Call.ListenerBase
        CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
                potentialPhoneAccounts -> {
                    Log.i(CallsManager.this, "make room for outgoing call stage");
                    if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
                    if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
                        return CompletableFuture.completedFuture(finalCall);
                    }
                    // If a call is being reused, then it has already passed the
@@ -2106,7 +2107,7 @@ public class CallsManager extends Call.ListenerBase
                    setIntentExtrasAndStartTime(callToUse, extras);
                    setCallSourceToAnalytics(callToUse, originalIntent);

                    if (isPotentialMMICode(handle) && !isSelfManaged) {
                    if (mMmiUtils.isPotentialMMICode(handle) && !isSelfManaged) {
                        // Do not add the call if it is a potential MMI code.
                        callToUse.addListener(this);
                    } else if (!mCalls.contains(callToUse)) {
@@ -4420,37 +4421,6 @@ public class CallsManager extends Call.ListenerBase
        }
    }

    private boolean isPotentialMMICode(Uri handle) {
        return (handle != null && handle.getSchemeSpecificPart() != null
                && handle.getSchemeSpecificPart().contains("#"));
    }

    /**
     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
     * MMI codes which can be dialed when one or more calls are in progress.
     * <P>
     * Checks for numbers formatted similar to the MMI codes defined in:
     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
     *
     * @param handle The URI to call.
     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
     */
    private boolean isPotentialInCallMMICode(Uri handle) {
        if (handle != null && handle.getSchemeSpecificPart() != null &&
                handle.getScheme() != null &&
                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {

            String dialedNumber = handle.getSchemeSpecificPart();
            return (dialedNumber.equals("0") ||
                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
                    dialedNumber.equals("3") ||
                    dialedNumber.equals("4") ||
                    dialedNumber.equals("5"));
        }
        return false;
    }

    /**
     * Determines if there are any ongoing self managed calls for the given package/user.
     * @param packageName The package name to check.
@@ -5523,8 +5493,10 @@ public class CallsManager extends Call.ListenerBase
    * @param call The call.
    */
    private void maybeShowErrorDialogOnDisconnect(Call call) {
        if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
                || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
        if (call.getState() == CallState.DISCONNECTED && (mMmiUtils.isPotentialMMICode(
                call.getHandle())
                || mMmiUtils.isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(
                call)) {
            DisconnectCause disconnectCause = call.getDisconnectCause();
            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
                    == DisconnectCause.ERROR) || (disconnectCause.getCode()
+184 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom;

import android.net.Uri;
import android.telecom.PhoneAccount;

import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MmiUtils {
    // See TS 22.030 6.5.2 "Structure of the MMI"

    private static Pattern sPatternSuppService = Pattern.compile(
            "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
        /*       1  2                    3          4  5       6   7         8    9     10  11
              12

                 1 = Full string up to and including #
                 2 = action (activation/interrogation/registration/erasure)
                 3 = service code
                 5 = SIA
                 7 = SIB
                 9 = SIC
                 10 = dialing number
        */
    //regex groups
    static final int MATCH_GROUP_POUND_STRING = 1;
    static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure)
    static final int MATCH_GROUP_SERVICE_CODE = 3;
    static final int MATCH_GROUP_SIA = 5;
    static final int MATCH_GROUP_SIB = 7;
    static final int MATCH_GROUP_SIC = 9;
    static final int MATCH_GROUP_PWD_CONFIRM = 11;
    static final int MATCH_GROUP_DIALING_NUMBER = 12;
    // Call Forwarding service codes
    static final String SC_CFU = "21";
    static final String SC_CFB = "67";
    static final String SC_CFNRy = "61";
    static final String SC_CFNR = "62";
    static final String SC_CF_All = "002";
    static final String SC_CF_All_Conditional = "004";

    //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
    @SuppressWarnings("DoubleBraceInitialization")
    private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>()
    {{
        add("*09"); //Selective Call Blocking/Reporting
        add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer
        add("*56"); //Change Forward-To Number for ISDN Call Forwarding
        add("*60"); //Selective Call Rejection Activation
        add("*63"); //Selective Call Forwarding Activation
        add("*64"); //Selective Call Acceptance Activation
        add("*68"); //Call Forwarding Busy Line/Don't Answer Activation
        add("*72"); //Call Forwarding Activation
        add("*77"); //Anonymous Call Rejection Activation
        add("*78"); //Do Not Disturb Activation
    }};
    private final int mMinLenInDangerousSet;
    private final int mMaxLenInDangerousSet;

    public MmiUtils() {
        mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
                .mapToInt(String::length)
                .min()
                .getAsInt();
        mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
                .mapToInt(String::length)
                .max()
                .getAsInt();
    }

    /**
     * Determines if the Uri represents a call forwarding related mmi code
     *
     * @param handle The URI to call.
     * @return {@code True} if the URI represents a call forwarding related MMI
     */
    private static boolean isCallForwardingMmiCode(Uri handle) {
        Matcher m;
        String dialString = handle.getSchemeSpecificPart();
        m = sPatternSuppService.matcher(dialString);

        if (m.matches()) {
            String sc = m.group(MATCH_GROUP_SERVICE_CODE);
            return sc != null &&
                    (sc.equals(SC_CFU)
                            || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
                            || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
                            || sc.equals(SC_CF_All_Conditional));
        }

        return false;

    }

    private static boolean isTelScheme(Uri handle) {
        return (handle != null && handle.getSchemeSpecificPart() != null &&
                handle.getScheme() != null &&
                handle.getScheme().equals(PhoneAccount.SCHEME_TEL));
    }

    private boolean isDangerousVerticalServiceCode(Uri handle) {
        if (isTelScheme(handle)) {
            String dialedNumber = handle.getSchemeSpecificPart();
            if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') {
                //we only check vertical codes defined by The North American Numbering Plan Admin
                //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
                //only two or 3-digit codes are valid as of today, but the code is generic enough.
                for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet;
                        prefixLen++) {
                    String prefix = dialedNumber.substring(0, prefixLen);
                    if (sDangerousVerticalServiceCodes.contains(prefix)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
     * MMI codes which can be dialed when one or more calls are in progress.
     * <P>
     * Checks for numbers formatted similar to the MMI codes defined in:
     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
     *
     * @param handle The URI to call.
     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
     */
    public boolean isPotentialInCallMMICode(Uri handle) {
        if (isTelScheme(handle)) {
            String dialedNumber = handle.getSchemeSpecificPart();
            return (dialedNumber.equals("0") ||
                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
                    dialedNumber.equals("3") ||
                    dialedNumber.equals("4") ||
                    dialedNumber.equals("5"));
        }
        return false;
    }

    public boolean isPotentialMMICode(Uri handle) {
        return (handle != null && handle.getSchemeSpecificPart() != null
                && handle.getSchemeSpecificPart().contains("#"));
    }

    /**
     * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous
     * codes are ones, for which,
     * we normally expect the user to be aware that an application has dialed them
     *
     * @param handle The URI to call.
     * @return {@code True} if the URI represents a dangerous code
     */
    public boolean isDangerousMmiOrVerticalCode(Uri handle) {
        if (isPotentialMMICode(handle)) {
            return isCallForwardingMmiCode(handle);
            //since some dangerous mmi codes could be carrier specific, in the future,
            //we can add a carrier config item which can list carrier specific dangerous mmi codes
        } else if (isDangerousVerticalServiceCode(handle)) {
            return true;
        }
        return false;
    }
}
+14 −4
Original line number Diff line number Diff line
@@ -16,14 +16,12 @@

package com.android.server.telecom;

import android.app.AppOpsManager;

import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
@@ -78,6 +76,7 @@ public class NewOutgoingCallIntentBroadcaster {
    private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
    private final TelecomSystem.SyncRoot mLock;
    private final DefaultDialerCache mDefaultDialerCache;
    private final MmiUtils mMmiUtils;

    /*
     * Whether or not the outgoing call intent originated from the default phone application. If
@@ -101,7 +100,7 @@ public class NewOutgoingCallIntentBroadcaster {
    @VisibleForTesting
    public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
            Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils) {
        mContext = context;
        mCallsManager = callsManager;
        mIntent = intent;
@@ -109,6 +108,7 @@ public class NewOutgoingCallIntentBroadcaster {
        mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
        mLock = mCallsManager.getLock();
        mDefaultDialerCache = defaultDialerCache;
        mMmiUtils = mmiUtils;
    }

    /**
@@ -291,6 +291,16 @@ public class NewOutgoingCallIntentBroadcaster {
                    result.callImmediately = true;
                    result.requestRedirection = false;
                }
            } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
                if (!mIsDefaultOrSystemPhoneApp) {
                    Log.w(this,
                            "Potentially dangerous MMI code %s with CALL Intent %s can only be "
                                    + "sent if caller is the system or default dialer",
                            number, intent);
                    launchSystemDialer(intent.getData());
                    result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
                    return result;
                }
            }
        } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
            if (!isEmergencyNumber) {
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.telecom.tests;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.net.Uri;
import android.test.suitebuilder.annotation.SmallTest;

import com.android.server.telecom.MmiUtils;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class MmiUtilsTest extends TelecomTestCase {

    private static final String[] sDangerousDialStrings = {
        "*21*1234567#", // fwd unconditionally to 1234567,
        "*67*1234567#", // fwd to 1234567 when line is busy
        "*61*1234567#", // fwd to 1234567 when no one picks up
        "*62*1234567#", // fwd to 1234567 when out of range
        "*004*1234567#", // fwd to 1234567 when busy, not pickup up, out of range
        "*004*1234567#", // fwd to 1234567 conditionally
        "**21*1234567#", // fwd unconditionally to 1234567

        // north american vertical service codes

        "*094565678", // Selective Call Blocking/Reporting
        "*4278889", // Change Forward-To Number for Customer Programmable Call Forwarding Don't
                    // Answer
        "*5644456", // Change Forward-To Number for ISDN Call Forwarding
        "*6045677", // Selective Call Rejection Activation
        "*635678", // Selective Call Forwarding Activation
        "*64678899", // Selective Call Acceptance Activation
        "*683456", // Call Forwarding Busy Line/Don't Answer Activation
        "*721234", // Call Forwarding Activation
        "*77", // Anonymous Call Rejection Activation
        "*78", // Do Not Disturb Activation
    };

    private MmiUtils mMmiUtils = new MmiUtils();
    private static final String[] sNonDangerousDialStrings = {"*6712345678", "*272", "*272911"};

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();
    }

    @Override
    @After
    public void tearDown() throws Exception {
        super.tearDown();
    }

    @SmallTest
    @Test
    public void testDangerousDialStringsDetected() throws Exception {
        for (String s : sDangerousDialStrings) {
            Uri.Builder b = new Uri.Builder();
            b.scheme("tel").opaquePart(s);
            assertTrue(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
        }
    }

    @SmallTest
    @Test
    public void testNonDangerousDialStringsNotDetected() throws Exception {
        for (String s : sNonDangerousDialStrings) {
            Uri.Builder b = new Uri.Builder();
            b.scheme("tel").opaquePart(s);
            assertFalse(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
        }
    }
}
Loading