| /* |
| * 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.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.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.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; |
| private 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; |
| |
| // Keep a record of active primary (non-opportunistic) subscription list. |
| @NonNull private List<Integer> 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; |
| |
| 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 onDataEnabledChanged(boolean enabled, |
| @TelephonyManager.DataEnabledChangedReason int reason, String callingPackage) { |
| int subId = mPhone.getSubId(); |
| // notifyUserDataEnabled if the change is called from external and reason is |
| // DATA_ENABLED_REASON_USER |
| if (SubscriptionManager.isValidSubscriptionId(subId) |
| && reason == TelephonyManager.DATA_ENABLED_REASON_USER |
| && !getInstance().mContext.getOpPackageName().equals(callingPackage)) { |
| getInstance().notifyUserDataEnabled(mPhone.getSubId(), 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) { |
| synchronized (MultiSimSettingController.class) { |
| if (sInstance == null) { |
| sInstance = new MultiSimSettingController(context); |
| } else { |
| Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); |
| } |
| return sInstance; |
| } |
| } |
| |
| @VisibleForTesting |
| public MultiSimSettingController(Context context) { |
| mContext = context; |
| mSubscriptionManagerService = SubscriptionManagerService.getInstance(); |
| |
| // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. |
| final int phoneCount = ((TelephonyManager) mContext.getSystemService( |
| Context.TELEPHONY_SERVICE)).getSupportedModemCount(); |
| mCarrierConfigLoadedSubIds = new int[phoneCount]; |
| Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID); |
| |
| 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)); |
| } |
| |
| /** |
| * 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 (Phone phone : PhoneFactory.getPhones()) { |
| if (phone.mCi.getRadioState() == TelephonyManager.RADIO_POWER_UNAVAILABLE) { |
| if (DBG) log("Radio unavailable. 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; |
| 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; |
| } |
| |
| // b/153860050 Occasionally we receive carrier config change broadcast without subId |
| // being specified in it. So here we do additional check to make sur we don't miss the |
| // subId. |
| if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| subId = SubscriptionManager.getSubscriptionId(phoneId); |
| if (SubscriptionManager.isValidSubscriptionId(subId)) { |
| CarrierConfigManager cm = mContext.getSystemService(CarrierConfigManager.class); |
| if (cm != null && cm.getConfigForSubId(subId) != null) { |
| loge("onCarrierConfigChanged with invalid subId while subId " |
| + subId + " is active and its config is loaded"); |
| } |
| } |
| } |
| |
| 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) { |
| // 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(); |
| if (DBG) { |
| log("isReadyToReevaluate: subInfoInitialized=" + mSubInfoInitialized |
| + ", carrierConfigsLoaded=" + carrierConfigsLoaded); |
| } |
| return mSubInfoInitialized && carrierConfigsLoaded; |
| } |
| |
| 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<SubscriptionInfo> 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<SubscriptionInfo> activeSubList = mSubscriptionManagerService |
| .getActiveSubscriptionInfoList(mContext.getOpPackageName(), |
| mContext.getAttributionTag()); |
| 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<SubscriptionInfo> activeSubInfos = mSubscriptionManagerService |
| .getActiveSubscriptionInfoList(mContext.getOpPackageName(), |
| mContext.getAttributionTag()); |
| |
| 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 |
| || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) |
| .getActiveModemCount() == 1)) { |
| int subId = mPrimarySubList.get(0); |
| if (DBG) log("updateDefaultValues: to only primary sub " + subId); |
| mSubscriptionManagerService.setDefaultDataSubId(subId); |
| mSubscriptionManagerService.setDefaultVoiceSubId(subId); |
| mSubscriptionManagerService.setDefaultSmsSubId(subId); |
| sendDefaultSubConfirmedNotification(subId); |
| return; |
| } |
| |
| if (DBG) log("updateDefaultValues: records: " + mPrimarySubList); |
| |
| boolean dataSelected, voiceSelected, smsSelected; |
| |
| // Update default data subscription. |
| if (DBG) log("updateDefaultValues: Update default data subscription"); |
| dataSelected = updateDefaultValue(mPrimarySubList, |
| mSubscriptionManagerService.getDefaultDataSubId(), |
| mSubscriptionManagerService::setDefaultDataSubId); |
| |
| // Update default voice subscription. |
| if (DBG) log("updateDefaultValues: Update default voice subscription"); |
| voiceSelected = updateDefaultValue(mPrimarySubList, |
| mSubscriptionManagerService.getDefaultVoiceSubId(), |
| mSubscriptionManagerService::setDefaultVoiceSubId); |
| |
| // 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) { |
| sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected); |
| } else { |
| updateUserPreferences(mPrimarySubList, dataSelected, voiceSelected, smsSelected); |
| } |
| } |
| |
| @PrimarySubChangeType |
| private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList) { |
| // Update mPrimarySubList. Opportunistic subscriptions can't be default |
| // data / voice / sms subscription. |
| List<Integer> prevPrimarySubList = mPrimarySubList; |
| mPrimarySubList = activeSubList.stream().filter(info -> !info.isOpportunistic()) |
| .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) { |
| @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); |
| } |
| } |
| |
| 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<String> 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<SubscriptionInfo> 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<SubscriptionInfo> 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<Integer> primarySubList, int oldValue, |
| UpdateDefaultAction action) { |
| return updateDefaultValue(primarySubList, oldValue, action, true); |
| } |
| |
| private boolean updateDefaultValue(List<Integer> 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<SubscriptionInfo> 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<Integer> 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 ((primarySubList.size() == 1) && !smsSelected) { |
| mSubscriptionManagerService.setDefaultSmsSubId(autoDefaultSubId); |
| } |
| |
| if ((primarySubList.size() == 1) && !voiceSelected) { |
| mSubscriptionManagerService.setDefaultVoiceSubId(autoDefaultSubId); |
| } |
| |
| int userPrefDataSubId = getUserPrefDataSubIdFromDB(); |
| |
| log("User pref subId = " + userPrefDataSubId + " current dds " + defaultDataSubId |
| + " next active subId " + autoDefaultSubId); |
| |
| // 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 (Phone phone : PhoneFactory.getPhones()) { |
| if ((phone.mCi != 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); |
| } |
| } |