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