1018 lines
36 KiB
Java
1018 lines
36 KiB
Java
/*
|
|
* 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 android.telephony.PhoneNumberUtils;
|
|
import android.telephony.SmsManager;
|
|
import android.telephony.SubscriptionInfo;
|
|
import android.telephony.SubscriptionManager;
|
|
import android.telephony.TelephonyManager;
|
|
import android.text.TextUtils;
|
|
|
|
import androidx.collection.ArrayMap;
|
|
import androidx.core.os.BuildCompat;
|
|
|
|
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.PhoneNumberUtil;
|
|
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
|
|
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
|
|
|
|
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() {
|
|
if (BuildCompat.isAtLeastT()) {
|
|
return mTelephonyManager.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;
|
|
}
|
|
}
|