• 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_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