1273 lines
58 KiB
Java
1273 lines
58 KiB
Java
/*
|
|
* 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<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;
|
|
/** 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<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(), 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<SubscriptionInfo> 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<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())
|
|
.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<SubscriptionInfo> 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<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 (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);
|
|
}
|
|
}
|