• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.content.Context;
20 import android.database.Cursor;
21 import android.database.SQLException;
22 import android.os.Binder;
23 import android.os.PersistableBundle;
24 import android.os.SystemProperties;
25 import android.telephony.CarrierConfigManager;
26 import android.telephony.PhoneNumberUtils;
27 import android.telephony.TelephonyManager;
28 import android.text.TextUtils;
29 import android.util.Base64;
30 import android.util.Log;
31 
32 import com.android.internal.telephony.HbpcdLookup.MccIdd;
33 import com.android.internal.telephony.HbpcdLookup.MccLookup;
34 import com.android.internal.telephony.util.TelephonyUtils;
35 
36 import java.security.MessageDigest;
37 import java.security.NoSuchAlgorithmException;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 
41 
42 /**
43  * This class implements handle the MO SMS target address before sending.
44  * This is special for VZW requirement. Follow the specifications of assisted dialing
45  * of MO SMS while traveling on VZW CDMA, international CDMA or GSM markets.
46  * {@hide}
47  */
48 public class SmsNumberUtils {
49     private static final String TAG = "SmsNumberUtils";
50     private static final boolean DBG = SystemProperties.getInt("ro.debuggable", 0) == 1;
51 
52     private static final String PLUS_SIGN = "+";
53 
54     private static final int NANP_SHORT_LENGTH = 7;
55     private static final int NANP_MEDIUM_LENGTH = 10;
56     private static final int NANP_LONG_LENGTH = 11;
57 
58     private static final int NANP_CC = 1;
59     private static final String NANP_NDD = "1";
60     private static final String NANP_IDD = "011";
61 
62     private static final int MIN_COUNTRY_AREA_LOCAL_LENGTH = 10;
63 
64     private static final int GSM_UMTS_NETWORK = 0;
65     private static final int CDMA_HOME_NETWORK = 1;
66     private static final int CDMA_ROAMING_NETWORK = 2;
67 
68     private static final int NP_NONE = 0;
69     private static final int NP_NANP_BEGIN = 1;
70 
71     /* <Phone Number>, <NXX>-<XXXX> N[2-9] */
72     private static final int NP_NANP_LOCAL = NP_NANP_BEGIN;
73 
74     /* <Area_code>-<Phone Number>, <NXX>-<NXX>-<XXXX> N[2-9] */
75     private static final int NP_NANP_AREA_LOCAL = NP_NANP_BEGIN + 1;
76 
77     /* <1>-<Area_code>-<Phone Number>, 1-<NXX>-<NXX>-<XXXX> N[2-9] */
78     private static final int NP_NANP_NDD_AREA_LOCAL = NP_NANP_BEGIN + 2;
79 
80     /* <+><U.S.Country_code><Area_code><Phone Number>, +1-<NXX>-<NXX>-<XXXX> N[2-9] */
81     private static final int NP_NANP_NBPCD_CC_AREA_LOCAL = NP_NANP_BEGIN + 3;
82 
83     /* <Local_IDD><Country_code><Area_code><Phone Number>, 001-1-<NXX>-<NXX>-<XXXX> N[2-9] */
84     private static final int NP_NANP_LOCALIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 4;
85 
86     /* <+><Home_IDD><Country_code><Area_code><Phone Number>, +011-1-<NXX>-<NXX>-<XXXX> N[2-9] */
87     private static final int NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_NANP_BEGIN + 5;
88 
89     private static final int NP_INTERNATIONAL_BEGIN = 100;
90     /* <+>-<Home_IDD>-<Country_code>-<Area_code>-<Phone Number>, +011-86-25-86281234 */
91     private static final int NP_NBPCD_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN;
92 
93     /* <Home_IDD>-<Country_code>-<Area_code>-<Phone Number>, 011-86-25-86281234 */
94     private static final int NP_HOMEIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 1;
95 
96     /* <NBPCD>-<Country_code>-<Area_code>-<Phone Number>, +1-86-25-86281234 */
97     private static final int NP_NBPCD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 2;
98 
99     /* <Local_IDD>-<Country_code>-<Area_code>-<Phone Number>, 00-86-25-86281234 */
100     private static final int NP_LOCALIDD_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 3;
101 
102     /* <Country_code>-<Area_code>-<Phone Number>, 86-25-86281234*/
103     private static final int NP_CC_AREA_LOCAL = NP_INTERNATIONAL_BEGIN + 4;
104 
105     private static int[] ALL_COUNTRY_CODES = null;
106     private static int MAX_COUNTRY_CODES_LENGTH;
107     private static HashMap<String, ArrayList<String>> IDDS_MAPS =
108             new HashMap<String, ArrayList<String>>();
109 
110     private static class NumberEntry {
111         public String number;
112         public String IDD;
113         public int countryCode;
NumberEntry(String number)114         public NumberEntry(String number) {
115             this.number = number;
116         }
117     }
118 
119     /**
120      * Breaks the given number down and formats it according to the rules
121      * for different number plans and different network.
122      *
123      * @param number destination number which need to be format
124      * @param activeMcc current network's mcc
125      * @param networkType current network type
126      *
127      * @return the number after formatting.
128      */
formatNumber(Context context, String number, String activeMcc, int networkType)129     private static String formatNumber(Context context, String number,
130                                String activeMcc,
131                                int networkType) {
132         if (number == null ) {
133             throw new IllegalArgumentException("number is null");
134         }
135 
136         if (activeMcc == null || activeMcc.trim().length() == 0) {
137             throw new IllegalArgumentException("activeMcc is null or empty!");
138         }
139 
140         String networkPortionNumber = PhoneNumberUtils.extractNetworkPortion(number);
141         if (networkPortionNumber == null || networkPortionNumber.length() == 0) {
142             throw new IllegalArgumentException("Number is invalid!");
143         }
144 
145         NumberEntry numberEntry = new NumberEntry(networkPortionNumber);
146         ArrayList<String> allIDDs = getAllIDDs(context, activeMcc);
147 
148         // First check whether the number is a NANP number.
149         int nanpState = checkNANP(numberEntry, allIDDs);
150         if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
151 
152         if ((nanpState == NP_NANP_LOCAL)
153             || (nanpState == NP_NANP_AREA_LOCAL)
154             || (nanpState == NP_NANP_NDD_AREA_LOCAL)) {
155             return networkPortionNumber;
156         } else if (nanpState == NP_NANP_NBPCD_CC_AREA_LOCAL) {
157             if (networkType == CDMA_HOME_NETWORK
158                     || networkType == CDMA_ROAMING_NETWORK) {
159                 // Remove "+"
160                 return networkPortionNumber.substring(1);
161             } else {
162                 return networkPortionNumber;
163             }
164         } else if (nanpState == NP_NANP_LOCALIDD_CC_AREA_LOCAL) {
165             if (networkType == CDMA_HOME_NETWORK) {
166                 return networkPortionNumber;
167             } else if (networkType == GSM_UMTS_NETWORK) {
168                 // Remove the local IDD and replace with "+"
169                 int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
170                 return PLUS_SIGN + networkPortionNumber.substring(iddLength);
171             } else if (networkType == CDMA_ROAMING_NETWORK) {
172                 // Remove the local IDD
173                 int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
174                 return  networkPortionNumber.substring(iddLength);
175             }
176         }
177 
178         int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
179                 NANP_IDD);
180         if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
181         String returnNumber = null;
182 
183         switch (internationalState) {
184             case NP_NBPCD_HOMEIDD_CC_AREA_LOCAL:
185                 if (networkType == GSM_UMTS_NETWORK) {
186                     // Remove "+"
187                     returnNumber = networkPortionNumber.substring(1);
188                 }
189                 break;
190 
191             case NP_NBPCD_CC_AREA_LOCAL:
192                 // Replace "+" with "011"
193                 returnNumber = NANP_IDD + networkPortionNumber.substring(1);
194                 break;
195 
196             case NP_LOCALIDD_CC_AREA_LOCAL:
197                 if (networkType == GSM_UMTS_NETWORK || networkType == CDMA_ROAMING_NETWORK) {
198                     int iddLength  =  numberEntry.IDD != null ? numberEntry.IDD.length() : 0;
199                     // Replace <Local IDD> to <Home IDD>("011")
200                     returnNumber = NANP_IDD + networkPortionNumber.substring(iddLength);
201                 }
202                 break;
203 
204             case NP_CC_AREA_LOCAL:
205                 int countryCode = numberEntry.countryCode;
206 
207                 if (!inExceptionListForNpCcAreaLocal(numberEntry)
208                     && networkPortionNumber.length() >= 11 && countryCode != NANP_CC) {
209                     // Add "011"
210                     returnNumber = NANP_IDD + networkPortionNumber;
211                 }
212                 break;
213 
214             case NP_HOMEIDD_CC_AREA_LOCAL:
215                 returnNumber = networkPortionNumber;
216                 break;
217 
218             default:
219                 // Replace "+" with 011 in CDMA network if the number's country
220                 // code is not in the HbpcdLookup database.
221                 if (networkPortionNumber.startsWith(PLUS_SIGN)
222                     && (networkType == CDMA_HOME_NETWORK || networkType == CDMA_ROAMING_NETWORK)) {
223                     if (networkPortionNumber.startsWith(PLUS_SIGN + NANP_IDD)) {
224                         // Only remove "+"
225                         returnNumber = networkPortionNumber.substring(1);
226                     } else {
227                         // Replace "+" with "011"
228                         returnNumber = NANP_IDD + networkPortionNumber.substring(1);
229                     }
230                 }
231         }
232 
233         if (returnNumber == null) {
234             returnNumber = networkPortionNumber;
235         }
236         return returnNumber;
237     }
238 
239     /**
240      * Query International direct dialing from HbpcdLookup.db
241      * for specified country code
242      *
243      * @param mcc current network's country code
244      *
245      * @return the IDD array list.
246      */
getAllIDDs(Context context, String mcc)247     private static ArrayList<String> getAllIDDs(Context context, String mcc) {
248         ArrayList<String> allIDDs = IDDS_MAPS.get(mcc);
249         if (allIDDs != null) {
250             return allIDDs;
251         } else {
252             allIDDs = new ArrayList<String>();
253         }
254 
255         String projection[] = {MccIdd.IDD, MccIdd.MCC};
256         String where = null;
257 
258         // if mcc is null         : return all rows
259         // if mcc is empty-string : return those rows whose mcc is emptry-string
260         String[] selectionArgs = null;
261         if (mcc != null) {
262             where = MccIdd.MCC + "=?";
263             selectionArgs = new String[] {mcc};
264         }
265 
266         Cursor cursor = null;
267         try {
268             cursor = context.getContentResolver().query(MccIdd.CONTENT_URI, projection,
269                     where, selectionArgs, null);
270             if (cursor.getCount() > 0) {
271                 while (cursor.moveToNext()) {
272                     String idd = cursor.getString(0);
273                     if (!allIDDs.contains(idd)) {
274                         allIDDs.add(idd);
275                     }
276                 }
277             }
278         } catch (SQLException e) {
279             Log.e(TAG, "Can't access HbpcdLookup database", e);
280         } finally {
281             if (cursor != null) {
282                 cursor.close();
283             }
284         }
285 
286         IDDS_MAPS.put(mcc, allIDDs);
287 
288         if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
289         return allIDDs;
290     }
291 
292 
293     /**
294      * Verify if the the destination number is a NANP number
295      *
296      * @param numberEntry including number and IDD array
297      * @param allIDDs the IDD array list of the current network's country code
298      *
299      * @return the number plan type related NANP
300      */
checkNANP(NumberEntry numberEntry, ArrayList<String> allIDDs)301     private static int checkNANP(NumberEntry numberEntry, ArrayList<String> allIDDs) {
302         boolean isNANP = false;
303         String number = numberEntry.number;
304 
305         if (number.length() == NANP_SHORT_LENGTH) {
306             // 7 digits - Seven digit phone numbers
307             char firstChar = number.charAt(0);
308             if (firstChar >= '2' && firstChar <= '9') {
309                 isNANP = true;
310                 for (int i=1; i< NANP_SHORT_LENGTH; i++ ) {
311                     char c= number.charAt(i);
312                     if (!PhoneNumberUtils.isISODigit(c)) {
313                         isNANP = false;
314                         break;
315                     }
316                 }
317             }
318             if (isNANP) {
319                 return NP_NANP_LOCAL;
320             }
321         } else if (number.length() == NANP_MEDIUM_LENGTH) {
322             // 10 digits - Three digit area code followed by seven digit phone numbers/
323             if (isNANP(number)) {
324                 return NP_NANP_AREA_LOCAL;
325             }
326         } else if (number.length() == NANP_LONG_LENGTH) {
327             // 11 digits - One digit U.S. NDD(National Direct Dial) prefix '1',
328             // followed by three digit area code and seven digit phone numbers
329             if (isNANP(number)) {
330                 return NP_NANP_NDD_AREA_LOCAL;
331             }
332         } else if (number.startsWith(PLUS_SIGN)) {
333             number = number.substring(1);
334             if (number.length() == NANP_LONG_LENGTH) {
335                 // '+' and 11 digits -'+', followed by NANP CC prefix '1' followed by
336                 // three digit area code and seven digit phone numbers
337                 if (isNANP(number)) {
338                     return NP_NANP_NBPCD_CC_AREA_LOCAL;
339                 }
340             } else if (number.startsWith(NANP_IDD) && number.length() == NANP_LONG_LENGTH + 3) {
341                 // '+' and 14 digits -'+', followed by NANP IDD "011" followed by NANP CC
342                 // prefix '1' followed by three digit area code and seven digit phone numbers
343                 number = number.substring(3);
344                 if (isNANP(number)) {
345                     return NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL;
346                 }
347             }
348         } else {
349             // Check whether it's NP_NANP_LOCALIDD_CC_AREA_LOCAL
350             for (String idd : allIDDs) {
351                 if (number.startsWith(idd)) {
352                     String number2 = number.substring(idd.length());
353                     if(number2 !=null && number2.startsWith(String.valueOf(NANP_CC))){
354                         if (isNANP(number2)) {
355                             numberEntry.IDD = idd;
356                             return NP_NANP_LOCALIDD_CC_AREA_LOCAL;
357                         }
358                     }
359                 }
360             }
361         }
362 
363         return NP_NONE;
364     }
365 
366     /**
367      * This function checks if the passed in string conforms to the NANP format
368      * i.e. NXX-NXX-XXXX, N is any digit 2-9 and X is any digit 0-9
369      */
isNANP(String number)370     private static boolean isNANP(String number) {
371         boolean retVal = false;
372 
373         if (number.length() == NANP_MEDIUM_LENGTH
374             || (number.length() == NANP_LONG_LENGTH  && number.startsWith(NANP_NDD))) {
375 
376             if (number.length() == NANP_LONG_LENGTH) {
377                 number = number.substring(1);
378             }
379 
380             if (isTwoToNine(number.charAt(0)) &&
381                 isTwoToNine(number.charAt(3))) {
382                 retVal = true;
383                 for (int i=1; i<NANP_MEDIUM_LENGTH; i++ ) {
384                     char c=number.charAt(i);
385                     if (!PhoneNumberUtils.isISODigit(c)) {
386                         retVal = false;
387                         break;
388                     }
389                  }
390              }
391         }
392         return retVal;
393     }
394 
isTwoToNine(char c)395     private static boolean isTwoToNine (char c) {
396         if (c >= '2' && c <= '9') {
397             return true;
398         } else {
399             return false;
400         }
401     }
402 
403     /**
404      * Verify if the the destination number is an internal number
405      *
406      * @param numberEntry including number and IDD array
407      * @param allIDDs the IDD array list of the current network's country code
408      *
409      * @return the number plan type related international number
410      */
checkInternationalNumberPlan(Context context, NumberEntry numberEntry, ArrayList<String> allIDDs,String homeIDD)411     private static int checkInternationalNumberPlan(Context context, NumberEntry numberEntry,
412             ArrayList<String> allIDDs,String homeIDD) {
413         String number = numberEntry.number;
414         int countryCode = -1;
415 
416         if (number.startsWith(PLUS_SIGN)) {
417             // +xxxxxxxxxx
418             String numberNoNBPCD = number.substring(1);
419             if (numberNoNBPCD.startsWith(homeIDD)) {
420                 // +011xxxxxxxx
421                 String numberCountryAreaLocal = numberNoNBPCD.substring(homeIDD.length());
422                 if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) {
423                     numberEntry.countryCode = countryCode;
424                     return NP_NBPCD_HOMEIDD_CC_AREA_LOCAL;
425                 }
426             } else if ((countryCode = getCountryCode(context, numberNoNBPCD)) > 0) {
427                 numberEntry.countryCode = countryCode;
428                 return NP_NBPCD_CC_AREA_LOCAL;
429             }
430 
431         } else if (number.startsWith(homeIDD)) {
432             // 011xxxxxxxxx
433             String numberCountryAreaLocal = number.substring(homeIDD.length());
434             if ((countryCode = getCountryCode(context, numberCountryAreaLocal)) > 0) {
435                 numberEntry.countryCode = countryCode;
436                 return NP_HOMEIDD_CC_AREA_LOCAL;
437             }
438         } else {
439             for (String exitCode : allIDDs) {
440                 if (number.startsWith(exitCode)) {
441                     String numberNoIDD = number.substring(exitCode.length());
442                     if ((countryCode = getCountryCode(context, numberNoIDD)) > 0) {
443                         numberEntry.countryCode = countryCode;
444                         numberEntry.IDD = exitCode;
445                         return NP_LOCALIDD_CC_AREA_LOCAL;
446                     }
447                 }
448             }
449 
450             if (!number.startsWith("0") && (countryCode = getCountryCode(context, number)) > 0) {
451                 numberEntry.countryCode = countryCode;
452                 return NP_CC_AREA_LOCAL;
453             }
454         }
455         return NP_NONE;
456     }
457 
458     /**
459      *  Returns the country code from the given number.
460      */
getCountryCode(Context context, String number)461     private static int getCountryCode(Context context, String number) {
462         int countryCode = -1;
463         if (number.length() >= MIN_COUNTRY_AREA_LOCAL_LENGTH) {
464             // Check Country code
465             int[] allCCs = getAllCountryCodes(context);
466             if (allCCs == null) {
467                 return countryCode;
468             }
469 
470             int[] ccArray = new int[MAX_COUNTRY_CODES_LENGTH];
471             for (int i = 0; i < MAX_COUNTRY_CODES_LENGTH; i ++) {
472                 ccArray[i] = Integer.parseInt(number.substring(0, i+1));
473             }
474 
475             for (int i = 0; i < allCCs.length; i ++) {
476                 int tempCC = allCCs[i];
477                 for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
478                     if (tempCC == ccArray[j]) {
479                         if (DBG) Log.d(TAG, "Country code = " + tempCC);
480                         return tempCC;
481                     }
482                 }
483             }
484         }
485 
486         return countryCode;
487     }
488 
489     /**
490      *  Gets all country Codes information with given MCC.
491      */
getAllCountryCodes(Context context)492     private static int[] getAllCountryCodes(Context context) {
493         if (ALL_COUNTRY_CODES != null) {
494             return ALL_COUNTRY_CODES;
495         }
496 
497         Cursor cursor = null;
498         try {
499             String projection[] = {MccLookup.COUNTRY_CODE};
500             cursor = context.getContentResolver().query(MccLookup.CONTENT_URI,
501                     projection, null, null, null);
502 
503             if (cursor.getCount() > 0) {
504                 ALL_COUNTRY_CODES = new int[cursor.getCount()];
505                 int i = 0;
506                 while (cursor.moveToNext()) {
507                     int countryCode = cursor.getInt(0);
508                     ALL_COUNTRY_CODES[i++] = countryCode;
509                     int length = String.valueOf(countryCode).trim().length();
510                     if (length > MAX_COUNTRY_CODES_LENGTH) {
511                         MAX_COUNTRY_CODES_LENGTH = length;
512                     }
513                 }
514             }
515         } catch (SQLException e) {
516             Log.e(TAG, "Can't access HbpcdLookup database", e);
517         } finally {
518             if (cursor != null) {
519                 cursor.close();
520             }
521         }
522         return ALL_COUNTRY_CODES;
523     }
524 
inExceptionListForNpCcAreaLocal(NumberEntry numberEntry)525     private static boolean inExceptionListForNpCcAreaLocal(NumberEntry numberEntry) {
526         int countryCode = numberEntry.countryCode;
527         boolean result = (numberEntry.number.length() == 12
528                           && (countryCode == 7 || countryCode == 20
529                               || countryCode == 65 || countryCode == 90));
530         return result;
531     }
532 
getNumberPlanType(int state)533     private static String getNumberPlanType(int state) {
534         String numberPlanType = "Number Plan type (" + state + "): ";
535 
536         if (state == NP_NANP_LOCAL) {
537             numberPlanType = "NP_NANP_LOCAL";
538         } else if (state == NP_NANP_AREA_LOCAL) {
539             numberPlanType = "NP_NANP_AREA_LOCAL";
540         } else if (state  == NP_NANP_NDD_AREA_LOCAL) {
541             numberPlanType = "NP_NANP_NDD_AREA_LOCAL";
542         } else if (state == NP_NANP_NBPCD_CC_AREA_LOCAL) {
543             numberPlanType = "NP_NANP_NBPCD_CC_AREA_LOCAL";
544         } else if (state == NP_NANP_LOCALIDD_CC_AREA_LOCAL) {
545             numberPlanType = "NP_NANP_LOCALIDD_CC_AREA_LOCAL";
546         } else if (state == NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL) {
547             numberPlanType = "NP_NANP_NBPCD_HOMEIDD_CC_AREA_LOCAL";
548         } else if (state == NP_NBPCD_HOMEIDD_CC_AREA_LOCAL) {
549             numberPlanType = "NP_NBPCD_HOMEIDD_CC_AREA_LOCAL";
550         } else if (state == NP_HOMEIDD_CC_AREA_LOCAL) {
551             numberPlanType = "NP_HOMEIDD_CC_AREA_LOCAL";
552         } else if (state == NP_NBPCD_CC_AREA_LOCAL) {
553             numberPlanType = "NP_NBPCD_CC_AREA_LOCAL";
554         } else if (state == NP_LOCALIDD_CC_AREA_LOCAL) {
555             numberPlanType = "NP_LOCALIDD_CC_AREA_LOCAL";
556         } else if (state == NP_CC_AREA_LOCAL) {
557             numberPlanType = "NP_CC_AREA_LOCAL";
558         } else {
559             numberPlanType = "Unknown type";
560         }
561         return numberPlanType;
562     }
563 
564     /**
565      * Filter the destination number if using VZW sim card.
566      */
filterDestAddr(Context context, int subId, String destAddr)567     public static String filterDestAddr(Context context, int subId, String destAddr) {
568         if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
569 
570         if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
571             Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
572                     " is not a global phone number! Nothing changed.");
573             return destAddr;
574         }
575 
576         final TelephonyManager telephonyManager = ((TelephonyManager) context
577                 .getSystemService(Context.TELEPHONY_SERVICE)).createForSubscriptionId(subId);
578         final String networkOperator = telephonyManager.getNetworkOperator();
579         String result = null;
580 
581         if (needToConvert(context, subId)) {
582             final int networkType = getNetworkType(telephonyManager);
583             if (networkType != -1 && !TextUtils.isEmpty(networkOperator)) {
584                 String networkMcc = networkOperator.substring(0, 3);
585                 if (networkMcc != null && networkMcc.trim().length() > 0) {
586                     result = formatNumber(context, destAddr, networkMcc, networkType);
587                 }
588             }
589         }
590 
591         if (DBG) {
592             Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
593             Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
594                     result) : pii(TAG, destAddr)) + "\"");
595         }
596         return result != null ? result : destAddr;
597     }
598 
599     /**
600      * Returns the current network type
601      */
getNetworkType(TelephonyManager telephonyManager)602     private static int getNetworkType(TelephonyManager telephonyManager) {
603         int networkType = -1;
604         int phoneType = telephonyManager.getPhoneType();
605 
606         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
607             networkType = GSM_UMTS_NETWORK;
608         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
609             if (isInternationalRoaming(telephonyManager)) {
610                 networkType = CDMA_ROAMING_NETWORK;
611             } else {
612                 networkType = CDMA_HOME_NETWORK;
613             }
614         } else {
615             if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
616         }
617 
618         return networkType;
619     }
620 
isInternationalRoaming(TelephonyManager telephonyManager)621     private static boolean isInternationalRoaming(TelephonyManager telephonyManager) {
622         String operatorIsoCountry = telephonyManager.getNetworkCountryIso();
623         String simIsoCountry = telephonyManager.getSimCountryIso();
624         boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoCountry)
625                 && !TextUtils.isEmpty(simIsoCountry)
626                 && !simIsoCountry.equals(operatorIsoCountry);
627         if (internationalRoaming) {
628             if ("us".equals(simIsoCountry)) {
629                 internationalRoaming = !"vi".equals(operatorIsoCountry);
630             } else if ("vi".equals(simIsoCountry)) {
631                 internationalRoaming = !"us".equals(operatorIsoCountry);
632             }
633         }
634         return internationalRoaming;
635     }
636 
needToConvert(Context context, int subId)637     private static boolean needToConvert(Context context, int subId) {
638         // Calling package may not have READ_PHONE_STATE which is required for getConfig().
639         // Clear the calling identity so that it is called as self.
640         final long identity = Binder.clearCallingIdentity();
641         try {
642             CarrierConfigManager configManager = (CarrierConfigManager)
643                     context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
644             if (configManager != null) {
645                 PersistableBundle bundle = configManager.getConfigForSubId(subId);
646                 if (bundle != null) {
647                     return bundle.getBoolean(CarrierConfigManager
648                             .KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL);
649                 }
650             }
651         } finally {
652             Binder.restoreCallingIdentity(identity);
653         }
654         // by default this value is false
655         return false;
656     }
657 
658     /**
659      * Redact personally identifiable information for production users.
660      * @param tag used to identify the source of a log message
661      * @param pii the personally identifiable information we want to apply secure hash on.
662      * @return If tag is loggable in verbose mode or pii is null, return the original input.
663      * otherwise return a secure Hash of input pii
664      */
pii(String tag, Object pii)665     private static String pii(String tag, Object pii) {
666         String val = String.valueOf(pii);
667         if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
668             return val;
669         }
670         return "[" + secureHash(val.getBytes()) + "]";
671     }
672 
673     /**
674      * Returns a secure hash (using the SHA1 algorithm) of the provided input.
675      *
676      * @return "****" if the build type is user, otherwise the hash
677      * @param input the bytes for which the secure hash should be computed.
678      */
secureHash(byte[] input)679     private static String secureHash(byte[] input) {
680         // Refrain from logging user personal information in user build.
681         if (TelephonyUtils.IS_USER) {
682             return "****";
683         }
684 
685         MessageDigest messageDigest;
686 
687         try {
688             messageDigest = MessageDigest.getInstance("SHA-1");
689         } catch (NoSuchAlgorithmException e) {
690             return "####";
691         }
692 
693         byte[] result = messageDigest.digest(input);
694         return Base64.encodeToString(
695                 result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
696     }
697 }
698