1 /*
2  * Copyright (C) 2022 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.internal.telephony;
18 
19 import android.text.TextUtils;
20 
21 import com.android.i18n.phonenumbers.NumberParseException;
22 import com.android.i18n.phonenumbers.PhoneNumberUtil;
23 import com.android.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
24 import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.telephony.uicc.AdnRecord;
27 import com.android.internal.telephony.uicc.AdnRecordCache;
28 import com.android.internal.telephony.uicc.IccConstants;
29 import com.android.internal.telephony.uicc.IccRecords;
30 import com.android.internal.telephony.uicc.UiccCardApplication;
31 import com.android.internal.telephony.uicc.UiccController;
32 import com.android.internal.telephony.uicc.UiccProfile;
33 import com.android.telephony.Rlog;
34 
35 import java.util.ArrayList;
36 import java.util.regex.PatternSyntaxException;
37 
38 /**
39  * This is a basic utility class for common functions related to Fixed Dialing Numbers
40  * designed as per 3GPP 22.101.
41  */
42 public class FdnUtils {
43     private static final boolean VDBG = false;
44     private static final String LOG_TAG = FdnUtils.class.getSimpleName();
45 
46     /**
47      * The following function checks if dialed number is blocked due to FDN.
48      *
49      * @param phoneId The phone object id for which the FDN check is performed
50      * @param dialStr dialed phone number
51      * @param defaultCountryIso country ISO for the subscription associated with this phone
52      * @return {@code true} if dialStr is blocked due to FDN check.
53      */
isNumberBlockedByFDN(int phoneId, String dialStr, String defaultCountryIso)54     public static boolean isNumberBlockedByFDN(int phoneId, String dialStr,
55             String defaultCountryIso) {
56         if (!isFdnEnabled(phoneId)) {
57             return false;
58         }
59 
60         ArrayList<AdnRecord> fdnList = getFdnList(phoneId);
61         return !isFDN(dialStr, defaultCountryIso, fdnList);
62     }
63 
64     /**
65      * Checks if FDN is enabled
66      * @param phoneId The phone object id for which the FDN check is performed
67      * @return {@code true} if FDN is enabled
68      */
isFdnEnabled(int phoneId)69     public static boolean isFdnEnabled(int phoneId) {
70         UiccCardApplication app = getUiccCardApplication(phoneId);
71         if (app == null || (!app.getIccFdnAvailable())) {
72             return false;
73         }
74 
75         return app.getIccFdnEnabled();
76     }
77 
78     /**
79      * If FDN is enabled, check to see if the given supplementary service control strings are
80      * blocked due to FDN.
81      * @param phoneId The phone object id for which the FDN check is performed
82      * @param controlStrings control strings associated with the supplementary service request
83      * @param defaultCountryIso country ISO for the subscription associated with this phone
84      * @return {@code true} if the FDN list does not contain any of the control strings.
85      */
isSuppServiceRequestBlockedByFdn(int phoneId, ArrayList<String> controlStrings, String defaultCountryIso)86     public static boolean isSuppServiceRequestBlockedByFdn(int phoneId,
87             ArrayList<String> controlStrings, String defaultCountryIso) {
88         if (!isFdnEnabled(phoneId)) {
89             return false;
90         }
91 
92         ArrayList<AdnRecord> fdnList = getFdnList(phoneId);
93         for(String controlString : controlStrings) {
94             if(isFDN(controlString, defaultCountryIso, fdnList)) {
95                 return false;
96             }
97         }
98         return true;
99     }
100 
101     /**
102      * Checks if dialStr is part of FDN list.
103      *
104      * @param fdnList List of all FDN records associated with a sim card
105      * @param dialStr dialed phone number
106      * @param defaultCountryIso country ISO for the subscription associated with this phone
107      * @return {@code true} if dialStr is present in the fdnList.
108      */
109     @VisibleForTesting
isFDN(String dialStr, String defaultCountryIso, ArrayList<AdnRecord> fdnList)110     public static boolean isFDN(String dialStr, String defaultCountryIso,
111             ArrayList<AdnRecord> fdnList) {
112         if (fdnList == null || fdnList.isEmpty() || TextUtils.isEmpty(dialStr)) {
113             Rlog.w(LOG_TAG, "isFDN: unexpected null value");
114             return false;
115         }
116 
117         // Parse the dialStr and convert it to E164 format
118         String dialStrE164 = null;
119         String dialStrNational = null;
120         final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
121         try {
122             PhoneNumber phoneNumber = phoneNumberUtil.parse(dialStr, defaultCountryIso);
123             dialStrE164 = phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
124             dialStrNational = String.valueOf(phoneNumber.getNationalNumber());
125         } catch (NumberParseException ignored) {
126             Rlog.w(LOG_TAG, "isFDN: could not parse dialStr");
127             dialStr = extractSMSC(dialStr);
128         }
129 
130         /**
131          * Returns true if dialStrE164 or dialStrNational or dialStr starts with fdnNumber
132          * E.g.1: returns true if fdnNumber="123" and dialStr="12345"
133          * E.g.2: does not return true if fdnNumber="1123" and dialStr="12345"
134          */
135         for (AdnRecord fdn: fdnList) {
136             String fdnNumber = fdn.getNumber();
137             if (TextUtils.isEmpty(fdnNumber)) {
138                 continue;
139             }
140 
141             if(!TextUtils.isEmpty(dialStrE164)) {
142                 if(dialStrE164.startsWith(fdnNumber)) {
143                     return true;
144                 }
145             }
146 
147             if(!TextUtils.isEmpty(dialStrNational)) {
148                 if (dialStrNational.startsWith(fdnNumber)) {
149                     return true;
150                 }
151             }
152 
153             if (dialStr.startsWith(fdnNumber)) {
154                 return true;
155             }
156         }
157 
158         if (VDBG) {
159             Rlog.i(LOG_TAG, "isFDN: dialed number not present in FDN list");
160         }
161         return false;
162     }
163 
getFdnList(int phoneId)164     private static ArrayList<AdnRecord> getFdnList(int phoneId) {
165         UiccCardApplication app = getUiccCardApplication(phoneId);
166         if (app == null) {
167             return null;
168         }
169 
170         IccRecords iccRecords = app.getIccRecords();
171         if (iccRecords == null) {
172             return null;
173         }
174 
175         AdnRecordCache adnRecordCache = iccRecords.getAdnCache();
176         if(adnRecordCache == null) {
177             return null;
178         }
179 
180         return adnRecordCache.getRecordsIfLoaded(IccConstants.EF_FDN);
181     }
182 
getUiccCardApplication(int phoneId)183     private static UiccCardApplication getUiccCardApplication(int phoneId) {
184         UiccProfile uiccProfile = UiccController.getInstance()
185                 .getUiccProfileForPhone(phoneId);
186         if (uiccProfile == null) {
187             return null;
188         }
189 
190         return uiccProfile.getApplication(UiccController.APP_FAM_3GPP);
191     }
192 
extractSMSC(String dialStr)193     private static String extractSMSC(String dialStr) {
194         try {
195             String[] dialStrParts = null;
196             if (dialStr.contains(",")) {
197                 // SMSC can be in the format of ""+123456789123",123"
198                 // Split into two parts using comma as delimiter
199                 // and first part of the string is used as smsc address
200                 dialStrParts = dialStr.split(",");
201             } else if (dialStr.contains("@")) {
202                 // SMSC can be in the format of "+123456789123@ims.mnc.org"
203                 // Split into two parts using @ as delimiter
204                 // and first part of the string is used as smsc address
205                 dialStrParts = dialStr.split("@");
206             }
207 
208             if (dialStrParts != null && dialStrParts.length >= 1) {
209                 if (dialStrParts[0].contains("\"")) {
210                     // If SMSC is in this format: ""+123456789123",123", after performing above
211                     // split we get string with double-quotation marks in it
212                     // dialStrParts[0] = ""+123456789123"".
213                     // Here, we remove double-quotation marks from the string.
214                     dialStrParts[0] = dialStrParts[0].replaceAll("\"", "");
215                 }
216                 return dialStrParts[0];
217             }
218         } catch (PatternSyntaxException ex) {
219             Rlog.w(LOG_TAG, "extractSMSC: Could not extract number from dialStr " + ex);
220         }
221 
222         // Return original dialStr if it is not in any of the formats mentions above.
223         return dialStr;
224     }
225 }