1 /* 2 * Copyright (C) 2015 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.messaging.util; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.database.Cursor; 26 import android.net.ConnectivityManager; 27 import android.provider.Settings; 28 import android.provider.Telephony; 29 import android.support.v4.util.ArrayMap; 30 import android.telephony.PhoneNumberUtils; 31 import android.telephony.SmsManager; 32 import android.telephony.SubscriptionInfo; 33 import android.telephony.SubscriptionManager; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 37 import com.android.messaging.Factory; 38 import com.android.messaging.R; 39 import com.android.messaging.datamodel.data.ParticipantData; 40 import com.android.messaging.sms.MmsSmsUtils; 41 import com.google.i18n.phonenumbers.NumberParseException; 42 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; 43 import com.google.i18n.phonenumbers.PhoneNumberUtil; 44 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat; 45 46 import java.lang.reflect.Method; 47 import java.util.ArrayList; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Locale; 51 52 /** 53 * This class abstracts away platform dependency of calling telephony related 54 * platform APIs, mostly involving TelephonyManager, SubscriptionManager and 55 * a bit of SmsManager. 56 * 57 * The class instance can only be obtained via the get(int subId) method parameterized 58 * by a SIM subscription ID. On pre-L_MR1, the subId is not used and it has to be 59 * the default subId (-1). 60 * 61 * A convenient getDefault() method is provided for default subId (-1) on any platform 62 */ 63 public abstract class PhoneUtils { 64 private static final String TAG = LogUtil.BUGLE_TAG; 65 66 private static final int MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT = 6; 67 68 private static final List<SubscriptionInfo> EMPTY_SUBSCRIPTION_LIST = new ArrayList<>(); 69 70 // The canonical phone number cache 71 // Each country gets its own cache. The following maps from ISO country code to 72 // the country's cache. Each cache maps from original phone number to canonicalized phone 73 private static final ArrayMap<String, ArrayMap<String, String>> sCanonicalPhoneNumberCache = 74 new ArrayMap<>(); 75 76 protected final Context mContext; 77 protected final TelephonyManager mTelephonyManager; 78 protected final int mSubId; 79 PhoneUtils(int subId)80 public PhoneUtils(int subId) { 81 mSubId = subId; 82 mContext = Factory.get().getApplicationContext(); 83 mTelephonyManager = 84 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 85 } 86 87 /** 88 * Get the SIM's country code 89 * 90 * @return the country code on the SIM 91 */ getSimCountry()92 public abstract String getSimCountry(); 93 94 /** 95 * Get number of SIM slots 96 * 97 * @return the SIM slot count 98 */ getSimSlotCount()99 public abstract int getSimSlotCount(); 100 101 /** 102 * Get SIM's carrier name 103 * 104 * @return the carrier name of the SIM 105 */ getCarrierName()106 public abstract String getCarrierName(); 107 108 /** 109 * Check if there is SIM inserted on the device 110 * 111 * @return true if there is SIM inserted, false otherwise 112 */ hasSim()113 public abstract boolean hasSim(); 114 115 /** 116 * Check if the SIM is roaming 117 * 118 * @return true if the SIM is in romaing state, false otherwise 119 */ isRoaming()120 public abstract boolean isRoaming(); 121 122 /** 123 * Get the MCC and MNC in integer of the SIM's provider 124 * 125 * @return an array of two ints, [0] is the MCC code and [1] is the MNC code 126 */ getMccMnc()127 public abstract int[] getMccMnc(); 128 129 /** 130 * Get the mcc/mnc string 131 * 132 * @return the text of mccmnc string 133 */ getSimOperatorNumeric()134 public abstract String getSimOperatorNumeric(); 135 136 /** 137 * Get the SIM's self raw number, i.e. not canonicalized 138 * 139 * @param allowOverride Whether to use the app's setting to override the self number 140 * @return the original self number 141 * @throws IllegalStateException if no active subscription on L-MR1+ 142 */ getSelfRawNumber(final boolean allowOverride)143 public abstract String getSelfRawNumber(final boolean allowOverride); 144 145 /** 146 * Returns the "effective" subId, or the subId used in the context of actual messages, 147 * conversations and subscription-specific settings, for the given "nominal" sub id. 148 * 149 * For pre-L-MR1 platform, this should always be 150 * {@value com.android.messaging.datamodel.data.ParticipantData#DEFAULT_SELF_SUB_ID}; 151 * 152 * On the other hand, for L-MR1 and above, DEFAULT_SELF_SUB_ID will be mapped to the system 153 * default subscription id for SMS. 154 * 155 * @param subId The input subId 156 * @return the real subId if we can convert 157 */ getEffectiveSubId(int subId)158 public abstract int getEffectiveSubId(int subId); 159 160 /** 161 * Returns the number of active subscriptions in the device. 162 */ getActiveSubscriptionCount()163 public abstract int getActiveSubscriptionCount(); 164 165 /** 166 * Get {@link SmsManager} instance 167 * 168 * @return the relevant SmsManager instance based on OS version and subId 169 */ getSmsManager()170 public abstract SmsManager getSmsManager(); 171 172 /** 173 * Get the default SMS subscription id 174 * 175 * @return the default sub ID 176 */ getDefaultSmsSubscriptionId()177 public abstract int getDefaultSmsSubscriptionId(); 178 179 /** 180 * Returns if there's currently a system default SIM selected for sending SMS. 181 */ getHasPreferredSmsSim()182 public abstract boolean getHasPreferredSmsSim(); 183 184 /** 185 * For L_MR1, system may return a negative subId. Convert this into our own 186 * subId, so that we consistently use -1 for invalid or default. 187 * 188 * see b/18629526 and b/18670346 189 * 190 * @param intent The push intent from system 191 * @param extraName The name of the sub id extra 192 * @return the subId that is valid and meaningful for the app 193 */ getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName)194 public abstract int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName); 195 196 /** 197 * Get the subscription_id column value from a telephony provider cursor 198 * 199 * @param cursor The database query cursor 200 * @param subIdIndex The index of the subId column in the cursor 201 * @return the subscription_id column value from the cursor 202 */ getSubIdFromTelephony(Cursor cursor, int subIdIndex)203 public abstract int getSubIdFromTelephony(Cursor cursor, int subIdIndex); 204 205 /** 206 * Check if data roaming is enabled 207 * 208 * @return true if data roaming is enabled, false otherwise 209 */ isDataRoamingEnabled()210 public abstract boolean isDataRoamingEnabled(); 211 212 /** 213 * Check if mobile data is enabled 214 * 215 * @return true if mobile data is enabled, false otherwise 216 */ isMobileDataEnabled()217 public abstract boolean isMobileDataEnabled(); 218 219 /** 220 * Get the set of self phone numbers, all normalized 221 * 222 * @return the set of normalized self phone numbers 223 */ getNormalizedSelfNumbers()224 public abstract HashSet<String> getNormalizedSelfNumbers(); 225 226 /** 227 * This interface packages methods should only compile on L_MR1. 228 * This is needed to make unit tests happy when mockito tries to 229 * mock these methods. Calling on these methods on L_MR1 requires 230 * an extra invocation of toMr1(). 231 */ 232 public interface LMr1 { 233 /** 234 * Get this SIM's information. Only applies to L_MR1 above 235 * 236 * @return the subscription info of the SIM 237 */ getActiveSubscriptionInfo()238 public abstract SubscriptionInfo getActiveSubscriptionInfo(); 239 240 /** 241 * Get the list of active SIMs in system. Only applies to L_MR1 above 242 * 243 * @return the list of subscription info for all inserted SIMs 244 */ getActiveSubscriptionInfoList()245 public abstract List<SubscriptionInfo> getActiveSubscriptionInfoList(); 246 247 /** 248 * Register subscription change listener. Only applies to L_MR1 above 249 * 250 * @param listener The listener to register 251 */ registerOnSubscriptionsChangedListener( SubscriptionManager.OnSubscriptionsChangedListener listener)252 public abstract void registerOnSubscriptionsChangedListener( 253 SubscriptionManager.OnSubscriptionsChangedListener listener); 254 } 255 256 /** 257 * The PhoneUtils class for pre L_MR1 258 */ 259 public static class PhoneUtilsPreLMR1 extends PhoneUtils { 260 private final ConnectivityManager mConnectivityManager; 261 PhoneUtilsPreLMR1()262 public PhoneUtilsPreLMR1() { 263 super(ParticipantData.DEFAULT_SELF_SUB_ID); 264 mConnectivityManager = 265 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 266 } 267 268 @Override getSimCountry()269 public String getSimCountry() { 270 final String country = mTelephonyManager.getSimCountryIso(); 271 if (TextUtils.isEmpty(country)) { 272 return null; 273 } 274 return country.toUpperCase(); 275 } 276 277 @Override getSimSlotCount()278 public int getSimSlotCount() { 279 // Don't support MSIM pre-L_MR1 280 return 1; 281 } 282 283 @Override getCarrierName()284 public String getCarrierName() { 285 return mTelephonyManager.getNetworkOperatorName(); 286 } 287 288 @Override hasSim()289 public boolean hasSim() { 290 return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT; 291 } 292 293 @Override isRoaming()294 public boolean isRoaming() { 295 return mTelephonyManager.isNetworkRoaming(); 296 } 297 298 @Override getMccMnc()299 public int[] getMccMnc() { 300 final String mccmnc = mTelephonyManager.getSimOperator(); 301 int mcc = 0; 302 int mnc = 0; 303 try { 304 mcc = Integer.parseInt(mccmnc.substring(0, 3)); 305 mnc = Integer.parseInt(mccmnc.substring(3)); 306 } catch (Exception e) { 307 LogUtil.w(TAG, "PhoneUtils.getMccMnc: invalid string " + mccmnc, e); 308 } 309 return new int[]{mcc, mnc}; 310 } 311 312 @Override getSimOperatorNumeric()313 public String getSimOperatorNumeric() { 314 return mTelephonyManager.getSimOperator(); 315 } 316 317 @Override getSelfRawNumber(final boolean allowOverride)318 public String getSelfRawNumber(final boolean allowOverride) { 319 if (allowOverride) { 320 final String userDefinedNumber = getNumberFromPrefs(mContext, 321 ParticipantData.DEFAULT_SELF_SUB_ID); 322 if (!TextUtils.isEmpty(userDefinedNumber)) { 323 return userDefinedNumber; 324 } 325 } 326 return mTelephonyManager.getLine1Number(); 327 } 328 329 @Override getEffectiveSubId(int subId)330 public int getEffectiveSubId(int subId) { 331 Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId); 332 return ParticipantData.DEFAULT_SELF_SUB_ID; 333 } 334 335 @Override getSmsManager()336 public SmsManager getSmsManager() { 337 return SmsManager.getDefault(); 338 } 339 340 @Override getDefaultSmsSubscriptionId()341 public int getDefaultSmsSubscriptionId() { 342 Assert.fail("PhoneUtils.getDefaultSmsSubscriptionId(): not supported before L MR1"); 343 return ParticipantData.DEFAULT_SELF_SUB_ID; 344 } 345 346 @Override getHasPreferredSmsSim()347 public boolean getHasPreferredSmsSim() { 348 // SIM selection is not supported pre-L_MR1. 349 return true; 350 } 351 352 @Override getActiveSubscriptionCount()353 public int getActiveSubscriptionCount() { 354 return hasSim() ? 1 : 0; 355 } 356 357 @Override getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName)358 public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) { 359 // Pre-L_MR1 always returns the default id 360 return ParticipantData.DEFAULT_SELF_SUB_ID; 361 } 362 363 @Override getSubIdFromTelephony(Cursor cursor, int subIdIndex)364 public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) { 365 // No subscription_id column before L_MR1 366 return ParticipantData.DEFAULT_SELF_SUB_ID; 367 } 368 369 @Override 370 @SuppressWarnings("deprecation") isDataRoamingEnabled()371 public boolean isDataRoamingEnabled() { 372 boolean dataRoamingEnabled = false; 373 final ContentResolver cr = mContext.getContentResolver(); 374 if (OsUtil.isAtLeastJB_MR1()) { 375 dataRoamingEnabled = 376 (Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0); 377 } else { 378 dataRoamingEnabled = 379 (Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0); 380 } 381 return dataRoamingEnabled; 382 } 383 384 @Override isMobileDataEnabled()385 public boolean isMobileDataEnabled() { 386 boolean mobileDataEnabled = false; 387 try { 388 final Class cmClass = mConnectivityManager.getClass(); 389 final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled"); 390 method.setAccessible(true); // Make the method callable 391 // get the setting for "mobile data" 392 mobileDataEnabled = (Boolean) method.invoke(mConnectivityManager); 393 } catch (final Exception e) { 394 LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e); 395 } 396 return mobileDataEnabled; 397 } 398 399 @Override getNormalizedSelfNumbers()400 public HashSet<String> getNormalizedSelfNumbers() { 401 final HashSet<String> numbers = new HashSet<>(); 402 numbers.add(getCanonicalForSelf(true/*allowOverride*/)); 403 return numbers; 404 } 405 } 406 407 /** 408 * The PhoneUtils class for L_MR1 409 */ 410 public static class PhoneUtilsLMR1 extends PhoneUtils implements LMr1 { 411 private final SubscriptionManager mSubscriptionManager; 412 PhoneUtilsLMR1(final int subId)413 public PhoneUtilsLMR1(final int subId) { 414 super(subId); 415 mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext()); 416 } 417 418 @Override getSimCountry()419 public String getSimCountry() { 420 final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); 421 if (subInfo != null) { 422 final String country = subInfo.getCountryIso(); 423 if (TextUtils.isEmpty(country)) { 424 return null; 425 } 426 return country.toUpperCase(); 427 } 428 return null; 429 } 430 431 @Override getSimSlotCount()432 public int getSimSlotCount() { 433 return mSubscriptionManager.getActiveSubscriptionInfoCountMax(); 434 } 435 436 @Override getCarrierName()437 public String getCarrierName() { 438 final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); 439 if (subInfo != null) { 440 final CharSequence displayName = subInfo.getDisplayName(); 441 if (!TextUtils.isEmpty(displayName)) { 442 return displayName.toString(); 443 } 444 final CharSequence carrierName = subInfo.getCarrierName(); 445 if (carrierName != null) { 446 return carrierName.toString(); 447 } 448 } 449 return null; 450 } 451 452 @Override hasSim()453 public boolean hasSim() { 454 return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0; 455 } 456 457 @Override isRoaming()458 public boolean isRoaming() { 459 return mSubscriptionManager.isNetworkRoaming(mSubId); 460 } 461 462 @Override getMccMnc()463 public int[] getMccMnc() { 464 int mcc = 0; 465 int mnc = 0; 466 final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); 467 if (subInfo != null) { 468 mcc = subInfo.getMcc(); 469 mnc = subInfo.getMnc(); 470 } 471 return new int[]{mcc, mnc}; 472 } 473 474 @Override getSimOperatorNumeric()475 public String getSimOperatorNumeric() { 476 // For L_MR1 we return the canonicalized (xxxxxx) string 477 return getMccMncString(getMccMnc()); 478 } 479 480 @Override getSelfRawNumber(final boolean allowOverride)481 public String getSelfRawNumber(final boolean allowOverride) { 482 if (allowOverride) { 483 final String userDefinedNumber = getNumberFromPrefs(mContext, mSubId); 484 if (!TextUtils.isEmpty(userDefinedNumber)) { 485 return userDefinedNumber; 486 } 487 } 488 489 final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); 490 if (subInfo != null) { 491 String phoneNumber = subInfo.getNumber(); 492 if (TextUtils.isEmpty(phoneNumber) && LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { 493 LogUtil.d(TAG, "SubscriptionInfo phone number for self is empty!"); 494 } 495 return phoneNumber; 496 } 497 LogUtil.w(TAG, "PhoneUtils.getSelfRawNumber: subInfo is null for " + mSubId); 498 throw new IllegalStateException("No active subscription"); 499 } 500 501 @Override getActiveSubscriptionInfo()502 public SubscriptionInfo getActiveSubscriptionInfo() { 503 try { 504 final SubscriptionInfo subInfo = 505 mSubscriptionManager.getActiveSubscriptionInfo(mSubId); 506 if (subInfo == null) { 507 if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { 508 // This is possible if the sub id is no longer available. 509 LogUtil.d(TAG, "PhoneUtils.getActiveSubscriptionInfo(): empty sub info for " 510 + mSubId); 511 } 512 } 513 return subInfo; 514 } catch (Exception e) { 515 LogUtil.e(TAG, "PhoneUtils.getActiveSubscriptionInfo: system exception for " 516 + mSubId, e); 517 } 518 return null; 519 } 520 521 @Override getActiveSubscriptionInfoList()522 public List<SubscriptionInfo> getActiveSubscriptionInfoList() { 523 final List<SubscriptionInfo> subscriptionInfos = 524 mSubscriptionManager.getActiveSubscriptionInfoList(); 525 if (subscriptionInfos != null) { 526 return subscriptionInfos; 527 } 528 return EMPTY_SUBSCRIPTION_LIST; 529 } 530 531 @Override getEffectiveSubId(int subId)532 public int getEffectiveSubId(int subId) { 533 if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) { 534 return getDefaultSmsSubscriptionId(); 535 } 536 return subId; 537 } 538 539 @Override registerOnSubscriptionsChangedListener( SubscriptionManager.OnSubscriptionsChangedListener listener)540 public void registerOnSubscriptionsChangedListener( 541 SubscriptionManager.OnSubscriptionsChangedListener listener) { 542 mSubscriptionManager.addOnSubscriptionsChangedListener(listener); 543 } 544 545 @Override getSmsManager()546 public SmsManager getSmsManager() { 547 return SmsManager.getSmsManagerForSubscriptionId(mSubId); 548 } 549 550 @Override getDefaultSmsSubscriptionId()551 public int getDefaultSmsSubscriptionId() { 552 final int systemDefaultSubId = SmsManager.getDefaultSmsSubscriptionId(); 553 if (systemDefaultSubId < 0) { 554 // Always use -1 for any negative subId from system 555 return ParticipantData.DEFAULT_SELF_SUB_ID; 556 } 557 return systemDefaultSubId; 558 } 559 560 @Override getHasPreferredSmsSim()561 public boolean getHasPreferredSmsSim() { 562 return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID; 563 } 564 565 @Override getActiveSubscriptionCount()566 public int getActiveSubscriptionCount() { 567 return mSubscriptionManager.getActiveSubscriptionInfoCount(); 568 } 569 570 @Override getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName)571 public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) { 572 return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName, 573 ParticipantData.DEFAULT_SELF_SUB_ID)); 574 } 575 getEffectiveIncomingSubIdFromSystem(int subId)576 private int getEffectiveIncomingSubIdFromSystem(int subId) { 577 if (subId < 0) { 578 if (mSubscriptionManager.getActiveSubscriptionInfoCount() > 1) { 579 // For multi-SIM device, we can not decide which SIM to use if system 580 // does not know either. So just make it the invalid sub id. 581 return ParticipantData.DEFAULT_SELF_SUB_ID; 582 } 583 // For single-SIM device, it must come from the only SIM we have 584 return getDefaultSmsSubscriptionId(); 585 } 586 return subId; 587 } 588 589 @Override getSubIdFromTelephony(Cursor cursor, int subIdIndex)590 public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) { 591 return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex)); 592 } 593 594 @Override isDataRoamingEnabled()595 public boolean isDataRoamingEnabled() { 596 final SubscriptionInfo subInfo = getActiveSubscriptionInfo(); 597 if (subInfo == null) { 598 // There is nothing we can do if system give us empty sub info 599 LogUtil.e(TAG, "PhoneUtils.isDataRoamingEnabled: system return empty sub info for " 600 + mSubId); 601 return false; 602 } 603 return subInfo.getDataRoaming() != SubscriptionManager.DATA_ROAMING_DISABLE; 604 } 605 606 @Override isMobileDataEnabled()607 public boolean isMobileDataEnabled() { 608 boolean mobileDataEnabled = false; 609 try { 610 final Class cmClass = mTelephonyManager.getClass(); 611 final Method method = cmClass.getDeclaredMethod("getDataEnabled", Integer.TYPE); 612 method.setAccessible(true); // Make the method callable 613 // get the setting for "mobile data" 614 mobileDataEnabled = (Boolean) method.invoke( 615 mTelephonyManager, Integer.valueOf(mSubId)); 616 } catch (final Exception e) { 617 LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e); 618 } 619 return mobileDataEnabled; 620 621 } 622 623 @Override getNormalizedSelfNumbers()624 public HashSet<String> getNormalizedSelfNumbers() { 625 final HashSet<String> numbers = new HashSet<>(); 626 for (SubscriptionInfo info : getActiveSubscriptionInfoList()) { 627 numbers.add(PhoneUtils.get(info.getSubscriptionId()).getCanonicalForSelf( 628 true/*allowOverride*/)); 629 } 630 return numbers; 631 } 632 } 633 634 /** 635 * A convenient get() method that uses the default SIM. Use this when SIM is 636 * not relevant, e.g. isDefaultSmsApp 637 * 638 * @return an instance of PhoneUtils for default SIM 639 */ getDefault()640 public static PhoneUtils getDefault() { 641 return Factory.get().getPhoneUtils(ParticipantData.DEFAULT_SELF_SUB_ID); 642 } 643 644 /** 645 * Get an instance of PhoneUtils associated with a specific SIM, which is also platform 646 * specific. 647 * 648 * @param subId The SIM's subscription ID 649 * @return the instance 650 */ get(int subId)651 public static PhoneUtils get(int subId) { 652 return Factory.get().getPhoneUtils(subId); 653 } 654 toLMr1()655 public LMr1 toLMr1() { 656 if (OsUtil.isAtLeastL_MR1()) { 657 return (LMr1) this; 658 } else { 659 Assert.fail("PhoneUtils.toLMr1(): invalid OS version"); 660 return null; 661 } 662 } 663 664 /** 665 * Check if this device supports SMS 666 * 667 * @return true if SMS is supported, false otherwise 668 */ isSmsCapable()669 public boolean isSmsCapable() { 670 return mTelephonyManager.isSmsCapable(); 671 } 672 673 /** 674 * Check if this device supports voice calling 675 * 676 * @return true if voice calling is supported, false otherwise 677 */ isVoiceCapable()678 public boolean isVoiceCapable() { 679 return mTelephonyManager.isVoiceCapable(); 680 } 681 682 /** 683 * Get the ISO country code from system locale setting 684 * 685 * @return the ISO country code from system locale 686 */ getLocaleCountry()687 private static String getLocaleCountry() { 688 final String country = Locale.getDefault().getCountry(); 689 if (TextUtils.isEmpty(country)) { 690 return null; 691 } 692 return country.toUpperCase(); 693 } 694 695 /** 696 * Get ISO country code from the SIM, if not available, fall back to locale 697 * 698 * @return SIM or locale ISO country code 699 */ getSimOrDefaultLocaleCountry()700 public String getSimOrDefaultLocaleCountry() { 701 String country = getSimCountry(); 702 if (country == null) { 703 country = getLocaleCountry(); 704 } 705 return country; 706 } 707 708 // Get or set the cache of canonicalized phone numbers for a specific country getOrAddCountryMapInCacheLocked(String country)709 private static ArrayMap<String, String> getOrAddCountryMapInCacheLocked(String country) { 710 if (country == null) { 711 country = ""; 712 } 713 ArrayMap<String, String> countryMap = sCanonicalPhoneNumberCache.get(country); 714 if (countryMap == null) { 715 countryMap = new ArrayMap<>(); 716 sCanonicalPhoneNumberCache.put(country, countryMap); 717 } 718 return countryMap; 719 } 720 721 // Get canonicalized phone number from cache getCanonicalFromCache(final String phoneText, String country)722 private static String getCanonicalFromCache(final String phoneText, String country) { 723 synchronized (sCanonicalPhoneNumberCache) { 724 final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country); 725 return countryMap.get(phoneText); 726 } 727 } 728 729 // Put canonicalized phone number into cache putCanonicalToCache(final String phoneText, String country, final String canonical)730 private static void putCanonicalToCache(final String phoneText, String country, 731 final String canonical) { 732 synchronized (sCanonicalPhoneNumberCache) { 733 final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country); 734 countryMap.put(phoneText, canonical); 735 } 736 } 737 738 /** 739 * Utility method to parse user input number into standard E164 number. 740 * 741 * @param phoneText Phone number text as input by user. 742 * @param country ISO country code based on which to parse the number. 743 * @return E164 phone number. Returns null in case parsing failed. 744 */ getValidE164Number(final String phoneText, final String country)745 private static String getValidE164Number(final String phoneText, final String country) { 746 final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); 747 try { 748 final PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country); 749 if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) { 750 return phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164); 751 } 752 } catch (final NumberParseException e) { 753 LogUtil.e(TAG, "PhoneUtils.getValidE164Number(): Not able to parse phone number " 754 + LogUtil.sanitizePII(phoneText) + " for country " + country); 755 } 756 return null; 757 } 758 759 /** 760 * Canonicalize phone number using system locale country 761 * 762 * @param phoneText The phone number to canonicalize 763 * @return the canonicalized number 764 */ getCanonicalBySystemLocale(final String phoneText)765 public String getCanonicalBySystemLocale(final String phoneText) { 766 return getCanonicalByCountry(phoneText, getLocaleCountry()); 767 } 768 769 /** 770 * Canonicalize phone number using SIM's country, may fall back to system locale country 771 * if SIM country can not be obtained 772 * 773 * @param phoneText The phone number to canonicalize 774 * @return the canonicalized number 775 */ getCanonicalBySimLocale(final String phoneText)776 public String getCanonicalBySimLocale(final String phoneText) { 777 return getCanonicalByCountry(phoneText, getSimOrDefaultLocaleCountry()); 778 } 779 780 /** 781 * Canonicalize phone number using a country code. 782 * This uses an internal cache per country to speed up. 783 * 784 * @param phoneText The phone number to canonicalize 785 * @param country The ISO country code to use 786 * @return the canonicalized number, or the original number if can't be parsed 787 */ getCanonicalByCountry(final String phoneText, final String country)788 private String getCanonicalByCountry(final String phoneText, final String country) { 789 Assert.notNull(phoneText); 790 791 String canonicalNumber = getCanonicalFromCache(phoneText, country); 792 if (canonicalNumber != null) { 793 return canonicalNumber; 794 } 795 canonicalNumber = getValidE164Number(phoneText, country); 796 if (canonicalNumber == null) { 797 // If we can't normalize this number, we just use the display string number. 798 // This is possible for short codes and other non-localizable numbers. 799 canonicalNumber = phoneText; 800 } 801 putCanonicalToCache(phoneText, country, canonicalNumber); 802 return canonicalNumber; 803 } 804 805 /** 806 * Canonicalize the self (per SIM) phone number 807 * 808 * @param allowOverride whether to use the override number in app settings 809 * @return the canonicalized self phone number 810 */ getCanonicalForSelf(final boolean allowOverride)811 public String getCanonicalForSelf(final boolean allowOverride) { 812 String selfNumber = null; 813 try { 814 selfNumber = getSelfRawNumber(allowOverride); 815 } catch (IllegalStateException e) { 816 // continue; 817 } 818 if (selfNumber == null) { 819 return ""; 820 } 821 return getCanonicalBySimLocale(selfNumber); 822 } 823 824 /** 825 * Get the SIM's phone number in NATIONAL format with only digits, used in sending 826 * as LINE1NOCOUNTRYCODE macro in mms_config 827 * 828 * @return all digits national format number of the SIM 829 */ getSimNumberNoCountryCode()830 public String getSimNumberNoCountryCode() { 831 String selfNumber = null; 832 try { 833 selfNumber = getSelfRawNumber(false/*allowOverride*/); 834 } catch (IllegalStateException e) { 835 // continue 836 } 837 if (selfNumber == null) { 838 selfNumber = ""; 839 } 840 final String country = getSimCountry(); 841 final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); 842 try { 843 final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country); 844 if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) { 845 return phoneNumberUtil 846 .format(phoneNumber, PhoneNumberFormat.NATIONAL) 847 .replaceAll("\\D", ""); 848 } 849 } catch (final NumberParseException e) { 850 LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number " 851 + LogUtil.sanitizePII(selfNumber) + " for country " + country); 852 } 853 return selfNumber; 854 855 } 856 857 /** 858 * Format a phone number for displaying, using system locale country. 859 * If the country code matches between the system locale and the input phone number, 860 * it will be formatted into NATIONAL format, otherwise, the INTERNATIONAL format 861 * 862 * @param phoneText The original phone text 863 * @return formatted number 864 */ formatForDisplay(final String phoneText)865 public String formatForDisplay(final String phoneText) { 866 // Only format a valid number which length >=6 867 if (TextUtils.isEmpty(phoneText) || 868 phoneText.replaceAll("\\D", "").length() < MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT) { 869 return phoneText; 870 } 871 final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); 872 final String systemCountry = getLocaleCountry(); 873 final int systemCountryCode = phoneNumberUtil.getCountryCodeForRegion(systemCountry); 874 try { 875 final PhoneNumber parsedNumber = phoneNumberUtil.parse(phoneText, systemCountry); 876 final PhoneNumberFormat phoneNumberFormat = 877 (systemCountryCode > 0 && parsedNumber.getCountryCode() == systemCountryCode) ? 878 PhoneNumberFormat.NATIONAL : PhoneNumberFormat.INTERNATIONAL; 879 return phoneNumberUtil.format(parsedNumber, phoneNumberFormat); 880 } catch (NumberParseException e) { 881 LogUtil.e(TAG, "PhoneUtils.formatForDisplay: invalid phone number " 882 + LogUtil.sanitizePII(phoneText) + " with country " + systemCountry); 883 return phoneText; 884 } 885 } 886 887 /** 888 * Is Messaging the default SMS app? 889 * - On KLP+ this checks the system setting. 890 * - On JB (and below) this always returns true, since the setting was added in KLP. 891 */ isDefaultSmsApp()892 public boolean isDefaultSmsApp() { 893 if (OsUtil.isAtLeastKLP()) { 894 final String configuredApplication = Telephony.Sms.getDefaultSmsPackage(mContext); 895 return mContext.getPackageName().equals(configuredApplication); 896 } 897 return true; 898 } 899 900 /** 901 * Get default SMS app package name 902 * 903 * @return the package name of default SMS app 904 */ getDefaultSmsApp()905 public String getDefaultSmsApp() { 906 if (OsUtil.isAtLeastKLP()) { 907 return Telephony.Sms.getDefaultSmsPackage(mContext); 908 } 909 return null; 910 } 911 912 /** 913 * Determines if SMS is currently enabled on this device. 914 * - Device must support SMS 915 * - On KLP+ we must be set as the default SMS app 916 */ isSmsEnabled()917 public boolean isSmsEnabled() { 918 return isSmsCapable() && isDefaultSmsApp(); 919 } 920 921 /** 922 * Returns the name of the default SMS app, or the empty string if there is 923 * an error or there is no default app (e.g. JB and below). 924 */ getDefaultSmsAppLabel()925 public String getDefaultSmsAppLabel() { 926 if (OsUtil.isAtLeastKLP()) { 927 final String packageName = Telephony.Sms.getDefaultSmsPackage(mContext); 928 final PackageManager pm = mContext.getPackageManager(); 929 try { 930 final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0); 931 return pm.getApplicationLabel(appInfo).toString(); 932 } catch (NameNotFoundException e) { 933 // Fall through and return empty string 934 } 935 } 936 return ""; 937 } 938 939 /** 940 * Gets the state of Airplane Mode. 941 * 942 * @return true if enabled. 943 */ 944 @SuppressWarnings("deprecation") isAirplaneModeOn()945 public boolean isAirplaneModeOn() { 946 if (OsUtil.isAtLeastJB_MR1()) { 947 return Settings.Global.getInt(mContext.getContentResolver(), 948 Settings.Global.AIRPLANE_MODE_ON, 0) != 0; 949 } else { 950 return Settings.System.getInt(mContext.getContentResolver(), 951 Settings.System.AIRPLANE_MODE_ON, 0) != 0; 952 } 953 } 954 getMccMncString(int[] mccmnc)955 public static String getMccMncString(int[] mccmnc) { 956 if (mccmnc == null || mccmnc.length != 2) { 957 return "000000"; 958 } 959 return String.format("%03d%03d", mccmnc[0], mccmnc[1]); 960 } 961 canonicalizeMccMnc(final String mcc, final String mnc)962 public static String canonicalizeMccMnc(final String mcc, final String mnc) { 963 try { 964 return String.format("%03d%03d", Integer.parseInt(mcc), Integer.parseInt(mnc)); 965 } catch (final NumberFormatException e) { 966 // Return invalid as is 967 LogUtil.w(TAG, "canonicalizeMccMnc: invalid mccmnc:" + mcc + " ," + mnc); 968 } 969 return mcc + mnc; 970 } 971 972 /** 973 * Returns whether the given destination is valid for sending SMS/MMS message. 974 */ isValidSmsMmsDestination(final String destination)975 public static boolean isValidSmsMmsDestination(final String destination) { 976 return PhoneNumberUtils.isWellFormedSmsAddress(destination) || 977 MmsSmsUtils.isEmailAddress(destination); 978 } 979 980 public interface SubscriptionRunnable { runForSubscription(int subId)981 void runForSubscription(int subId); 982 } 983 984 /** 985 * A convenience method for iterating through all active subscriptions 986 * 987 * @param runnable a {@link SubscriptionRunnable} for performing work on each subscription. 988 */ forEachActiveSubscription(final SubscriptionRunnable runnable)989 public static void forEachActiveSubscription(final SubscriptionRunnable runnable) { 990 if (OsUtil.isAtLeastL_MR1()) { 991 final List<SubscriptionInfo> subscriptionList = 992 getDefault().toLMr1().getActiveSubscriptionInfoList(); 993 for (final SubscriptionInfo subscriptionInfo : subscriptionList) { 994 runnable.runForSubscription(subscriptionInfo.getSubscriptionId()); 995 } 996 } else { 997 runnable.runForSubscription(ParticipantData.DEFAULT_SELF_SUB_ID); 998 } 999 } 1000 getNumberFromPrefs(final Context context, final int subId)1001 private static String getNumberFromPrefs(final Context context, final int subId) { 1002 final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId); 1003 final String mmsPhoneNumberPrefKey = 1004 context.getString(R.string.mms_phone_number_pref_key); 1005 final String userDefinedNumber = prefs.getString(mmsPhoneNumberPrefKey, null); 1006 if (!TextUtils.isEmpty(userDefinedNumber)) { 1007 return userDefinedNumber; 1008 } 1009 return null; 1010 } 1011 } 1012