blob: 671c65e35872b694313b01b4050af18d838d6d26 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.messaging.util;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.provider.Settings;
import android.provider.Telephony;
import androidx.collection.ArrayMap;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.data.ParticipantData;
import com.android.messaging.sms.MmsSmsUtils;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
/**
* This class abstracts away platform dependency of calling telephony related
* platform APIs, mostly involving TelephonyManager, SubscriptionManager and
* a bit of SmsManager.
*
* The class instance can only be obtained via the get(int subId) method parameterized
* by a SIM subscription ID. On pre-L_MR1, the subId is not used and it has to be
* the default subId (-1).
*
* A convenient getDefault() method is provided for default subId (-1) on any platform
*/
public abstract class PhoneUtils {
private static final String TAG = LogUtil.BUGLE_TAG;
private static final int MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT = 6;
private static final List<SubscriptionInfo> EMPTY_SUBSCRIPTION_LIST = new ArrayList<>();
// The canonical phone number cache
// Each country gets its own cache. The following maps from ISO country code to
// the country's cache. Each cache maps from original phone number to canonicalized phone
private static final ArrayMap<String, ArrayMap<String, String>> sCanonicalPhoneNumberCache =
new ArrayMap<>();
protected final Context mContext;
protected final TelephonyManager mTelephonyManager;
protected final int mSubId;
public PhoneUtils(int subId) {
mSubId = subId;
mContext = Factory.get().getApplicationContext();
mTelephonyManager =
(TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
}
/**
* Get the SIM's country code
*
* @return the country code on the SIM
*/
public abstract String getSimCountry();
/**
* Get number of SIM slots
*
* @return the SIM slot count
*/
public abstract int getSimSlotCount();
/**
* Get SIM's carrier name
*
* @return the carrier name of the SIM
*/
public abstract String getCarrierName();
/**
* Check if there is SIM inserted on the device
*
* @return true if there is SIM inserted, false otherwise
*/
public abstract boolean hasSim();
/**
* Check if the SIM is roaming
*
* @return true if the SIM is in romaing state, false otherwise
*/
public abstract boolean isRoaming();
/**
* Get the MCC and MNC in integer of the SIM's provider
*
* @return an array of two ints, [0] is the MCC code and [1] is the MNC code
*/
public abstract int[] getMccMnc();
/**
* Get the mcc/mnc string
*
* @return the text of mccmnc string
*/
public abstract String getSimOperatorNumeric();
/**
* Get the SIM's self raw number, i.e. not canonicalized
*
* @param allowOverride Whether to use the app's setting to override the self number
* @return the original self number
* @throws IllegalStateException if no active subscription on L-MR1+
*/
public abstract String getSelfRawNumber(final boolean allowOverride);
/**
* Returns the "effective" subId, or the subId used in the context of actual messages,
* conversations and subscription-specific settings, for the given "nominal" sub id.
*
* For pre-L-MR1 platform, this should always be
* {@value com.android.messaging.datamodel.data.ParticipantData#DEFAULT_SELF_SUB_ID};
*
* On the other hand, for L-MR1 and above, DEFAULT_SELF_SUB_ID will be mapped to the system
* default subscription id for SMS.
*
* @param subId The input subId
* @return the real subId if we can convert
*/
public abstract int getEffectiveSubId(int subId);
/**
* Returns the number of active subscriptions in the device.
*/
public abstract int getActiveSubscriptionCount();
/**
* Get {@link SmsManager} instance
*
* @return the relevant SmsManager instance based on OS version and subId
*/
public abstract SmsManager getSmsManager();
/**
* Get the default SMS subscription id
*
* @return the default sub ID
*/
public abstract int getDefaultSmsSubscriptionId();
/**
* Returns if there's currently a system default SIM selected for sending SMS.
*/
public abstract boolean getHasPreferredSmsSim();
/**
* For L_MR1, system may return a negative subId. Convert this into our own
* subId, so that we consistently use -1 for invalid or default.
*
* see b/18629526 and b/18670346
*
* @param intent The push intent from system
* @param extraName The name of the sub id extra
* @return the subId that is valid and meaningful for the app
*/
public abstract int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName);
/**
* Get the subscription_id column value from a telephony provider cursor
*
* @param cursor The database query cursor
* @param subIdIndex The index of the subId column in the cursor
* @return the subscription_id column value from the cursor
*/
public abstract int getSubIdFromTelephony(Cursor cursor, int subIdIndex);
/**
* Check if data roaming is enabled
*
* @return true if data roaming is enabled, false otherwise
*/
public abstract boolean isDataRoamingEnabled();
/**
* Check if mobile data is enabled
*
* @return true if mobile data is enabled, false otherwise
*/
public abstract boolean isMobileDataEnabled();
/**
* Get the set of self phone numbers, all normalized
*
* @return the set of normalized self phone numbers
*/
public abstract HashSet<String> getNormalizedSelfNumbers();
/**
* This interface packages methods should only compile on L_MR1.
* This is needed to make unit tests happy when mockito tries to
* mock these methods. Calling on these methods on L_MR1 requires
* an extra invocation of toMr1().
*/
public interface LMr1 {
/**
* Get this SIM's information. Only applies to L_MR1 above
*
* @return the subscription info of the SIM
*/
public abstract SubscriptionInfo getActiveSubscriptionInfo();
/**
* Get the list of active SIMs in system. Only applies to L_MR1 above
*
* @return the list of subscription info for all inserted SIMs
*/
public abstract List<SubscriptionInfo> getActiveSubscriptionInfoList();
/**
* Register subscription change listener. Only applies to L_MR1 above
*
* @param listener The listener to register
*/
public abstract void registerOnSubscriptionsChangedListener(
SubscriptionManager.OnSubscriptionsChangedListener listener);
}
/**
* The PhoneUtils class for pre L_MR1
*/
public static class PhoneUtilsPreLMR1 extends PhoneUtils {
private final ConnectivityManager mConnectivityManager;
public PhoneUtilsPreLMR1() {
super(ParticipantData.DEFAULT_SELF_SUB_ID);
mConnectivityManager =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
}
@Override
public String getSimCountry() {
final String country = mTelephonyManager.getSimCountryIso();
if (TextUtils.isEmpty(country)) {
return null;
}
return country.toUpperCase();
}
@Override
public int getSimSlotCount() {
// Don't support MSIM pre-L_MR1
return 1;
}
@Override
public String getCarrierName() {
return mTelephonyManager.getNetworkOperatorName();
}
@Override
public boolean hasSim() {
return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
}
@Override
public boolean isRoaming() {
return mTelephonyManager.isNetworkRoaming();
}
@Override
public int[] getMccMnc() {
final String mccmnc = mTelephonyManager.getSimOperator();
int mcc = 0;
int mnc = 0;
try {
mcc = Integer.parseInt(mccmnc.substring(0, 3));
mnc = Integer.parseInt(mccmnc.substring(3));
} catch (Exception e) {
LogUtil.w(TAG, "PhoneUtils.getMccMnc: invalid string " + mccmnc, e);
}
return new int[]{mcc, mnc};
}
@Override
public String getSimOperatorNumeric() {
return mTelephonyManager.getSimOperator();
}
@Override
public String getSelfRawNumber(final boolean allowOverride) {
if (allowOverride) {
final String userDefinedNumber = getNumberFromPrefs(mContext,
ParticipantData.DEFAULT_SELF_SUB_ID);
if (!TextUtils.isEmpty(userDefinedNumber)) {
return userDefinedNumber;
}
}
return mTelephonyManager.getLine1Number();
}
@Override
public int getEffectiveSubId(int subId) {
Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
@Override
public SmsManager getSmsManager() {
return SmsManager.getDefault();
}
@Override
public int getDefaultSmsSubscriptionId() {
Assert.fail("PhoneUtils.getDefaultSmsSubscriptionId(): not supported before L MR1");
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
@Override
public boolean getHasPreferredSmsSim() {
// SIM selection is not supported pre-L_MR1.
return true;
}
@Override
public int getActiveSubscriptionCount() {
return hasSim() ? 1 : 0;
}
@Override
public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
// Pre-L_MR1 always returns the default id
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
@Override
public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
// No subscription_id column before L_MR1
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
@Override
@SuppressWarnings("deprecation")
public boolean isDataRoamingEnabled() {
boolean dataRoamingEnabled = false;
final ContentResolver cr = mContext.getContentResolver();
if (OsUtil.isAtLeastJB_MR1()) {
dataRoamingEnabled =
(Settings.Global.getInt(cr, Settings.Global.DATA_ROAMING, 0) != 0);
} else {
dataRoamingEnabled =
(Settings.System.getInt(cr, Settings.System.DATA_ROAMING, 0) != 0);
}
return dataRoamingEnabled;
}
@Override
public boolean isMobileDataEnabled() {
boolean mobileDataEnabled = false;
try {
final Class cmClass = mConnectivityManager.getClass();
final Method method = cmClass.getDeclaredMethod("getMobileDataEnabled");
method.setAccessible(true); // Make the method callable
// get the setting for "mobile data"
mobileDataEnabled = (Boolean) method.invoke(mConnectivityManager);
} catch (final Exception e) {
LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
}
return mobileDataEnabled;
}
@Override
public HashSet<String> getNormalizedSelfNumbers() {
final HashSet<String> numbers = new HashSet<>();
numbers.add(getCanonicalForSelf(true/*allowOverride*/));
return numbers;
}
}
/**
* The PhoneUtils class for L_MR1
*/
public static class PhoneUtilsLMR1 extends PhoneUtils implements LMr1 {
private final SubscriptionManager mSubscriptionManager;
public PhoneUtilsLMR1(final int subId) {
super(subId);
mSubscriptionManager = SubscriptionManager.from(Factory.get().getApplicationContext());
}
@Override
public String getSimCountry() {
final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
if (subInfo != null) {
final String country = subInfo.getCountryIso();
if (TextUtils.isEmpty(country)) {
return null;
}
return country.toUpperCase();
}
return null;
}
@Override
public int getSimSlotCount() {
return mSubscriptionManager.getActiveSubscriptionInfoCountMax();
}
@Override
public String getCarrierName() {
final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
if (subInfo != null) {
final CharSequence displayName = subInfo.getDisplayName();
if (!TextUtils.isEmpty(displayName)) {
return displayName.toString();
}
final CharSequence carrierName = subInfo.getCarrierName();
if (carrierName != null) {
return carrierName.toString();
}
}
return null;
}
@Override
public boolean hasSim() {
return mSubscriptionManager.getActiveSubscriptionInfoCount() > 0;
}
@Override
public boolean isRoaming() {
return mSubscriptionManager.isNetworkRoaming(mSubId);
}
@Override
public int[] getMccMnc() {
int mcc = 0;
int mnc = 0;
final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
if (subInfo != null) {
mcc = subInfo.getMcc();
mnc = subInfo.getMnc();
}
return new int[]{mcc, mnc};
}
@Override
public String getSimOperatorNumeric() {
// For L_MR1 we return the canonicalized (xxxxxx) string
return getMccMncString(getMccMnc());
}
@Override
public String getSelfRawNumber(final boolean allowOverride) {
if (allowOverride) {
final String userDefinedNumber = getNumberFromPrefs(mContext, mSubId);
if (!TextUtils.isEmpty(userDefinedNumber)) {
return userDefinedNumber;
}
}
final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
if (subInfo != null) {
String phoneNumber = subInfo.getNumber();
if (TextUtils.isEmpty(phoneNumber) && LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
LogUtil.d(TAG, "SubscriptionInfo phone number for self is empty!");
}
return phoneNumber;
}
LogUtil.w(TAG, "PhoneUtils.getSelfRawNumber: subInfo is null for " + mSubId);
throw new IllegalStateException("No active subscription");
}
@Override
public SubscriptionInfo getActiveSubscriptionInfo() {
try {
final SubscriptionInfo subInfo =
mSubscriptionManager.getActiveSubscriptionInfo(mSubId);
if (subInfo == null) {
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
// This is possible if the sub id is no longer available.
LogUtil.d(TAG, "PhoneUtils.getActiveSubscriptionInfo(): empty sub info for "
+ mSubId);
}
}
return subInfo;
} catch (Exception e) {
LogUtil.e(TAG, "PhoneUtils.getActiveSubscriptionInfo: system exception for "
+ mSubId, e);
}
return null;
}
@Override
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
final List<SubscriptionInfo> subscriptionInfos =
mSubscriptionManager.getActiveSubscriptionInfoList();
if (subscriptionInfos != null) {
return subscriptionInfos;
}
return EMPTY_SUBSCRIPTION_LIST;
}
@Override
public int getEffectiveSubId(int subId) {
if (subId == ParticipantData.DEFAULT_SELF_SUB_ID) {
return getDefaultSmsSubscriptionId();
}
return subId;
}
@Override
public void registerOnSubscriptionsChangedListener(
SubscriptionManager.OnSubscriptionsChangedListener listener) {
mSubscriptionManager.addOnSubscriptionsChangedListener(listener);
}
@Override
public SmsManager getSmsManager() {
return SmsManager.getSmsManagerForSubscriptionId(mSubId);
}
@Override
public int getDefaultSmsSubscriptionId() {
final int systemDefaultSubId = SmsManager.getDefaultSmsSubscriptionId();
if (systemDefaultSubId < 0) {
// Always use -1 for any negative subId from system
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
return systemDefaultSubId;
}
@Override
public boolean getHasPreferredSmsSim() {
return getDefaultSmsSubscriptionId() != ParticipantData.DEFAULT_SELF_SUB_ID;
}
@Override
public int getActiveSubscriptionCount() {
return mSubscriptionManager.getActiveSubscriptionInfoCount();
}
@Override
public int getEffectiveIncomingSubIdFromSystem(Intent intent, String extraName) {
return getEffectiveIncomingSubIdFromSystem(intent.getIntExtra(extraName,
ParticipantData.DEFAULT_SELF_SUB_ID));
}
private int getEffectiveIncomingSubIdFromSystem(int subId) {
if (subId < 0) {
if (mSubscriptionManager.getActiveSubscriptionInfoCount() > 1) {
// For multi-SIM device, we can not decide which SIM to use if system
// does not know either. So just make it the invalid sub id.
return ParticipantData.DEFAULT_SELF_SUB_ID;
}
// For single-SIM device, it must come from the only SIM we have
return getDefaultSmsSubscriptionId();
}
return subId;
}
@Override
public int getSubIdFromTelephony(Cursor cursor, int subIdIndex) {
return getEffectiveIncomingSubIdFromSystem(cursor.getInt(subIdIndex));
}
@Override
public boolean isDataRoamingEnabled() {
final SubscriptionInfo subInfo = getActiveSubscriptionInfo();
if (subInfo == null) {
// There is nothing we can do if system give us empty sub info
LogUtil.e(TAG, "PhoneUtils.isDataRoamingEnabled: system return empty sub info for "
+ mSubId);
return false;
}
return subInfo.getDataRoaming() != SubscriptionManager.DATA_ROAMING_DISABLE;
}
@Override
public boolean isMobileDataEnabled() {
boolean mobileDataEnabled = false;
try {
final Class cmClass = mTelephonyManager.getClass();
final Method method = cmClass.getDeclaredMethod("getDataEnabled", Integer.TYPE);
method.setAccessible(true); // Make the method callable
// get the setting for "mobile data"
mobileDataEnabled = (Boolean) method.invoke(
mTelephonyManager, Integer.valueOf(mSubId));
} catch (final Exception e) {
LogUtil.e(TAG, "PhoneUtil.isMobileDataEnabled: system api not found", e);
}
return mobileDataEnabled;
}
@Override
public HashSet<String> getNormalizedSelfNumbers() {
final HashSet<String> numbers = new HashSet<>();
for (SubscriptionInfo info : getActiveSubscriptionInfoList()) {
numbers.add(PhoneUtils.get(info.getSubscriptionId()).getCanonicalForSelf(
true/*allowOverride*/));
}
return numbers;
}
}
/**
* A convenient get() method that uses the default SIM. Use this when SIM is
* not relevant, e.g. isDefaultSmsApp
*
* @return an instance of PhoneUtils for default SIM
*/
public static PhoneUtils getDefault() {
return Factory.get().getPhoneUtils(ParticipantData.DEFAULT_SELF_SUB_ID);
}
/**
* Get an instance of PhoneUtils associated with a specific SIM, which is also platform
* specific.
*
* @param subId The SIM's subscription ID
* @return the instance
*/
public static PhoneUtils get(int subId) {
return Factory.get().getPhoneUtils(subId);
}
public LMr1 toLMr1() {
if (OsUtil.isAtLeastL_MR1()) {
return (LMr1) this;
} else {
Assert.fail("PhoneUtils.toLMr1(): invalid OS version");
return null;
}
}
/**
* Check if this device supports SMS
*
* @return true if SMS is supported, false otherwise
*/
public boolean isSmsCapable() {
return mTelephonyManager.isSmsCapable();
}
/**
* Check if this device supports voice calling
*
* @return true if voice calling is supported, false otherwise
*/
public boolean isVoiceCapable() {
return mTelephonyManager.isVoiceCapable();
}
/**
* Get the ISO country code from system locale setting
*
* @return the ISO country code from system locale
*/
private static String getLocaleCountry() {
final String country = Locale.getDefault().getCountry();
if (TextUtils.isEmpty(country)) {
return null;
}
return country.toUpperCase();
}
/**
* Get ISO country code from the SIM, if not available, fall back to locale
*
* @return SIM or locale ISO country code
*/
public String getSimOrDefaultLocaleCountry() {
String country = getSimCountry();
if (country == null) {
country = getLocaleCountry();
}
return country;
}
// Get or set the cache of canonicalized phone numbers for a specific country
private static ArrayMap<String, String> getOrAddCountryMapInCacheLocked(String country) {
if (country == null) {
country = "";
}
ArrayMap<String, String> countryMap = sCanonicalPhoneNumberCache.get(country);
if (countryMap == null) {
countryMap = new ArrayMap<>();
sCanonicalPhoneNumberCache.put(country, countryMap);
}
return countryMap;
}
// Get canonicalized phone number from cache
private static String getCanonicalFromCache(final String phoneText, String country) {
synchronized (sCanonicalPhoneNumberCache) {
final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
return countryMap.get(phoneText);
}
}
// Put canonicalized phone number into cache
private static void putCanonicalToCache(final String phoneText, String country,
final String canonical) {
synchronized (sCanonicalPhoneNumberCache) {
final ArrayMap<String, String> countryMap = getOrAddCountryMapInCacheLocked(country);
countryMap.put(phoneText, canonical);
}
}
/**
* Utility method to parse user input number into standard E164 number.
*
* @param phoneText Phone number text as input by user.
* @param country ISO country code based on which to parse the number.
* @return E164 phone number. Returns null in case parsing failed.
*/
private static String getValidE164Number(final String phoneText, final String country) {
final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
try {
final PhoneNumber phoneNumber = phoneNumberUtil.parse(phoneText, country);
if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
return phoneNumberUtil.format(phoneNumber, PhoneNumberFormat.E164);
}
} catch (final NumberParseException e) {
LogUtil.e(TAG, "PhoneUtils.getValidE164Number(): Not able to parse phone number "
+ LogUtil.sanitizePII(phoneText) + " for country " + country);
}
return null;
}
/**
* Canonicalize phone number using system locale country
*
* @param phoneText The phone number to canonicalize
* @return the canonicalized number
*/
public String getCanonicalBySystemLocale(final String phoneText) {
return getCanonicalByCountry(phoneText, getLocaleCountry());
}
/**
* Canonicalize phone number using SIM's country, may fall back to system locale country
* if SIM country can not be obtained
*
* @param phoneText The phone number to canonicalize
* @return the canonicalized number
*/
public String getCanonicalBySimLocale(final String phoneText) {
return getCanonicalByCountry(phoneText, getSimOrDefaultLocaleCountry());
}
/**
* Canonicalize phone number using a country code.
* This uses an internal cache per country to speed up.
*
* @param phoneText The phone number to canonicalize
* @param country The ISO country code to use
* @return the canonicalized number, or the original number if can't be parsed
*/
private String getCanonicalByCountry(final String phoneText, final String country) {
Assert.notNull(phoneText);
String canonicalNumber = getCanonicalFromCache(phoneText, country);
if (canonicalNumber != null) {
return canonicalNumber;
}
canonicalNumber = getValidE164Number(phoneText, country);
if (canonicalNumber == null) {
// If we can't normalize this number, we just use the display string number.
// This is possible for short codes and other non-localizable numbers.
canonicalNumber = phoneText;
}
putCanonicalToCache(phoneText, country, canonicalNumber);
return canonicalNumber;
}
/**
* Canonicalize the self (per SIM) phone number
*
* @param allowOverride whether to use the override number in app settings
* @return the canonicalized self phone number
*/
public String getCanonicalForSelf(final boolean allowOverride) {
String selfNumber = null;
try {
selfNumber = getSelfRawNumber(allowOverride);
} catch (IllegalStateException e) {
// continue;
}
if (selfNumber == null) {
return "";
}
return getCanonicalBySimLocale(selfNumber);
}
/**
* Get the SIM's phone number in NATIONAL format with only digits, used in sending
* as LINE1NOCOUNTRYCODE macro in mms_config
*
* @return all digits national format number of the SIM
*/
public String getSimNumberNoCountryCode() {
String selfNumber = null;
try {
selfNumber = getSelfRawNumber(false/*allowOverride*/);
} catch (IllegalStateException e) {
// continue
}
if (selfNumber == null) {
selfNumber = "";
}
final String country = getSimCountry();
final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
try {
final PhoneNumber phoneNumber = phoneNumberUtil.parse(selfNumber, country);
if (phoneNumber != null && phoneNumberUtil.isValidNumber(phoneNumber)) {
return phoneNumberUtil
.format(phoneNumber, PhoneNumberFormat.NATIONAL)
.replaceAll("\\D", "");
}
} catch (final NumberParseException e) {
LogUtil.e(TAG, "PhoneUtils.getSimNumberNoCountryCode(): Not able to parse phone number "
+ LogUtil.sanitizePII(selfNumber) + " for country " + country);
}
return selfNumber;
}
/**
* Format a phone number for displaying, using system locale country.
* If the country code matches between the system locale and the input phone number,
* it will be formatted into NATIONAL format, otherwise, the INTERNATIONAL format
*
* @param phoneText The original phone text
* @return formatted number
*/
public String formatForDisplay(final String phoneText) {
// Only format a valid number which length >=6
if (TextUtils.isEmpty(phoneText) ||
phoneText.replaceAll("\\D", "").length() < MINIMUM_PHONE_NUMBER_LENGTH_TO_FORMAT) {
return phoneText;
}
final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
final String systemCountry = getLocaleCountry();
final int systemCountryCode = phoneNumberUtil.getCountryCodeForRegion(systemCountry);
try {
final PhoneNumber parsedNumber = phoneNumberUtil.parse(phoneText, systemCountry);
final PhoneNumberFormat phoneNumberFormat =
(systemCountryCode > 0 && parsedNumber.getCountryCode() == systemCountryCode) ?
PhoneNumberFormat.NATIONAL : PhoneNumberFormat.INTERNATIONAL;
return phoneNumberUtil.format(parsedNumber, phoneNumberFormat);
} catch (NumberParseException e) {
LogUtil.e(TAG, "PhoneUtils.formatForDisplay: invalid phone number "
+ LogUtil.sanitizePII(phoneText) + " with country " + systemCountry);
return phoneText;
}
}
/**
* Is Messaging the default SMS app?
* - On KLP+ this checks the system setting.
* - On JB (and below) this always returns true, since the setting was added in KLP.
*/
public boolean isDefaultSmsApp() {
if (OsUtil.isAtLeastKLP()) {
final String configuredApplication = Telephony.Sms.getDefaultSmsPackage(mContext);
return mContext.getPackageName().equals(configuredApplication);
}
return true;
}
/**
* Get default SMS app package name
*
* @return the package name of default SMS app
*/
public String getDefaultSmsApp() {
if (OsUtil.isAtLeastKLP()) {
return Telephony.Sms.getDefaultSmsPackage(mContext);
}
return null;
}
/**
* Determines if SMS is currently enabled on this device.
* - Device must support SMS
* - On KLP+ we must be set as the default SMS app
*/
public boolean isSmsEnabled() {
return isSmsCapable() && isDefaultSmsApp();
}
/**
* Returns the name of the default SMS app, or the empty string if there is
* an error or there is no default app (e.g. JB and below).
*/
public String getDefaultSmsAppLabel() {
if (OsUtil.isAtLeastKLP()) {
final String packageName = Telephony.Sms.getDefaultSmsPackage(mContext);
final PackageManager pm = mContext.getPackageManager();
try {
final ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
return pm.getApplicationLabel(appInfo).toString();
} catch (NameNotFoundException e) {
// Fall through and return empty string
}
}
return "";
}
/**
* Gets the state of Airplane Mode.
*
* @return true if enabled.
*/
@SuppressWarnings("deprecation")
public boolean isAirplaneModeOn() {
if (OsUtil.isAtLeastJB_MR1()) {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
} else {
return Settings.System.getInt(mContext.getContentResolver(),
Settings.System.AIRPLANE_MODE_ON, 0) != 0;
}
}
public static String getMccMncString(int[] mccmnc) {
if (mccmnc == null || mccmnc.length != 2) {
return "000000";
}
return String.format("%03d%03d", mccmnc[0], mccmnc[1]);
}
public static String canonicalizeMccMnc(final String mcc, final String mnc) {
try {
return String.format("%03d%03d", Integer.parseInt(mcc), Integer.parseInt(mnc));
} catch (final NumberFormatException e) {
// Return invalid as is
LogUtil.w(TAG, "canonicalizeMccMnc: invalid mccmnc:" + mcc + " ," + mnc);
}
return mcc + mnc;
}
/**
* Returns whether the given destination is valid for sending SMS/MMS message.
*/
public static boolean isValidSmsMmsDestination(final String destination) {
return PhoneNumberUtils.isWellFormedSmsAddress(destination) ||
MmsSmsUtils.isEmailAddress(destination);
}
public interface SubscriptionRunnable {
void runForSubscription(int subId);
}
/**
* A convenience method for iterating through all active subscriptions
*
* @param runnable a {@link SubscriptionRunnable} for performing work on each subscription.
*/
public static void forEachActiveSubscription(final SubscriptionRunnable runnable) {
if (OsUtil.isAtLeastL_MR1()) {
final List<SubscriptionInfo> subscriptionList =
getDefault().toLMr1().getActiveSubscriptionInfoList();
for (final SubscriptionInfo subscriptionInfo : subscriptionList) {
runnable.runForSubscription(subscriptionInfo.getSubscriptionId());
}
} else {
runnable.runForSubscription(ParticipantData.DEFAULT_SELF_SUB_ID);
}
}
private static String getNumberFromPrefs(final Context context, final int subId) {
final BuglePrefs prefs = BuglePrefs.getSubscriptionPrefs(subId);
final String mmsPhoneNumberPrefKey =
context.getString(R.string.mms_phone_number_pref_key);
final String userDefinedNumber = prefs.getString(mmsPhoneNumberPrefKey, null);
if (!TextUtils.isEmpty(userDefinedNumber)) {
return userDefinedNumber;
}
return null;
}
}