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.content.Context; 34 import android.content.Intent; 35 import android.os.Handler; 36 import android.os.Message; 37 import android.os.ParcelUuid; 38 import android.provider.Settings; 39 import android.provider.Settings.SettingNotFoundException; 40 import android.telephony.SubscriptionInfo; 41 import android.telephony.SubscriptionManager; 42 import android.telephony.TelephonyManager; 43 import android.text.TextUtils; 44 import android.util.Log; 45 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.util.ArrayUtils; 48 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.stream.Collectors; 54 55 /** 56 * This class will make sure below setting rules are coordinated across different subscriptions 57 * and phones in multi-SIM case: 58 * 59 * 1) Grouped subscriptions will have same settings for MOBILE_DATA and DATA_ROAMING. 60 * 2) Default settings updated automatically. It may be cleared or inherited within group. 61 * If default subscription A switches to profile B which is in the same group, B will 62 * become the new default. 63 * 3) For primary subscriptions, only default data subscription will have MOBILE_DATA on. 64 */ 65 public class MultiSimSettingController extends Handler { 66 private static final String LOG_TAG = "MultiSimSettingController"; 67 private static final boolean DBG = true; 68 private static final int EVENT_USER_DATA_ENABLED = 1; 69 private static final int EVENT_ROAMING_DATA_ENABLED = 2; 70 private static final int EVENT_ALL_SUBSCRIPTIONS_LOADED = 3; 71 private static final int EVENT_SUBSCRIPTION_INFO_CHANGED = 4; 72 private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED = 5; 73 private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6; 74 75 @Retention(RetentionPolicy.SOURCE) 76 @IntDef(prefix = {"PRIMARY_SUB_"}, 77 value = { 78 PRIMARY_SUB_NO_CHANGE, 79 PRIMARY_SUB_ADDED, 80 PRIMARY_SUB_REMOVED, 81 PRIMARY_SUB_SWAPPED, 82 PRIMARY_SUB_SWAPPED_IN_GROUP, 83 PRIMARY_SUB_MARKED_OPPT, 84 PRIMARY_SUB_INITIALIZED 85 }) 86 private @interface PrimarySubChangeType {} 87 88 // Primary subscription not change. 89 private static final int PRIMARY_SUB_NO_CHANGE = 0; 90 // One or more primary subscriptions are activated. 91 private static final int PRIMARY_SUB_ADDED = 1; 92 // One or more primary subscriptions are deactivated. 93 private static final int PRIMARY_SUB_REMOVED = 2; 94 // One or more primary subscriptions are swapped. 95 private static final int PRIMARY_SUB_SWAPPED = 3; 96 // One or more primary subscriptions are swapped but within same sub group. 97 private static final int PRIMARY_SUB_SWAPPED_IN_GROUP = 4; 98 // One or more primary subscriptions are marked as opportunistic. 99 private static final int PRIMARY_SUB_MARKED_OPPT = 5; 100 // Subscription information is initially loaded. 101 private static final int PRIMARY_SUB_INITIALIZED = 6; 102 103 private final Context mContext; 104 private final SubscriptionController mSubController; 105 // Keep a record of active primary (non-opportunistic) subscription list. 106 @NonNull private List<Integer> mPrimarySubList = new ArrayList<>(); 107 108 /** The singleton instance. */ 109 private static MultiSimSettingController sInstance = null; 110 111 // This will be set true when handling EVENT_ALL_SUBSCRIPTIONS_LOADED. The reason of keeping 112 // a local variable instead of calling SubscriptionInfoUpdater#isSubInfoInitialized is, there 113 // might be a race condition that we receive EVENT_SUBSCRIPTION_INFO_CHANGED first, then 114 // EVENT_ALL_SUBSCRIPTIONS_LOADED. And calling SubscriptionInfoUpdater#isSubInfoInitialized 115 // will make us handle EVENT_SUBSCRIPTION_INFO_CHANGED unexpectedly and causing us to believe 116 // the SIMs are newly inserted instead of being initialized. 117 private boolean mSubInfoInitialized = false; 118 119 /** 120 * Return the singleton or create one if not existed. 121 */ getInstance()122 public static MultiSimSettingController getInstance() { 123 synchronized (SubscriptionController.class) { 124 if (sInstance == null) { 125 Log.wtf(LOG_TAG, "getInstance null"); 126 } 127 128 return sInstance; 129 } 130 } 131 132 /** 133 * Init instance of MultiSimSettingController. 134 */ init(Context context, SubscriptionController sc)135 public static MultiSimSettingController init(Context context, SubscriptionController sc) { 136 synchronized (SubscriptionController.class) { 137 if (sInstance == null) { 138 sInstance = new MultiSimSettingController(context, sc); 139 } else { 140 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 141 } 142 return sInstance; 143 } 144 } 145 146 @VisibleForTesting MultiSimSettingController(Context context, SubscriptionController sc)147 public MultiSimSettingController(Context context, SubscriptionController sc) { 148 mContext = context; 149 mSubController = sc; 150 } 151 152 /** 153 * Notify MOBILE_DATA of a subscription is changed. 154 */ notifyUserDataEnabled(int subId, boolean enable)155 public void notifyUserDataEnabled(int subId, boolean enable) { 156 obtainMessage(EVENT_USER_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); 157 } 158 159 /** 160 * Notify DATA_ROAMING of a subscription is changed. 161 */ notifyRoamingDataEnabled(int subId, boolean enable)162 public void notifyRoamingDataEnabled(int subId, boolean enable) { 163 obtainMessage(EVENT_ROAMING_DATA_ENABLED, subId, enable ? 1 : 0).sendToTarget(); 164 } 165 166 /** 167 * Notify that, for the first time after boot, SIMs are initialized. 168 * Should only be triggered once. 169 */ notifyAllSubscriptionLoaded()170 public void notifyAllSubscriptionLoaded() { 171 obtainMessage(EVENT_ALL_SUBSCRIPTIONS_LOADED).sendToTarget(); 172 } 173 174 /** 175 * Notify subscription info change. 176 */ notifySubscriptionInfoChanged()177 public void notifySubscriptionInfoChanged() { 178 obtainMessage(EVENT_SUBSCRIPTION_INFO_CHANGED).sendToTarget(); 179 } 180 181 /** 182 * Notify subscription group information change. 183 */ notifySubscriptionGroupChanged(ParcelUuid groupUuid)184 public void notifySubscriptionGroupChanged(ParcelUuid groupUuid) { 185 obtainMessage(EVENT_SUBSCRIPTION_GROUP_CHANGED, groupUuid).sendToTarget(); 186 } 187 188 /** 189 * Notify default data subscription change. 190 */ notifyDefaultDataSubChanged()191 public void notifyDefaultDataSubChanged() { 192 obtainMessage(EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED).sendToTarget(); 193 } 194 195 @Override handleMessage(Message msg)196 public void handleMessage(Message msg) { 197 switch (msg.what) { 198 case EVENT_USER_DATA_ENABLED: { 199 int subId = msg.arg1; 200 boolean enable = msg.arg2 != 0; 201 onUserDataEnabled(subId, enable); 202 break; 203 } 204 case EVENT_ROAMING_DATA_ENABLED: { 205 int subId = msg.arg1; 206 boolean enable = msg.arg2 != 0; 207 onRoamingDataEnabled(subId, enable); 208 break; 209 } 210 case EVENT_ALL_SUBSCRIPTIONS_LOADED: 211 onAllSubscriptionsLoaded(); 212 break; 213 case EVENT_SUBSCRIPTION_INFO_CHANGED: 214 onSubscriptionsChanged(); 215 break; 216 case EVENT_SUBSCRIPTION_GROUP_CHANGED: 217 ParcelUuid groupUuid = (ParcelUuid) msg.obj; 218 onSubscriptionGroupChanged(groupUuid); 219 break; 220 case EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED: 221 onDefaultDataSettingChanged(); 222 break; 223 } 224 } 225 226 /** 227 * Make sure MOBILE_DATA of subscriptions in same group are synced. 228 * 229 * If user is enabling a non-default non-opportunistic subscription, make it default 230 * data subscription. 231 */ onUserDataEnabled(int subId, boolean enable)232 private void onUserDataEnabled(int subId, boolean enable) { 233 if (DBG) log("onUserDataEnabled"); 234 // Make sure MOBILE_DATA of subscriptions in same group are synced. 235 setUserDataEnabledForGroup(subId, enable); 236 237 // If user is enabling a non-default non-opportunistic subscription, make it default. 238 if (mSubController.getDefaultDataSubId() != subId && !mSubController.isOpportunistic(subId) 239 && enable) { 240 mSubController.setDefaultDataSubId(subId); 241 } 242 } 243 244 /** 245 * Make sure DATA_ROAMING of subscriptions in same group are synced. 246 */ onRoamingDataEnabled(int subId, boolean enable)247 private void onRoamingDataEnabled(int subId, boolean enable) { 248 if (DBG) log("onRoamingDataEnabled"); 249 setRoamingDataEnabledForGroup(subId, enable); 250 251 // Also inform SubscriptionController as it keeps another copy of user setting. 252 mSubController.setDataRoaming(enable ? 1 : 0, subId); 253 } 254 255 /** 256 * Upon initialization, update defaults and mobile data enabling. 257 * Should only be triggered once. 258 */ onAllSubscriptionsLoaded()259 private void onAllSubscriptionsLoaded() { 260 if (DBG) log("onAllSubscriptionsLoaded"); 261 mSubInfoInitialized = true; 262 updateDefaults(/*init*/ true); 263 disableDataForNonDefaultNonOpportunisticSubscriptions(); 264 } 265 266 /** 267 * Make sure default values are cleaned or updated. 268 * 269 * Make sure non-default non-opportunistic subscriptions has data off. 270 */ onSubscriptionsChanged()271 private void onSubscriptionsChanged() { 272 if (DBG) log("onSubscriptionsChanged"); 273 if (!mSubInfoInitialized) return; 274 updateDefaults(/*init*/ false); 275 disableDataForNonDefaultNonOpportunisticSubscriptions(); 276 } 277 278 /** 279 * Make sure non-default non-opportunistic subscriptions has data disabled. 280 */ onDefaultDataSettingChanged()281 private void onDefaultDataSettingChanged() { 282 if (DBG) log("onDefaultDataSettingChanged"); 283 disableDataForNonDefaultNonOpportunisticSubscriptions(); 284 } 285 286 /** 287 * When a subscription group is created or new subscriptions are added in the group, make 288 * sure the settings among them are synced. 289 * TODO: b/130258159 have a separate database table for grouped subscriptions so we don't 290 * manually sync each setting. 291 */ onSubscriptionGroupChanged(ParcelUuid groupUuid)292 private void onSubscriptionGroupChanged(ParcelUuid groupUuid) { 293 if (DBG) log("onSubscriptionGroupChanged"); 294 295 List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( 296 groupUuid, mContext.getOpPackageName()); 297 if (infoList == null || infoList.isEmpty()) return; 298 299 // Get a reference subscription to copy settings from. 300 // TODO: the reference sub should be passed in from external caller. 301 int refSubId = infoList.get(0).getSubscriptionId(); 302 for (SubscriptionInfo info : infoList) { 303 int subId = info.getSubscriptionId(); 304 if (mSubController.isActiveSubId(subId) && !mSubController.isOpportunistic(subId)) { 305 refSubId = subId; 306 break; 307 } 308 } 309 if (DBG) log("refSubId is " + refSubId); 310 311 boolean enable = false; 312 try { 313 enable = GlobalSettingsHelper.getBoolean( 314 mContext, Settings.Global.MOBILE_DATA, refSubId); 315 onUserDataEnabled(refSubId, enable); 316 } catch (SettingNotFoundException exception) { 317 //pass invalid refSubId to fetch the single-sim setting 318 enable = GlobalSettingsHelper.getBoolean( 319 mContext, Settings.Global.MOBILE_DATA, INVALID_SUBSCRIPTION_ID, enable); 320 onUserDataEnabled(refSubId, enable); 321 } 322 323 enable = false; 324 try { 325 enable = GlobalSettingsHelper.getBoolean( 326 mContext, Settings.Global.DATA_ROAMING, refSubId); 327 onRoamingDataEnabled(refSubId, enable); 328 } catch (SettingNotFoundException exception) { 329 //pass invalid refSubId to fetch the single-sim setting 330 enable = GlobalSettingsHelper.getBoolean( 331 mContext, Settings.Global.DATA_ROAMING, INVALID_SUBSCRIPTION_ID, enable); 332 onRoamingDataEnabled(refSubId, enable); 333 } 334 335 // Sync settings in subscription database.. 336 mSubController.syncGroupedSetting(refSubId); 337 } 338 339 /** 340 * Automatically update default settings (data / voice / sms). 341 * 342 * Opportunistic subscriptions can't be default data / voice / sms subscription. 343 * 344 * 1) If the default subscription is still active, keep it unchanged. 345 * 2) Or if there's another active primary subscription that's in the same group, 346 * make it the new default value. 347 * 3) Or if there's only one active primary subscription, automatically set default 348 * data subscription on it. Because default data in Android Q is an internal value, 349 * not a user settable value anymore. 350 * 4) If non above is met, clear the default value to INVALID. 351 * 352 * @param init whether the subscriptions are just initialized. 353 */ updateDefaults(boolean init)354 private void updateDefaults(boolean init) { 355 if (DBG) log("updateDefaults"); 356 357 if (!mSubInfoInitialized) return; 358 359 List<SubscriptionInfo> activeSubInfos = mSubController 360 .getActiveSubscriptionInfoList(mContext.getOpPackageName()); 361 362 if (ArrayUtils.isEmpty(activeSubInfos)) { 363 mPrimarySubList.clear(); 364 if (DBG) log("[updateDefaultValues] No active sub. Setting default to INVALID sub."); 365 mSubController.setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 366 mSubController.setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 367 mSubController.setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID); 368 return; 369 } 370 371 int change = updatePrimarySubListAndGetChangeType(activeSubInfos, init); 372 if (DBG) log("[updateDefaultValues] change: " + change); 373 if (change == PRIMARY_SUB_NO_CHANGE) return; 374 375 // If there's only one primary subscription active, we trigger PREFERRED_PICK_DIALOG 376 // dialog if and only if there were multiple primary SIM cards and one is removed. 377 // Otherwise, if user just inserted their first SIM, or there's one primary and one 378 // opportunistic subscription active (activeSubInfos.size() > 1), we automatically 379 // set the primary to be default SIM and return. 380 if (mPrimarySubList.size() == 1 && change != PRIMARY_SUB_REMOVED) { 381 int subId = mPrimarySubList.get(0); 382 if (DBG) log("[updateDefaultValues] to only primary sub " + subId); 383 mSubController.setDefaultDataSubId(subId); 384 mSubController.setDefaultVoiceSubId(subId); 385 mSubController.setDefaultSmsSubId(subId); 386 return; 387 } 388 389 if (DBG) log("[updateDefaultValues] records: " + mPrimarySubList); 390 391 // Update default data subscription. 392 if (DBG) log("[updateDefaultValues] Update default data subscription"); 393 boolean dataSelected = updateDefaultValue(mPrimarySubList, 394 mSubController.getDefaultDataSubId(), 395 (newValue -> mSubController.setDefaultDataSubId(newValue))); 396 397 // Update default voice subscription. 398 if (DBG) log("[updateDefaultValues] Update default voice subscription"); 399 boolean voiceSelected = updateDefaultValue(mPrimarySubList, 400 mSubController.getDefaultVoiceSubId(), 401 (newValue -> mSubController.setDefaultVoiceSubId(newValue))); 402 403 // Update default sms subscription. 404 if (DBG) log("[updateDefaultValues] Update default sms subscription"); 405 boolean smsSelected = updateDefaultValue(mPrimarySubList, 406 mSubController.getDefaultSmsSubId(), 407 (newValue -> mSubController.setDefaultSmsSubId(newValue))); 408 409 sendSubChangeNotificationIfNeeded(change, dataSelected, voiceSelected, smsSelected); 410 } 411 412 @PrimarySubChangeType updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList, boolean init)413 private int updatePrimarySubListAndGetChangeType(List<SubscriptionInfo> activeSubList, 414 boolean init) { 415 // Update mPrimarySubList. Opportunistic subscriptions can't be default 416 // data / voice / sms subscription. 417 List<Integer> prevPrimarySubList = mPrimarySubList; 418 mPrimarySubList = activeSubList.stream().filter(info -> !info.isOpportunistic()) 419 .map(info -> info.getSubscriptionId()) 420 .collect(Collectors.toList()); 421 422 if (init) return PRIMARY_SUB_INITIALIZED; 423 if (mPrimarySubList.equals(prevPrimarySubList)) return PRIMARY_SUB_NO_CHANGE; 424 if (mPrimarySubList.size() > prevPrimarySubList.size()) return PRIMARY_SUB_ADDED; 425 426 if (mPrimarySubList.size() == prevPrimarySubList.size()) { 427 // We need to differentiate PRIMARY_SUB_SWAPPED and PRIMARY_SUB_SWAPPED_IN_GROUP: 428 // For SWAPPED_IN_GROUP, we never pop up dialog to ask data sub selection again. 429 for (int subId : mPrimarySubList) { 430 boolean swappedInSameGroup = false; 431 for (int prevSubId : prevPrimarySubList) { 432 if (areSubscriptionsInSameGroup(subId, prevSubId)) { 433 swappedInSameGroup = true; 434 break; 435 } 436 } 437 if (!swappedInSameGroup) return PRIMARY_SUB_SWAPPED; 438 } 439 return PRIMARY_SUB_SWAPPED_IN_GROUP; 440 } else /* mPrimarySubList.size() < prevPrimarySubList.size() */ { 441 // We need to differentiate whether the missing subscription is removed or marked as 442 // opportunistic. Usually only one subscription may change at a time, But to be safe, if 443 // any previous primary subscription becomes inactive, we consider it 444 for (int subId : prevPrimarySubList) { 445 if (mPrimarySubList.contains(subId)) continue; 446 if (!mSubController.isActiveSubId(subId)) return PRIMARY_SUB_REMOVED; 447 if (!mSubController.isOpportunistic(subId)) { 448 // Should never happen. 449 loge("[updatePrimarySubListAndGetChangeType]: missing active primary subId " 450 + subId); 451 } 452 } 453 return PRIMARY_SUB_MARKED_OPPT; 454 } 455 } 456 sendSubChangeNotificationIfNeeded(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected)457 private void sendSubChangeNotificationIfNeeded(int change, boolean dataSelected, 458 boolean voiceSelected, boolean smsSelected) { 459 @TelephonyManager.DefaultSubscriptionSelectType 460 int simSelectDialogType = getSimSelectDialogType( 461 change, dataSelected, voiceSelected, smsSelected); 462 SimCombinationWarningParams simCombinationParams = getSimCombinationWarningParams(change); 463 464 if (simSelectDialogType != EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE 465 || simCombinationParams.mWarningType != EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE) { 466 log("[sendSubChangeNotificationIfNeeded] showing dialog type " 467 + simSelectDialogType); 468 log("[sendSubChangeNotificationIfNeeded] showing sim warning " 469 + simCombinationParams.mWarningType); 470 Intent intent = new Intent(); 471 intent.setAction(ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED); 472 intent.setClassName("com.android.settings", 473 "com.android.settings.sim.SimSelectNotification"); 474 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 475 476 intent.putExtra(EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE, simSelectDialogType); 477 if (simSelectDialogType == EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL) { 478 intent.putExtra(EXTRA_SUBSCRIPTION_ID, mPrimarySubList.get(0)); 479 } 480 481 intent.putExtra(EXTRA_SIM_COMBINATION_WARNING_TYPE, simCombinationParams.mWarningType); 482 if (simCombinationParams.mWarningType == EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA) { 483 intent.putExtra(EXTRA_SIM_COMBINATION_NAMES, simCombinationParams.mSimNames); 484 } 485 mContext.sendBroadcast(intent); 486 } 487 } 488 getSimSelectDialogType(int change, boolean dataSelected, boolean voiceSelected, boolean smsSelected)489 private int getSimSelectDialogType(int change, boolean dataSelected, 490 boolean voiceSelected, boolean smsSelected) { 491 int dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_NONE; 492 493 // If a primary subscription is removed and only one is left active, ask user 494 // for preferred sub selection if any default setting is not set. 495 // If another primary subscription is added or default data is not selected, ask 496 // user to select default for data as it's most important. 497 if (mPrimarySubList.size() == 1 && change == PRIMARY_SUB_REMOVED 498 && (!dataSelected || !smsSelected || !voiceSelected)) { 499 dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL; 500 } else if (mPrimarySubList.size() > 1 && isUserVisibleChange(change)) { 501 // If change is SWAPPED_IN_GROUP or MARKED_OPPT orINITIALIZED, don't ask user again. 502 dialogType = EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DATA; 503 } 504 505 return dialogType; 506 } 507 508 private class SimCombinationWarningParams { 509 @TelephonyManager.SimCombinationWarningType 510 int mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE; 511 String mSimNames; 512 } 513 getSimCombinationWarningParams(int change)514 private SimCombinationWarningParams getSimCombinationWarningParams(int change) { 515 SimCombinationWarningParams params = new SimCombinationWarningParams(); 516 // If it's single SIM active, no SIM combination warning is needed. 517 if (mPrimarySubList.size() <= 1) return params; 518 // If it's no primary SIM change or it's not user visible change 519 // (initialized or swapped in a group), no SIM combination warning is needed. 520 if (!isUserVisibleChange(change)) return params; 521 522 List<String> simNames = new ArrayList<>(); 523 int cdmaPhoneCount = 0; 524 for (int subId : mPrimarySubList) { 525 Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId)); 526 // If a dual CDMA SIM combination warning is needed. 527 if (phone != null && phone.isCdmaSubscriptionAppPresent()) { 528 cdmaPhoneCount++; 529 String simName = mSubController.getActiveSubscriptionInfo( 530 subId, mContext.getOpPackageName()).getDisplayName().toString(); 531 if (TextUtils.isEmpty(simName)) { 532 // Fall back to carrier name. 533 simName = phone.getCarrierName(); 534 } 535 simNames.add(simName); 536 } 537 } 538 539 if (cdmaPhoneCount > 1) { 540 params.mWarningType = EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA; 541 params.mSimNames = String.join(" & ", simNames); 542 } 543 544 return params; 545 } 546 isUserVisibleChange(int change)547 private boolean isUserVisibleChange(int change) { 548 return (change == PRIMARY_SUB_ADDED || change == PRIMARY_SUB_REMOVED 549 || change == PRIMARY_SUB_SWAPPED); 550 } 551 disableDataForNonDefaultNonOpportunisticSubscriptions()552 private void disableDataForNonDefaultNonOpportunisticSubscriptions() { 553 if (!mSubInfoInitialized) return; 554 555 int defaultDataSub = mSubController.getDefaultDataSubId(); 556 557 for (Phone phone : PhoneFactory.getPhones()) { 558 if (phone.getSubId() != defaultDataSub 559 && SubscriptionManager.isValidSubscriptionId(phone.getSubId()) 560 && !mSubController.isOpportunistic(phone.getSubId()) 561 && phone.isUserDataEnabled() 562 && !areSubscriptionsInSameGroup(defaultDataSub, phone.getSubId())) { 563 log("setting data to false on " + phone.getSubId()); 564 phone.getDataEnabledSettings().setUserDataEnabled(false); 565 } 566 } 567 } 568 areSubscriptionsInSameGroup(int subId1, int subId2)569 private boolean areSubscriptionsInSameGroup(int subId1, int subId2) { 570 if (!SubscriptionManager.isUsableSubscriptionId(subId1) 571 || !SubscriptionManager.isUsableSubscriptionId(subId2)) return false; 572 if (subId1 == subId2) return true; 573 574 ParcelUuid groupUuid1 = mSubController.getGroupUuid(subId1); 575 ParcelUuid groupUuid2 = mSubController.getGroupUuid(subId2); 576 return groupUuid1 != null && groupUuid1.equals(groupUuid2); 577 } 578 579 /** 580 * Make sure MOBILE_DATA of subscriptions in the same group with the subId 581 * are synced. 582 */ setUserDataEnabledForGroup(int subId, boolean enable)583 private void setUserDataEnabledForGroup(int subId, boolean enable) { 584 log("setUserDataEnabledForGroup subId " + subId + " enable " + enable); 585 List<SubscriptionInfo> infoList = mSubController.getSubscriptionsInGroup( 586 mSubController.getGroupUuid(subId), mContext.getOpPackageName()); 587 588 if (infoList == null) return; 589 590 for (SubscriptionInfo info : infoList) { 591 int currentSubId = info.getSubscriptionId(); 592 // TODO: simplify when setUserDataEnabled becomes singleton 593 if (mSubController.isActiveSubId(currentSubId)) { 594 // For active subscription, call setUserDataEnabled through DataEnabledSettings. 595 Phone phone = PhoneFactory.getPhone(mSubController.getPhoneId(currentSubId)); 596 // If enable is true and it's not opportunistic subscription, we don't enable it, 597 // as there can't e two 598 if (phone != null) { 599 phone.getDataEnabledSettings().setUserDataEnabled(enable); 600 } 601 } else { 602 // For inactive subscription, directly write into global settings. 603 GlobalSettingsHelper.setBoolean( 604 mContext, Settings.Global.MOBILE_DATA, currentSubId, enable); 605 } 606 } 607 } 608 609 /** 610 * Make sure DATA_ROAMING of subscriptions in the same group with the subId 611 * are synced. 612 */ setRoamingDataEnabledForGroup(int subId, boolean enable)613 private void setRoamingDataEnabledForGroup(int subId, boolean enable) { 614 SubscriptionController subController = SubscriptionController.getInstance(); 615 List<SubscriptionInfo> infoList = subController.getSubscriptionsInGroup( 616 mSubController.getGroupUuid(subId), mContext.getOpPackageName()); 617 618 if (infoList == null) return; 619 620 for (SubscriptionInfo info : infoList) { 621 // For inactive subscription, directly write into global settings. 622 GlobalSettingsHelper.setBoolean( 623 mContext, Settings.Global.DATA_ROAMING, info.getSubscriptionId(), enable); 624 } 625 } 626 627 private interface UpdateDefaultAction { update(int newValue)628 void update(int newValue); 629 } 630 631 // Returns whether the new default value is valid. updateDefaultValue(List<Integer> primarySubList, int oldValue, UpdateDefaultAction action)632 private boolean updateDefaultValue(List<Integer> primarySubList, int oldValue, 633 UpdateDefaultAction action) { 634 int newValue = INVALID_SUBSCRIPTION_ID; 635 636 if (primarySubList.size() > 0) { 637 for (int subId : primarySubList) { 638 if (DBG) log("[updateDefaultValue] Record.id: " + subId); 639 // If the old subId is still active, or there's another active primary subscription 640 // that is in the same group, that should become the new default subscription. 641 if (areSubscriptionsInSameGroup(subId, oldValue)) { 642 newValue = subId; 643 log("[updateDefaultValue] updates to subId=" + newValue); 644 break; 645 } 646 } 647 } 648 649 if (oldValue != newValue) { 650 if (DBG) log("[updateDefaultValue: subId] from " + oldValue + " to " + newValue); 651 action.update(newValue); 652 } 653 654 return SubscriptionManager.isValidSubscriptionId(newValue); 655 } 656 log(String msg)657 private void log(String msg) { 658 Log.d(LOG_TAG, msg); 659 } 660 loge(String msg)661 private void loge(String msg) { 662 Log.e(LOG_TAG, msg); 663 } 664 } 665