Loading src/java/com/android/internal/telephony/GlobalSettingsHelper.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; /** * Helper class that reads and writes Global.Setting values for Telephony. It will: * For Single SIM case, read from or write to the singleton setting value. * For Multi-SIM case, read from or write to the per subscription value. */ public class GlobalSettingsHelper { /** * Helper function to get integer value. */ public static int getInt(Context context, String settingName, int subId, int defaultValue) { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName, defaultValue); } /** * Helper function to get boolean value. */ public static boolean getBoolean(Context context, String settingName, int subId, boolean defaultValue) { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName, defaultValue ? 1 : 0) == 1; } /** * Helper function to get boolean value or throws SettingNotFoundException if not set. */ public static boolean getBoolean(Context context, String settingName, int subId) throws SettingNotFoundException { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName) == 1; } /** * Helper function to set integer value. * Returns whether the value is changed or initially set. */ public static boolean setInt(Context context, String settingName, int subId, int value) { settingName = getSettingName(context, settingName, subId); boolean needChange; try { needChange = Settings.Global.getInt(context.getContentResolver(), settingName) != value; } catch (SettingNotFoundException exception) { needChange = true; } if (needChange) Settings.Global.putInt(context.getContentResolver(), settingName, value); return needChange; } /** * Helper function to set boolean value. * Returns whether the value is changed or initially set. */ public static boolean setBoolean(Context context, String settingName, int subId, boolean value) { return setInt(context, settingName, subId, value ? 1 : 0); } private static String getSettingName(Context context, String settingName, int subId) { // For single SIM phones, this is a per phone property. Or if it's invalid subId, we // read default setting. if (TelephonyManager.from(context).getSimCount() > 1 && SubscriptionManager.isValidSubscriptionId(subId)) { return settingName + subId; } else { return settingName; } } } src/java/com/android/internal/telephony/MultiSimSettingController.java 0 → 100644 +338 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.List; 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 { static final String LOG_TAG = "MultiSimSettingController"; static final boolean DBG = true; private final Context mContext; private final Phone[] mPhones; private final SubscriptionController mSubController; private boolean mIsAllSubscriptionsLoaded; /** The singleton instance. */ private static MultiSimSettingController sInstance = null; /** * Return the singleton or create one if not existed. */ public static MultiSimSettingController getInstance() { synchronized (SubscriptionController.class) { if (sInstance == null) { sInstance = new MultiSimSettingController(); } return sInstance; } } @VisibleForTesting public MultiSimSettingController() { mContext = PhoneFactory.getDefaultPhone().getContext(); mPhones = PhoneFactory.getPhones(); mSubController = SubscriptionController.getInstance(); } /** * 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. */ public synchronized void onUserDataEnabled(int subId, boolean enable) { if (DBG) log("onUserDataEnabled"); // Make sure MOBILE_DATA of subscriptions in same group are synced. setUserDataEnabledForGroup(subId, enable); // If user is enabling a non-default non-opportunistic subscription, make it default. if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId) && enable) { mSubController.setDefaultDataSubId(subId); } } /** * Make sure DATA_ROAMING of subscriptions in same group are synced. */ public synchronized void onRoamingDataEnabled(int subId, boolean enable) { if (DBG) log("onRoamingDataEnabled"); setRoamingDataEnabledForGroup(subId, enable); // Also inform SubscriptionController as it keeps another copy of user setting. mSubController.setDataRoaming(enable ? 1 : 0, subId); } /** * Mark mIsAllSubscriptionsLoaded and update defaults and mobile data enabling. */ public synchronized void onAllSubscriptionsLoaded() { if (DBG) log("onAllSubscriptionsLoaded"); mIsAllSubscriptionsLoaded = true; updateDefaults(); disableDataForNonDefaultNonOpportunisticSubscriptions(); } /** * Make sure default values are cleaned or updated. * * Make sure non-default non-opportunistic subscriptions has data off. */ public synchronized void onSubscriptionsChanged() { if (DBG) log("onSubscriptionsChanged"); if (!mIsAllSubscriptionsLoaded) return; updateDefaults(); disableDataForNonDefaultNonOpportunisticSubscriptions(); } /** * Make sure non-default non-opportunistic subscriptions has data disabled. */ public synchronized 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. */ public synchronized void onSubscriptionGroupCreated(int[] subGroup) { if (DBG) log("onSubscriptionGroupCreated"); if (subGroup == null || subGroup.length == 0) return; // Get a referece subscription from which all subscriptions in the group will copy settings. // TODO: the reference sub should be passed in from external caller. int refSubId = subGroup[0]; for (int subId : subGroup) { if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) { refSubId = subId; break; } } if (DBG) log("refSubId is " + refSubId); try { boolean enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.MOBILE_DATA, refSubId); onUserDataEnabled(refSubId, enable); } catch (SettingNotFoundException exception) { // Do nothing if it's never set. } try { boolean enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.DATA_ROAMING, refSubId); onRoamingDataEnabled(refSubId, enable); } catch (SettingNotFoundException exception) { // Do nothing if it's never set. } } /** * 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. */ public synchronized void updateDefaults() { if (DBG) log("updateDefaults"); List<SubscriptionInfo> subInfos = mSubController .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // If subscription controller is not ready, do nothing. if (subInfos == null) return; // Opportunistic subscriptions can't be default data / voice / sms subscription. subInfos = subInfos.stream() .filter(info -> !info.isOpportunistic()) .collect(Collectors.toList()); if (DBG) log("[updateDefaultValues] records: " + subInfos); // TODO: b/121394765 update logic once confirmed the behaviors. // Update default data subscription. if (DBG) log("[updateDefaultValues] Update default data subscription"); updateDefaultValue(subInfos, mSubController.getDefaultDataSubId(), true, true, (newValue -> mSubController.setDefaultDataSubId(newValue))); // Update default voice subscription. if (DBG) log("[updateDefaultValues] Update default voice subscription"); updateDefaultValue(subInfos, mSubController.getDefaultVoiceSubId(), true, false, (newValue -> mSubController.setDefaultVoiceSubId(newValue))); // Update default sms subscription. if (DBG) log("[updateDefaultValues] Update default sms subscription"); updateDefaultValue(subInfos, mSubController.getDefaultSmsSubId(), true, false, (newValue -> mSubController.setDefaultSmsSubId(newValue))); } private void disableDataForNonDefaultNonOpportunisticSubscriptions() { int defaultDataSub = mSubController.getDefaultDataSubId(); for (Phone phone : mPhones) { if (phone.getSubId() != defaultDataSub && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) && !mSubController.isOpportunistic(phone.getSubId()) && phone.isUserDataEnabled()) { log("setting data to false on " + phone.getSubId()); phone.getDataEnabledSettings().setUserDataEnabled(false); } } } /** * Make sure MOBILE_DATA of subscriptions in the same group with the subId * are synced. */ private synchronized void setUserDataEnabledForGroup(int subId, boolean enable) { log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( subId, mContext.getOpPackageName()); if (infoList == null) return; for (SubscriptionInfo info : infoList) { int currentSubId = info.getSubscriptionId(); if (currentSubId == subId) continue; // TODO: simplify when setUserDataEnabled becomes singleton if (mSubController.isActiveSubId(currentSubId)) { // If we end up enabling two active primary subscriptions, don't enable the // non-default data sub. This will only happen if two primary subscriptions // in a group are both active. This is not a valid use-case now, but we are // handling it just in case. if (enable && !mSubController.isOpportunistic(currentSubId) && currentSubId != mSubController.getDefaultSubId()) { loge("Can not enable two active primary subscriptions."); continue; } // For active subscription, call setUserDataEnabled through DataEnabledSettings. Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId)); // If enable is true and it's not opportunistic subscription, we don't enable it, // as there can't e two if (phone != null) { phone.getDataEnabledSettings().setUserDataEnabled(enable); } } 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 synchronized void setRoamingDataEnabledForGroup(int subId, boolean enable) { SubscriptionController subController = SubscriptionController.getInstance(); List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup( subId, mContext.getOpPackageName()); 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 String getGroupUuid(int subId) { String groupUuid; List<SubscriptionInfo> subInfo = mSubController.getSubInfo( SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); if (subInfo == null || subInfo.size() == 0) { groupUuid = null; } else { groupUuid = subInfo.get(0).getGroupUuid(); } return groupUuid; } private interface UpdateDefaultAction { void update(int newValue); } private void updateDefaultValue(List<SubscriptionInfo> subInfos, int oldValue, boolean inheritWithinGroup, boolean setToOnlyCandidate, UpdateDefaultAction action) { int newValue = SubscriptionManager.INVALID_SUBSCRIPTION_ID; if (subInfos.size() == 1 && setToOnlyCandidate) { newValue = subInfos.get(0).getSubscriptionId(); } else if (subInfos.size() > 0) { // Get groupUuid of old String groupUuid = getGroupUuid(oldValue); for (SubscriptionInfo info : subInfos) { int id = info.getSubscriptionId(); if (DBG) log("[updateDefaultValue] Record.id: " + id); // 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. if (id == oldValue || (groupUuid != null && groupUuid.equals(info.getGroupUuid()) && inheritWithinGroup)) { log("[updateDefaultValue] updates to subId=" + id); newValue = id; break; } } } if (oldValue != newValue) { if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue); action.update(newValue); } } private void log(String msg) { Log.d(LOG_TAG, msg); } private void loge(String msg) { Log.e(LOG_TAG, msg); } } src/java/com/android/internal/telephony/SubscriptionController.java +29 −55 Original line number Diff line number Diff line Loading @@ -306,6 +306,7 @@ public class SubscriptionController extends ISub.Stub { // FIXME: Remove if listener technique accepted. broadcastSimInfoContentChanged(); MultiSimSettingController.getInstance().onSubscriptionsChanged(); TelephonyMetrics metrics = TelephonyMetrics.getInstance(); metrics.updateActiveSubscriptionInfoList( Collections.unmodifiableList(mCacheActiveSubInfoList)); Loading Loading @@ -405,7 +406,7 @@ public class SubscriptionController extends ISub.Stub { * @return Array list of queried result from database */ @UnsupportedAppUsage private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) { public List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) { if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey); String[] selectionArgs = null; if (queryKey != null) { Loading Loading @@ -1630,8 +1631,7 @@ public class SubscriptionController extends ISub.Stub { value.put(SubscriptionManager.DATA_ROAMING, roaming); if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set"); int result = mContext.getContentResolver().update( SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); int result = databaseUpdateHelper(value, subId, true); // Refresh the Cache of Active Subscription Info List refreshCachedActiveSubscriptionInfoList(); Loading @@ -1644,6 +1644,24 @@ public class SubscriptionController extends ISub.Stub { } } private int databaseUpdateHelper(ContentValues value, int subId, boolean updateEntireGroup) { List<SubscriptionInfo> infoList = getSubscriptionsInGroup(subId, mContext.getOpPackageName()); if (!updateEntireGroup || infoList == null || infoList.size() == 0) { // Only update specified subscriptions. return mContext.getContentResolver().update( SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); } else { // Update all subscriptions in the same group. int[] subIdList = new int[infoList.size()]; for (int i = 0; i < infoList.size(); i++) { subIdList[i] = infoList.get(i).getSubscriptionId(); } return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); } } /** * Set carrier id by subId * @param carrierId the subscription carrier id. Loading Loading @@ -2073,7 +2091,7 @@ public class SubscriptionController extends ISub.Stub { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId); updateDataEnabledSettings(); MultiSimSettingController.getInstance().onDefaultDataSettingChanged(); broadcastDefaultDataSubIdChanged(subId); } Loading Loading @@ -2156,64 +2174,18 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); try { final List<SubscriptionInfo> records = getActiveSubscriptionInfoList( mContext.getOpPackageName()); if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records); if (shouldDefaultBeCleared(records, getDefaultDataSubId())) { if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id"); setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) { if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id"); setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) { if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id"); setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } MultiSimSettingController.getInstance().updateDefaults(); } finally { Binder.restoreCallingIdentity(identity); } } private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) { if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId); if (records == null) { if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId); return true; } if (!SubscriptionManager.isValidSubscriptionId(subId)) { // If the subId parameter is not valid its already cleared so return false. if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId); return false; } for (SubscriptionInfo record : records) { int id = record.getSubscriptionId(); if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id); if (id == subId) { logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId); return false; } } if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId); return true; } /** * Make sure in multi SIM scenarios, user data is enabled or disabled correctly. * Whether a subscription is opportunistic or not. */ public void updateDataEnabledSettings() { Phone[] phones = PhoneFactory.getPhones(); if (phones == null || phones.length < 2) return; for (Phone phone : phones) { if (isActiveSubId(phone.getSubId())) { // Only enable it if it was enabled and it's the default data subscription. // Otherwise it should be disabled. phone.getDataEnabledSettings().setUserDataEnabled( phone.isUserDataEnabled() && phone.getSubId() == getDefaultDataSubId()); } } public boolean isOpportunistic(int subId) { SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName()); return (info != null) && info.isOpportunistic(); } // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true Loading Loading @@ -2826,6 +2798,8 @@ public class SubscriptionController extends ISub.Stub { notifySubscriptionInfoChanged(); MultiSimSettingController.getInstance().onSubscriptionGroupCreated(subIdList); return groupUUID; } finally { Binder.restoreCallingIdentity(identity); Loading src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java +1 −2 Original line number Diff line number Diff line Loading @@ -610,8 +610,7 @@ public class SubscriptionInfoUpdater extends Handler { .forEach(cardId -> updateEmbeddedSubscriptions(cardId)); } // update default subId SubscriptionController.getInstance().clearDefaultsForInactiveSubIds(); SubscriptionController.getInstance().updateDataEnabledSettings(); MultiSimSettingController.getInstance().onAllSubscriptionsLoaded(); } SubscriptionController.getInstance().notifySubscriptionInfoChanged(); Loading src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java +24 −60 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/java/com/android/internal/telephony/GlobalSettingsHelper.java 0 → 100644 +96 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; /** * Helper class that reads and writes Global.Setting values for Telephony. It will: * For Single SIM case, read from or write to the singleton setting value. * For Multi-SIM case, read from or write to the per subscription value. */ public class GlobalSettingsHelper { /** * Helper function to get integer value. */ public static int getInt(Context context, String settingName, int subId, int defaultValue) { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName, defaultValue); } /** * Helper function to get boolean value. */ public static boolean getBoolean(Context context, String settingName, int subId, boolean defaultValue) { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName, defaultValue ? 1 : 0) == 1; } /** * Helper function to get boolean value or throws SettingNotFoundException if not set. */ public static boolean getBoolean(Context context, String settingName, int subId) throws SettingNotFoundException { settingName = getSettingName(context, settingName, subId); return Settings.Global.getInt(context.getContentResolver(), settingName) == 1; } /** * Helper function to set integer value. * Returns whether the value is changed or initially set. */ public static boolean setInt(Context context, String settingName, int subId, int value) { settingName = getSettingName(context, settingName, subId); boolean needChange; try { needChange = Settings.Global.getInt(context.getContentResolver(), settingName) != value; } catch (SettingNotFoundException exception) { needChange = true; } if (needChange) Settings.Global.putInt(context.getContentResolver(), settingName, value); return needChange; } /** * Helper function to set boolean value. * Returns whether the value is changed or initially set. */ public static boolean setBoolean(Context context, String settingName, int subId, boolean value) { return setInt(context, settingName, subId, value ? 1 : 0); } private static String getSettingName(Context context, String settingName, int subId) { // For single SIM phones, this is a per phone property. Or if it's invalid subId, we // read default setting. if (TelephonyManager.from(context).getSimCount() > 1 && SubscriptionManager.isValidSubscriptionId(subId)) { return settingName + subId; } else { return settingName; } } }
src/java/com/android/internal/telephony/MultiSimSettingController.java 0 → 100644 +338 −0 Original line number Diff line number Diff line /* * 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 android.content.Context; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.util.List; 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 { static final String LOG_TAG = "MultiSimSettingController"; static final boolean DBG = true; private final Context mContext; private final Phone[] mPhones; private final SubscriptionController mSubController; private boolean mIsAllSubscriptionsLoaded; /** The singleton instance. */ private static MultiSimSettingController sInstance = null; /** * Return the singleton or create one if not existed. */ public static MultiSimSettingController getInstance() { synchronized (SubscriptionController.class) { if (sInstance == null) { sInstance = new MultiSimSettingController(); } return sInstance; } } @VisibleForTesting public MultiSimSettingController() { mContext = PhoneFactory.getDefaultPhone().getContext(); mPhones = PhoneFactory.getPhones(); mSubController = SubscriptionController.getInstance(); } /** * 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. */ public synchronized void onUserDataEnabled(int subId, boolean enable) { if (DBG) log("onUserDataEnabled"); // Make sure MOBILE_DATA of subscriptions in same group are synced. setUserDataEnabledForGroup(subId, enable); // If user is enabling a non-default non-opportunistic subscription, make it default. if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId) && enable) { mSubController.setDefaultDataSubId(subId); } } /** * Make sure DATA_ROAMING of subscriptions in same group are synced. */ public synchronized void onRoamingDataEnabled(int subId, boolean enable) { if (DBG) log("onRoamingDataEnabled"); setRoamingDataEnabledForGroup(subId, enable); // Also inform SubscriptionController as it keeps another copy of user setting. mSubController.setDataRoaming(enable ? 1 : 0, subId); } /** * Mark mIsAllSubscriptionsLoaded and update defaults and mobile data enabling. */ public synchronized void onAllSubscriptionsLoaded() { if (DBG) log("onAllSubscriptionsLoaded"); mIsAllSubscriptionsLoaded = true; updateDefaults(); disableDataForNonDefaultNonOpportunisticSubscriptions(); } /** * Make sure default values are cleaned or updated. * * Make sure non-default non-opportunistic subscriptions has data off. */ public synchronized void onSubscriptionsChanged() { if (DBG) log("onSubscriptionsChanged"); if (!mIsAllSubscriptionsLoaded) return; updateDefaults(); disableDataForNonDefaultNonOpportunisticSubscriptions(); } /** * Make sure non-default non-opportunistic subscriptions has data disabled. */ public synchronized 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. */ public synchronized void onSubscriptionGroupCreated(int[] subGroup) { if (DBG) log("onSubscriptionGroupCreated"); if (subGroup == null || subGroup.length == 0) return; // Get a referece subscription from which all subscriptions in the group will copy settings. // TODO: the reference sub should be passed in from external caller. int refSubId = subGroup[0]; for (int subId : subGroup) { if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) { refSubId = subId; break; } } if (DBG) log("refSubId is " + refSubId); try { boolean enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.MOBILE_DATA, refSubId); onUserDataEnabled(refSubId, enable); } catch (SettingNotFoundException exception) { // Do nothing if it's never set. } try { boolean enable = GlobalSettingsHelper.getBoolean( mContext, Settings.Global.DATA_ROAMING, refSubId); onRoamingDataEnabled(refSubId, enable); } catch (SettingNotFoundException exception) { // Do nothing if it's never set. } } /** * 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. */ public synchronized void updateDefaults() { if (DBG) log("updateDefaults"); List<SubscriptionInfo> subInfos = mSubController .getActiveSubscriptionInfoList(mContext.getOpPackageName()); // If subscription controller is not ready, do nothing. if (subInfos == null) return; // Opportunistic subscriptions can't be default data / voice / sms subscription. subInfos = subInfos.stream() .filter(info -> !info.isOpportunistic()) .collect(Collectors.toList()); if (DBG) log("[updateDefaultValues] records: " + subInfos); // TODO: b/121394765 update logic once confirmed the behaviors. // Update default data subscription. if (DBG) log("[updateDefaultValues] Update default data subscription"); updateDefaultValue(subInfos, mSubController.getDefaultDataSubId(), true, true, (newValue -> mSubController.setDefaultDataSubId(newValue))); // Update default voice subscription. if (DBG) log("[updateDefaultValues] Update default voice subscription"); updateDefaultValue(subInfos, mSubController.getDefaultVoiceSubId(), true, false, (newValue -> mSubController.setDefaultVoiceSubId(newValue))); // Update default sms subscription. if (DBG) log("[updateDefaultValues] Update default sms subscription"); updateDefaultValue(subInfos, mSubController.getDefaultSmsSubId(), true, false, (newValue -> mSubController.setDefaultSmsSubId(newValue))); } private void disableDataForNonDefaultNonOpportunisticSubscriptions() { int defaultDataSub = mSubController.getDefaultDataSubId(); for (Phone phone : mPhones) { if (phone.getSubId() != defaultDataSub && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) && !mSubController.isOpportunistic(phone.getSubId()) && phone.isUserDataEnabled()) { log("setting data to false on " + phone.getSubId()); phone.getDataEnabledSettings().setUserDataEnabled(false); } } } /** * Make sure MOBILE_DATA of subscriptions in the same group with the subId * are synced. */ private synchronized void setUserDataEnabledForGroup(int subId, boolean enable) { log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( subId, mContext.getOpPackageName()); if (infoList == null) return; for (SubscriptionInfo info : infoList) { int currentSubId = info.getSubscriptionId(); if (currentSubId == subId) continue; // TODO: simplify when setUserDataEnabled becomes singleton if (mSubController.isActiveSubId(currentSubId)) { // If we end up enabling two active primary subscriptions, don't enable the // non-default data sub. This will only happen if two primary subscriptions // in a group are both active. This is not a valid use-case now, but we are // handling it just in case. if (enable && !mSubController.isOpportunistic(currentSubId) && currentSubId != mSubController.getDefaultSubId()) { loge("Can not enable two active primary subscriptions."); continue; } // For active subscription, call setUserDataEnabled through DataEnabledSettings. Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId)); // If enable is true and it's not opportunistic subscription, we don't enable it, // as there can't e two if (phone != null) { phone.getDataEnabledSettings().setUserDataEnabled(enable); } } 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 synchronized void setRoamingDataEnabledForGroup(int subId, boolean enable) { SubscriptionController subController = SubscriptionController.getInstance(); List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup( subId, mContext.getOpPackageName()); 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 String getGroupUuid(int subId) { String groupUuid; List<SubscriptionInfo> subInfo = mSubController.getSubInfo( SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null); if (subInfo == null || subInfo.size() == 0) { groupUuid = null; } else { groupUuid = subInfo.get(0).getGroupUuid(); } return groupUuid; } private interface UpdateDefaultAction { void update(int newValue); } private void updateDefaultValue(List<SubscriptionInfo> subInfos, int oldValue, boolean inheritWithinGroup, boolean setToOnlyCandidate, UpdateDefaultAction action) { int newValue = SubscriptionManager.INVALID_SUBSCRIPTION_ID; if (subInfos.size() == 1 && setToOnlyCandidate) { newValue = subInfos.get(0).getSubscriptionId(); } else if (subInfos.size() > 0) { // Get groupUuid of old String groupUuid = getGroupUuid(oldValue); for (SubscriptionInfo info : subInfos) { int id = info.getSubscriptionId(); if (DBG) log("[updateDefaultValue] Record.id: " + id); // 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. if (id == oldValue || (groupUuid != null && groupUuid.equals(info.getGroupUuid()) && inheritWithinGroup)) { log("[updateDefaultValue] updates to subId=" + id); newValue = id; break; } } } if (oldValue != newValue) { if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue); action.update(newValue); } } private void log(String msg) { Log.d(LOG_TAG, msg); } private void loge(String msg) { Log.e(LOG_TAG, msg); } }
src/java/com/android/internal/telephony/SubscriptionController.java +29 −55 Original line number Diff line number Diff line Loading @@ -306,6 +306,7 @@ public class SubscriptionController extends ISub.Stub { // FIXME: Remove if listener technique accepted. broadcastSimInfoContentChanged(); MultiSimSettingController.getInstance().onSubscriptionsChanged(); TelephonyMetrics metrics = TelephonyMetrics.getInstance(); metrics.updateActiveSubscriptionInfoList( Collections.unmodifiableList(mCacheActiveSubInfoList)); Loading Loading @@ -405,7 +406,7 @@ public class SubscriptionController extends ISub.Stub { * @return Array list of queried result from database */ @UnsupportedAppUsage private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) { public List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) { if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey); String[] selectionArgs = null; if (queryKey != null) { Loading Loading @@ -1630,8 +1631,7 @@ public class SubscriptionController extends ISub.Stub { value.put(SubscriptionManager.DATA_ROAMING, roaming); if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set"); int result = mContext.getContentResolver().update( SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); int result = databaseUpdateHelper(value, subId, true); // Refresh the Cache of Active Subscription Info List refreshCachedActiveSubscriptionInfoList(); Loading @@ -1644,6 +1644,24 @@ public class SubscriptionController extends ISub.Stub { } } private int databaseUpdateHelper(ContentValues value, int subId, boolean updateEntireGroup) { List<SubscriptionInfo> infoList = getSubscriptionsInGroup(subId, mContext.getOpPackageName()); if (!updateEntireGroup || infoList == null || infoList.size() == 0) { // Only update specified subscriptions. return mContext.getContentResolver().update( SubscriptionManager.getUriForSubscriptionId(subId), value, null, null); } else { // Update all subscriptions in the same group. int[] subIdList = new int[infoList.size()]; for (int i = 0; i < infoList.size(); i++) { subIdList[i] = infoList.get(i).getSubscriptionId(); } return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, getSelectionForSubIdList(subIdList), null); } } /** * Set carrier id by subId * @param carrierId the subscription carrier id. Loading Loading @@ -2073,7 +2091,7 @@ public class SubscriptionController extends ISub.Stub { Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId); updateDataEnabledSettings(); MultiSimSettingController.getInstance().onDefaultDataSettingChanged(); broadcastDefaultDataSubIdChanged(subId); } Loading Loading @@ -2156,64 +2174,18 @@ public class SubscriptionController extends ISub.Stub { // Now that all security checks passes, perform the operation as ourselves. final long identity = Binder.clearCallingIdentity(); try { final List<SubscriptionInfo> records = getActiveSubscriptionInfoList( mContext.getOpPackageName()); if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records); if (shouldDefaultBeCleared(records, getDefaultDataSubId())) { if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id"); setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) { if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id"); setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) { if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id"); setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); } MultiSimSettingController.getInstance().updateDefaults(); } finally { Binder.restoreCallingIdentity(identity); } } private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) { if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId); if (records == null) { if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId); return true; } if (!SubscriptionManager.isValidSubscriptionId(subId)) { // If the subId parameter is not valid its already cleared so return false. if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId); return false; } for (SubscriptionInfo record : records) { int id = record.getSubscriptionId(); if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id); if (id == subId) { logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId); return false; } } if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId); return true; } /** * Make sure in multi SIM scenarios, user data is enabled or disabled correctly. * Whether a subscription is opportunistic or not. */ public void updateDataEnabledSettings() { Phone[] phones = PhoneFactory.getPhones(); if (phones == null || phones.length < 2) return; for (Phone phone : phones) { if (isActiveSubId(phone.getSubId())) { // Only enable it if it was enabled and it's the default data subscription. // Otherwise it should be disabled. phone.getDataEnabledSettings().setUserDataEnabled( phone.isUserDataEnabled() && phone.getSubId() == getDefaultDataSubId()); } } public boolean isOpportunistic(int subId) { SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName()); return (info != null) && info.isOpportunistic(); } // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true Loading Loading @@ -2826,6 +2798,8 @@ public class SubscriptionController extends ISub.Stub { notifySubscriptionInfoChanged(); MultiSimSettingController.getInstance().onSubscriptionGroupCreated(subIdList); return groupUUID; } finally { Binder.restoreCallingIdentity(identity); Loading
src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java +1 −2 Original line number Diff line number Diff line Loading @@ -610,8 +610,7 @@ public class SubscriptionInfoUpdater extends Handler { .forEach(cardId -> updateEmbeddedSubscriptions(cardId)); } // update default subId SubscriptionController.getInstance().clearDefaultsForInactiveSubIds(); SubscriptionController.getInstance().updateDataEnabledSettings(); MultiSimSettingController.getInstance().onAllSubscriptionsLoaded(); } SubscriptionController.getInstance().notifySubscriptionInfoChanged(); Loading
src/java/com/android/internal/telephony/dataconnection/DataEnabledSettings.java +24 −60 File changed.Preview size limit exceeded, changes collapsed. Show changes