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