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