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
17package com.android.messaging.util;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.database.Cursor;
26import android.net.ConnectivityManager;
27import android.provider.Settings;
28import android.provider.Telephony;
29import android.support.v4.util.ArrayMap;
30import android.telephony.PhoneNumberUtils;
31import android.telephony.SmsManager;
32import android.telephony.SubscriptionInfo;
33import android.telephony.SubscriptionManager;
34import android.telephony.TelephonyManager;
35import android.text.TextUtils;
36
37import com.android.messaging.Factory;
38import com.android.messaging.R;
39import com.android.messaging.datamodel.data.ParticipantData;
40import com.android.messaging.sms.MmsSmsUtils;
41import com.google.i18n.phonenumbers.NumberParseException;
42import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
43import com.google.i18n.phonenumbers.PhoneNumberUtil;
44import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
45
46import java.lang.reflect.Method;
47import java.util.ArrayList;
48import java.util.HashSet;
49import java.util.List;
50import 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 */
63public 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