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