Home | History | Annotate | Download | only in util
      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 
     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      */
     92     public abstract String getSimCountry();
     93 
     94     /**
     95      * Get number of SIM slots
     96      *
     97      * @return the SIM slot count
     98      */
     99     public abstract int getSimSlotCount();
    100 
    101     /**
    102      * Get SIM's carrier name
    103      *
    104      * @return the carrier name of the SIM
    105      */
    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      */
    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      */
    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      */
    127     public abstract int[] getMccMnc();
    128 
    129     /**
    130      * Get the mcc/mnc string
    131      *
    132      * @return the text of mccmnc string
    133      */
    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      */
    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      */
    158     public abstract int getEffectiveSubId(int subId);
    159 
    160     /**
    161      * Returns the number of active subscriptions in the device.
    162      */
    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      */
    170     public abstract SmsManager getSmsManager();
    171 
    172     /**
    173      * Get the default SMS subscription id
    174      *
    175      * @return the default sub ID
    176      */
    177     public abstract int getDefaultSmsSubscriptionId();
    178 
    179     /**
    180      * Returns if there's currently a system default SIM selected for sending SMS.
    181      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
    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          */
    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          */
    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 
    262         public PhoneUtilsPreLMR1() {
    263             super(ParticipantData.DEFAULT_SELF_SUB_ID);
    264             mConnectivityManager =
    265                     (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    266         }
    267 
    268         @Override
    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
    278         public int getSimSlotCount() {
    279             // Don't support MSIM pre-L_MR1
    280             return 1;
    281         }
    282 
    283         @Override
    284         public String getCarrierName() {
    285             return mTelephonyManager.getNetworkOperatorName();
    286         }
    287 
    288         @Override
    289         public boolean hasSim() {
    290             return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
    291         }
    292 
    293         @Override
    294         public boolean isRoaming() {
    295             return mTelephonyManager.isNetworkRoaming();
    296         }
    297 
    298         @Override
    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
    313         public String getSimOperatorNumeric() {
    314             return mTelephonyManager.getSimOperator();
    315         }
    316 
    317         @Override
    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
    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
    336         public SmsManager getSmsManager() {
    337             return SmsManager.getDefault();
    338         }
    339 
    340         @Override
    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
    347         public boolean getHasPreferredSmsSim() {
    348             // SIM selection is not supported pre-L_MR1.
    349             return true;
    350         }
    351 
    352         @Override
    353         public int getActiveSubscriptionCount() {
    354             return hasSim() ? 1 : 0;
    355         }
    356 
    357         @Override
    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
    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")
    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
    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
    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 
    413         public PhoneUtilsLMR1(final int subId) {
    414             super(subId);
    415             mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext());
    416         }
    417 
    418         @Override
    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
    432         public int getSimSlotCount() {
    433             return mSubscriptionManager.getActiveSubscriptionInfoCountMax();
    434         }
    435 
    436         @Override
    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
    453         public boolean hasSim() {
    454             return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0;
    455         }
    456 
    457         @Override
    458         public boolean isRoaming() {
    459             return mSubscriptionManager.isNetworkRoaming(mSubId);
    460         }
    461 
    462         @Override
    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
    475         public String getSimOperatorNumeric() {
    476             // For L_MR1 we return the canonicalized (xxxxxx) string
    477             return getMccMncString(getMccMnc());
    478         }
    479 
    480         @Override
    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
    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
    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
    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
    540         public void registerOnSubscriptionsChangedListener(
    541                 SubscriptionManager.OnSubscriptionsChangedListener listener) {
    542             mSubscriptionManager.addOnSubscriptionsChangedListener(listener);
    543         }
    544 
    545         @Override
    546         public SmsManager getSmsManager() {
    547             return SmsManager.getSmsManagerForSubscriptionId(mSubId);
    548         }
    549 
    550         @Override
    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
    561         public boolean getHasPreferredSmsSim() {
    562             return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID;
    563         }
    564 
    565         @Override
    566         public int getActiveSubscriptionCount() {
    567             return mSubscriptionManager.getActiveSubscriptionInfoCount();
    568         }
    569 
    570         @Override
    571         public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
    572             return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName,
    573                     ParticipantData.DEFAULT_SELF_SUB_ID));
    574         }
    575 
    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
    590         public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
    591             return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex));
    592         }
    593 
    594         @Override
    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
    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
    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      */
    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      */
    651     public static PhoneUtils get(int subId) {
    652         return Factory.get().getPhoneUtils(subId);
    653     }
    654 
    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      */
    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      */
    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      */
    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      */
    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
    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
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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")
    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 
    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 
    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      */
    975     public static boolean isValidSmsMmsDestination(final String destination) {
    976         return PhoneNumberUtils.isWellFormedSmsAddress(destination) ||
    977                 MmsSmsUtils.isEmailAddress(destination);
    978     }
    979 
    980     public interface SubscriptionRunnable {
    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      */
    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 
   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