Loading src/com/android/server/telecom/CallIntentProcessor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading src/com/android/server/telecom/CallsManager.java +7 −35 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading Loading @@ -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)) { Loading Loading @@ -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. Loading Loading @@ -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() Loading src/com/android/server/telecom/MmiUtils.java 0 → 100644 +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; } } src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java +14 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading @@ -109,6 +108,7 @@ public class NewOutgoingCallIntentBroadcaster { mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp; mLock = mCallsManager.getLock(); mDefaultDialerCache = defaultDialerCache; mMmiUtils = mmiUtils; } /** Loading Loading @@ -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) { Loading tests/src/com/android/server/telecom/tests/MmiUtilsTest.java 0 → 100644 +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
src/com/android/server/telecom/CallIntentProcessor.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading
src/com/android/server/telecom/CallsManager.java +7 −35 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading Loading @@ -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)) { Loading Loading @@ -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. Loading Loading @@ -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() Loading
src/com/android/server/telecom/MmiUtils.java 0 → 100644 +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; } }
src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java +14 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading @@ -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; Loading @@ -109,6 +108,7 @@ public class NewOutgoingCallIntentBroadcaster { mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp; mLock = mCallsManager.getLock(); mDefaultDialerCache = defaultDialerCache; mMmiUtils = mmiUtils; } /** Loading Loading @@ -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) { Loading
tests/src/com/android/server/telecom/tests/MmiUtilsTest.java 0 → 100644 +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())); } } }