• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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