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