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