1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.net.Uri; 20 import android.telecom.PhoneAccount; 21 22 import java.util.HashSet; 23 import java.util.Set; 24 import java.util.regex.Matcher; 25 import java.util.regex.Pattern; 26 27 public class MmiUtils { 28 // See TS 22.030 6.5.2 "Structure of the MMI" 29 30 private static Pattern sPatternSuppService = Pattern.compile( 31 "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)"); 32 /* 1 2 3 4 5 6 7 8 9 10 11 33 12 34 35 1 = Full string up to and including # 36 2 = action (activation/interrogation/registration/erasure) 37 3 = service code 38 5 = SIA 39 7 = SIB 40 9 = SIC 41 10 = dialing number 42 */ 43 //regex groups 44 static final int MATCH_GROUP_POUND_STRING = 1; 45 static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure) 46 static final int MATCH_GROUP_SERVICE_CODE = 3; 47 static final int MATCH_GROUP_SIA = 5; 48 static final int MATCH_GROUP_SIB = 7; 49 static final int MATCH_GROUP_SIC = 9; 50 static final int MATCH_GROUP_PWD_CONFIRM = 11; 51 static final int MATCH_GROUP_DIALING_NUMBER = 12; 52 // Call Forwarding service codes 53 static final String SC_CFU = "21"; 54 static final String SC_CFB = "67"; 55 static final String SC_CFNRy = "61"; 56 static final String SC_CFNR = "62"; 57 static final String SC_CF_All = "002"; 58 static final String SC_CF_All_Conditional = "004"; 59 60 //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html 61 @SuppressWarnings("DoubleBraceInitialization") 62 private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>() 63 {{ 64 add("*09"); //Selective Call Blocking/Reporting 65 add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer 66 add("*56"); //Change Forward-To Number for ISDN Call Forwarding 67 add("*60"); //Selective Call Rejection Activation 68 add("*63"); //Selective Call Forwarding Activation 69 add("*64"); //Selective Call Acceptance Activation 70 add("*68"); //Call Forwarding Busy Line/Don't Answer Activation 71 add("*72"); //Call Forwarding Activation 72 add("*77"); //Anonymous Call Rejection Activation 73 add("*78"); //Do Not Disturb Activation 74 }}; 75 private final int mMinLenInDangerousSet; 76 private final int mMaxLenInDangerousSet; 77 MmiUtils()78 public MmiUtils() { 79 mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream() 80 .mapToInt(String::length) 81 .min() 82 .getAsInt(); 83 mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream() 84 .mapToInt(String::length) 85 .max() 86 .getAsInt(); 87 } 88 89 /** 90 * Determines if the Uri represents a call forwarding related mmi code 91 * 92 * @param handle The URI to call. 93 * @return {@code True} if the URI represents a call forwarding related MMI 94 */ isCallForwardingMmiCode(Uri handle)95 private static boolean isCallForwardingMmiCode(Uri handle) { 96 Matcher m; 97 String dialString = handle.getSchemeSpecificPart(); 98 m = sPatternSuppService.matcher(dialString); 99 100 if (m.matches()) { 101 String sc = m.group(MATCH_GROUP_SERVICE_CODE); 102 return sc != null && 103 (sc.equals(SC_CFU) 104 || sc.equals(SC_CFB) || sc.equals(SC_CFNRy) 105 || sc.equals(SC_CFNR) || sc.equals(SC_CF_All) 106 || sc.equals(SC_CF_All_Conditional)); 107 } 108 109 return false; 110 111 } 112 isTelScheme(Uri handle)113 private static boolean isTelScheme(Uri handle) { 114 return (handle != null && handle.getSchemeSpecificPart() != null && 115 handle.getScheme() != null && 116 handle.getScheme().equals(PhoneAccount.SCHEME_TEL)); 117 } 118 isDangerousVerticalServiceCode(Uri handle)119 private boolean isDangerousVerticalServiceCode(Uri handle) { 120 if (isTelScheme(handle)) { 121 String dialedNumber = handle.getSchemeSpecificPart(); 122 if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') { 123 //we only check vertical codes defined by The North American Numbering Plan Admin 124 //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html 125 //only two or 3-digit codes are valid as of today, but the code is generic enough. 126 for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet; 127 prefixLen++) { 128 String prefix = dialedNumber.substring(0, prefixLen); 129 if (sDangerousVerticalServiceCodes.contains(prefix)) { 130 return true; 131 } 132 } 133 } 134 } 135 return false; 136 } 137 138 /** 139 * Determines if a dialed number is potentially an In-Call MMI code. In-Call MMI codes are 140 * MMI codes which can be dialed when one or more calls are in progress. 141 * <P> 142 * Checks for numbers formatted similar to the MMI codes defined in: 143 * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)} 144 * 145 * @param handle The URI to call. 146 * @return {@code True} if the URI represents a number which could be an in-call MMI code. 147 */ isPotentialInCallMMICode(Uri handle)148 public boolean isPotentialInCallMMICode(Uri handle) { 149 if (isTelScheme(handle)) { 150 String dialedNumber = handle.getSchemeSpecificPart(); 151 return (dialedNumber.equals("0") || 152 (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) || 153 (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) || 154 dialedNumber.equals("3") || 155 dialedNumber.equals("4") || 156 dialedNumber.equals("5")); 157 } 158 return false; 159 } 160 isPotentialMMICode(Uri handle)161 public boolean isPotentialMMICode(Uri handle) { 162 return (handle != null && handle.getSchemeSpecificPart() != null 163 && handle.getSchemeSpecificPart().contains("#")); 164 } 165 166 /** 167 * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous 168 * codes are ones, for which, 169 * we normally expect the user to be aware that an application has dialed them 170 * 171 * @param handle The URI to call. 172 * @return {@code True} if the URI represents a dangerous code 173 */ isDangerousMmiOrVerticalCode(Uri handle)174 public boolean isDangerousMmiOrVerticalCode(Uri handle) { 175 if (isPotentialMMICode(handle)) { 176 return isCallForwardingMmiCode(handle); 177 //since some dangerous mmi codes could be carrier specific, in the future, 178 //we can add a carrier config item which can list carrier specific dangerous mmi codes 179 } else if (isDangerousVerticalServiceCode(handle)) { 180 return true; 181 } 182 return false; 183 } 184 } 185