1 /* 2 * Copyright 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony; 18 19 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; 20 import static android.telephony.TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED; 21 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE; 22 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL; 23 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA; 24 import static android.telephony.TelephonyManager.EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE; 25 import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_NAMES; 26 import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE; 27 import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA; 28 import static android.telephony.TelephonyManager.EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE; 29 import static android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID; 30 31 import android.annotation.IntDef; 32 import android.annotation.NonNull; 33 import android.app.PendingIntent; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.os.AsyncResult; 39 import android.os.Handler; 40 import android.os.Message; 41 import android.os.ParcelUuid; 42 import android.provider.Settings; 43 import android.provider.Settings.SettingNotFoundException; 44 import android.telephony.CarrierConfigManager; 45 import android.telephony.SubscriptionInfo; 46 import android.telephony.SubscriptionManager; 47 import android.telephony.TelephonyManager; 48 import android.telephony.euicc.EuiccManager; 49 import android.text.TextUtils; 50 import android.util.Log; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.telephony.util.ArrayUtils; 54 55 import java.lang.annotation.Retention; 56 import java.lang.annotation.RetentionPolicy; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 import java.util.stream.Collectors; 61 62 /** 63 * This class will make sure below setting rules are coordinated across different subscriptions 64 * and phones in multi-SIM case: 65 * 66 * 1) Grouped subscriptions will have same settings for MOBILE_DATA and DATA_ROAMING. 67 * 2) Default settings updated automatically. It may be cleared or inherited within group. 68 * If default subscription A switches to profile B which is in the same group, B will 69 * become the new default. 70 * 3) For primary subscriptions, only default data subscription will have MOBILE_DATA on. 71 */ 72 public class MultiSimSettingController extends Handler { 73 private static final String LOG_TAG = "MultiSimSettingController"; 74 private static final boolean DBG = true; 75 private static final int EVENT_USER_DATA_ENABLED = 1; 76 private static final int EVENT_ROAMING_DATA_ENABLED = 2; 77 private static final int EVENT_ALL_SUBSCRIPTIONS_LOADED = 3; 78 private static final int EVENT_SUBSCRIPTION_INFO_CHANGED = 4; 79 private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED = 5; 80 private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6; 81 private static final int EVENT_CARRIER_CONFIG_CHANGED = 7; 82 private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 8; 83 84 @Retention(RetentionPolicy.SOURCE) 85 @IntDef(prefix = {"PRIMARY_SUB_"}, 86 value = { 87 PRIMARY_SUB_NO_CHANGE, 88 PRIMARY_SUB_ADDED, 89 PRIMARY_SUB_REMOVED, 90 PRIMARY_SUB_SWAPPED, 91 PRIMARY_SUB_SWAPPED_IN_GROUP, 92 PRIMARY_SUB_MARKED_OPPT, 93 PRIMARY_SUB_INITIALIZED 94 }) 95 private @interface PrimarySubChangeType {} 96 97 // Primary subscription not change. 98 private static final int PRIMARY_SUB_NO_CHANGE = 0; 99 // One or more primary subscriptions are activated. 100 private static final int PRIMARY_SUB_ADDED = 1; 101 // One or more primary subscriptions are deactivated. 102 private static final int PRIMARY_SUB_REMOVED = 2; 103 // One or more primary subscriptions are swapped. 104 private static final int PRIMARY_SUB_SWAPPED = 3; 105 // One or more primary subscriptions are swapped but within same sub group. 106 private static final int PRIMARY_SUB_SWAPPED_IN_GROUP = 4; 107 // One or more primary subscriptions are marked as opportunistic. 108 private static final int PRIMARY_SUB_MARKED_OPPT = 5; 109 // Subscription information is initially loaded. 110 private static final int PRIMARY_SUB_INITIALIZED = 6; 111 112 protected final Context mContext; 113 protected final SubscriptionController mSubController; 114 // Keep a record of active primary (non-opportunistic) subscription list. 115 @NonNull private List<Integer> mPrimarySubList = new ArrayList<>(); 116 117 /** The singleton instance. */ 118 protected static MultiSimSettingController sInstance = null; 119 120 // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping 121 // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there 122 // might be a race condition that we receive EVENT_SUBSCRIPTION_INFO_CHANGED first, then 123 // EVENT_ALL_SUBSCRIPTIONS_LOADED. And calling SubscriptionInfoUpdater#isSubInfoInitialized 124 // will make us handle EVENT_SUBSCRIPTION_INFO_CHANGED unexpectedly and causing us to believe 125 // the SIMs are newly inserted instead of being initialized. 126 private boolean mSubInfoInitialized = false; 127 128 // mInitialHandling is to make sure we don't always ask user to re-select data SIM after reboot. 129 // After boot-up when things are firstly initialized (mSubInfoInitialized is changed to true 130 // and carrier configs are all loaded), we do a reEvaluateAll(). In the first reEvaluateAll(), 131 // mInitialHandling will be true and we won't pop up SIM select dialog. 132 private boolean mInitialHandling = true; 133 134 // Keep a record of which subIds have carrier config loaded. Length of the array is phone count. 135 // The index is phoneId, and value is subId. For example: 136 // If carrier config of subId 2 is loaded on phone 0,mCarrierConfigLoadedSubIds[0] = 2. 137 // Then if subId 2 is deactivated from phone 0, the value becomes INVALID, 138 // mCarrierConfigLoadedSubIds[0] = INVALID_SUBSCRIPTION_ID. 139 private int[] mCarrierConfigLoadedSubIds; 140 141 private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { 142 @Override 143 public void onReceive(Context context, Intent intent) { 144 if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { 145 int phoneId = intent.getIntExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 146 SubscriptionManager.INVALID_SIM_SLOT_INDEX); 147 int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, 148 SubscriptionManager.INVALID_SUBSCRIPTION_ID); 149 notifyCarrierConfigChanged(phoneId, subId); 150 } 151 } 152 }; 153 154 /** 155 * Return the singleton or create one if not existed. 156 */ getInstance()157 public static MultiSimSettingController getInstance() { 158 synchronized (MultiSimSettingController.class) { 159 if (sInstance == null) { 160 Log.wtf(LOG_TAG, "getInstance null"); 161 } 162 163 return sInstance; 164 } 165 } 166 167 /** 168 * Init instance of MultiSimSettingController. 169 */ init(Context context, SubscriptionController sc)170 public static MultiSimSettingController init(Context context, SubscriptionController sc) { 171 synchronized (MultiSimSettingController.class) { 172 if (sInstance == null) { 173 sInstance = new MultiSimSettingController(context, sc); 174 } else { 175 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 176 } 177 return sInstance; 178 } 179 } 180 181 @VisibleForTesting MultiSimSettingController(Context context, SubscriptionController sc)182 public MultiSimSettingController(Context context, SubscriptionController sc) { 183 mContext = context; 184 mSubController = sc; 185 186 // Initialize mCarrierConfigLoadedSubIds and register to listen to carrier config change. 187 final int phoneCount = ((TelephonyManager) mContext.getSystemService( 188 Context.TELEPHONY_SERVICE)).getSupportedModemCount(); 189 mCarrierConfigLoadedSubIds = new int[phoneCount]; 190 Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID); 191 192 PhoneConfigurationManager.registerForMultiSimConfigChange( 193 this, EVENT_MULTI_SIM_CONFIG_CHANGED, null); 194 195 context.registerReceiver(mIntentReceiver, new IntentFilter( 196 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)); 197 } 198 199 /** 200 * Notify MOBILE_DATA of a subscription is changed. 201 */ notifyUserDataEnabled(int subId, boolean enable)202 public void notifyUserDataEnabled(int subId, boolean enable) { 203 if (SubscriptionManager.isValidSubscriptionId(subId)) { 204 obtainMessage(EVENT_USER_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); 205 } 206 } 207 208 /** 209 * Notify DATA_ROAMING of a subscription is changed. 210 */ notifyRoamingDataEnabled(int subId, boolean enable)211 public void notifyRoamingDataEnabled(int subId, boolean enable) { 212 if (SubscriptionManager.isValidSubscriptionId(subId)) { 213 obtainMessage(EVENT_ROAMING_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); 214 } 215 } 216 217 /** 218 * Notify that, for the first time after boot, SIMs are initialized. 219 * Should only be triggered once. 220 */ notifyAllSubscriptionLoaded()221 public void notifyAllSubscriptionLoaded() { 222 obtainMessage(EVENT_ALL_SUBSCRIPTIONS_LOADED).sendToTarget(); 223 } 224 225 /** 226 * Notify subscription info change. 227 */ notifySubscriptionInfoChanged()228 public void notifySubscriptionInfoChanged() { 229 obtainMessage(EVENT_SUBSCRIPTION_INFO_CHANGED).sendToTarget(); 230 } 231 232 /** 233 * Notify subscription group information change. 234 */ notifySubscriptionGroupChanged(ParcelUuid groupUuid)235 public void notifySubscriptionGroupChanged(ParcelUuid groupUuid) { 236 obtainMessage(EVENT_SUBSCRIPTION_GROUP_CHANGED, groupUuid).sendToTarget(); 237 } 238 239 /** 240 * Notify default data subscription change. 241 */ notifyDefaultDataSubChanged()242 public void notifyDefaultDataSubChanged() { 243 obtainMessage(EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED).sendToTarget(); 244 } 245 246 @Override handleMessage(Message msg)247 public void handleMessage(Message msg) { 248 switch (msg.what) { 249 case EVENT_USER_DATA_ENABLED: { 250 int subId = msg.arg1; 251 boolean enable = msg.arg2 != 0; 252 onUserDataEnabled(subId, enable); 253 break; 254 } 255 case EVENT_ROAMING_DATA_ENABLED: { 256 int subId = msg.arg1; 257 boolean enable = msg.arg2 != 0; 258 onRoamingDataEnabled(subId, enable); 259 break; 260 } 261 case EVENT_ALL_SUBSCRIPTIONS_LOADED: 262 onAllSubscriptionsLoaded(); 263 break; 264 case EVENT_SUBSCRIPTION_INFO_CHANGED: 265 onSubscriptionsChanged(); 266 break; 267 case EVENT_SUBSCRIPTION_GROUP_CHANGED: 268 ParcelUuid groupUuid = (ParcelUuid) msg.obj; 269 onSubscriptionGroupChanged(groupUuid); 270 break; 271 case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED: 272 onDefaultDataSettingChanged(); 273 break; 274 case EVENT_CARRIER_CONFIG_CHANGED: 275 int phoneId = msg.arg1; 276 int subId = msg.arg2; 277 onCarrierConfigChanged(phoneId, subId); 278 break; 279 case EVENT_MULTI_SIM_CONFIG_CHANGED: 280 int activeModems = (int) ((AsyncResult) msg.obj).result; 281 onMultiSimConfigChanged(activeModems); 282 } 283 } 284 285 /** 286 * Make sure MOBILE_DATA of subscriptions in same group are synced. 287 * 288 * If user is enabling a non-default non-opportunistic subscription, make it default 289 * data subscription. 290 */ onUserDataEnabled(int subId, boolean enable)291 protected void onUserDataEnabled(int subId, boolean enable) { 292 if (DBG) log("onUserDataEnabled"); 293 // Make sure MOBILE_DATA of subscriptions in same group are synced. 294 setUserDataEnabledForGroup(subId, enable); 295 296 // If user is enabling a non-default non-opportunistic subscription, make it default. 297 if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId) 298 && enable && mSubController.isActiveSubId(subId)) { 299 mSubController.setDefaultDataSubId(subId); 300 } 301 } 302 303 /** 304 * Make sure DATA_ROAMING of subscriptions in same group are synced. 305 */ onRoamingDataEnabled(int subId, boolean enable)306 private void onRoamingDataEnabled(int subId, boolean enable) { 307 if (DBG) log("onRoamingDataEnabled"); 308 setRoamingDataEnabledForGroup(subId, enable); 309 310 // Also inform SubscriptionController as it keeps another copy of user setting. 311 mSubController.setDataRoaming(enable ? 1 : 0, subId); 312 } 313 314 /** 315 * Upon initialization, update defaults and mobile data enabling. 316 * Should only be triggered once. 317 */ onAllSubscriptionsLoaded()318 private void onAllSubscriptionsLoaded() { 319 if (DBG) log("onAllSubscriptionsLoaded"); 320 mSubInfoInitialized = true; 321 reEvaluateAll(); 322 } 323 324 /** 325 * Make sure default values are cleaned or updated. 326 * 327 * Make sure non-default non-opportunistic subscriptions has data off. 328 */ onSubscriptionsChanged()329 private void onSubscriptionsChanged() { 330 if (DBG) log("onSubscriptionsChanged"); 331 reEvaluateAll(); 332 } 333 334 /** 335 * Called when carrier config changes on any phone. 336 */ 337 @VisibleForTesting notifyCarrierConfigChanged(int phoneId, int subId)338 public void notifyCarrierConfigChanged(int phoneId, int subId) { 339 obtainMessage(EVENT_CARRIER_CONFIG_CHANGED, phoneId, subId).sendToTarget(); 340 } 341 onCarrierConfigChanged(int phoneId, int subId)342 private void onCarrierConfigChanged(int phoneId, int subId) { 343 log("onCarrierConfigChanged phoneId " + phoneId + " subId " + subId); 344 if (!SubscriptionManager.isValidPhoneId(phoneId)) { 345 loge("Carrier config change with invalid phoneId " + phoneId); 346 return; 347 } 348 349 // b/153860050 Occasionally we receive carrier config change broadcast without subId 350 // being specified in it. So here we do additional check to make sur we don't miss the 351 // subId. 352 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 353 int[] subIds = mSubController.getSubId(phoneId); 354 if (!ArrayUtils.isEmpty(subIds)) { 355 CarrierConfigManager cm = (CarrierConfigManager) mContext.getSystemService( 356 mContext.CARRIER_CONFIG_SERVICE); 357 if (cm != null && cm.getConfigForSubId(subIds[0]) != null) { 358 loge("onCarrierConfigChanged with invalid subId while subd " 359 + subIds[0] + " is active and its config is loaded"); 360 subId = subIds[0]; 361 } 362 } 363 } 364 365 mCarrierConfigLoadedSubIds[phoneId] = subId; 366 reEvaluateAll(); 367 } 368 isCarrierConfigLoadedForAllSub()369 private boolean isCarrierConfigLoadedForAllSub() { 370 int[] activeSubIds = mSubController.getActiveSubIdList(false); 371 for (int activeSubId : activeSubIds) { 372 boolean isLoaded = false; 373 for (int configLoadedSub : mCarrierConfigLoadedSubIds) { 374 if (configLoadedSub == activeSubId) { 375 isLoaded = true; 376 break; 377 } 378 } 379 if (!isLoaded) { 380 if (DBG) log("Carrier config subId " + activeSubId + " is not loaded."); 381 return false; 382 } 383 } 384 385 return true; 386 } 387 onMultiSimConfigChanged(int activeModems)388 private void onMultiSimConfigChanged(int activeModems) { 389 // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active 390 // subscription change. 391 for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) { 392 mCarrierConfigLoadedSubIds[phoneId] = INVALID_SUBSCRIPTION_ID; 393 } 394 } 395 396 /** 397 * Wait for subInfo initialization (after boot up) and carrier config load for all active 398 * subscriptions before re-evaluate multi SIM settings. 399 */ isReadyToReevaluate()400 private boolean isReadyToReevaluate() { 401 return mSubInfoInitialized && isCarrierConfigLoadedForAllSub(); 402 } 403 reEvaluateAll()404 private void reEvaluateAll() { 405 if (!isReadyToReevaluate()) return; 406 updateDefaults(); 407 disableDataForNonDefaultNonOpportunisticSubscriptions(); 408 deactivateGroupedOpportunisticSubscriptionIfNeeded(); 409 } 410 411 /** 412 * Make sure non-default non-opportunistic subscriptions has data disabled. 413 */ onDefaultDataSettingChanged()414 private void onDefaultDataSettingChanged() { 415 if (DBG) log("onDefaultDataSettingChanged"); 416 disableDataForNonDefaultNonOpportunisticSubscriptions(); 417 } 418 419 /** 420 * When a subscription group is created or new subscriptions are added in the group, make 421 * sure the settings among them are synced. 422 * TODO: b/130258159 have a separate database table for grouped subscriptions so we don't 423 * manually sync each setting. 424 */ onSubscriptionGroupChanged(ParcelUuid groupUuid)425 private void onSubscriptionGroupChanged(ParcelUuid groupUuid) { 426 if (DBG) log("onSubscriptionGroupChanged"); 427 428 List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( 429 groupUuid, mContext.getOpPackageName(), null); 430 if (infoList == null || infoList.isEmpty()) return; 431 432 // Get a reference subscription to copy settings from. 433 // TODO: the reference sub should be passed in from external caller. 434 int refSubId = infoList.get(0).getSubscriptionId(); 435 for (SubscriptionInfo info : infoList) { 436 int subId = info.getSubscriptionId(); 437 if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) { 438 refSubId = subId; 439 break; 440 } 441 } 442 if (DBG) log("refSubId is " + refSubId); 443 444 boolean enable = false; 445 try { 446 enable = GlobalSettingsHelper.getBoolean( 447 mContext, Settings.Global.MOBILE_DATA, refSubId); 448 onUserDataEnabled(refSubId, enable); 449 } catch (SettingNotFoundException exception) { 450 //pass invalid refSubId to fetch the single-sim setting 451 enable = GlobalSettingsHelper.getBoolean( 452 mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable); 453 onUserDataEnabled(refSubId, enable); 454 } 455 456 enable = false; 457 try { 458 enable = GlobalSettingsHelper.getBoolean( 459 mContext, Settings.Global.DATA_ROAMING, refSubId); 460 onRoamingDataEnabled(refSubId, enable); 461 } catch (SettingNotFoundException exception) { 462 //pass invalid refSubId to fetch the single-sim setting 463 enable = GlobalSettingsHelper.getBoolean( 464 mContext, Settings.Global.DATA_ROAMING, INVALID_SUBSCRIPTION_ID, enable); 465 onRoamingDataEnabled(refSubId, enable); 466 } 467 468 // Sync settings in subscription database.. 469 mSubController.syncGroupedSetting(refSubId); 470 } 471 472 /** 473 * Automatically update default settings (data / voice / sms). 474 * 475 * Opportunistic subscriptions can't be default data / voice / sms subscription. 476 * 477 * 1) If the default subscription is still active, keep it unchanged. 478 * 2) Or if there's another active primary subscription that's in the same group, 479 * make it the new default value. 480 * 3) Or if there's only one active primary subscription, automatically set default 481 * data subscription on it. Because default data in Android Q is an internal value, 482 * not a user settable value anymore. 483 * 4) If non above is met, clear the default value to INVALID. 484 * 485 */ updateDefaults()486 protected void updateDefaults() { 487 if (DBG) log("updateDefaults"); 488 489 if (!isReadyToReevaluate()) return; 490 491 List<SubscriptionInfo> activeSubInfos = mSubController 492 .getActiveSubscriptionInfoList(mContext.getOpPackageName(), 493 null); 494 495 if (ArrayUtils.isEmpty(activeSubInfos)) { 496 mPrimarySubList.clear(); 497 if (DBG) log("[updateDefaultValues] No active sub. Setting default to INVALID sub."); 498 mSubController.setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 499 mSubController.setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 500 mSubController.setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 501 return; 502 } 503 504 int change = updatePrimarySubListAndGetChangeType(activeSubInfos); 505 if (DBG) log("[updateDefaultValues] change: " + change); 506 if (change == PRIMARY_SUB_NO_CHANGE) return; 507 508 // If there's only one primary subscription active, we trigger PREFERRED_PICK_DIALOG 509 // dialog if and only if there were multiple primary SIM cards and one is removed. 510 // Otherwise, if user just inserted their first SIM, or there's one primary and one 511 // opportunistic subscription active (activeSubInfos.size() > 1), we automatically 512 // set the primary to be default SIM and return. 513 if (mPrimarySubList.size() == 1 && (change != PRIMARY_SUB_REMOVED 514 || ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) 515 .getActiveModemCount() == 1)) { 516 int subId = mPrimarySubList.get(0); 517 if (DBG) log("[updateDefaultValues] to only primary sub " + subId); 518 mSubController.setDefaultDataSubId(subId); 519 mSubController.setDefaultVoiceSubId(subId); 520 mSubController.setDefaultSmsSubId(subId); 521 return; 522 } 523 524 if (DBG) log("[updateDefaultValues] records: " + mPrimarySubList); 525 526 // Update default data subscription. 527 if (DBG) log("[updateDefaultValues] Update default data subscription"); 528 boolean dataSelected = updateDefaultValue(mPrimarySubList, 529 mSubController.getDefaultDataSubId(), 530 (newValue -> mSubController.setDefaultDataSubId(newValue))); 531 532 // Update default voice subscription. 533 if (DBG) log("[updateDefaultValues] Update default voice subscription"); 534 boolean voiceSelected = updateDefaultValue(mPrimarySubList, 535 mSubController.getDefaultVoiceSubId(), 536 (newValue -> mSubController.setDefaultVoiceSubId(newValue))); 537 538 // Update default sms subscription. 539 if (DBG) log("[updateDefaultValues] Update default sms subscription"); 540 boolean smsSelected = updateDefaultValue(mPrimarySubList, 541 mSubController.getDefaultSmsSubId(), 542 (newValue -> mSubController.setDefaultSmsSubId(newValue))); 543 544 sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected); 545 } 546 547 @PrimarySubChangeType updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList)548 private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList) { 549 // Update mPrimarySubList. Opportunistic subscriptions can't be default 550 // data / voice / sms subscription. 551 List<Integer> prevPrimarySubList = mPrimarySubList; 552 mPrimarySubList = activeSubList.stream().filter(info -> !info.isOpportunistic()) 553 .map(info -> info.getSubscriptionId()) 554 .collect(Collectors.toList()); 555 556 if (mInitialHandling) { 557 mInitialHandling = false; 558 return PRIMARY_SUB_INITIALIZED; 559 } 560 if (mPrimarySubList.equals(prevPrimarySubList)) return PRIMARY_SUB_NO_CHANGE; 561 if (mPrimarySubList.size() > prevPrimarySubList.size()) return PRIMARY_SUB_ADDED; 562 563 if (mPrimarySubList.size() == prevPrimarySubList.size()) { 564 // We need to differentiate PRIMARY_SUB_SWAPPED and PRIMARY_SUB_SWAPPED_IN_GROUP: 565 // For SWAPPED_IN_GROUP, we never pop up dialog to ask data sub selection again. 566 for (int subId : mPrimarySubList) { 567 boolean swappedInSameGroup = false; 568 for (int prevSubId : prevPrimarySubList) { 569 if (areSubscriptionsInSameGroup(subId, prevSubId)) { 570 swappedInSameGroup = true; 571 break; 572 } 573 } 574 if (!swappedInSameGroup) return PRIMARY_SUB_SWAPPED; 575 } 576 return PRIMARY_SUB_SWAPPED_IN_GROUP; 577 } else /* mPrimarySubList.size() < prevPrimarySubList.size() */ { 578 // We need to differentiate whether the missing subscription is removed or marked as 579 // opportunistic. Usually only one subscription may change at a time, But to be safe, if 580 // any previous primary subscription becomes inactive, we consider it 581 for (int subId : prevPrimarySubList) { 582 if (mPrimarySubList.contains(subId)) continue; 583 if (!mSubController.isActiveSubId(subId)) return PRIMARY_SUB_REMOVED; 584 if (!mSubController.isOpportunistic(subId)) { 585 // Should never happen. 586 loge("[updatePrimarySubListAndGetChangeType]: missing active primary subId " 587 + subId); 588 } 589 } 590 return PRIMARY_SUB_MARKED_OPPT; 591 } 592 } 593 sendSubChangeNotificationIfNeeded(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected)594 private void sendSubChangeNotificationIfNeeded(int change, boolean dataSelected, 595 boolean voiceSelected, boolean smsSelected) { 596 @TelephonyManager.DefaultSubscriptionSelectType 597 int simSelectDialogType = getSimSelectDialogType( 598 change, dataSelected, voiceSelected, smsSelected); 599 SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change); 600 601 if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE 602 || simCombinationParams.mWarningType != EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE) { 603 log("[sendSubChangeNotificationIfNeeded] showing dialog type " 604 + simSelectDialogType); 605 log("[sendSubChangeNotificationIfNeeded] showing sim warning " 606 + simCombinationParams.mWarningType); 607 Intent intent = new Intent(); 608 intent.setAction(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); 609 intent.setClassName("com.android.settings", 610 "com.android.settings.sim.SimSelectNotification"); 611 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 612 613 intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, simSelectDialogType); 614 if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL) { 615 intent.putExtra(EXTRA_SUBSCRIPTION_ID, mPrimarySubList.get(0)); 616 } 617 618 intent.putExtra(EXTRA_SIM_COMBINATION_WARNING_TYPE, simCombinationParams.mWarningType); 619 if (simCombinationParams.mWarningType == EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA) { 620 intent.putExtra(EXTRA_SIM_COMBINATION_NAMES, simCombinationParams.mSimNames); 621 } 622 mContext.sendBroadcast(intent); 623 } 624 } 625 getSimSelectDialogType(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected)626 private int getSimSelectDialogType(int change, boolean dataSelected, 627 boolean voiceSelected, boolean smsSelected) { 628 int dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE; 629 630 // If a primary subscription is removed and only one is left active, ask user 631 // for preferred sub selection if any default setting is not set. 632 // If another primary subscription is added or default data is not selected, ask 633 // user to select default for data as it's most important. 634 if (mPrimarySubList.size() == 1 && change == PRIMARY_SUB_REMOVED 635 && (!dataSelected || !smsSelected || !voiceSelected)) { 636 dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL; 637 } else if (mPrimarySubList.size() > 1 && isUserVisibleChange(change)) { 638 // If change is SWAPPED_IN_GROUP or MARKED_OPPT orINITIALIZED, don't ask user again. 639 dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA; 640 } 641 642 return dialogType; 643 } 644 645 private class SimCombinationWarningParams { 646 @TelephonyManager.SimCombinationWarningType 647 int mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE; 648 String mSimNames; 649 } 650 getSimCombinationWarningParams(int change)651 private SimCombinationWarningParams getSimCombinationWarningParams(int change) { 652 SimCombinationWarningParams params = new SimCombinationWarningParams(); 653 // If it's single SIM active, no SIM combination warning is needed. 654 if (mPrimarySubList.size() <= 1) return params; 655 // If it's no primary SIM change or it's not user visible change 656 // (initialized or swapped in a group), no SIM combination warning is needed. 657 if (!isUserVisibleChange(change)) return params; 658 659 List<String> simNames = new ArrayList<>(); 660 int cdmaPhoneCount = 0; 661 for (int subId : mPrimarySubList) { 662 Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId)); 663 // If a dual CDMA SIM combination warning is needed. 664 if (phone != null && phone.isCdmaSubscriptionAppPresent()) { 665 cdmaPhoneCount++; 666 String simName = mSubController.getActiveSubscriptionInfo( 667 subId, mContext.getOpPackageName(), null) 668 .getDisplayName().toString(); 669 if (TextUtils.isEmpty(simName)) { 670 // Fall back to carrier name. 671 simName = phone.getCarrierName(); 672 } 673 simNames.add(simName); 674 } 675 } 676 677 if (cdmaPhoneCount > 1) { 678 params.mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA; 679 params.mSimNames = String.join(" & ", simNames); 680 } 681 682 return params; 683 } 684 isUserVisibleChange(int change)685 private boolean isUserVisibleChange(int change) { 686 return (change == PRIMARY_SUB_ADDED || change == PRIMARY_SUB_REMOVED 687 || change == PRIMARY_SUB_SWAPPED); 688 } 689 disableDataForNonDefaultNonOpportunisticSubscriptions()690 protected void disableDataForNonDefaultNonOpportunisticSubscriptions() { 691 if (!isReadyToReevaluate()) return; 692 693 int defaultDataSub = mSubController.getDefaultDataSubId(); 694 695 for (Phone phone : PhoneFactory.getPhones()) { 696 if (phone.getSubId() != defaultDataSub 697 && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) 698 && !mSubController.isOpportunistic(phone.getSubId()) 699 && phone.isUserDataEnabled() 700 && !areSubscriptionsInSameGroup(defaultDataSub, phone.getSubId())) { 701 log("setting data to false on " + phone.getSubId()); 702 phone.getDataEnabledSettings().setUserDataEnabled(false); 703 } 704 } 705 } 706 areSubscriptionsInSameGroup(int subId1, int subId2)707 private boolean areSubscriptionsInSameGroup(int subId1, int subId2) { 708 if (!SubscriptionManager.isUsableSubscriptionId(subId1) 709 || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false; 710 if (subId1 == subId2) return true; 711 712 ParcelUuid groupUuid1 = mSubController.getGroupUuid(subId1); 713 ParcelUuid groupUuid2 = mSubController.getGroupUuid(subId2); 714 return groupUuid1 != null && groupUuid1.equals(groupUuid2); 715 } 716 717 /** 718 * Make sure MOBILE_DATA of subscriptions in the same group with the subId 719 * are synced. 720 */ setUserDataEnabledForGroup(int subId, boolean enable)721 protected void setUserDataEnabledForGroup(int subId, boolean enable) { 722 log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); 723 List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( 724 mSubController.getGroupUuid(subId), mContext.getOpPackageName(), 725 null); 726 727 if (infoList == null) return; 728 729 for (SubscriptionInfo info : infoList) { 730 int currentSubId = info.getSubscriptionId(); 731 // TODO: simplify when setUserDataEnabled becomes singleton 732 if (mSubController.isActiveSubId(currentSubId)) { 733 // For active subscription, call setUserDataEnabled through DataEnabledSettings. 734 Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId)); 735 // If enable is true and it's not opportunistic subscription, we don't enable it, 736 // as there can't e two 737 if (phone != null) { 738 phone.getDataEnabledSettings().setUserDataEnabled(enable, false); 739 } 740 } else { 741 // For inactive subscription, directly write into global settings. 742 GlobalSettingsHelper.setBoolean( 743 mContext, Settings.Global.MOBILE_DATA, currentSubId, enable); 744 } 745 } 746 } 747 748 /** 749 * Make sure DATA_ROAMING of subscriptions in the same group with the subId 750 * are synced. 751 */ setRoamingDataEnabledForGroup(int subId, boolean enable)752 private void setRoamingDataEnabledForGroup(int subId, boolean enable) { 753 SubscriptionController subController = SubscriptionController.getInstance(); 754 List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup( 755 mSubController.getGroupUuid(subId), mContext.getOpPackageName(), 756 null); 757 758 if (infoList == null) return; 759 760 for (SubscriptionInfo info : infoList) { 761 // For inactive subscription, directly write into global settings. 762 GlobalSettingsHelper.setBoolean( 763 mContext, Settings.Global.DATA_ROAMING, info.getSubscriptionId(), enable); 764 } 765 } 766 767 private interface UpdateDefaultAction { update(int newValue)768 void update(int newValue); 769 } 770 771 // Returns whether the new default value is valid. updateDefaultValue(List<Integer> primarySubList, int oldValue, UpdateDefaultAction action)772 private boolean updateDefaultValue(List<Integer> primarySubList, int oldValue, 773 UpdateDefaultAction action) { 774 int newValue = INVALID_SUBSCRIPTION_ID; 775 776 if (primarySubList.size() > 0) { 777 for (int subId : primarySubList) { 778 if (DBG) log("[updateDefaultValue] Record.id: " + subId); 779 // If the old subId is still active, or there's another active primary subscription 780 // that is in the same group, that should become the new default subscription. 781 if (areSubscriptionsInSameGroup(subId, oldValue)) { 782 newValue = subId; 783 log("[updateDefaultValue] updates to subId=" + newValue); 784 break; 785 } 786 } 787 } 788 789 if (oldValue != newValue) { 790 if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue); 791 action.update(newValue); 792 } 793 794 return SubscriptionManager.isValidSubscriptionId(newValue); 795 } 796 797 // When a primary and its grouped opportunistic subscriptions were active, and the primary 798 // subscription gets deactivated or removed, we need to automatically disable the grouped 799 // opportunistic subscription, which will be marked isGroupDisabled as true by SubController. deactivateGroupedOpportunisticSubscriptionIfNeeded()800 private void deactivateGroupedOpportunisticSubscriptionIfNeeded() { 801 if (!SubscriptionInfoUpdater.isSubInfoInitialized()) return; 802 803 List<SubscriptionInfo> opptSubList = mSubController.getOpportunisticSubscriptions( 804 mContext.getOpPackageName(), null); 805 806 if (ArrayUtils.isEmpty(opptSubList)) return; 807 808 for (SubscriptionInfo info : opptSubList) { 809 if (info.isGroupDisabled() && mSubController.isActiveSubId(info.getSubscriptionId())) { 810 log("[deactivateGroupedOpptSubIfNeeded] " 811 + "Deactivating grouped opportunistic subscription " 812 + info.getSubscriptionId()); 813 deactivateSubscription(info); 814 } 815 } 816 } 817 deactivateSubscription(SubscriptionInfo info)818 private void deactivateSubscription(SubscriptionInfo info) { 819 // TODO: b/133379187 have a way to deactivate pSIM. 820 if (info.isEmbedded()) { 821 log("[deactivateSubscription] eSIM profile " + info.getSubscriptionId()); 822 EuiccManager euiccManager = (EuiccManager) 823 mContext.getSystemService(Context.EUICC_SERVICE); 824 euiccManager.switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, 825 PendingIntent.getService(mContext, 0, new Intent(), 0)); 826 } 827 } 828 log(String msg)829 private void log(String msg) { 830 Log.d(LOG_TAG, msg); 831 } 832 loge(String msg)833 private void loge(String msg) { 834 Log.e(LOG_TAG, msg); 835 } 836 } 837