• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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