/* * Copyright 2019 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.internal.telephony; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; import static android.telephony.SubscriptionManager.TRANSFER_STATUS_CONVERTED; import static android.telephony.TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED; import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE; import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL; import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA; import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS; import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE; import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_NAMES; import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE; import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA; import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE; import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.AsyncResult; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.ParcelUuid; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.data.DataSettingsManager.DataSettingsManagerCallback; import com.android.internal.telephony.flags.FeatureFlags; import com.android.internal.telephony.satellite.SatelliteController; import com.android.internal.telephony.subscription.SubscriptionInfoInternal; import com.android.internal.telephony.subscription.SubscriptionManagerService; import com.android.internal.telephony.util.ArrayUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.stream.Collectors; /** * This class will make sure below setting rules are coordinated across different subscriptions * and phones in multi-SIM case: * * 1) Grouped subscriptions will have same settings for MOBILE_DATA and DATA_ROAMING. * 2) Default settings updated automatically. It may be cleared or inherited within group. * If default subscription A switches to profile B which is in the same group, B will * become the new default. * 3) For primary subscriptions, only default data subscription will have MOBILE_DATA on. */ public class MultiSimSettingController extends Handler { private static final String LOG_TAG = "MultiSimSettingController"; private static final boolean DBG = true; private static final int EVENT_USER_DATA_ENABLED = 1; private static final int EVENT_ROAMING_DATA_ENABLED = 2; private static final int EVENT_ALL_SUBSCRIPTIONS_LOADED = 3; private static final int EVENT_SUBSCRIPTION_INFO_CHANGED = 4; private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED = 5; private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6; @VisibleForTesting public static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 8; @VisibleForTesting public static final int EVENT_RADIO_STATE_CHANGED = 9; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"PRIMARY_SUB_"}, value = { PRIMARY_SUB_NO_CHANGE, PRIMARY_SUB_ADDED, PRIMARY_SUB_REMOVED, PRIMARY_SUB_SWAPPED, PRIMARY_SUB_SWAPPED_IN_GROUP, PRIMARY_SUB_MARKED_OPPT, PRIMARY_SUB_INITIALIZED, PRIMARY_SUB_REMOVED_IN_GROUP }) private @interface PrimarySubChangeType {} // Primary subscription not change. private static final int PRIMARY_SUB_NO_CHANGE = 0; // One or more primary subscriptions are activated. private static final int PRIMARY_SUB_ADDED = 1; // One or more primary subscriptions are deactivated. private static final int PRIMARY_SUB_REMOVED = 2; // One or more primary subscriptions are swapped. private static final int PRIMARY_SUB_SWAPPED = 3; // One or more primary subscriptions are swapped but within same sub group. private static final int PRIMARY_SUB_SWAPPED_IN_GROUP = 4; // One or more primary subscriptions are marked as opportunistic. private static final int PRIMARY_SUB_MARKED_OPPT = 5; // Subscription information is initially loaded. private static final int PRIMARY_SUB_INITIALIZED = 6; // One or more primary subscriptions are deactivated but within the same group as another active // sub. private static final int PRIMARY_SUB_REMOVED_IN_GROUP = 7; protected final Context mContext; private final SubscriptionManagerService mSubscriptionManagerService; private final @NonNull FeatureFlags mFeatureFlags; // Keep a record of active primary (non-opportunistic) subscription list. @NonNull private List mPrimarySubList = new ArrayList<>(); /** The singleton instance. */ protected static MultiSimSettingController sInstance = null; // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. private boolean mSubInfoInitialized = false; // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot. // After boot-up when things are firstly initialized (mSubInfoInitialized is changed to true // and carrier configs are all loaded), we do a reEvaluateAll(). In the first reEvaluateAll(), // mInitialHandling will be true and we won't pop up SIM select dialog. private boolean mInitialHandling = true; // Keep a record of which subIds have carrier config loaded. Length of the array is phone count. // The index is phoneId, and value is subId. For example: // If carrier config of subId 2 is loaded on phone 0,mCarrierConfigLoadedSubIds[0] = 2. // Then if subId 2 is deactivated from phone 0, the value becomes INVALID, // mCarrierConfigLoadedSubIds[0] = INVALID_SUBSCRIPTION_ID. private int[] mCarrierConfigLoadedSubIds; // It indicates whether "Ask every time" option for default SMS subscription is supported by the // device. private final boolean mIsAskEverytimeSupportedForSms; // The number of existing DataSettingsControllerCallback private int mCallbacksCount; /** The number of active modem count. */ private int mActiveModemCount; private boolean mNeedSetDefaultVoice; private boolean mNeedSetDefaultSms; private boolean mNeedSetDefaultData; private int mConvertedPsimSubId; private static final String SETTING_USER_PREF_DATA_SUB = "user_preferred_data_sub"; private static class DataSettingsControllerCallback extends DataSettingsManagerCallback { private final Phone mPhone; DataSettingsControllerCallback(@NonNull Phone phone, @NonNull @CallbackExecutor Executor executor) { super(executor); mPhone = phone; } @Override public void onUserDataEnabledChanged(boolean enabled, @NonNull String callingPackage) { int subId = mPhone.getSubId(); // only notifyUserDataEnabled if the change is called from external to avoid // setUserDataEnabledForGroup infinite loop if (SubscriptionManager.isValidSubscriptionId(subId) && !getInstance().mContext.getOpPackageName().equals(callingPackage)) { getInstance().notifyUserDataEnabled(subId, enabled); } } @Override public void onDataRoamingEnabledChanged(boolean enabled) { int subId = mPhone.getSubId(); if (SubscriptionManager.isValidSubscriptionId(subId)) { getInstance().notifyRoamingDataEnabled(mPhone.getSubId(), enabled); } } } /** * Return the singleton or create one if not existed. */ public static MultiSimSettingController getInstance() { synchronized (MultiSimSettingController.class) { if (sInstance == null) { Log.wtf(LOG_TAG, "getInstance null"); } return sInstance; } } /** * Init instance of MultiSimSettingController. */ public static MultiSimSettingController init(Context context, @NonNull FeatureFlags featureFlags) { synchronized (MultiSimSettingController.class) { if (sInstance == null) { sInstance = new MultiSimSettingController(context, featureFlags); } else { Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); } return sInstance; } } @VisibleForTesting public MultiSimSettingController(Context context, @NonNull FeatureFlags featureFlags) { mContext = context; mSubscriptionManagerService = SubscriptionManagerService.getInstance(); mFeatureFlags = featureFlags; // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. TelephonyManager telephonyManager = ((TelephonyManager) mContext.getSystemService( TelephonyManager.class)); final int phoneCount = telephonyManager.getSupportedModemCount(); mCarrierConfigLoadedSubIds = new int[phoneCount]; Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID); mActiveModemCount = telephonyManager.getActiveModemCount(); PhoneConfigurationManager.registerForMultiSimConfigChange( this, EVENT_MULTI_SIM_CONFIG_CHANGED, null); mIsAskEverytimeSupportedForSms = mContext.getResources() .getBoolean(com.android.internal.R.bool.config_sms_ask_every_time_support); CarrierConfigManager ccm = mContext.getSystemService(CarrierConfigManager.class); // Listener callback is executed on handler thread to directly handle config change ccm.registerCarrierConfigChangeListener(this::post, (slotIndex, subId, carrierId, specificCarrierId) -> onCarrierConfigChanged(slotIndex, subId)); mConvertedPsimSubId = getConvertedPsimSubscriptionId(); } private boolean hasCalling() { if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; return mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_CALLING); } private boolean hasData() { if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; return mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_DATA); } private boolean hasMessaging() { if (!mFeatureFlags.minimalTelephonyCdmCheck()) return true; return mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY_MESSAGING); } /** * Notify MOBILE_DATA of a subscription is changed. */ public void notifyUserDataEnabled(int subId, boolean enable) { if (SubscriptionManager.isValidSubscriptionId(subId)) { obtainMessage(EVENT_USER_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); } } /** * Notify DATA_ROAMING of a subscription is changed. */ public void notifyRoamingDataEnabled(int subId, boolean enable) { if (SubscriptionManager.isValidSubscriptionId(subId)) { obtainMessage(EVENT_ROAMING_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); } } /** * Notify that, for the first time after boot, SIMs are initialized. * Should only be triggered once. */ public void notifyAllSubscriptionLoaded() { obtainMessage(EVENT_ALL_SUBSCRIPTIONS_LOADED).sendToTarget(); } /** * Notify subscription info change. */ public void notifySubscriptionInfoChanged() { log("notifySubscriptionInfoChanged"); obtainMessage(EVENT_SUBSCRIPTION_INFO_CHANGED).sendToTarget(); } /** * Notify subscription group information change. */ public void notifySubscriptionGroupChanged(ParcelUuid groupUuid) { obtainMessage(EVENT_SUBSCRIPTION_GROUP_CHANGED, groupUuid).sendToTarget(); } /** * Notify default data subscription change. */ public void notifyDefaultDataSubChanged() { obtainMessage(EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED).sendToTarget(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_USER_DATA_ENABLED: { int subId = msg.arg1; boolean enable = msg.arg2 != 0; onUserDataEnabled(subId, enable, true); break; } case EVENT_ROAMING_DATA_ENABLED: { int subId = msg.arg1; boolean enable = msg.arg2 != 0; onRoamingDataEnabled(subId, enable); break; } case EVENT_ALL_SUBSCRIPTIONS_LOADED: onAllSubscriptionsLoaded(); break; case EVENT_SUBSCRIPTION_INFO_CHANGED: onSubscriptionsChanged(); break; case EVENT_SUBSCRIPTION_GROUP_CHANGED: ParcelUuid groupUuid = (ParcelUuid) msg.obj; onSubscriptionGroupChanged(groupUuid); break; case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED: onDefaultDataSettingChanged(); break; case EVENT_MULTI_SIM_CONFIG_CHANGED: int activeModems = (int) ((AsyncResult) msg.obj).result; onMultiSimConfigChanged(activeModems); break; case EVENT_RADIO_STATE_CHANGED: for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) { Phone phone = PhoneFactory.getPhone(phoneId); if (phone != null && phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) { if (DBG) { log("Radio unavailable on phone " + phoneId + ", clearing sub info initialized flag"); } mSubInfoInitialized = false; break; } } break; } } /** * Make sure MOBILE_DATA of subscriptions in same group are synced. * * If user is enabling a non-default non-opportunistic subscription, make it default * data subscription. */ private void onUserDataEnabled(int subId, boolean enable, boolean setDefaultData) { if (DBG) log("[onUserDataEnabled] subId=" + subId + " enable=" + enable + " setDefaultData=" + setDefaultData); // Make sure MOBILE_DATA of subscriptions in same group are synced. setUserDataEnabledForGroup(subId, enable); SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); // If user is enabling a non-default non-opportunistic subscription, make it default. if (defaultDataSubId != subId && subInfo != null && !subInfo.isOpportunistic() && enable && subInfo.isActive() && setDefaultData) { android.provider.Settings.Global.putInt(mContext.getContentResolver(), SETTING_USER_PREF_DATA_SUB, subId); mSubscriptionManagerService.setDefaultDataSubId(subId); } } /** * Make sure DATA_ROAMING of subscriptions in same group are synced. */ private void onRoamingDataEnabled(int subId, boolean enable) { if (DBG) log("onRoamingDataEnabled"); setRoamingDataEnabledForGroup(subId, enable); // Also inform SubscriptionManagerService as it keeps another copy of user setting. mSubscriptionManagerService.setDataRoaming(enable ? 1 : 0, subId); } /** * Upon initialization or radio available, update defaults and mobile data enabling. * Should only be triggered once. */ private void onAllSubscriptionsLoaded() { if (DBG) log("onAllSubscriptionsLoaded: mSubInfoInitialized=" + mSubInfoInitialized); if (!mSubInfoInitialized) { mSubInfoInitialized = true; mConvertedPsimSubId = getConvertedPsimSubscriptionId(); for (Phone phone : PhoneFactory.getPhones()) { phone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); } reEvaluateAll(); } registerDataSettingsControllerCallbackAsNeeded(); } /** * Make sure default values are cleaned or updated. * * Make sure non-default non-opportunistic subscriptions has data off. */ private void onSubscriptionsChanged() { if (DBG) log("onSubscriptionsChanged"); reEvaluateAll(); } /** * This method is called when a phone object is removed (for example when going from multi-sim * to single-sim). * NOTE: This method does not post a message to self, instead it calls reEvaluateAll() directly. * so it should only be called from the main thread. The reason is to update defaults asap * after multi_sim_config property has been updated (see b/163582235). */ public void onPhoneRemoved() { if (DBG) log("onPhoneRemoved"); if (Looper.myLooper() != this.getLooper()) { throw new RuntimeException("This method must be called from the same looper as " + "MultiSimSettingController."); } reEvaluateAll(); } private void onCarrierConfigChanged(int phoneId, int subId) { log("onCarrierConfigChanged phoneId " + phoneId + " subId " + subId); if (!SubscriptionManager.isValidPhoneId(phoneId)) { loge("Carrier config change with invalid phoneId " + phoneId); return; } CarrierConfigManager cm; if (!SubscriptionManager.isValidSubscriptionId(subId) // record SIM absent. || ((cm = mContext.getSystemService(CarrierConfigManager.class)) != null && CarrierConfigManager.isConfigForIdentifiedCarrier( cm.getConfigForSubId(subId)))) { mCarrierConfigLoadedSubIds[phoneId] = subId; reEvaluateAll(); } } /** * Check whether carrier config loaded for all subs */ @VisibleForTesting public boolean isCarrierConfigLoadedForAllSub() { int[] activeSubIds = mSubscriptionManagerService.getActiveSubIdList(false); for (int activeSubId : activeSubIds) { boolean isLoaded = false; for (int configLoadedSub : mCarrierConfigLoadedSubIds) { if (configLoadedSub == activeSubId) { isLoaded = true; break; } } if (!isLoaded) { if (DBG) log("Carrier config subId " + activeSubId + " is not loaded."); return false; } } return true; } private void onMultiSimConfigChanged(int activeModems) { mActiveModemCount = activeModems; log("onMultiSimConfigChanged: current ActiveModemCount=" + mActiveModemCount); // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active // subscription change. for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) { mCarrierConfigLoadedSubIds[phoneId] = INVALID_SUBSCRIPTION_ID; } for (Phone phone : PhoneFactory.getPhones()) { phone.mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null); } registerDataSettingsControllerCallbackAsNeeded(); } /** * Wait for subInfo initialization (after boot up or radio unavailable) and carrier config load * for all active subscriptions before re-evaluate multi SIM settings. */ private boolean isReadyToReevaluate() { boolean carrierConfigsLoaded = isCarrierConfigLoadedForAllSub(); SatelliteController satelliteController = SatelliteController.getInstance(); boolean isSatelliteEnabledOrBeingEnabled = false; if (satelliteController != null) { isSatelliteEnabledOrBeingEnabled = satelliteController.isSatelliteEnabled() || satelliteController.isSatelliteBeingEnabled(); } if (DBG) { log("isReadyToReevaluate: subInfoInitialized=" + mSubInfoInitialized + ", carrierConfigsLoaded=" + carrierConfigsLoaded + ", satelliteEnabledOrBeingEnabled=" + isSatelliteEnabledOrBeingEnabled); } return mSubInfoInitialized && carrierConfigsLoaded && !isSatelliteEnabledOrBeingEnabled; } private void reEvaluateAll() { if (!isReadyToReevaluate()) return; updateDefaults(); disableDataForNonDefaultNonOpportunisticSubscriptions(); deactivateGroupedOpportunisticSubscriptionIfNeeded(); } /** * Make sure non-default non-opportunistic subscriptions has data disabled. */ private void onDefaultDataSettingChanged() { if (DBG) log("onDefaultDataSettingChanged"); disableDataForNonDefaultNonOpportunisticSubscriptions(); } /** * When a subscription group is created or new subscriptions are added in the group, make * sure the settings among them are synced. * TODO: b/130258159 have a separate database table for grouped subscriptions so we don't * manually sync each setting. */ private void onSubscriptionGroupChanged(ParcelUuid groupUuid) { if (DBG) log("onSubscriptionGroupChanged"); List infoList = mSubscriptionManagerService.getSubscriptionsInGroup( groupUuid, mContext.getOpPackageName(), mContext.getAttributionTag()); if (infoList == null || infoList.isEmpty()) return; // Get a reference subscription to copy settings from. // TODO: the reference sub should be passed in from external caller. int refSubId = infoList.get(0).getSubscriptionId(); for (SubscriptionInfo info : infoList) { int subId = info.getSubscriptionId(); if (info.isActive() && !info.isOpportunistic()) { refSubId = subId; break; } } if (DBG) log("refSubId is " + refSubId); boolean enable = false; try { enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.MOBILE_DATA, refSubId); } catch (SettingNotFoundException exception) { //pass invalid refSubId to fetch the single-sim setting enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable); } boolean setDefaultData = true; List activeSubList = mSubscriptionManagerService .getActiveSubscriptionInfoList(mContext.getOpPackageName(), mContext.getAttributionTag(), true/*isForAllProfile*/); for (SubscriptionInfo activeInfo : activeSubList) { if (!(groupUuid.equals(activeInfo.getGroupUuid()))) { // Do not set refSubId as defaultDataSubId if there are other active // subscriptions which does not belong to this groupUuid setDefaultData = false; break; } } onUserDataEnabled(refSubId, enable, setDefaultData); enable = false; try { enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.DATA_ROAMING, refSubId); onRoamingDataEnabled(refSubId, enable); } catch (SettingNotFoundException exception) { //pass invalid refSubId to fetch the single-sim setting enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.DATA_ROAMING, INVALID_SUBSCRIPTION_ID, enable); onRoamingDataEnabled(refSubId, enable); } mSubscriptionManagerService.syncGroupedSetting(refSubId); } /** * Automatically update default settings (data / voice / sms). * * Opportunistic subscriptions can't be default data / voice / sms subscription. * * 1) If the default subscription is still active, keep it unchanged. * 2) Or if there's another active primary subscription that's in the same group, * make it the new default value. * 3) Or if there's only one active primary subscription, automatically set default * data subscription on it. Because default data in Android Q is an internal value, * not a user settable value anymore. * 4) If non above is met, clear the default value to INVALID. * */ protected void updateDefaults() { if (DBG) log("updateDefaults"); if (!isReadyToReevaluate()) return; List activeSubInfos = mSubscriptionManagerService .getActiveSubscriptionInfoList(mContext.getOpPackageName(), mContext.getAttributionTag(), true/*isForAllProfile*/); if (ArrayUtils.isEmpty(activeSubInfos)) { mPrimarySubList.clear(); if (DBG) log("updateDefaults: No active sub. Setting default to INVALID sub."); mSubscriptionManagerService.setDefaultDataSubId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); mSubscriptionManagerService.setDefaultVoiceSubId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); mSubscriptionManagerService.setDefaultSmsSubId( SubscriptionManager.INVALID_SUBSCRIPTION_ID); return; } int change = updatePrimarySubListAndGetChangeType(activeSubInfos); if (DBG) log("updateDefaultValues: change: " + change); if (change == PRIMARY_SUB_NO_CHANGE) return; // If there's only one primary subscription active, we trigger PREFERRED_PICK_DIALOG // dialog if and only if there were multiple primary SIM cards and one is removed. // Otherwise, if user just inserted their first SIM, or there's one primary and one // opportunistic subscription active (activeSubInfos.size() > 1), we automatically // set the primary to be default SIM and return. if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED || mActiveModemCount == 1)) { int subId = mPrimarySubList.get(0); if (DBG) log("updateDefaultValues: to only primary sub " + subId); if (hasData()) mSubscriptionManagerService.setDefaultDataSubId(subId); if (hasCalling()) mSubscriptionManagerService.setDefaultVoiceSubId(subId); if (hasMessaging()) mSubscriptionManagerService.setDefaultSmsSubId(subId); if (!mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) { sendDefaultSubConfirmedNotification(subId); } return; } if (DBG) log("updateDefaultValues: records: " + mPrimarySubList); boolean dataSelected = false; boolean voiceSelected = false; boolean smsSelected = false; if (hasData()) { // Update default data subscription. if (DBG) log("updateDefaultValues: Update default data subscription"); dataSelected = updateDefaultValue(mPrimarySubList, mSubscriptionManagerService.getDefaultDataSubId(), mSubscriptionManagerService::setDefaultDataSubId); } if (hasCalling()) { // Update default voice subscription. if (DBG) log("updateDefaultValues: Update default voice subscription"); voiceSelected = updateDefaultValue(mPrimarySubList, mSubscriptionManagerService.getDefaultVoiceSubId(), mSubscriptionManagerService::setDefaultVoiceSubId); } if (hasMessaging()) { // Update default sms subscription. if (DBG) log("updateDefaultValues: Update default sms subscription"); smsSelected = updateDefaultValue(mPrimarySubList, mSubscriptionManagerService.getDefaultSmsSubId(), mSubscriptionManagerService::setDefaultSmsSubId, mIsAskEverytimeSupportedForSms); } boolean autoFallbackEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_data_sms_auto_fallback); // Based on config config_voice_data_sms_auto_fallback value choose voice/data/sms // preference auto selection logic or display notification for end used to // select voice/data/SMS preferences. if (!autoFallbackEnabled) { // Hide the dialog for preferred SIM/data pick if the primary subscription change is // due to the pSIM conversion. if (!setDefaultForPsimConversionChanged(change, dataSelected, voiceSelected, smsSelected)) { sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected); } } else { updateUserPreferences(mPrimarySubList, dataSelected, voiceSelected, smsSelected); } } @PrimarySubChangeType private int updatePrimarySubListAndGetChangeType(List activeSubList) { // Update mPrimarySubList. Opportunistic subscriptions can't be default // data / voice / sms subscription. List prevPrimarySubList = mPrimarySubList; mPrimarySubList = activeSubList.stream() .filter(info -> !info.isOpportunistic()) .filter(info -> info.getProfileClass() != PROFILE_CLASS_PROVISIONING) .map(info -> info.getSubscriptionId()) .collect(Collectors.toList()); if (mInitialHandling) { mInitialHandling = false; return PRIMARY_SUB_INITIALIZED; } if (mPrimarySubList.equals(prevPrimarySubList)) return PRIMARY_SUB_NO_CHANGE; if (mPrimarySubList.size() > prevPrimarySubList.size()) return PRIMARY_SUB_ADDED; if (mPrimarySubList.size() == prevPrimarySubList.size()) { // We need to differentiate PRIMARY_SUB_SWAPPED and PRIMARY_SUB_SWAPPED_IN_GROUP: // For SWAPPED_IN_GROUP, we never pop up dialog to ask data sub selection again. for (int subId : mPrimarySubList) { boolean swappedInSameGroup = false; for (int prevSubId : prevPrimarySubList) { if (areSubscriptionsInSameGroup(subId, prevSubId)) { swappedInSameGroup = true; break; } } if (!swappedInSameGroup) return PRIMARY_SUB_SWAPPED; } return PRIMARY_SUB_SWAPPED_IN_GROUP; } else /* mPrimarySubList.size() < prevPrimarySubList.size() */ { // We need to differentiate whether the missing subscription is removed or marked as // opportunistic. Usually only one subscription may change at a time, But to be safe, if // any previous primary subscription becomes inactive, we consider it for (int subId : prevPrimarySubList) { if (mPrimarySubList.contains(subId)) continue; SubscriptionInfo subInfo = mSubscriptionManagerService.getSubscriptionInfo(subId); if (subInfo == null || !subInfo.isActive()) { for (int currentSubId : mPrimarySubList) { if (areSubscriptionsInSameGroup(currentSubId, subId)) { return PRIMARY_SUB_REMOVED_IN_GROUP; } } return PRIMARY_SUB_REMOVED; } if (!subInfo.isOpportunistic()) { // Should never happen. loge("[updatePrimarySubListAndGetChangeType]: missing active primary " + "subId " + subId); } } return PRIMARY_SUB_MARKED_OPPT; } } private void sendDefaultSubConfirmedNotification(int defaultSubId) { Intent intent = new Intent(); intent.setAction(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); intent.setClassName("com.android.settings", "com.android.settings.sim.SimSelectNotification"); intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS); intent.putExtra(EXTRA_SUBSCRIPTION_ID, defaultSubId); mContext.sendBroadcast(intent); } private void sendSubChangeNotificationIfNeeded(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected) { if (mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) { log("esim bootstrap activation in progress, skip notification"); return; } @TelephonyManager.DefaultSubscriptionSelectType int simSelectDialogType = getSimSelectDialogType( change, dataSelected, voiceSelected, smsSelected); log("sendSubChangeNotificationIfNeeded: simSelectDialogType=" + simSelectDialogType); SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change); if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE || simCombinationParams.mWarningType != EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE) { log("[sendSubChangeNotificationIfNeeded] showing dialog type " + simSelectDialogType); log("[sendSubChangeNotificationIfNeeded] showing sim warning " + simCombinationParams.mWarningType); Intent intent = new Intent(); intent.setAction(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); intent.setClassName("com.android.settings", "com.android.settings.sim.SimSelectNotification"); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, simSelectDialogType); if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL) { intent.putExtra(EXTRA_SUBSCRIPTION_ID, mPrimarySubList.get(0)); } intent.putExtra(EXTRA_SIM_COMBINATION_WARNING_TYPE, simCombinationParams.mWarningType); if (simCombinationParams.mWarningType == EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA) { intent.putExtra(EXTRA_SIM_COMBINATION_NAMES, simCombinationParams.mSimNames); } mContext.sendBroadcast(intent); } } /** * Check that the primary subscription has changed due to the pSIM conversion. * @param change Whether to update the mPrimarySubList. * @param dataSelected Whether the default data subscription is updated * @param voiceSelected Whether the default voice subscription is updated * @param smsSelected Whether the default sms subscription is updated * @return {@code true} if the primary subscription has changed due to the pSIM conversion, * {@code false} otherwise. */ private boolean setDefaultForPsimConversionChanged(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected) { if (!mFeatureFlags.supportPsimToEsimConversion()) { log("pSIM to eSIM conversion is not supported"); return false; } if (mSubscriptionManagerService.isEsimBootStrapProvisioningActivated()) { log("esim bootstrap activation in progress, skip notification"); return false; } @TelephonyManager.DefaultSubscriptionSelectType int simSelectDialogType = getSimSelectDialogType( change, dataSelected, voiceSelected, smsSelected); SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change); log("[setDefaultForPsimConversionChanged]showing dialog type:" + simSelectDialogType); if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE || simCombinationParams.mWarningType != EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE) { log("[setDefaultForPsimConversionChanged]Converted pSIM:" + mConvertedPsimSubId); int subId = getConvertedPsimSubscriptionId(); if (subId != INVALID_SUBSCRIPTION_ID && subId != mConvertedPsimSubId) { // If a primary subscription is removed and only one is left active, ask user // for preferred sub selection if any default setting is not set. if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL) { // check if pSIM's preference is voice. if (mSubscriptionManagerService.getDefaultVoiceSubId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { mNeedSetDefaultVoice = true; } // check if pSIM's preference is sms. if (mSubscriptionManagerService.getDefaultSmsSubId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { mNeedSetDefaultSms = true; } // check if pSIM's preference is data. if (mSubscriptionManagerService.getDefaultDataSubId() == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { mNeedSetDefaultData = true; } log("select type all, set preferred SIM :" + mPrimarySubList.get(0)); mSubscriptionManagerService.setDefaultVoiceSubId(mPrimarySubList.get(0)); mSubscriptionManagerService.setDefaultSmsSubId(mPrimarySubList.get(0)); mSubscriptionManagerService.setDefaultDataSubId(mPrimarySubList.get(0)); return true; } else if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA) { // If another primary subscription is added or default data is not selected, ask // user to select default for data as it's most important. int newSubId = mPrimarySubList.get(0); log("need to set voice:" + mNeedSetDefaultVoice + ", sms:" + mNeedSetDefaultSms + ", data:" + mNeedSetDefaultData); // if the converted pSIM's preference is voice, set the default // setting for the changed primary subscription to voice. if (mNeedSetDefaultVoice) { log("set preferred call, subId:" + newSubId); mSubscriptionManagerService.setDefaultVoiceSubId(newSubId); mNeedSetDefaultVoice = false; } // if the converted pSIM's preference is sms, set the default // setting for the changed primary subscription to sms. if (mNeedSetDefaultSms) { log("set preferred sms, subId:" + newSubId); mSubscriptionManagerService.setDefaultSmsSubId(newSubId); mNeedSetDefaultSms = false; } // if the converted pSIM's preference is data, set the default // setting for the changed primary subscription to data. if (mNeedSetDefaultData) { log("set preferred data, subId:" + newSubId); mSubscriptionManagerService.setDefaultDataSubId(newSubId); mNeedSetDefaultData = false; } mConvertedPsimSubId = subId; log("set converted pSIM subId:" + mConvertedPsimSubId); return true; } } } return false; } private int getConvertedPsimSubscriptionId() { // Check to see if any subscription has been converted due to the pSIM conversion. // When the primary subscription is changed, if it is the same subscription as // the previously converted subscription, it is not due to the pSIM conversion. // So the dialog for preferred SIM/data pick should show. // TODO(b/332261793): On Android W, we need to add CONVERTING status. // The CONVERTING status allows us to determine if pSIM is in the process of converting, // so we don't need to check for information about previously converted subscriptions. int convertedSubId = INVALID_SUBSCRIPTION_ID; if (mFeatureFlags.supportPsimToEsimConversion()) { List infos = mSubscriptionManagerService.getAvailableSubscriptionInfoList( mContext.getOpPackageName(), mContext.getAttributionTag()); for (SubscriptionInfo info : infos) { if (!info.isEmbedded() && info.getTransferStatus() == TRANSFER_STATUS_CONVERTED) { convertedSubId = info.getSubscriptionId(); } } } log("getConvertedPsimSubscriptionId: convertedSubId=" + convertedSubId); return convertedSubId; } private int getSimSelectDialogType(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected) { int dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE; // Do not show preference selection dialog during SuW as there is fullscreen activity to // choose preference. if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) == 0) { return dialogType; } // If a primary subscription is removed and only one is left active, ask user // for preferred sub selection if any default setting is not set. // If another primary subscription is added or default data is not selected, ask // user to select default for data as it's most important. if (mPrimarySubList.size() == 1 && change == PRIMARY_SUB_REMOVED && (!dataSelected || !smsSelected || !voiceSelected)) { dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL; } else if (mPrimarySubList.size() > 1 && (isUserVisibleChange(change) || (change == PRIMARY_SUB_INITIALIZED && !dataSelected))) { // If change is SWAPPED_IN_GROUP or MARKED_OPPT, don't ask user again. dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA; } return dialogType; } private class SimCombinationWarningParams { @TelephonyManager.SimCombinationWarningType int mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE; String mSimNames; } private SimCombinationWarningParams getSimCombinationWarningParams(int change) { SimCombinationWarningParams params = new SimCombinationWarningParams(); // If it's single SIM active, no SIM combination warning is needed. if (mPrimarySubList.size() <= 1) return params; // If it's no primary SIM change or it's not user visible change // (initialized or swapped in a group), no SIM combination warning is needed. if (!isUserVisibleChange(change)) return params; List simNames = new ArrayList<>(); int cdmaPhoneCount = 0; for (int subId : mPrimarySubList) { Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId)); // If a dual CDMA SIM combination warning is needed. if (phone != null && phone.isCdmaSubscriptionAppPresent()) { cdmaPhoneCount++; String simName = null; SubscriptionInfoInternal subInfo = mSubscriptionManagerService .getSubscriptionInfoInternal(subId); if (subInfo != null) { simName = subInfo.getDisplayName(); } if (TextUtils.isEmpty(simName)) { // Fall back to carrier name. simName = phone.getCarrierName(); } simNames.add(simName); } } if (cdmaPhoneCount > 1) { params.mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA; params.mSimNames = String.join(" & ", simNames); } return params; } private boolean isUserVisibleChange(int change) { return (change == PRIMARY_SUB_ADDED || change == PRIMARY_SUB_REMOVED || change == PRIMARY_SUB_SWAPPED); } protected void disableDataForNonDefaultNonOpportunisticSubscriptions() { if (!isReadyToReevaluate()) return; int defaultDataSub = mSubscriptionManagerService.getDefaultDataSubId(); for (Phone phone : PhoneFactory.getPhones()) { SubscriptionInfoInternal subInfo = mSubscriptionManagerService .getSubscriptionInfoInternal(phone.getSubId()); boolean isOpportunistic = subInfo != null && subInfo.isOpportunistic(); if (phone.getSubId() != defaultDataSub && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) && !isOpportunistic && phone.isUserDataEnabled() && !areSubscriptionsInSameGroup(defaultDataSub, phone.getSubId())) { log("setting data to false on " + phone.getSubId()); phone.getDataSettingsManager().setDataEnabled( TelephonyManager.DATA_ENABLED_REASON_USER, false, mContext.getOpPackageName()); } } } private boolean areSubscriptionsInSameGroup(int subId1, int subId2) { if (!SubscriptionManager.isUsableSubscriptionId(subId1) || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false; if (subId1 == subId2) return true; SubscriptionInfoInternal subInfo1 = mSubscriptionManagerService.getSubscriptionInfoInternal(subId1); SubscriptionInfoInternal subInfo2 = mSubscriptionManagerService.getSubscriptionInfoInternal(subId2); return subInfo1 != null && subInfo2 != null && !TextUtils.isEmpty(subInfo1.getGroupUuid()) && subInfo1.getGroupUuid().equals(subInfo2.getGroupUuid()); } /** * Make sure MOBILE_DATA of subscriptions in the same group with the subId * are synced. */ protected void setUserDataEnabledForGroup(int subId, boolean enable) { log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); List infoList = null; SubscriptionInfoInternal subInfo = mSubscriptionManagerService .getSubscriptionInfoInternal(subId); if (subInfo != null && !subInfo.getGroupUuid().isEmpty()) { infoList = mSubscriptionManagerService.getSubscriptionsInGroup( ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(), mContext.getAttributionTag()); } if (infoList == null) return; for (SubscriptionInfo info : infoList) { int currentSubId = info.getSubscriptionId(); // TODO: simplify when setUserDataEnabled becomes singleton if (info.isActive()) { // For active subscription, call setUserDataEnabled through DataSettingsManager. Phone phone = PhoneFactory.getPhone(mSubscriptionManagerService .getPhoneId(currentSubId)); // If enable is true and it's not opportunistic subscription, we don't enable it, // as there can't be two if (phone != null) { phone.getDataSettingsManager().setDataEnabled( TelephonyManager.DATA_ENABLED_REASON_USER, enable, mContext.getOpPackageName()); } } else { // For inactive subscription, directly write into global settings. GlobalSettingsHelper.setBoolean( mContext, Settings.Global.MOBILE_DATA, currentSubId, enable); } } } /** * Make sure DATA_ROAMING of subscriptions in the same group with the subId * are synced. */ private void setRoamingDataEnabledForGroup(int subId, boolean enable) { SubscriptionInfoInternal subInfo = mSubscriptionManagerService .getSubscriptionInfoInternal(subId); if (subInfo == null || subInfo.getGroupUuid().isEmpty()) return; List infoList = SubscriptionManagerService.getInstance() .getSubscriptionsInGroup(ParcelUuid.fromString(subInfo.getGroupUuid()), mContext.getOpPackageName(), mContext.getAttributionTag()); if (infoList == null) return; for (SubscriptionInfo info : infoList) { // For inactive subscription, directly write into global settings. GlobalSettingsHelper.setBoolean( mContext, Settings.Global.DATA_ROAMING, info.getSubscriptionId(), enable); } } private interface UpdateDefaultAction { void update(int newValue); } // Returns whether the new default value is valid. private boolean updateDefaultValue(List primarySubList, int oldValue, UpdateDefaultAction action) { return updateDefaultValue(primarySubList, oldValue, action, true); } private boolean updateDefaultValue(List primarySubList, int oldValue, UpdateDefaultAction action, boolean allowInvalidSubId) { int newValue = INVALID_SUBSCRIPTION_ID; if (primarySubList.size() > 0) { for (int subId : primarySubList) { if (DBG) log("[updateDefaultValue] Record.id: " + subId); // 1) If the old subId is still active, or there's another active primary // subscription that is in the same group, that should become the new default // subscription. // 2) If the old subId is INVALID_SUBSCRIPTION_ID and allowInvalidSubId is false, // first active subscription is used for new default always. if (areSubscriptionsInSameGroup(subId, oldValue) || (!allowInvalidSubId && oldValue == INVALID_SUBSCRIPTION_ID)) { newValue = subId; log("[updateDefaultValue] updates to subId=" + newValue); break; } } } if (oldValue != newValue) { if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue); action.update(newValue); } return SubscriptionManager.isValidSubscriptionId(newValue); } // When a primary and its grouped opportunistic subscriptions were active, and the primary // subscription gets deactivated or removed, we need to automatically disable the grouped // opportunistic subscription, which will be marked isGroupDisabled as true by SubController. private void deactivateGroupedOpportunisticSubscriptionIfNeeded() { List opptSubList = mSubscriptionManagerService.getAllSubInfoList( mContext.getOpPackageName(), mContext.getAttributionTag()).stream() .filter(SubscriptionInfo::isOpportunistic) .collect(Collectors.toList()); if (ArrayUtils.isEmpty(opptSubList)) return; for (SubscriptionInfo info : opptSubList) { if (info.isGroupDisabled() && info.isActive()) { log("deactivateGroupedOpportunisticSubscriptionIfNeeded: " + "Deactivating grouped opportunistic subscription " + info.getSubscriptionId()); deactivateSubscription(info); } } } private void deactivateSubscription(SubscriptionInfo info) { // TODO: b/133379187 have a way to deactivate pSIM. if (info.isEmbedded()) { log("[deactivateSubscription] eSIM profile " + info.getSubscriptionId()); EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, info.getPortIndex(), PendingIntent.getService( mContext, 0, new Intent(), PendingIntent.FLAG_IMMUTABLE)); } } // Voice/Data/SMS preferences would be auto selected without any user // confirmation in following scenarios, // 1. When device powered-up with only one SIM Inserted or while two SIM cards // present if one SIM is removed(or turned OFF) the reaiming SIM would be // selected as preferred voice/data/sms SIM. // 2. When device powered-up with two SIM cards or if two SIM cards // present on device with new SIM insert(or SIM turn ON) the first inserted SIM // would be selected as preferred voice/data/sms SIM. private void updateUserPreferences(List primarySubList, boolean dataSelected, boolean voiceSelected, boolean smsSelected) { // In Single SIM case or if there are no activated subs available, no need to update. // EXIT. if ((primarySubList.isEmpty()) || (mSubscriptionManagerService .getActiveSubInfoCountMax() == 1)) { return; } if (!isRadioAvailableOnAllSubs()) { log("Radio is in Invalid state, Ignore Updating User Preference!!!"); return; } final int defaultDataSubId = mSubscriptionManagerService.getDefaultDataSubId(); if (DBG) { log("updateUserPreferences: dds = " + defaultDataSubId + " voice = " + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId()); } int autoDefaultSubId = primarySubList.get(0); if (hasMessaging() && (primarySubList.size() == 1) && !smsSelected) { mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId); } if (hasCalling() && (primarySubList.size() == 1) && !voiceSelected) { mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId); } int userPrefDataSubId = getUserPrefDataSubIdFromDB(); log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId + " next active subId " + autoDefaultSubId); if (hasData()) { // If earlier user selected DDS is now available, set that as DDS subId. if (primarySubList.contains(userPrefDataSubId) && SubscriptionManager.isValidSubscriptionId(userPrefDataSubId) && (defaultDataSubId != userPrefDataSubId)) { mSubscriptionManagerService.setDefaultDataSubId(userPrefDataSubId); } else if (!dataSelected) { mSubscriptionManagerService.setDefaultDataSubId(autoDefaultSubId); } } if (DBG) { log("updateUserPreferences: after dds = " + mSubscriptionManagerService.getDefaultDataSubId() + " voice = " + mSubscriptionManagerService.getDefaultVoiceSubId() + " sms = " + mSubscriptionManagerService.getDefaultSmsSubId()); } } private int getUserPrefDataSubIdFromDB() { return android.provider.Settings.Global.getInt(mContext.getContentResolver(), SETTING_USER_PREF_DATA_SUB, SubscriptionManager.INVALID_SUBSCRIPTION_ID); } private boolean isRadioAvailableOnAllSubs() { for (int phoneId = 0; phoneId < mActiveModemCount; phoneId++) { Phone phone = PhoneFactory.getPhone(phoneId); if (phone != null && (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE || phone.isShuttingDown())) { return false; } } return true; } private void registerDataSettingsControllerCallbackAsNeeded() { // Only register callbacks for new phone instance as PhoneFactory does not remove // existing phone instance. Phone[] phones = PhoneFactory.getPhones(); for (int i = mCallbacksCount; i < phones.length; i++) { phones[i].getDataSettingsManager().registerCallback( new DataSettingsControllerCallback(phones[i], this::post)); } mCallbacksCount = phones.length; } private void log(String msg) { Log.d(LOG_TAG, msg); } private void loge(String msg) { Log.e(LOG_TAG, msg); } }