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