1 /*
2 * Copyright (C) 2014 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.content.pm.PackageManager.PERMISSION_GRANTED;
20 import static android.telephony.TelephonyManager.MULTISIM_ALLOWED;
21 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION;
22 import static android.telephony.UiccSlotInfo.CARD_STATE_INFO_PRESENT;
23 
24 import android.Manifest;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.app.AppOpsManager;
28 import android.app.PendingIntent;
29 import android.compat.annotation.UnsupportedAppUsage;
30 import android.content.ContentResolver;
31 import android.content.ContentValues;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.graphics.BitmapFactory;
37 import android.net.Uri;
38 import android.os.Binder;
39 import android.os.Handler;
40 import android.os.ParcelUuid;
41 import android.os.RegistrantList;
42 import android.os.RemoteException;
43 import android.os.TelephonyServiceManager.ServiceRegisterer;
44 import android.os.UserHandle;
45 import android.provider.Settings;
46 import android.telecom.PhoneAccountHandle;
47 import android.telecom.TelecomManager;
48 import android.telephony.AnomalyReporter;
49 import android.telephony.CarrierConfigManager;
50 import android.telephony.RadioAccessFamily;
51 import android.telephony.SubscriptionInfo;
52 import android.telephony.SubscriptionManager;
53 import android.telephony.SubscriptionManager.SimDisplayNameSource;
54 import android.telephony.TelephonyFrameworkInitializer;
55 import android.telephony.TelephonyManager;
56 import android.telephony.TelephonyRegistryManager;
57 import android.telephony.UiccAccessRule;
58 import android.telephony.UiccSlotInfo;
59 import android.telephony.euicc.EuiccManager;
60 import android.text.TextUtils;
61 import android.util.LocalLog;
62 import android.util.Log;
63 
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.internal.telephony.IccCardConstants.State;
66 import com.android.internal.telephony.dataconnection.DataEnabledOverride;
67 import com.android.internal.telephony.metrics.TelephonyMetrics;
68 import com.android.internal.telephony.uicc.IccUtils;
69 import com.android.internal.telephony.uicc.UiccCard;
70 import com.android.internal.telephony.uicc.UiccController;
71 import com.android.internal.telephony.uicc.UiccSlot;
72 import com.android.internal.telephony.util.ArrayUtils;
73 import com.android.internal.telephony.util.TelephonyUtils;
74 import com.android.telephony.Rlog;
75 
76 import java.io.FileDescriptor;
77 import java.io.PrintWriter;
78 import java.util.ArrayList;
79 import java.util.Arrays;
80 import java.util.Collections;
81 import java.util.Comparator;
82 import java.util.HashSet;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Map.Entry;
86 import java.util.Objects;
87 import java.util.Set;
88 import java.util.UUID;
89 import java.util.concurrent.ConcurrentHashMap;
90 import java.util.concurrent.atomic.AtomicBoolean;
91 import java.util.stream.Collectors;
92 
93 /**
94  * Implementation of the ISub interface.
95  *
96  * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the
97  * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
98  *
99  * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling
100  * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()).
101  *
102  * Finally, any getters which perform the mapping between subscriptions, slots and phones will
103  * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters
104  * will fail and return the appropriate error value. Ie calling
105  * getSlotIndex(INVALID_SUBSCRIPTION_ID) will return INVALID_SIM_SLOT_INDEX and calling
106  * getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID) will return null.
107  *
108  */
109 public class SubscriptionController extends ISub.Stub {
110     private static final String LOG_TAG = "SubscriptionController";
111     private static final boolean DBG = true;
112     private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
113     private static final boolean DBG_CACHE = false;
114     private static final int DEPRECATED_SETTING = -1;
115     private static final ParcelUuid INVALID_GROUP_UUID =
116             ParcelUuid.fromString(CarrierConfigManager.REMOVE_GROUP_UUID_STRING);
117     private final LocalLog mLocalLog = new LocalLog(200);
118     private static final int SUB_ID_FOUND = 1;
119     private static final int NO_ENTRY_FOR_SLOT_INDEX = -1;
120     private static final int SUB_ID_NOT_IN_SLOT = -2;
121 
122     // Lock that both mCacheActiveSubInfoList and mCacheOpportunisticSubInfoList use.
123     private Object mSubInfoListLock = new Object();
124 
125     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
126     private final List<SubscriptionInfo> mCacheActiveSubInfoList = new ArrayList<>();
127 
128     /* Similar to mCacheActiveSubInfoList but only caching opportunistic subscriptions. */
129     private List<SubscriptionInfo> mCacheOpportunisticSubInfoList = new ArrayList<>();
130     private AtomicBoolean mOpptSubInfoListChangedDirtyBit = new AtomicBoolean();
131 
132     private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
133             (arg0, arg1) -> {
134                 // Primary sort key on SimSlotIndex
135                 int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
136                 if (flag == 0) {
137                     // Secondary sort on SubscriptionId
138                     return arg0.getSubscriptionId() - arg1.getSubscriptionId();
139                 }
140                 return flag;
141             };
142 
143     @UnsupportedAppUsage
144     protected final Object mLock = new Object();
145 
146     /** The singleton instance. */
147     protected static SubscriptionController sInstance = null;
148     @UnsupportedAppUsage
149     protected Context mContext;
150     protected TelephonyManager mTelephonyManager;
151     protected UiccController mUiccController;
152 
153     private AppOpsManager mAppOps;
154 
155     // Allows test mocks to avoid SELinux failures on invalidate calls.
156     private static boolean sCachingEnabled = true;
157 
158     // Each slot can have multiple subs.
159     private static class WatchedSlotIndexToSubIds {
160         private Map<Integer, ArrayList<Integer>> mSlotIndexToSubIds = new ConcurrentHashMap<>();
161 
WatchedSlotIndexToSubIds()162         WatchedSlotIndexToSubIds() {
163         }
164 
clear()165         public void clear() {
166             mSlotIndexToSubIds.clear();
167             invalidateDefaultSubIdCaches();
168             invalidateSlotIndexCaches();
169         }
170 
entrySet()171         public Set<Entry<Integer, ArrayList<Integer>>> entrySet() {
172             return mSlotIndexToSubIds.entrySet();
173         }
174 
175         // Force all updates to data structure through wrapper.
getCopy(int slotIndex)176         public ArrayList<Integer> getCopy(int slotIndex) {
177             ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
178             if (subIdList == null) {
179                 return null;
180             }
181 
182             return new ArrayList<Integer>(subIdList);
183         }
184 
put(int slotIndex, ArrayList<Integer> value)185         public void put(int slotIndex, ArrayList<Integer> value) {
186             mSlotIndexToSubIds.put(slotIndex, value);
187             invalidateDefaultSubIdCaches();
188             invalidateSlotIndexCaches();
189         }
190 
remove(int slotIndex)191         public void remove(int slotIndex) {
192             mSlotIndexToSubIds.remove(slotIndex);
193             invalidateDefaultSubIdCaches();
194             invalidateSlotIndexCaches();
195         }
196 
size()197         public int size() {
198             return mSlotIndexToSubIds.size();
199         }
200 
201         @VisibleForTesting
getMap()202         public Map<Integer, ArrayList<Integer>> getMap() {
203             return mSlotIndexToSubIds;
204         }
205 
removeFromSubIdList(int slotIndex, int subId)206         public int removeFromSubIdList(int slotIndex, int subId) {
207             ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
208             if (subIdList == null) {
209                 return NO_ENTRY_FOR_SLOT_INDEX;
210             } else {
211                 if (subIdList.contains(subId)) {
212                     subIdList.remove(new Integer(subId));
213                     if (subIdList.isEmpty()) {
214                         mSlotIndexToSubIds.remove(slotIndex);
215                     }
216                     invalidateDefaultSubIdCaches();
217                     invalidateSlotIndexCaches();
218                     return SUB_ID_FOUND;
219                 } else {
220                     return SUB_ID_NOT_IN_SLOT;
221                 }
222             }
223         }
224 
addToSubIdList(int slotIndex, Integer value)225         public void addToSubIdList(int slotIndex, Integer value) {
226             ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
227             if (subIdList == null) {
228                 subIdList = new ArrayList<Integer>();
229                 subIdList.add(value);
230                 mSlotIndexToSubIds.put(slotIndex, subIdList);
231             } else {
232                 subIdList.add(value);
233             }
234             invalidateDefaultSubIdCaches();
235             invalidateSlotIndexCaches();
236         }
237 
clearSubIdList(int slotIndex)238         public void clearSubIdList(int slotIndex) {
239             ArrayList<Integer> subIdList = mSlotIndexToSubIds.get(slotIndex);
240             if (subIdList != null) {
241                 subIdList.clear();
242                 invalidateDefaultSubIdCaches();
243                 invalidateSlotIndexCaches();
244             }
245         }
246     }
247 
248     public static class WatchedInt {
249         private int mValue;
250 
WatchedInt(int initialValue)251         public WatchedInt(int initialValue) {
252             mValue = initialValue;
253         }
254 
get()255         public int get() {
256             return mValue;
257         }
258 
set(int newValue)259         public void set(int newValue) {
260             mValue = newValue;
261         }
262     }
263 
264     private static WatchedSlotIndexToSubIds sSlotIndexToSubIds = new WatchedSlotIndexToSubIds();
265 
266     protected static WatchedInt sDefaultFallbackSubId =
267             new WatchedInt(SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
268         @Override
269         public void set(int newValue) {
270             super.set(newValue);
271             invalidateDefaultSubIdCaches();
272             invalidateSlotIndexCaches();
273         }
274     };
275 
276     @UnsupportedAppUsage
277     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
278 
279     @UnsupportedAppUsage
280     private int[] colorArr;
281     private long mLastISubServiceRegTime;
282     private RegistrantList mUiccAppsEnableChangeRegList = new RegistrantList();
283 
284     // The properties that should be shared and synced across grouped subscriptions.
285     private static final Set<String> GROUP_SHARING_PROPERTIES = new HashSet<>(Arrays.asList(
286             SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
287             SubscriptionManager.VT_IMS_ENABLED,
288             SubscriptionManager.WFC_IMS_ENABLED,
289             SubscriptionManager.WFC_IMS_MODE,
290             SubscriptionManager.WFC_IMS_ROAMING_MODE,
291             SubscriptionManager.WFC_IMS_ROAMING_ENABLED,
292             SubscriptionManager.DATA_ROAMING,
293             SubscriptionManager.DISPLAY_NAME,
294             SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES,
295             SubscriptionManager.UICC_APPLICATIONS_ENABLED,
296             SubscriptionManager.IMS_RCS_UCE_ENABLED));
297 
init(Context c)298     public static SubscriptionController init(Context c) {
299         synchronized (SubscriptionController.class) {
300             if (sInstance == null) {
301                 sInstance = new SubscriptionController(c);
302             } else {
303                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
304             }
305             return sInstance;
306         }
307     }
308 
309     @UnsupportedAppUsage
getInstance()310     public static SubscriptionController getInstance() {
311         if (sInstance == null) {
312            Log.wtf(LOG_TAG, "getInstance null");
313         }
314 
315         return sInstance;
316     }
317 
SubscriptionController(Context c)318     protected SubscriptionController(Context c) {
319         internalInit(c);
320         migrateImsSettings();
321     }
322 
internalInit(Context c)323     protected void internalInit(Context c) {
324         mContext = c;
325         mTelephonyManager = TelephonyManager.from(mContext);
326 
327         try {
328             mUiccController = UiccController.getInstance();
329         } catch(RuntimeException ex) {
330             throw new RuntimeException(
331                     "UiccController has to be initialised before SubscriptionController init");
332         }
333 
334         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
335 
336         ServiceRegisterer subscriptionServiceRegisterer = TelephonyFrameworkInitializer
337                 .getTelephonyServiceManager()
338                 .getSubscriptionServiceRegisterer();
339         if (subscriptionServiceRegisterer.get() == null) {
340             subscriptionServiceRegisterer.register(this);
341             mLastISubServiceRegTime = System.currentTimeMillis();
342         }
343 
344         // clear SLOT_INDEX for all subs
345         clearSlotIndexForSubInfoRecords();
346 
347         // Cache Setting values
348         cacheSettingValues();
349 
350         // Initial invalidate activates caching.
351         invalidateDefaultSubIdCaches();
352         invalidateDefaultDataSubIdCaches();
353         invalidateDefaultSmsSubIdCaches();
354         invalidateActiveDataSubIdCaches();
355         invalidateSlotIndexCaches();
356 
357         if (DBG) logdl("[SubscriptionController] init by Context");
358     }
359 
360     /**
361      * Should only be triggered once.
362      */
notifySubInfoReady()363     public void notifySubInfoReady() {
364         // broadcast default subId.
365         sendDefaultChangedBroadcast(SubscriptionManager.getDefaultSubscriptionId());
366     }
367 
368     @UnsupportedAppUsage
isSubInfoReady()369     private boolean isSubInfoReady() {
370         return SubscriptionInfoUpdater.isSubInfoInitialized();
371     }
372 
373     /**
374      * This function marks SIM_SLOT_INDEX as INVALID for all subscriptions in the database. This
375      * should be done as part of initialization.
376      *
377      * TODO: SIM_SLOT_INDEX is based on current state and should not even be persisted in the
378      * database.
379      */
clearSlotIndexForSubInfoRecords()380     private void clearSlotIndexForSubInfoRecords() {
381         if (mContext == null) {
382             logel("[clearSlotIndexForSubInfoRecords] TelephonyManager or mContext is null");
383             return;
384         }
385 
386         // Update all subscriptions in simInfo db with invalid slot index
387         ContentValues value = new ContentValues(1);
388         value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
389         mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value, null, null);
390     }
391 
392     /**
393      * Cache the Settings values by reading these values from Setting from disk to prevent disk I/O
394      * access during the API calling. This is based on an assumption that the Settings system will
395      * itself cache this value after the first read and thus only the first read after boot will
396      * access the disk.
397      */
cacheSettingValues()398     private void cacheSettingValues() {
399         Settings.Global.getInt(mContext.getContentResolver(),
400                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
401                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
402 
403         Settings.Global.getInt(mContext.getContentResolver(),
404                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
405                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
406 
407         Settings.Global.getInt(mContext.getContentResolver(),
408                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
409                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
410     }
411 
412     @UnsupportedAppUsage
enforceModifyPhoneState(String message)413     protected void enforceModifyPhoneState(String message) {
414         mContext.enforceCallingOrSelfPermission(
415                 android.Manifest.permission.MODIFY_PHONE_STATE, message);
416     }
417 
enforceReadPrivilegedPhoneState(String message)418     private void enforceReadPrivilegedPhoneState(String message) {
419         mContext.enforceCallingOrSelfPermission(
420                 Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
421     }
422 
423     /**
424      * Returns whether the {@code callingPackage} has access to subscriber identifiers on the
425      * specified {@code subId} using the provided {@code message} in any resulting
426      * SecurityException.
427      */
hasSubscriberIdentifierAccess(int subId, String callingPackage, String callingFeatureId, String message)428     private boolean hasSubscriberIdentifierAccess(int subId, String callingPackage,
429             String callingFeatureId, String message) {
430         try {
431             return TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mContext, subId,
432                     callingPackage, callingFeatureId, message);
433         } catch (SecurityException e) {
434             // A SecurityException indicates that the calling package is targeting at least the
435             // minimum level that enforces identifier access restrictions and the new access
436             // requirements are not met.
437             return false;
438         }
439     }
440 
441     /**
442      * Returns whether the {@code callingPackage} has access to the phone number on the specified
443      * {@code subId} using the provided {@code message} in any resulting SecurityException.
444      */
hasPhoneNumberAccess(int subId, String callingPackage, String callingFeatureId, String message)445     private boolean hasPhoneNumberAccess(int subId, String callingPackage, String callingFeatureId,
446             String message) {
447         try {
448             return TelephonyPermissions.checkCallingOrSelfReadPhoneNumber(mContext, subId,
449                     callingPackage, callingFeatureId, message);
450         } catch (SecurityException e) {
451             return false;
452         }
453     }
454 
455     /**
456      * Broadcast when SubscriptionInfo has changed
457      * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
458      */
broadcastSimInfoContentChanged()459      private void broadcastSimInfoContentChanged() {
460         Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
461         mContext.sendBroadcast(intent);
462         intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
463         mContext.sendBroadcast(intent);
464      }
465 
466     /**
467      * Notify the changed of subscription info.
468      */
469     @UnsupportedAppUsage
notifySubscriptionInfoChanged()470     public void notifySubscriptionInfoChanged() {
471         TelephonyRegistryManager trm =
472                 (TelephonyRegistryManager)
473                         mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
474         if (DBG) logd("notifySubscriptionInfoChanged:");
475         trm.notifySubscriptionInfoChanged();
476 
477         // FIXME: Remove if listener technique accepted.
478         broadcastSimInfoContentChanged();
479 
480         MultiSimSettingController.getInstance().notifySubscriptionInfoChanged();
481         TelephonyMetrics metrics = TelephonyMetrics.getInstance();
482         List<SubscriptionInfo> subInfos;
483         synchronized (mSubInfoListLock) {
484             subInfos = new ArrayList<>(mCacheActiveSubInfoList);
485         }
486 
487         if (mOpptSubInfoListChangedDirtyBit.getAndSet(false)) {
488             notifyOpportunisticSubscriptionInfoChanged();
489         }
490         metrics.updateActiveSubscriptionInfoList(subInfos);
491         for (Phone phone : PhoneFactory.getPhones()) {
492             phone.getVoiceCallSessionStats().onActiveSubscriptionInfoChanged(subInfos);
493         }
494     }
495 
496     /**
497      * New SubInfoRecord instance and fill in detail info
498      * @param cursor
499      * @return the query result of desired SubInfoRecord
500      */
501     @UnsupportedAppUsage
getSubInfoRecord(Cursor cursor)502     private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
503         int id = cursor.getInt(cursor.getColumnIndexOrThrow(
504                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
505         String iccId = cursor.getString(cursor.getColumnIndexOrThrow(
506                 SubscriptionManager.ICC_ID));
507         int simSlotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
508                 SubscriptionManager.SIM_SLOT_INDEX));
509         String displayName = cursor.getString(cursor.getColumnIndexOrThrow(
510                 SubscriptionManager.DISPLAY_NAME));
511         String carrierName = cursor.getString(cursor.getColumnIndexOrThrow(
512                 SubscriptionManager.CARRIER_NAME));
513         int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
514                 SubscriptionManager.NAME_SOURCE));
515         int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
516                 SubscriptionManager.HUE));
517         String number = cursor.getString(cursor.getColumnIndexOrThrow(
518                 SubscriptionManager.NUMBER));
519         int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
520                 SubscriptionManager.DATA_ROAMING));
521         // Get the blank bitmap for this SubInfoRecord
522         Bitmap iconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
523                 com.android.internal.R.drawable.ic_sim_card_multi_24px_clr);
524         String mcc = cursor.getString(cursor.getColumnIndexOrThrow(
525                 SubscriptionManager.MCC_STRING));
526         String mnc = cursor.getString(cursor.getColumnIndexOrThrow(
527                 SubscriptionManager.MNC_STRING));
528         String ehplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow(
529                 SubscriptionManager.EHPLMNS));
530         String hplmnsRaw = cursor.getString(cursor.getColumnIndexOrThrow(
531                 SubscriptionManager.HPLMNS));
532         String[] ehplmns = ehplmnsRaw == null ? null : ehplmnsRaw.split(",");
533         String[] hplmns = hplmnsRaw == null ? null : hplmnsRaw.split(",");
534 
535         // cardId is the private ICCID/EID string, also known as the card string
536         String cardId = cursor.getString(cursor.getColumnIndexOrThrow(
537                 SubscriptionManager.CARD_ID));
538         String countryIso = cursor.getString(cursor.getColumnIndexOrThrow(
539                 SubscriptionManager.ISO_COUNTRY_CODE));
540         // publicCardId is the publicly exposed int card ID
541         int publicCardId = mUiccController.convertToPublicCardId(cardId);
542         boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
543                 SubscriptionManager.IS_EMBEDDED)) == 1;
544         int carrierId = cursor.getInt(cursor.getColumnIndexOrThrow(
545                 SubscriptionManager.CARRIER_ID));
546         UiccAccessRule[] accessRules;
547         if (isEmbedded) {
548             accessRules = UiccAccessRule.decodeRules(cursor.getBlob(
549                     cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)));
550         } else {
551             accessRules = null;
552         }
553         UiccAccessRule[] carrierConfigAccessRules = UiccAccessRule.decodeRules(cursor.getBlob(
554             cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES_FROM_CARRIER_CONFIGS)));
555         boolean isOpportunistic = cursor.getInt(cursor.getColumnIndexOrThrow(
556                 SubscriptionManager.IS_OPPORTUNISTIC)) == 1;
557         String groupUUID = cursor.getString(cursor.getColumnIndexOrThrow(
558                 SubscriptionManager.GROUP_UUID));
559         int profileClass = cursor.getInt(cursor.getColumnIndexOrThrow(
560                 SubscriptionManager.PROFILE_CLASS));
561         int subType = cursor.getInt(cursor.getColumnIndexOrThrow(
562                 SubscriptionManager.SUBSCRIPTION_TYPE));
563         String groupOwner = getOptionalStringFromCursor(cursor, SubscriptionManager.GROUP_OWNER,
564                 /*defaultVal*/ null);
565         boolean areUiccApplicationsEnabled = cursor.getInt(cursor.getColumnIndexOrThrow(
566                 SubscriptionManager.UICC_APPLICATIONS_ENABLED)) == 1;
567 
568         if (VDBG) {
569             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
570             String cardIdToPrint = SubscriptionInfo.givePrintableIccid(cardId);
571             logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
572                     + simSlotIndex + " carrierid:" + carrierId + " displayName:" + displayName
573                     + " nameSource:" + nameSource + " iconTint:" + iconTint
574                     + " dataRoaming:" + dataRoaming + " mcc:" + mcc + " mnc:" + mnc
575                     + " countIso:" + countryIso + " isEmbedded:"
576                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
577                     + " carrierConfigAccessRules: " + Arrays.toString(carrierConfigAccessRules)
578                     + " cardId:" + cardIdToPrint + " publicCardId:" + publicCardId
579                     + " isOpportunistic:" + isOpportunistic + " groupUUID:" + groupUUID
580                     + " profileClass:" + profileClass + " subscriptionType: " + subType
581                     + " carrierConfigAccessRules:" + carrierConfigAccessRules
582                     + " areUiccApplicationsEnabled: " + areUiccApplicationsEnabled);
583         }
584 
585         // If line1number has been set to a different number, use it instead.
586         String line1Number = mTelephonyManager.getLine1Number(id);
587         if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
588             number = line1Number;
589         }
590         SubscriptionInfo info = new SubscriptionInfo(id, iccId, simSlotIndex, displayName,
591                 carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc,
592                 countryIso, isEmbedded, accessRules, cardId, publicCardId, isOpportunistic,
593                 groupUUID, false /* isGroupDisabled */, carrierId, profileClass, subType,
594                 groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled);
595         info.setAssociatedPlmns(ehplmns, hplmns);
596         return info;
597     }
598 
getOptionalStringFromCursor(Cursor cursor, String column, String defaultVal)599     private String getOptionalStringFromCursor(Cursor cursor, String column, String defaultVal) {
600         // Return defaultVal if the column doesn't exist.
601         int columnIndex = cursor.getColumnIndex(column);
602         return (columnIndex == -1) ? defaultVal : cursor.getString(columnIndex);
603     }
604 
605     /**
606      * Get a subscription that matches IccId.
607      * @return null if there isn't a match, or subscription info if there is one.
608      */
getSubInfoForIccId(String iccId)609     public SubscriptionInfo getSubInfoForIccId(String iccId) {
610         List<SubscriptionInfo> info = getSubInfo(
611                 SubscriptionManager.ICC_ID + "=\'" + iccId + "\'", null);
612         if (info == null || info.size() == 0) return null;
613         // Should be at most one subscription with the iccid.
614         return info.get(0);
615     }
616 
617     /**
618      * Query SubInfoRecord(s) from subinfo database
619      * @param selection A filter declaring which rows to return
620      * @param queryKey query key content
621      * @return Array list of queried result from database
622      */
623     @UnsupportedAppUsage
getSubInfo(String selection, Object queryKey)624     public List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
625         if (VDBG) logd("selection:" + selection + ", querykey: " + queryKey);
626         String[] selectionArgs = null;
627         if (queryKey != null) {
628             selectionArgs = new String[] {queryKey.toString()};
629         }
630         ArrayList<SubscriptionInfo> subList = null;
631         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
632                 null, selection, selectionArgs, null);
633         try {
634             if (cursor != null) {
635                 while (cursor.moveToNext()) {
636                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
637                     if (subInfo != null) {
638                         if (subList == null) {
639                             subList = new ArrayList<SubscriptionInfo>();
640                         }
641                         subList.add(subInfo);
642                     }
643                 }
644             } else {
645                 if (DBG) logd("Query fail");
646             }
647         } finally {
648             if (cursor != null) {
649                 cursor.close();
650             }
651         }
652 
653         return subList;
654     }
655 
656     /**
657      * Find unused color to be set for new SubInfoRecord
658      * @param callingPackage The package making the IPC.
659      * @param callingFeatureId The feature in the package
660      * @return RGB integer value of color
661      */
getUnusedColor(String callingPackage, String callingFeatureId)662     private int getUnusedColor(String callingPackage, String callingFeatureId) {
663         List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage,
664                 callingFeatureId);
665         colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
666         int colorIdx = 0;
667 
668         if (availableSubInfos != null) {
669             for (int i = 0; i < colorArr.length; i++) {
670                 int j;
671                 for (j = 0; j < availableSubInfos.size(); j++) {
672                     if (colorArr[i] == availableSubInfos.get(j).getIconTint()) {
673                         break;
674                     }
675                 }
676                 if (j == availableSubInfos.size()) {
677                     return colorArr[i];
678                 }
679             }
680             colorIdx = availableSubInfos.size() % colorArr.length;
681         }
682         return colorArr[colorIdx];
683     }
684 
685     @Deprecated
686     @UnsupportedAppUsage
getActiveSubscriptionInfo(int subId, String callingPackage)687     public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
688         return getActiveSubscriptionInfo(subId, callingPackage, null);
689     }
690 
691     /**
692      * Get the active SubscriptionInfo with the subId key
693      * @param subId The unique SubscriptionInfo key in database
694      * @param callingPackage The package making the IPC.
695      * @param callingFeatureId The feature in the package
696      * @return SubscriptionInfo, maybe null if its not active
697      */
698     @Override
getActiveSubscriptionInfo(int subId, String callingPackage, String callingFeatureId)699     public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage,
700             String callingFeatureId) {
701         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
702                 callingFeatureId, "getActiveSubscriptionInfo")) {
703             return null;
704         }
705 
706         // Now that all security checks passes, perform the operation as ourselves.
707         final long identity = Binder.clearCallingIdentity();
708         List<SubscriptionInfo> subList;
709         try {
710             subList = getActiveSubscriptionInfoList(
711                     mContext.getOpPackageName(), mContext.getAttributionTag());
712         } finally {
713             Binder.restoreCallingIdentity(identity);
714         }
715         if (subList != null) {
716             for (SubscriptionInfo si : subList) {
717                 if (si.getSubscriptionId() == subId) {
718                     if (VDBG) {
719                         logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
720                     }
721                     return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
722                             "getActiveSubscriptionInfo");
723                 }
724             }
725         }
726         if (DBG) {
727             logd("[getActiveSubscriptionInfo]- subId=" + subId
728                     + " subList=" + subList + " subInfo=null");
729         }
730 
731         return null;
732     }
733 
734     /**
735      * Get a single subscription info record for a given subscription.
736      *
737      * @param subId the subId to query.
738      *
739      * @hide
740      */
getSubscriptionInfo(int subId)741     public SubscriptionInfo getSubscriptionInfo(int subId) {
742         // check cache for active subscriptions first, before querying db
743         for (SubscriptionInfo subInfo : mCacheActiveSubInfoList) {
744             if (subInfo.getSubscriptionId() == subId) {
745                 return subInfo;
746             }
747         }
748         // check cache for opportunistic subscriptions too, before querying db
749         for (SubscriptionInfo subInfo : mCacheOpportunisticSubInfoList) {
750             if (subInfo.getSubscriptionId() == subId) {
751                 return subInfo;
752             }
753         }
754 
755         List<SubscriptionInfo> subInfoList = getSubInfo(
756                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
757         if (subInfoList == null || subInfoList.isEmpty()) return null;
758         return subInfoList.get(0);
759     }
760 
761     /**
762      * Get the active SubscriptionInfo associated with the iccId
763      * @param iccId the IccId of SIM card
764      * @param callingPackage The package making the IPC.
765      * @param callingFeatureId The feature in the package
766      * @return SubscriptionInfo, maybe null if its not active
767      */
768     @Override
getActiveSubscriptionInfoForIccId(String iccId, String callingPackage, String callingFeatureId)769     public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage,
770             String callingFeatureId) {
771         enforceReadPrivilegedPhoneState("getActiveSubscriptionInfoForIccId");
772         return getActiveSubscriptionInfoForIccIdInternal(iccId);
773     }
774 
775     /**
776      * Get the active SubscriptionInfo associated with the given iccId. The caller *must* perform
777      * permission checks when using this method.
778      */
getActiveSubscriptionInfoForIccIdInternal(String iccId)779     private SubscriptionInfo getActiveSubscriptionInfoForIccIdInternal(String iccId) {
780         if (iccId == null) {
781             return null;
782         }
783 
784         final long identity = Binder.clearCallingIdentity();
785         try {
786             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
787                     mContext.getOpPackageName(), mContext.getAttributionTag());
788             if (subList != null) {
789                 for (SubscriptionInfo si : subList) {
790                     if (iccId.equals(si.getIccId())) {
791                         if (DBG)
792                             logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si);
793                         return si;
794                     }
795                 }
796             }
797             if (DBG) {
798                 logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId
799                         + " subList=" + subList + " subInfo=null");
800             }
801         } finally {
802             Binder.restoreCallingIdentity(identity);
803         }
804 
805         return null;
806     }
807 
808     /**
809      * Get the active SubscriptionInfo associated with the slotIndex.
810      * This API does not return details on Remote-SIM subscriptions.
811      * @param slotIndex the slot which the subscription is inserted
812      * @param callingPackage The package making the IPC.
813      * @param callingFeatureId The feature in the package
814      * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
815      */
816     @Override
getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage, String callingFeatureId)817     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
818             String callingPackage, String callingFeatureId) {
819         Phone phone = PhoneFactory.getPhone(slotIndex);
820         if (phone == null) {
821             if (DBG) {
822                 loge("[getActiveSubscriptionInfoForSimSlotIndex] no phone, slotIndex=" + slotIndex);
823             }
824             return null;
825         }
826         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
827                 mContext, phone.getSubId(), callingPackage, callingFeatureId,
828                 "getActiveSubscriptionInfoForSimSlotIndex")) {
829             return null;
830         }
831 
832         // Now that all security checks passes, perform the operation as ourselves.
833         final long identity = Binder.clearCallingIdentity();
834         List<SubscriptionInfo> subList;
835         try {
836             subList = getActiveSubscriptionInfoList(
837                     mContext.getOpPackageName(), mContext.getAttributionTag());
838         } finally {
839             Binder.restoreCallingIdentity(identity);
840         }
841         if (subList != null) {
842             for (SubscriptionInfo si : subList) {
843                 if (si.getSimSlotIndex() == slotIndex) {
844                     if (DBG) {
845                         logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
846                                 + slotIndex + " subId=" + si);
847                     }
848                     return conditionallyRemoveIdentifiers(si, callingPackage, callingFeatureId,
849                             "getActiveSubscriptionInfoForSimSlotIndex");
850                 }
851             }
852             if (DBG) {
853                 logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex
854                         + " subId=null");
855             }
856         } else {
857             if (DBG) {
858                 logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
859             }
860         }
861 
862 
863         return null;
864     }
865 
866     /**
867      * @param callingPackage The package making the IPC.
868      * @param callingFeatureId The feature in the package
869      * @return List of all SubscriptionInfo records in database,
870      * include those that were inserted before, maybe empty but not null.
871      * @hide
872      */
873     @Override
getAllSubInfoList(String callingPackage, String callingFeatureId)874     public List<SubscriptionInfo> getAllSubInfoList(String callingPackage,
875             String callingFeatureId) {
876         if (VDBG) logd("[getAllSubInfoList]+");
877 
878         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
879         // about carrier-privileged callers not having access.
880         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
881                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
882                 callingFeatureId, "getAllSubInfoList")) {
883             return null;
884         }
885 
886         // Now that all security checks passes, perform the operation as ourselves.
887         final long identity = Binder.clearCallingIdentity();
888         try {
889             List<SubscriptionInfo> subList = null;
890             subList = getSubInfo(null, null);
891             if (subList != null) {
892                 if (VDBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
893             } else {
894                 if (VDBG) logd("[getAllSubInfoList]- no info return");
895             }
896             return subList;
897         } finally {
898             Binder.restoreCallingIdentity(identity);
899         }
900     }
901 
902     @Deprecated
903     @UnsupportedAppUsage
getActiveSubscriptionInfoList(String callingPackage)904     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
905         return getSubscriptionInfoListFromCacheHelper(callingPackage, null,
906                 mCacheActiveSubInfoList);
907     }
908 
909     /**
910      * Get the SubInfoRecord(s) of the currently active SIM(s) - which include both local
911      * and remote SIMs.
912      * @param callingPackage The package making the IPC.
913      * @param callingFeatureId The feature in the package
914      * @return Array list of currently inserted SubInfoRecord(s)
915      */
916     @Override
getActiveSubscriptionInfoList(String callingPackage, String callingFeatureId)917     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
918             String callingFeatureId) {
919         return getSubscriptionInfoListFromCacheHelper(callingPackage, callingFeatureId,
920                 mCacheActiveSubInfoList);
921     }
922 
923     /**
924      * Refresh the cache of SubInfoRecord(s) of the currently available SIM(s) - including
925      * local & remote SIMs.
926      */
927     @VisibleForTesting  // For mockito to mock this method
refreshCachedActiveSubscriptionInfoList()928     public void refreshCachedActiveSubscriptionInfoList() {
929         boolean opptSubListChanged;
930 
931         synchronized (mSubInfoListLock) {
932             List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
933                     SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
934                     + SubscriptionManager.SUBSCRIPTION_TYPE + "="
935                     + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM,
936                     null);
937 
938             if (activeSubscriptionInfoList != null) {
939                 // Log when active sub info changes.
940                 if (mCacheActiveSubInfoList.size() != activeSubscriptionInfoList.size()
941                         || !mCacheActiveSubInfoList.containsAll(activeSubscriptionInfoList)) {
942                     logdl("Active subscription info list changed. " + activeSubscriptionInfoList);
943                 }
944 
945                 mCacheActiveSubInfoList.clear();
946                 activeSubscriptionInfoList.sort(SUBSCRIPTION_INFO_COMPARATOR);
947                 mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
948             } else {
949                 logd("activeSubscriptionInfoList is null.");
950                 mCacheActiveSubInfoList.clear();
951             }
952 
953             // Refresh cached opportunistic sub list and detect whether it's changed.
954             refreshCachedOpportunisticSubscriptionInfoList();
955 
956             if (DBG_CACHE) {
957                 if (!mCacheActiveSubInfoList.isEmpty()) {
958                     for (SubscriptionInfo si : mCacheActiveSubInfoList) {
959                         logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached info="
960                                 + si);
961                     }
962                 } else {
963                     logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
964                 }
965             }
966         }
967     }
968 
969     @Deprecated
970     @UnsupportedAppUsage
getActiveSubInfoCount(String callingPackage)971     public int getActiveSubInfoCount(String callingPackage) {
972         return getActiveSubInfoCount(callingPackage, null);
973     }
974 
975     /**
976      * Get the SUB count of active SUB(s)
977      * @param callingPackage The package making the IPC.
978      * @param callingFeatureId The feature in the package.
979      * @return active SIM count
980      */
981     @Override
getActiveSubInfoCount(String callingPackage, String callingFeatureId)982     public int getActiveSubInfoCount(String callingPackage, String callingFeatureId) {
983         // Let getActiveSubscriptionInfoList perform permission checks / filtering.
984         List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage,
985                 callingFeatureId);
986         if (records == null) {
987             if (VDBG) logd("[getActiveSubInfoCount] records null");
988             return 0;
989         }
990         if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
991         return records.size();
992     }
993 
994     /**
995      * Get the SUB count of all SUB(s) in SubscriptoinInfo database
996      * @param callingPackage The package making the IPC.
997      * @param callingFeatureId The feature in the package
998      * @return all SIM count in database, include what was inserted before
999      */
1000     @Override
getAllSubInfoCount(String callingPackage, String callingFeatureId)1001     public int getAllSubInfoCount(String callingPackage, String callingFeatureId) {
1002         if (DBG) logd("[getAllSubInfoCount]+");
1003 
1004         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
1005         // about carrier-privileged callers not having access.
1006         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
1007                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
1008                 callingFeatureId, "getAllSubInfoCount")) {
1009             return 0;
1010         }
1011 
1012         // Now that all security checks passes, perform the operation as ourselves.
1013         final long identity = Binder.clearCallingIdentity();
1014         try {
1015             Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
1016                     null, null, null, null);
1017             try {
1018                 if (cursor != null) {
1019                     int count = cursor.getCount();
1020                     if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
1021                     return count;
1022                 }
1023             } finally {
1024                 if (cursor != null) {
1025                     cursor.close();
1026                 }
1027             }
1028             if (DBG) logd("[getAllSubInfoCount]- no SUB in DB");
1029 
1030             return 0;
1031         } finally {
1032             Binder.restoreCallingIdentity(identity);
1033         }
1034     }
1035 
1036     /**
1037      * @return the maximum number of local subscriptions this device will support at any one time.
1038      */
1039     @Override
getActiveSubInfoCountMax()1040     public int getActiveSubInfoCountMax() {
1041         // FIXME: This valid now but change to use TelephonyDevController in the future
1042         return mTelephonyManager.getSimCount();
1043     }
1044 
1045     @Override
getAvailableSubscriptionInfoList(String callingPackage, String callingFeatureId)1046     public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage,
1047             String callingFeatureId) {
1048         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
1049         // about carrier-privileged callers not having access.
1050         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
1051                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
1052                 callingFeatureId, "getAvailableSubscriptionInfoList")) {
1053             throw new SecurityException("Need READ_PHONE_STATE to call "
1054                     + " getAvailableSubscriptionInfoList");
1055         }
1056 
1057         // Now that all security checks pass, perform the operation as ourselves.
1058         final long identity = Binder.clearCallingIdentity();
1059         try {
1060             String selection = SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
1061                     + SubscriptionManager.SUBSCRIPTION_TYPE + "="
1062                     + SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
1063 
1064             EuiccManager euiccManager =
1065                     (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
1066             if (euiccManager.isEnabled()) {
1067                 selection += " OR " + SubscriptionManager.IS_EMBEDDED + "=1";
1068             }
1069 
1070             // Available eSIM profiles are reported by EuiccManager. However for physical SIMs if
1071             // they are in inactive slot or programmatically disabled, they are still considered
1072             // available. In this case we get their iccid from slot info and include their
1073             // subscriptionInfos.
1074             List<String> iccIds = getIccIdsOfInsertedPhysicalSims();
1075 
1076             if (!iccIds.isEmpty()) {
1077                 selection += " OR ("  + getSelectionForIccIdList(iccIds.toArray(new String[0]))
1078                         + ")";
1079             }
1080 
1081             List<SubscriptionInfo> subList = getSubInfo(selection, null /* queryKey */);
1082 
1083             if (subList != null) {
1084                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
1085 
1086                 if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return");
1087             } else {
1088                 if (DBG) logdl("[getAvailableSubInfoList]- no info return");
1089             }
1090 
1091             return subList;
1092         } finally {
1093             Binder.restoreCallingIdentity(identity);
1094         }
1095     }
1096 
getIccIdsOfInsertedPhysicalSims()1097     private List<String> getIccIdsOfInsertedPhysicalSims() {
1098         List<String> ret = new ArrayList<>();
1099         UiccSlot[] uiccSlots = UiccController.getInstance().getUiccSlots();
1100         if (uiccSlots == null) return ret;
1101 
1102         for (UiccSlot uiccSlot : uiccSlots) {
1103             if (uiccSlot != null && uiccSlot.getCardState() != null
1104                     && uiccSlot.getCardState().isCardPresent()
1105                     && !uiccSlot.isEuicc()
1106                     && !TextUtils.isEmpty(uiccSlot.getIccId())) {
1107                 ret.add(IccUtils.stripTrailingFs(uiccSlot.getIccId()));
1108             }
1109         }
1110 
1111         return ret;
1112     }
1113 
1114     @Override
getAccessibleSubscriptionInfoList(String callingPackage)1115     public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
1116         EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
1117         if (!euiccManager.isEnabled()) {
1118             if (DBG) {
1119                 logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled");
1120             }
1121             return null;
1122         }
1123 
1124         // Verify that the given package belongs to the calling UID.
1125         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
1126 
1127         // Perform the operation as ourselves. If the caller cannot read phone state, they may still
1128         // have carrier privileges per the subscription metadata, so we always need to make the
1129         // query and then filter the results.
1130         final long identity = Binder.clearCallingIdentity();
1131         List<SubscriptionInfo> subList;
1132         try {
1133             subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null);
1134         } finally {
1135             Binder.restoreCallingIdentity(identity);
1136         }
1137 
1138         if (subList == null) {
1139             if (DBG) logdl("[getAccessibleSubInfoList] No info returned");
1140             return null;
1141         }
1142 
1143         // Filter the list to only include subscriptions which the (restored) caller can manage.
1144         List<SubscriptionInfo> filteredList = subList.stream()
1145                 .filter(subscriptionInfo ->
1146                         subscriptionInfo.canManageSubscription(mContext, callingPackage))
1147                 .sorted(SUBSCRIPTION_INFO_COMPARATOR)
1148                 .collect(Collectors.toList());
1149         if (VDBG) {
1150             logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned");
1151         }
1152         return filteredList;
1153     }
1154 
1155     /**
1156      * Return the list of subscriptions in the database which are either:
1157      * <ul>
1158      * <li>Embedded (but see note about {@code includeNonRemovableSubscriptions}, or
1159      * <li>In the given list of current embedded ICCIDs (which may not yet be in the database, or
1160      *     which may not currently be marked as embedded).
1161      * </ul>
1162      *
1163      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
1164      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
1165      *
1166      * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface
1167      *     entries for profiles which had been previously deleted.
1168      * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions
1169      *     will only be returned if the current ICCID is not removable; otherwise, they are left
1170      *     alone (not returned here unless in the embeddedIccids list) under the assumption that
1171      *     they will still be accessible when the eUICC containing them is activated.
1172      */
1173     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getSubscriptionInfoListForEmbeddedSubscriptionUpdate( String[] embeddedIccids, boolean isEuiccRemovable)1174     public List<SubscriptionInfo> getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
1175             String[] embeddedIccids, boolean isEuiccRemovable) {
1176         StringBuilder whereClause = new StringBuilder();
1177         whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1");
1178         if (isEuiccRemovable) {
1179             // Current eUICC is removable, so don't return non-removable subscriptions (which would
1180             // be deleted), as these are expected to still be present on a different, non-removable
1181             // eUICC.
1182             whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1");
1183         }
1184         // Else, return both removable and non-removable subscriptions. This is expected to delete
1185         // all removable subscriptions, which is desired as they may not be accessible.
1186 
1187         whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN (");
1188         // ICCIDs are validated to contain only numbers when passed in, and come from a trusted
1189         // app, so no need to escape.
1190         for (int i = 0; i < embeddedIccids.length; i++) {
1191             if (i > 0) {
1192                 whereClause.append(",");
1193             }
1194             whereClause.append("\"").append(embeddedIccids[i]).append("\"");
1195         }
1196         whereClause.append(")");
1197 
1198         List<SubscriptionInfo> list = getSubInfo(whereClause.toString(), null);
1199         if (list == null) {
1200             return Collections.emptyList();
1201         }
1202         return list;
1203     }
1204 
1205     @Override
requestEmbeddedSubscriptionInfoListRefresh(int cardId)1206     public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
1207         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
1208                 "requestEmbeddedSubscriptionInfoListRefresh");
1209         long token = Binder.clearCallingIdentity();
1210         try {
1211             PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, null /* callback */);
1212         } finally {
1213             Binder.restoreCallingIdentity(token);
1214         }
1215     }
1216 
1217     /**
1218      * Asynchronously refresh the embedded subscription info list for the embedded card has the
1219      * given card id {@code cardId}.
1220      *
1221      * @param callback Optional callback to execute after the refresh completes. Must terminate
1222      *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
1223      */
1224     // No permission check needed as this is not exposed via AIDL.
requestEmbeddedSubscriptionInfoListRefresh( int cardId, @Nullable Runnable callback)1225     public void requestEmbeddedSubscriptionInfoListRefresh(
1226             int cardId, @Nullable Runnable callback) {
1227         PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(cardId, callback);
1228     }
1229 
1230     /**
1231      * Asynchronously refresh the embedded subscription info list for the embedded card has the
1232      * default card id return by {@link TelephonyManager#getCardIdForDefaultEuicc()}.
1233      *
1234      * @param callback Optional callback to execute after the refresh completes. Must terminate
1235      *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
1236      */
1237     // No permission check needed as this is not exposed via AIDL.
requestEmbeddedSubscriptionInfoListRefresh(@ullable Runnable callback)1238     public void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) {
1239         PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(
1240                 mTelephonyManager.getCardIdForDefaultEuicc(), callback);
1241     }
1242 
1243     /**
1244      * Add a new SubInfoRecord to subinfo database if needed
1245      * @param iccId the IccId of the SIM card
1246      * @param slotIndex the slot which the SIM is inserted
1247      * @return 0 if success, < 0 on error.
1248      */
1249     @Override
addSubInfoRecord(String iccId, int slotIndex)1250     public int addSubInfoRecord(String iccId, int slotIndex) {
1251         return addSubInfo(iccId, null, slotIndex, SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
1252     }
1253 
1254     /**
1255      * Add a new subscription info record, if needed.
1256      * @param uniqueId This is the unique identifier for the subscription within the specific
1257      *                 subscription type.
1258      * @param displayName human-readable name of the device the subscription corresponds to.
1259      * @param slotIndex value for {@link SubscriptionManager#SIM_SLOT_INDEX}
1260      * @param subscriptionType the type of subscription to be added.
1261      * @return 0 if success, < 0 on error.
1262      */
1263     @Override
addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType)1264     public int addSubInfo(String uniqueId, String displayName, int slotIndex,
1265             int subscriptionType) {
1266         if (DBG) {
1267             String iccIdStr = uniqueId;
1268             if (!isSubscriptionForRemoteSim(subscriptionType)) {
1269                 iccIdStr = SubscriptionInfo.givePrintableIccid(uniqueId);
1270             }
1271             logdl("[addSubInfoRecord]+ iccid: " + iccIdStr
1272                     + ", slotIndex: " + slotIndex
1273                     + ", subscriptionType: " + subscriptionType);
1274         }
1275 
1276         enforceModifyPhoneState("addSubInfo");
1277 
1278         // Now that all security checks passes, perform the operation as ourselves.
1279         final long identity = Binder.clearCallingIdentity();
1280         try {
1281             if (uniqueId == null) {
1282                 if (DBG) logdl("[addSubInfo]- null iccId");
1283                 return -1;
1284             }
1285 
1286             ContentResolver resolver = mContext.getContentResolver();
1287             String selection = SubscriptionManager.ICC_ID + "=?";
1288             String[] args;
1289             if (isSubscriptionForRemoteSim(subscriptionType)) {
1290                 selection += " AND " + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
1291                 args = new String[]{uniqueId, Integer.toString(subscriptionType)};
1292             } else {
1293                 selection += " OR " + SubscriptionManager.ICC_ID + "=?";
1294                 args = new String[]{uniqueId, IccUtils.getDecimalSubstring(uniqueId)};
1295             }
1296             Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
1297                     new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
1298                             SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
1299                             SubscriptionManager.ICC_ID, SubscriptionManager.CARD_ID},
1300                     selection, args, null);
1301 
1302             boolean setDisplayName = false;
1303             try {
1304                 boolean recordsDoNotExist = (cursor == null || !cursor.moveToFirst());
1305                 if (isSubscriptionForRemoteSim(subscriptionType)) {
1306                     if (recordsDoNotExist) {
1307                         // create a Subscription record
1308                         slotIndex = SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB;
1309                         Uri uri = insertEmptySubInfoRecord(uniqueId, displayName,
1310                                 slotIndex, subscriptionType);
1311                         if (DBG) logd("[addSubInfoRecord] New record created: " + uri);
1312                     } else {
1313                         if (DBG) logdl("[addSubInfoRecord] Record already exists");
1314                     }
1315                 } else {  // Handle Local SIM devices
1316                     if (recordsDoNotExist) {
1317                         setDisplayName = true;
1318                         Uri uri = insertEmptySubInfoRecord(uniqueId, slotIndex);
1319                         if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
1320                     } else { // there are matching records in the database for the given ICC_ID
1321                         int subId = cursor.getInt(0);
1322                         int oldSimInfoId = cursor.getInt(1);
1323                         int nameSource = cursor.getInt(2);
1324                         String oldIccId = cursor.getString(3);
1325                         String oldCardId = cursor.getString(4);
1326                         ContentValues value = new ContentValues();
1327 
1328                         if (slotIndex != oldSimInfoId) {
1329                             value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
1330                         }
1331 
1332                         if (oldIccId != null && oldIccId.length() < uniqueId.length()
1333                                 && (oldIccId.equals(IccUtils.getDecimalSubstring(uniqueId)))) {
1334                             value.put(SubscriptionManager.ICC_ID, uniqueId);
1335                         }
1336 
1337                         UiccCard card = mUiccController.getUiccCardForPhone(slotIndex);
1338                         if (card != null) {
1339                             String cardId = card.getCardId();
1340                             if (cardId != null && cardId != oldCardId) {
1341                                 value.put(SubscriptionManager.CARD_ID, cardId);
1342                             }
1343                         }
1344 
1345                         if (value.size() > 0) {
1346                             resolver.update(SubscriptionManager.getUriForSubscriptionId(subId),
1347                                     value, null, null);
1348                         }
1349 
1350                         if (DBG) logdl("[addSubInfoRecord] Record already exists");
1351                     }
1352                 }
1353             } finally {
1354                 if (cursor != null) {
1355                     cursor.close();
1356                 }
1357             }
1358 
1359             selection = SubscriptionManager.SIM_SLOT_INDEX + "=?";
1360             args = new String[] {String.valueOf(slotIndex)};
1361             if (isSubscriptionForRemoteSim(subscriptionType)) {
1362                 selection = SubscriptionManager.ICC_ID + "=? AND "
1363                         + SubscriptionManager.SUBSCRIPTION_TYPE + "=?";
1364                 args = new String[]{uniqueId, Integer.toString(subscriptionType)};
1365             }
1366             cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
1367                     selection, args, null);
1368             try {
1369                 if (cursor != null && cursor.moveToFirst()) {
1370                     do {
1371                         int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
1372                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
1373                         // If sSlotIndexToSubIds already has the same subId for a slotIndex/phoneId,
1374                         // do not add it.
1375                         if (addToSubIdList(slotIndex, subId, subscriptionType)) {
1376                             // TODO While two subs active, if user deactivats first
1377                             // one, need to update the default subId with second one.
1378 
1379                             // FIXME: Currently we assume phoneId == slotIndex which in the future
1380                             // may not be true, for instance with multiple subs per slot.
1381                             // But is true at the moment.
1382                             int subIdCountMax = getActiveSubInfoCountMax();
1383                             int defaultSubId = getDefaultSubId();
1384                             if (DBG) {
1385                                 logdl("[addSubInfoRecord]"
1386                                         + " sSlotIndexToSubIds.size=" + sSlotIndexToSubIds.size()
1387                                         + " slotIndex=" + slotIndex + " subId=" + subId
1388                                         + " defaultSubId=" + defaultSubId
1389                                         + " simCount=" + subIdCountMax);
1390                             }
1391 
1392                             // Set the default sub if not set or if single sim device
1393                             if (!isSubscriptionForRemoteSim(subscriptionType)) {
1394                                 if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
1395                                         || subIdCountMax == 1) {
1396                                     logdl("setting default fallback subid to " + subId);
1397                                     setDefaultFallbackSubId(subId, subscriptionType);
1398                                 }
1399                                 // If single sim device, set this subscription as the default for
1400                                 // everything
1401                                 if (subIdCountMax == 1) {
1402                                     if (DBG) {
1403                                         logdl("[addSubInfoRecord] one sim set defaults to subId="
1404                                                 + subId);
1405                                     }
1406                                     setDefaultDataSubId(subId);
1407                                     setDefaultSmsSubId(subId);
1408                                     setDefaultVoiceSubId(subId);
1409                                 }
1410                             } else {
1411                                 updateDefaultSubIdsIfNeeded(subId, subscriptionType);
1412                             }
1413                         } else {
1414                             if (DBG) {
1415                                 logdl("[addSubInfoRecord] current SubId is already known, "
1416                                         + "IGNORE");
1417                             }
1418                         }
1419                         if (DBG) {
1420                             logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
1421                         }
1422                     } while (cursor.moveToNext());
1423                 }
1424             } finally {
1425                 if (cursor != null) {
1426                     cursor.close();
1427                 }
1428             }
1429 
1430             // Refresh the Cache of Active Subscription Info List. This should be done after
1431             // updating sSlotIndexToSubIds which is done through addToSubIdList() above.
1432             refreshCachedActiveSubscriptionInfoList();
1433 
1434             if (isSubscriptionForRemoteSim(subscriptionType)) {
1435                 notifySubscriptionInfoChanged();
1436             } else {  // Handle Local SIM devices
1437                 // Set Display name after sub id is set above so as to get valid simCarrierName
1438                 int subId = getSubIdUsingPhoneId(slotIndex);
1439                 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1440                     if (DBG) {
1441                         logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
1442                     }
1443                     return -1;
1444                 }
1445                 if (setDisplayName) {
1446                     String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
1447                     String nameToSet;
1448 
1449                     if (!TextUtils.isEmpty(simCarrierName)) {
1450                         nameToSet = simCarrierName;
1451                     } else {
1452                         nameToSet = "CARD " + Integer.toString(slotIndex + 1);
1453                     }
1454 
1455                     ContentValues value = new ContentValues();
1456                     value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
1457                     resolver.update(SubscriptionManager.getUriForSubscriptionId(subId), value,
1458                             null, null);
1459 
1460                     // Refresh the Cache of Active Subscription Info List
1461                     refreshCachedActiveSubscriptionInfoList();
1462 
1463                     if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
1464                 }
1465 
1466                 if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubIds.size());
1467             }
1468 
1469         } finally {
1470             Binder.restoreCallingIdentity(identity);
1471         }
1472         return 0;
1473     }
1474 
updateDefaultSubIdsIfNeeded(int newDefault, int subscriptionType)1475     private void updateDefaultSubIdsIfNeeded(int newDefault, int subscriptionType) {
1476         if (DBG) {
1477             logdl("[updateDefaultSubIdsIfNeeded] newDefault=" + newDefault
1478                     + ", subscriptionType=" + subscriptionType);
1479         }
1480         // Set the default ot new value only if the current default is invalid.
1481         if (!isActiveSubscriptionId(getDefaultSubId())) {
1482             // current default is not valid anylonger. set a new default
1483             if (DBG) {
1484                 logdl("[updateDefaultSubIdsIfNeeded] set sDefaultFallbackSubId=" + newDefault);
1485             }
1486             setDefaultFallbackSubId(newDefault, subscriptionType);
1487         }
1488 
1489         int value = getDefaultSmsSubId();
1490         if (!isActiveSubscriptionId(value)) {
1491             // current default is not valid. set it to the given newDefault value
1492             setDefaultSmsSubId(newDefault);
1493         }
1494         value = getDefaultDataSubId();
1495         if (!isActiveSubscriptionId(value)) {
1496             setDefaultDataSubId(newDefault);
1497         }
1498         value = getDefaultVoiceSubId();
1499         if (!isActiveSubscriptionId(value)) {
1500             setDefaultVoiceSubId(newDefault);
1501         }
1502     }
1503 
1504     /**
1505      * This method returns true if the given subId is among the list of currently active
1506      * subscriptions.
1507      */
isActiveSubscriptionId(int subId)1508     private boolean isActiveSubscriptionId(int subId) {
1509         if (!SubscriptionManager.isValidSubscriptionId(subId)) return false;
1510         ArrayList<Integer> subIdList = getActiveSubIdArrayList();
1511         if (subIdList.isEmpty()) return false;
1512         return subIdList.contains(new Integer(subId));
1513     }
1514 
1515     /*
1516      * Delete subscription info record for the given device.
1517      * @param uniqueId This is the unique identifier for the subscription within the specific
1518      *                 subscription type.
1519      * @param subscriptionType the type of subscription to be removed
1520      * @return 0 if success, < 0 on error.
1521      */
1522     @Override
removeSubInfo(String uniqueId, int subscriptionType)1523     public int removeSubInfo(String uniqueId, int subscriptionType) {
1524         enforceModifyPhoneState("removeSubInfo");
1525         if (DBG) {
1526             logd("[removeSubInfo] uniqueId: " + uniqueId
1527                     + ", subscriptionType: " + subscriptionType);
1528         }
1529 
1530         // validate the given info - does it exist in the active subscription list
1531         int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1532         int slotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
1533         for (SubscriptionInfo info : mCacheActiveSubInfoList) {
1534             if ((info.getSubscriptionType() == subscriptionType)
1535                     && info.getIccId().equalsIgnoreCase(uniqueId)) {
1536                 subId = info.getSubscriptionId();
1537                 slotIndex = info.getSimSlotIndex();
1538                 break;
1539             }
1540         }
1541         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1542             if (DBG) {
1543                 logd("Invalid subscription details: subscriptionType = " + subscriptionType
1544                         + ", uniqueId = " + uniqueId);
1545             }
1546             return -1;
1547         }
1548 
1549         if (DBG) logd("removing the subid : " + subId);
1550 
1551         // Now that all security checks passes, perform the operation as ourselves.
1552         int result = 0;
1553         final long identity = Binder.clearCallingIdentity();
1554         try {
1555             ContentResolver resolver = mContext.getContentResolver();
1556             result = resolver.delete(SubscriptionManager.CONTENT_URI,
1557                     SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=? AND "
1558                             + SubscriptionManager.SUBSCRIPTION_TYPE + "=?",
1559                     new String[]{Integer.toString(subId), Integer.toString(subscriptionType)});
1560             if (result != 1) {
1561                 if (DBG) {
1562                     logd("found NO subscription to remove with subscriptionType = "
1563                             + subscriptionType + ", uniqueId = " + uniqueId);
1564                 }
1565                 return -1;
1566             }
1567             refreshCachedActiveSubscriptionInfoList();
1568             result = sSlotIndexToSubIds.removeFromSubIdList(slotIndex, subId);
1569             if (result == NO_ENTRY_FOR_SLOT_INDEX) {
1570                 loge("sSlotIndexToSubIds has no entry for slotIndex = " + slotIndex);
1571             } else if (result == SUB_ID_NOT_IN_SLOT) {
1572                 loge("sSlotIndexToSubIds has no subid: " + subId + ", in index: " + slotIndex);
1573             }
1574 
1575             // Since a subscription is removed, if this one is set as default for any setting,
1576             // set some other subid as the default.
1577             int newDefault = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1578             SubscriptionInfo info = null;
1579             final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
1580                     mContext.getOpPackageName(), mContext.getAttributionTag());
1581             if (!records.isEmpty()) {
1582                 // yes, we have more subscriptions. pick the first one.
1583                 // FIXME do we need a policy to figure out which one is to be next default
1584                 info = records.get(0);
1585             }
1586             updateDefaultSubIdsIfNeeded(info.getSubscriptionId(), info.getSubscriptionType());
1587 
1588             notifySubscriptionInfoChanged();
1589         } finally {
1590             Binder.restoreCallingIdentity(identity);
1591         }
1592         return result;
1593     }
1594 
1595     /**
1596      * Clear an subscriptionInfo to subinfo database if needed by updating slot index to invalid.
1597      * @param slotIndex the slot which the SIM is removed
1598      */
clearSubInfoRecord(int slotIndex)1599     public void clearSubInfoRecord(int slotIndex) {
1600         if (DBG) logdl("[clearSubInfoRecord]+ iccId:" + " slotIndex:" + slotIndex);
1601 
1602         // update simInfo db with invalid slot index
1603         ContentResolver resolver = mContext.getContentResolver();
1604         ContentValues value = new ContentValues(1);
1605         value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
1606         String where = "(" + SubscriptionManager.SIM_SLOT_INDEX + "=" + slotIndex + ")";
1607         resolver.update(SubscriptionManager.CONTENT_URI, value, where, null);
1608 
1609         // Refresh the Cache of Active Subscription Info List
1610         refreshCachedActiveSubscriptionInfoList();
1611 
1612         sSlotIndexToSubIds.remove(slotIndex);
1613     }
1614 
1615     /**
1616      * Insert an empty SubInfo record into the database.
1617      *
1618      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
1619      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
1620      *
1621      * <p>Precondition: No record exists with this iccId.
1622      */
1623     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
insertEmptySubInfoRecord(String iccId, int slotIndex)1624     public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
1625         return insertEmptySubInfoRecord(iccId, null, slotIndex,
1626                 SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
1627     }
1628 
insertEmptySubInfoRecord(String uniqueId, String displayName, int slotIndex, int subscriptionType)1629     Uri insertEmptySubInfoRecord(String uniqueId, String displayName, int slotIndex,
1630             int subscriptionType) {
1631         ContentResolver resolver = mContext.getContentResolver();
1632         ContentValues value = new ContentValues();
1633         value.put(SubscriptionManager.ICC_ID, uniqueId);
1634         int color = getUnusedColor(mContext.getOpPackageName(), mContext.getAttributionTag());
1635         // default SIM color differs between slots
1636         value.put(SubscriptionManager.HUE, color);
1637         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
1638         value.put(SubscriptionManager.CARRIER_NAME, "");
1639         value.put(SubscriptionManager.CARD_ID, uniqueId);
1640         value.put(SubscriptionManager.SUBSCRIPTION_TYPE, subscriptionType);
1641         if (!TextUtils.isEmpty(displayName)) {
1642             value.put(SubscriptionManager.DISPLAY_NAME, displayName);
1643         }
1644         if (!isSubscriptionForRemoteSim(subscriptionType)) {
1645             UiccCard card = mUiccController.getUiccCardForPhone(slotIndex);
1646             if (card != null) {
1647                 String cardId = card.getCardId();
1648                 if (cardId != null) {
1649                     value.put(SubscriptionManager.CARD_ID, cardId);
1650                 }
1651             }
1652         }
1653 
1654         Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
1655 
1656         // Refresh the Cache of Active Subscription Info List
1657         refreshCachedActiveSubscriptionInfoList();
1658 
1659         return uri;
1660     }
1661 
1662     /**
1663      * Generate and set carrier text based on input parameters
1664      * @param showPlmn flag to indicate if plmn should be included in carrier text
1665      * @param plmn plmn to be included in carrier text
1666      * @param showSpn flag to indicate if spn should be included in carrier text
1667      * @param spn spn to be included in carrier text
1668      * @return true if carrier text is set, false otherwise
1669      */
1670     @UnsupportedAppUsage
setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn, String spn)1671     public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
1672                               String spn) {
1673         synchronized (mLock) {
1674             int subId = getSubIdUsingPhoneId(slotIndex);
1675             if (mContext.getPackageManager().resolveContentProvider(
1676                     SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
1677                     !SubscriptionManager.isValidSubscriptionId(subId)) {
1678                 // No place to store this info. Notify registrants of the change anyway as they
1679                 // might retrieve the SPN/PLMN text from the SST sticky broadcast.
1680                 // TODO: This can be removed once SubscriptionController is not running on devices
1681                 // that don't need it, such as TVs.
1682                 if (DBG) logd("[setPlmnSpn] No valid subscription to store info");
1683                 notifySubscriptionInfoChanged();
1684                 return false;
1685             }
1686             String carrierText = "";
1687             if (showPlmn) {
1688                 carrierText = plmn;
1689                 if (showSpn) {
1690                     // Need to show both plmn and spn if both are not same.
1691                     if(!Objects.equals(spn, plmn)) {
1692                         String separator = mContext.getString(
1693                                 com.android.internal.R.string.kg_text_message_separator).toString();
1694                         carrierText = new StringBuilder().append(carrierText).append(separator)
1695                                 .append(spn).toString();
1696                     }
1697                 }
1698             } else if (showSpn) {
1699                 carrierText = spn;
1700             }
1701             setCarrierText(carrierText, subId);
1702             return true;
1703         }
1704     }
1705 
1706     /**
1707      * Set carrier text by simInfo index
1708      * @param text new carrier text
1709      * @param subId the unique SubInfoRecord index in database
1710      * @return the number of records updated
1711      */
setCarrierText(String text, int subId)1712     private int setCarrierText(String text, int subId) {
1713         if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);
1714 
1715         enforceModifyPhoneState("setCarrierText");
1716 
1717         // Now that all security checks passes, perform the operation as ourselves.
1718         final long identity = Binder.clearCallingIdentity();
1719         try {
1720             ContentValues value = new ContentValues(1);
1721             value.put(SubscriptionManager.CARRIER_NAME, text);
1722 
1723             int result = mContext.getContentResolver().update(
1724                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
1725 
1726             // Refresh the Cache of Active Subscription Info List
1727             refreshCachedActiveSubscriptionInfoList();
1728 
1729             notifySubscriptionInfoChanged();
1730 
1731             return result;
1732         } finally {
1733             Binder.restoreCallingIdentity(identity);
1734         }
1735     }
1736 
1737     /**
1738      * Set SIM color tint by simInfo index
1739      * @param tint the tint color of the SIM
1740      * @param subId the unique SubInfoRecord index in database
1741      * @return the number of records updated
1742      */
1743     @Override
setIconTint(int tint, int subId)1744     public int setIconTint(int tint, int subId) {
1745         if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
1746 
1747         enforceModifyPhoneState("setIconTint");
1748 
1749         // Now that all security checks passes, perform the operation as ourselves.
1750         final long identity = Binder.clearCallingIdentity();
1751         try {
1752             validateSubId(subId);
1753             ContentValues value = new ContentValues(1);
1754             value.put(SubscriptionManager.HUE, tint);
1755             if (DBG) logd("[setIconTint]- tint:" + tint + " set");
1756 
1757             int result = mContext.getContentResolver().update(
1758                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
1759 
1760             // Refresh the Cache of Active Subscription Info List
1761             refreshCachedActiveSubscriptionInfoList();
1762 
1763             notifySubscriptionInfoChanged();
1764 
1765             return result;
1766         } finally {
1767             Binder.restoreCallingIdentity(identity);
1768         }
1769     }
1770 
1771     /**
1772      * This is only for internal use and the returned priority is arbitrary. The idea is to give a
1773      * higher value to name source that has higher priority to override other name sources.
1774      * @param nameSource Source of display name
1775      * @return int representing the priority. Higher value means higher priority.
1776      */
getNameSourcePriority(@imDisplayNameSource int nameSource)1777     public static int getNameSourcePriority(@SimDisplayNameSource int nameSource) {
1778         int index = Arrays.asList(
1779                 SubscriptionManager.NAME_SOURCE_CARRIER_ID,
1780                 SubscriptionManager.NAME_SOURCE_SIM_PNN,
1781                 SubscriptionManager.NAME_SOURCE_SIM_SPN,
1782                 SubscriptionManager.NAME_SOURCE_CARRIER,
1783                 SubscriptionManager.NAME_SOURCE_USER_INPUT // user has highest priority.
1784         ).indexOf(nameSource);
1785         return (index < 0) ? 0 : index;
1786     }
1787 
1788     /**
1789      * Set display name by simInfo index with name source
1790      * @param displayName the display name of SIM card
1791      * @param subId the unique SubInfoRecord index in database
1792      * @param nameSource SIM display name source
1793      * @return the number of records updated
1794      */
1795     @Override
setDisplayNameUsingSrc(String displayName, int subId, @SimDisplayNameSource int nameSource)1796     public int setDisplayNameUsingSrc(String displayName, int subId,
1797                                       @SimDisplayNameSource int nameSource) {
1798         if (DBG) {
1799             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
1800                 + " nameSource:" + nameSource);
1801         }
1802 
1803         enforceModifyPhoneState("setDisplayNameUsingSrc");
1804 
1805         // Now that all security checks passes, perform the operation as ourselves.
1806         final long identity = Binder.clearCallingIdentity();
1807         try {
1808             validateSubId(subId);
1809             List<SubscriptionInfo> allSubInfo = getSubInfo(null, null);
1810             // if there is no sub in the db, return 0 since subId does not exist in db
1811             if (allSubInfo == null || allSubInfo.isEmpty()) return 0;
1812             for (SubscriptionInfo subInfo : allSubInfo) {
1813                 if (subInfo.getSubscriptionId() == subId
1814                         && (getNameSourcePriority(subInfo.getNameSource())
1815                                 > getNameSourcePriority(nameSource)
1816                         || (displayName != null && displayName.equals(subInfo.getDisplayName())))) {
1817                     logd("Name source " + subInfo.getNameSource() + "'s priority "
1818                             + getNameSourcePriority(subInfo.getNameSource()) + " is greater than "
1819                             + "name source " + nameSource + "'s priority "
1820                             + getNameSourcePriority(nameSource) + ", return now.");
1821                     return 0;
1822                 }
1823             }
1824             String nameToSet;
1825             if (TextUtils.isEmpty(displayName) || displayName.trim().length() == 0) {
1826                 nameToSet = mTelephonyManager.getSimOperatorName(subId);
1827                 if (TextUtils.isEmpty(nameToSet)) {
1828                     if (nameSource == SubscriptionManager.NAME_SOURCE_USER_INPUT
1829                             && SubscriptionManager.isValidSlotIndex(getSlotIndex(subId))) {
1830                         nameToSet = "CARD " + (getSlotIndex(subId) + 1);
1831                     } else {
1832                         nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
1833                     }
1834                 }
1835             } else {
1836                 nameToSet = displayName;
1837             }
1838             ContentValues value = new ContentValues(1);
1839             value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
1840             if (nameSource >= SubscriptionManager.NAME_SOURCE_CARRIER_ID) {
1841                 if (DBG) logd("Set nameSource=" + nameSource);
1842                 value.put(SubscriptionManager.NAME_SOURCE, nameSource);
1843             }
1844             if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
1845 
1846             // Update the nickname on the eUICC chip if it's an embedded subscription.
1847             SubscriptionInfo sub = getSubscriptionInfo(subId);
1848             if (sub != null && sub.isEmbedded()) {
1849                 // Ignore the result.
1850                 int cardId = sub.getCardId();
1851                 if (DBG) logd("Updating embedded sub nickname on cardId: " + cardId);
1852                 EuiccManager euiccManager = ((EuiccManager)
1853                         mContext.getSystemService(Context.EUICC_SERVICE)).createForCardId(cardId);
1854                 euiccManager.updateSubscriptionNickname(subId, displayName,
1855                         // This PendingIntent simply fulfills the requirement to pass in a callback;
1856                         // we don't care about the result (hence 0 requestCode and no action
1857                         // specified on the intent).
1858                         PendingIntent.getService(
1859                             mContext, 0 /* requestCode */, new Intent(), 0 /* flags */));
1860             }
1861 
1862             int result = updateDatabase(value, subId, true);
1863 
1864             // Refresh the Cache of Active Subscription Info List
1865             refreshCachedActiveSubscriptionInfoList();
1866 
1867             notifySubscriptionInfoChanged();
1868 
1869             return result;
1870         } finally {
1871             Binder.restoreCallingIdentity(identity);
1872         }
1873     }
1874 
1875     /**
1876      * Set phone number by subId
1877      * @param number the phone number of the SIM
1878      * @param subId the unique SubInfoRecord index in database
1879      * @return the number of records updated
1880      */
1881     @Override
setDisplayNumber(String number, int subId)1882     public int setDisplayNumber(String number, int subId) {
1883         if (DBG) logd("[setDisplayNumber]+ subId:" + subId);
1884 
1885         enforceModifyPhoneState("setDisplayNumber");
1886 
1887         // Now that all security checks passes, perform the operation as ourselves.
1888         final long identity = Binder.clearCallingIdentity();
1889         try {
1890             validateSubId(subId);
1891             int result;
1892             int phoneId = getPhoneId(subId);
1893 
1894             if (number == null || phoneId < 0 ||
1895                     phoneId >= mTelephonyManager.getPhoneCount()) {
1896                 if (DBG) logd("[setDispalyNumber]- fail");
1897                 return -1;
1898             }
1899             ContentValues value = new ContentValues(1);
1900             value.put(SubscriptionManager.NUMBER, number);
1901 
1902             // This function had a call to update number on the SIM (Phone.setLine1Number()) but
1903             // that was removed as there doesn't seem to be a reason for that. If it is added
1904             // back, watch out for deadlocks.
1905 
1906             result = mContext.getContentResolver().update(
1907                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
1908 
1909             // Refresh the Cache of Active Subscription Info List
1910             refreshCachedActiveSubscriptionInfoList();
1911 
1912             if (DBG) logd("[setDisplayNumber]- update result :" + result);
1913             notifySubscriptionInfoChanged();
1914 
1915             return result;
1916         } finally {
1917             Binder.restoreCallingIdentity(identity);
1918         }
1919     }
1920 
1921     /**
1922      * Set the EHPLMNs and HPLMNs associated with the subscription.
1923      */
setAssociatedPlmns(String[] ehplmns, String[] hplmns, int subId)1924     public void setAssociatedPlmns(String[] ehplmns, String[] hplmns, int subId) {
1925         if (DBG) logd("[setAssociatedPlmns]+ subId:" + subId);
1926 
1927         validateSubId(subId);
1928         int phoneId = getPhoneId(subId);
1929 
1930         if (phoneId < 0 || phoneId >= mTelephonyManager.getPhoneCount()) {
1931             if (DBG) logd("[setAssociatedPlmns]- fail");
1932             return;
1933         }
1934 
1935         String formattedEhplmns = ehplmns == null ? "" : String.join(",", ehplmns);
1936         String formattedHplmns = hplmns == null ? "" : String.join(",", hplmns);
1937 
1938         ContentValues value = new ContentValues(2);
1939         value.put(SubscriptionManager.EHPLMNS, formattedEhplmns);
1940         value.put(SubscriptionManager.HPLMNS, formattedHplmns);
1941 
1942         int count = mContext.getContentResolver().update(
1943                 SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
1944 
1945         // Refresh the Cache of Active Subscription Info List
1946         refreshCachedActiveSubscriptionInfoList();
1947 
1948         if (DBG) logd("[setAssociatedPlmns]- update result :" + count);
1949         notifySubscriptionInfoChanged();
1950     }
1951 
1952     /**
1953      * Set data roaming by simInfo index
1954      * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
1955      * @param subId the unique SubInfoRecord index in database
1956      * @return the number of records updated
1957      */
1958     @Override
setDataRoaming(int roaming, int subId)1959     public int setDataRoaming(int roaming, int subId) {
1960         if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
1961 
1962         enforceModifyPhoneState("setDataRoaming");
1963 
1964         // Now that all security checks passes, perform the operation as ourselves.
1965         final long identity = Binder.clearCallingIdentity();
1966         try {
1967             validateSubId(subId);
1968             if (roaming < 0) {
1969                 if (DBG) logd("[setDataRoaming]- fail");
1970                 return -1;
1971             }
1972             ContentValues value = new ContentValues(1);
1973             value.put(SubscriptionManager.DATA_ROAMING, roaming);
1974             if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
1975 
1976             int result = updateDatabase(value, subId, true);
1977 
1978             // Refresh the Cache of Active Subscription Info List
1979             refreshCachedActiveSubscriptionInfoList();
1980 
1981             notifySubscriptionInfoChanged();
1982 
1983             return result;
1984         } finally {
1985             Binder.restoreCallingIdentity(identity);
1986         }
1987     }
1988 
syncGroupedSetting(int refSubId)1989     public void syncGroupedSetting(int refSubId) {
1990         logd("syncGroupedSetting");
1991         try (Cursor cursor = mContext.getContentResolver().query(
1992                 SubscriptionManager.CONTENT_URI, null,
1993                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
1994                 new String[] {String.valueOf(refSubId)}, null)) {
1995             if (cursor == null || !cursor.moveToFirst()) {
1996                 logd("[syncGroupedSetting] failed. Can't find refSubId " + refSubId);
1997                 return;
1998             }
1999 
2000             ContentValues values = new ContentValues(GROUP_SHARING_PROPERTIES.size());
2001             for (String propKey : GROUP_SHARING_PROPERTIES) {
2002                 copyDataFromCursorToContentValue(propKey, cursor, values);
2003             }
2004             updateDatabase(values, refSubId, true);
2005         }
2006     }
2007 
copyDataFromCursorToContentValue(String propKey, Cursor cursor, ContentValues values)2008     private void copyDataFromCursorToContentValue(String propKey, Cursor cursor,
2009             ContentValues values) {
2010         int columnIndex = cursor.getColumnIndex(propKey);
2011         if (columnIndex == -1) {
2012             logd("[copyDataFromCursorToContentValue] can't find column " + propKey);
2013             return;
2014         }
2015 
2016         switch (propKey) {
2017             case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
2018             case SubscriptionManager.VT_IMS_ENABLED:
2019             case SubscriptionManager.WFC_IMS_ENABLED:
2020             case SubscriptionManager.WFC_IMS_MODE:
2021             case SubscriptionManager.WFC_IMS_ROAMING_MODE:
2022             case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
2023             case SubscriptionManager.DATA_ROAMING:
2024             case SubscriptionManager.IMS_RCS_UCE_ENABLED:
2025                 values.put(propKey, cursor.getInt(columnIndex));
2026                 break;
2027             case SubscriptionManager.DISPLAY_NAME:
2028             case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
2029                 values.put(propKey, cursor.getString(columnIndex));
2030                 break;
2031             default:
2032                 loge("[copyDataFromCursorToContentValue] invalid propKey " + propKey);
2033         }
2034     }
2035 
2036     // TODO: replace all updates with this helper method.
updateDatabase(ContentValues value, int subId, boolean updateEntireGroup)2037     private int updateDatabase(ContentValues value, int subId, boolean updateEntireGroup) {
2038         List<SubscriptionInfo> infoList = getSubscriptionsInGroup(getGroupUuid(subId),
2039                 mContext.getOpPackageName(), mContext.getAttributionTag());
2040         if (!updateEntireGroup || infoList == null || infoList.size() == 0) {
2041             // Only update specified subscriptions.
2042             return mContext.getContentResolver().update(
2043                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2044         } else {
2045             // Update all subscriptions in the same group.
2046             int[] subIdList = new int[infoList.size()];
2047             for (int i = 0; i < infoList.size(); i++) {
2048                 subIdList[i] = infoList.get(i).getSubscriptionId();
2049             }
2050             return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
2051                     value, getSelectionForSubIdList(subIdList), null);
2052         }
2053     }
2054 
2055     /**
2056      * Set carrier id by subId
2057      * @param carrierId the subscription carrier id.
2058      * @param subId the unique SubInfoRecord index in database
2059      * @return the number of records updated
2060      *
2061      * @see TelephonyManager#getSimCarrierId()
2062      */
setCarrierId(int carrierId, int subId)2063     public int setCarrierId(int carrierId, int subId) {
2064         if (DBG) logd("[setCarrierId]+ carrierId:" + carrierId + " subId:" + subId);
2065 
2066         enforceModifyPhoneState("setCarrierId");
2067 
2068         // Now that all security checks passes, perform the operation as ourselves.
2069         final long identity = Binder.clearCallingIdentity();
2070         try {
2071             validateSubId(subId);
2072             ContentValues value = new ContentValues(1);
2073             value.put(SubscriptionManager.CARRIER_ID, carrierId);
2074             int result = mContext.getContentResolver().update(
2075                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2076 
2077             // Refresh the Cache of Active Subscription Info List
2078             refreshCachedActiveSubscriptionInfoList();
2079 
2080             notifySubscriptionInfoChanged();
2081 
2082             return result;
2083         } finally {
2084             Binder.restoreCallingIdentity(identity);
2085         }
2086     }
2087 
2088     /**
2089      * Set MCC/MNC by subscription ID
2090      * @param mccMnc MCC/MNC associated with the subscription
2091      * @param subId the unique SubInfoRecord index in database
2092      * @return the number of records updated
2093      */
setMccMnc(String mccMnc, int subId)2094     public int setMccMnc(String mccMnc, int subId) {
2095         String mccString = mccMnc.substring(0, 3);
2096         String mncString = mccMnc.substring(3);
2097         int mcc = 0;
2098         int mnc = 0;
2099         try {
2100             mcc = Integer.parseInt(mccString);
2101             mnc = Integer.parseInt(mncString);
2102         } catch (NumberFormatException e) {
2103             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
2104         }
2105         if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
2106         ContentValues value = new ContentValues(4);
2107         value.put(SubscriptionManager.MCC, mcc);
2108         value.put(SubscriptionManager.MNC, mnc);
2109         value.put(SubscriptionManager.MCC_STRING, mccString);
2110         value.put(SubscriptionManager.MNC_STRING, mncString);
2111 
2112         int result = mContext.getContentResolver().update(
2113                 SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2114 
2115         // Refresh the Cache of Active Subscription Info List
2116         refreshCachedActiveSubscriptionInfoList();
2117 
2118         notifySubscriptionInfoChanged();
2119 
2120         return result;
2121     }
2122 
2123     /**
2124      * Set IMSI by subscription ID
2125      * @param imsi IMSI (International Mobile Subscriber Identity)
2126      * @return the number of records updated
2127      */
setImsi(String imsi, int subId)2128     public int setImsi(String imsi, int subId) {
2129         if (DBG) logd("[setImsi]+ imsi:" + imsi + " subId:" + subId);
2130         ContentValues value = new ContentValues(1);
2131         value.put(SubscriptionManager.IMSI, imsi);
2132 
2133         int result = mContext.getContentResolver().update(
2134                 SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2135 
2136         // Refresh the Cache of Active Subscription Info List
2137         refreshCachedActiveSubscriptionInfoList();
2138 
2139         notifySubscriptionInfoChanged();
2140 
2141         return result;
2142     }
2143 
2144     /**
2145      * Set uicc applications being enabled or disabled.
2146      * @param enabled whether uicc applications are enabled or disabled.
2147      * @return the number of records updated
2148      */
setUiccApplicationsEnabled(boolean enabled, int subId)2149     public int setUiccApplicationsEnabled(boolean enabled, int subId) {
2150         if (DBG) logd("[setUiccApplicationsEnabled]+ enabled:" + enabled + " subId:" + subId);
2151 
2152         enforceModifyPhoneState("setUiccApplicationsEnabled");
2153 
2154         long identity = Binder.clearCallingIdentity();
2155         try {
2156             ContentValues value = new ContentValues(1);
2157             value.put(SubscriptionManager.UICC_APPLICATIONS_ENABLED, enabled);
2158 
2159             int result = mContext.getContentResolver().update(
2160                     SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2161 
2162             // Refresh the Cache of Active Subscription Info List
2163             refreshCachedActiveSubscriptionInfoList();
2164 
2165             notifyUiccAppsEnableChanged();
2166             notifySubscriptionInfoChanged();
2167 
2168             return result;
2169         } finally {
2170             Binder.restoreCallingIdentity(identity);
2171         }
2172     }
2173 
2174     /**
2175      * Register to change of uicc applications enablement changes.
2176      * @param notifyNow whether to notify target upon registration.
2177      */
registerForUiccAppsEnabled(Handler handler, int what, Object object, boolean notifyNow)2178     public void registerForUiccAppsEnabled(Handler handler, int what, Object object,
2179             boolean notifyNow) {
2180         mUiccAppsEnableChangeRegList.addUnique(handler, what, object);
2181         if (notifyNow) {
2182             handler.obtainMessage(what, object).sendToTarget();
2183         }
2184     }
2185 
2186     /**
2187      * Unregister to change of uicc applications enablement changes.
2188      */
unregisterForUiccAppsEnabled(Handler handler)2189     public void unregisterForUiccAppsEnabled(Handler handler) {
2190         mUiccAppsEnableChangeRegList.remove(handler);
2191     }
2192 
notifyUiccAppsEnableChanged()2193     private void notifyUiccAppsEnableChanged() {
2194         mUiccAppsEnableChangeRegList.notifyRegistrants();
2195     }
2196 
2197     /**
2198      * Get IMSI by subscription ID
2199      * For active subIds, this will always return the corresponding imsi
2200      * For inactive subIds, once they are activated once, even if they are deactivated at the time
2201      *   of calling this function, the corresponding imsi will be returned
2202      * When calling this method, the permission check should have already been done to allow
2203      *   only privileged read
2204      *
2205      * @return imsi
2206      */
getImsiPrivileged(int subId)2207     public String getImsiPrivileged(int subId) {
2208         try (Cursor cursor = mContext.getContentResolver().query(
2209                 SubscriptionManager.CONTENT_URI, null,
2210                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
2211                 new String[] {String.valueOf(subId)}, null)) {
2212             String imsi = null;
2213             if (cursor != null) {
2214                 if (cursor.moveToNext()) {
2215                     imsi = getOptionalStringFromCursor(cursor, SubscriptionManager.IMSI,
2216                             /*defaultVal*/ null);
2217                 }
2218             } else {
2219                 logd("getImsiPrivileged: failed to retrieve imsi.");
2220             }
2221 
2222             return imsi;
2223         }
2224     }
2225 
2226     /**
2227      * Set ISO country code by subscription ID
2228      * @param iso iso country code associated with the subscription
2229      * @param subId the unique SubInfoRecord index in database
2230      * @return the number of records updated
2231      */
setCountryIso(String iso, int subId)2232     public int setCountryIso(String iso, int subId) {
2233         if (DBG) logd("[setCountryIso]+ iso:" + iso + " subId:" + subId);
2234         ContentValues value = new ContentValues();
2235         value.put(SubscriptionManager.ISO_COUNTRY_CODE, iso);
2236 
2237         int result = mContext.getContentResolver().update(
2238                 SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);
2239 
2240         // Refresh the Cache of Active Subscription Info List
2241         refreshCachedActiveSubscriptionInfoList();
2242 
2243         notifySubscriptionInfoChanged();
2244         return result;
2245     }
2246 
2247     @Override
getSlotIndex(int subId)2248     public int getSlotIndex(int subId) {
2249         if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId);
2250 
2251         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2252             subId = getDefaultSubId();
2253         }
2254         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
2255             if (DBG) logd("[getSlotIndex]- subId invalid");
2256             return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
2257         }
2258 
2259         int size = sSlotIndexToSubIds.size();
2260 
2261         if (size == 0) {
2262             if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead");
2263             return SubscriptionManager.SIM_NOT_INSERTED;
2264         }
2265 
2266         for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
2267             int sim = entry.getKey();
2268             ArrayList<Integer> subs = entry.getValue();
2269 
2270             if (subs != null && subs.contains(subId)) {
2271                 if (VDBG) logv("[getSlotIndex]- return = " + sim);
2272                 return sim;
2273             }
2274         }
2275 
2276         if (DBG) logd("[getSlotIndex]- return fail");
2277         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
2278     }
2279 
2280     /**
2281      * Return the subId for specified slot Id.
2282      * @deprecated
2283      */
2284     @UnsupportedAppUsage
2285     @Override
2286     @Deprecated
getSubId(int slotIndex)2287     public int[] getSubId(int slotIndex) {
2288         if (VDBG) printStackTrace("[getSubId]+ slotIndex=" + slotIndex);
2289 
2290         // Map default slotIndex to the current default subId.
2291         // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous
2292         // as a slot maybe used for multiple different type of "connections"
2293         // such as: voice, data and sms. But we're doing the best we can and using
2294         // getDefaultSubId which makes a best guess.
2295         if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
2296             slotIndex = getSlotIndex(getDefaultSubId());
2297             if (VDBG) logd("[getSubId] map default slotIndex=" + slotIndex);
2298         }
2299 
2300         // Check that we have a valid slotIndex or the slotIndex is for a remote SIM (remote SIM
2301         // uses special slot index that may be invalid otherwise)
2302         if (!SubscriptionManager.isValidSlotIndex(slotIndex)
2303                 && slotIndex != SubscriptionManager.SLOT_INDEX_FOR_REMOTE_SIM_SUB) {
2304             if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex);
2305             return null;
2306         }
2307 
2308         // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate.
2309         int size = sSlotIndexToSubIds.size();
2310         if (size == 0) {
2311             if (VDBG) {
2312                 logd("[getSubId]- sSlotIndexToSubIds.size == 0, return null slotIndex="
2313                         + slotIndex);
2314             }
2315             return null;
2316         }
2317 
2318         // Convert ArrayList to array
2319         ArrayList<Integer> subIds = sSlotIndexToSubIds.getCopy(slotIndex);
2320         if (subIds != null && subIds.size() > 0) {
2321             int[] subIdArr = new int[subIds.size()];
2322             for (int i = 0; i < subIds.size(); i++) {
2323                 subIdArr[i] = subIds.get(i);
2324             }
2325             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
2326             return subIdArr;
2327         } else {
2328             if (DBG) logd("[getSubId]- numSubIds == 0, return null slotIndex=" + slotIndex);
2329             return null;
2330         }
2331     }
2332 
2333     @UnsupportedAppUsage
2334     @Override
getPhoneId(int subId)2335     public int getPhoneId(int subId) {
2336         if (VDBG) printStackTrace("[getPhoneId] subId=" + subId);
2337         int phoneId;
2338 
2339         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2340             subId = getDefaultSubId();
2341             if (DBG) logd("[getPhoneId] asked for default subId=" + subId);
2342         }
2343 
2344         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
2345             if (VDBG) {
2346                 logdl("[getPhoneId]- invalid subId return="
2347                         + SubscriptionManager.INVALID_PHONE_INDEX);
2348             }
2349             return SubscriptionManager.INVALID_PHONE_INDEX;
2350         }
2351 
2352         int size = sSlotIndexToSubIds.size();
2353         if (size == 0) {
2354             phoneId = mDefaultPhoneId;
2355             if (VDBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
2356             return phoneId;
2357         }
2358 
2359         // FIXME: Assumes phoneId == slotIndex
2360         for (Entry<Integer, ArrayList<Integer>> entry: sSlotIndexToSubIds.entrySet()) {
2361             int sim = entry.getKey();
2362             ArrayList<Integer> subs = entry.getValue();
2363 
2364             if (subs != null && subs.contains(subId)) {
2365                 if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
2366                 return sim;
2367             }
2368         }
2369 
2370         phoneId = mDefaultPhoneId;
2371         if (VDBG) {
2372             logd("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId);
2373         }
2374         return phoneId;
2375 
2376     }
2377 
2378     /**
2379      * @return the number of records cleared
2380      */
2381     @Override
clearSubInfo()2382     public int clearSubInfo() {
2383         enforceModifyPhoneState("clearSubInfo");
2384 
2385         // Now that all security checks passes, perform the operation as ourselves.
2386         final long identity = Binder.clearCallingIdentity();
2387         try {
2388             int size = sSlotIndexToSubIds.size();
2389 
2390             if (size == 0) {
2391                 if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
2392                 return 0;
2393             }
2394 
2395             sSlotIndexToSubIds.clear();
2396             if (DBG) logdl("[clearSubInfo]- clear size=" + size);
2397             return size;
2398         } finally {
2399             Binder.restoreCallingIdentity(identity);
2400         }
2401     }
2402 
logvl(String msg)2403     private void logvl(String msg) {
2404         logv(msg);
2405         mLocalLog.log(msg);
2406     }
2407 
logv(String msg)2408     private void logv(String msg) {
2409         Rlog.v(LOG_TAG, msg);
2410     }
2411 
2412     @UnsupportedAppUsage
logdl(String msg)2413     protected void logdl(String msg) {
2414         logd(msg);
2415         mLocalLog.log(msg);
2416     }
2417 
2418     @UnsupportedAppUsage
logd(String msg)2419     private void logd(String msg) {
2420         Rlog.d(LOG_TAG, msg);
2421     }
2422 
logel(String msg)2423     private void logel(String msg) {
2424         loge(msg);
2425         mLocalLog.log(msg);
2426     }
2427 
2428     @UnsupportedAppUsage
loge(String msg)2429     private void loge(String msg) {
2430         Rlog.e(LOG_TAG, msg);
2431     }
2432 
2433     @UnsupportedAppUsage
2434     @Override
getDefaultSubId()2435     public int getDefaultSubId() {
2436         int subId;
2437         boolean isVoiceCapable = mTelephonyManager.isVoiceCapable();
2438         if (isVoiceCapable) {
2439             subId = getDefaultVoiceSubId();
2440             if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
2441         } else {
2442             subId = getDefaultDataSubId();
2443             if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
2444         }
2445         if (!isActiveSubId(subId)) {
2446             subId = sDefaultFallbackSubId.get();
2447             if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
2448         }
2449         if (VDBG) logv("[getDefaultSubId]- value = " + subId);
2450         return subId;
2451     }
2452 
2453     @UnsupportedAppUsage
2454     @Override
setDefaultSmsSubId(int subId)2455     public void setDefaultSmsSubId(int subId) {
2456         enforceModifyPhoneState("setDefaultSmsSubId");
2457 
2458         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2459             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
2460         }
2461         if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
2462         setGlobalSetting(Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
2463         broadcastDefaultSmsSubIdChanged(subId);
2464     }
2465 
broadcastDefaultSmsSubIdChanged(int subId)2466     private void broadcastDefaultSmsSubIdChanged(int subId) {
2467         // Broadcast an Intent for default sms sub change
2468         if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
2469         Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
2470         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2471         SubscriptionManager.putSubscriptionIdExtra(intent, subId);
2472         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
2473     }
2474 
2475     @UnsupportedAppUsage
2476     @Override
getDefaultSmsSubId()2477     public int getDefaultSmsSubId() {
2478         int subId = Settings.Global.getInt(mContext.getContentResolver(),
2479                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
2480                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
2481         if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId);
2482         return subId;
2483     }
2484 
2485     @UnsupportedAppUsage
2486     @Override
setDefaultVoiceSubId(int subId)2487     public void setDefaultVoiceSubId(int subId) {
2488         enforceModifyPhoneState("setDefaultVoiceSubId");
2489 
2490         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2491             throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
2492         }
2493         if (DBG) logdl("[setDefaultVoiceSubId] subId=" + subId);
2494 
2495         int previousDefaultSub = getDefaultSubId();
2496 
2497         setGlobalSetting(Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
2498         broadcastDefaultVoiceSubIdChanged(subId);
2499 
2500         PhoneAccountHandle newHandle =
2501                 subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
2502                         ? null : mTelephonyManager.getPhoneAccountHandleForSubscriptionId(
2503                         subId);
2504 
2505         TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
2506         PhoneAccountHandle currentHandle = telecomManager.getUserSelectedOutgoingPhoneAccount();
2507 
2508         if (!Objects.equals(currentHandle, newHandle)) {
2509             telecomManager.setUserSelectedOutgoingPhoneAccount(newHandle);
2510             logd("[setDefaultVoiceSubId] change to phoneAccountHandle=" + newHandle);
2511         } else {
2512             logd("[setDefaultVoiceSubId] default phone account not changed");
2513         }
2514 
2515         if (previousDefaultSub != getDefaultSubId()) {
2516             sendDefaultChangedBroadcast(getDefaultSubId());
2517         }
2518     }
2519 
2520     /**
2521      * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
2522      * @hide
2523      */
2524     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
broadcastDefaultVoiceSubIdChanged(int subId)2525     public void broadcastDefaultVoiceSubIdChanged(int subId) {
2526         // Broadcast an Intent for default voice sub change
2527         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
2528         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
2529         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2530         SubscriptionManager.putSubscriptionIdExtra(intent, subId);
2531         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
2532     }
2533 
2534     @UnsupportedAppUsage
2535     @Override
getDefaultVoiceSubId()2536     public int getDefaultVoiceSubId() {
2537         int subId = Settings.Global.getInt(mContext.getContentResolver(),
2538                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
2539                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
2540         if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
2541         return subId;
2542     }
2543 
2544     @UnsupportedAppUsage
2545     @Override
getDefaultDataSubId()2546     public int getDefaultDataSubId() {
2547         int subId = Settings.Global.getInt(mContext.getContentResolver(),
2548                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
2549                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
2550         if (VDBG) logd("[getDefaultDataSubId] subId=" + subId);
2551         return subId;
2552     }
2553 
2554     @UnsupportedAppUsage
2555     @Override
setDefaultDataSubId(int subId)2556     public void setDefaultDataSubId(int subId) {
2557         enforceModifyPhoneState("setDefaultDataSubId");
2558 
2559         final long identity = Binder.clearCallingIdentity();
2560         try {
2561             if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2562                 throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
2563             }
2564 
2565             ProxyController proxyController = ProxyController.getInstance();
2566             int len = TelephonyManager.from(mContext).getActiveModemCount();
2567             logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId);
2568 
2569             if (SubscriptionManager.isValidSubscriptionId(subId)) {
2570                 // Only re-map modems if the new default data sub is valid
2571                 RadioAccessFamily[] rafs = new RadioAccessFamily[len];
2572                 boolean atLeastOneMatch = false;
2573                 for (int phoneId = 0; phoneId < len; phoneId++) {
2574                     Phone phone = PhoneFactory.getPhone(phoneId);
2575                     int raf;
2576                     int id = phone.getSubId();
2577                     if (id == subId) {
2578                         // TODO Handle the general case of N modems and M subscriptions.
2579                         raf = proxyController.getMaxRafSupported();
2580                         atLeastOneMatch = true;
2581                     } else {
2582                         // TODO Handle the general case of N modems and M subscriptions.
2583                         raf = proxyController.getMinRafSupported();
2584                     }
2585                     logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF="
2586                             + raf);
2587                     rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
2588                 }
2589                 if (atLeastOneMatch) {
2590                     proxyController.setRadioCapability(rafs);
2591                 } else {
2592                     if (DBG) logdl("[setDefaultDataSubId] no valid subId's found - not updating.");
2593                 }
2594             }
2595 
2596             int previousDefaultSub = getDefaultSubId();
2597             setGlobalSetting(Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
2598             MultiSimSettingController.getInstance().notifyDefaultDataSubChanged();
2599             broadcastDefaultDataSubIdChanged(subId);
2600             if (previousDefaultSub != getDefaultSubId()) {
2601                 sendDefaultChangedBroadcast(getDefaultSubId());
2602             }
2603         } finally {
2604             Binder.restoreCallingIdentity(identity);
2605         }
2606     }
2607 
2608     @UnsupportedAppUsage
broadcastDefaultDataSubIdChanged(int subId)2609     private void broadcastDefaultDataSubIdChanged(int subId) {
2610         // Broadcast an Intent for default data sub change
2611         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
2612         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
2613         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2614         SubscriptionManager.putSubscriptionIdExtra(intent, subId);
2615         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
2616     }
2617 
2618     /* Sets the default subscription. If only one sub is active that
2619      * sub is set as default subId. If two or more  sub's are active
2620      * the first sub is set as default subscription
2621      */
2622     @UnsupportedAppUsage
setDefaultFallbackSubId(int subId, int subscriptionType)2623     protected void setDefaultFallbackSubId(int subId, int subscriptionType) {
2624         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2625             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
2626         }
2627         if (DBG) {
2628             logdl("[setDefaultFallbackSubId] subId=" + subId + ", subscriptionType="
2629                     + subscriptionType);
2630         }
2631         int previousDefaultSub = getDefaultSubId();
2632         if (isSubscriptionForRemoteSim(subscriptionType)) {
2633             sDefaultFallbackSubId.set(subId);
2634             return;
2635         }
2636         if (SubscriptionManager.isValidSubscriptionId(subId)) {
2637             int phoneId = getPhoneId(subId);
2638             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
2639                     || mTelephonyManager.getSimCount() == 1)) {
2640                 if (DBG) logdl("[setDefaultFallbackSubId] set sDefaultFallbackSubId=" + subId);
2641                 sDefaultFallbackSubId.set(subId);
2642                 // Update MCC MNC device configuration information
2643                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
2644                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc);
2645             } else {
2646                 if (DBG) {
2647                     logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
2648                             + " subId=" + subId);
2649                 }
2650             }
2651         }
2652         if (previousDefaultSub != getDefaultSubId()) {
2653             sendDefaultChangedBroadcast(getDefaultSubId());
2654         }
2655     }
2656 
sendDefaultChangedBroadcast(int subId)2657     public void sendDefaultChangedBroadcast(int subId) {
2658         // Broadcast an Intent for default sub change
2659         int phoneId = SubscriptionManager.getPhoneId(subId);
2660         Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
2661         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2662         SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
2663         if (DBG) {
2664             logdl("[sendDefaultChangedBroadcast] broadcast default subId changed phoneId="
2665                     + phoneId + " subId=" + subId);
2666         }
2667         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
2668     }
2669 
2670     /**
2671      * Whether a subscription is opportunistic or not.
2672      */
isOpportunistic(int subId)2673     public boolean isOpportunistic(int subId) {
2674         SubscriptionInfo info = getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
2675                 mContext.getAttributionTag());
2676         return (info != null) && info.isOpportunistic();
2677     }
2678 
2679     // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true
2680     // when there are multiple subscriptions per sim and probably for other reasons.
2681     @UnsupportedAppUsage
getSubIdUsingPhoneId(int phoneId)2682     public int getSubIdUsingPhoneId(int phoneId) {
2683         int[] subIds = getSubId(phoneId);
2684         if (subIds == null || subIds.length == 0) {
2685             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
2686         }
2687         return subIds[0];
2688     }
2689 
2690     /** Must be public for access from instrumentation tests. */
2691     @VisibleForTesting
getSubInfoUsingSlotIndexPrivileged(int slotIndex)2692     public List<SubscriptionInfo> getSubInfoUsingSlotIndexPrivileged(int slotIndex) {
2693         if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]+ slotIndex:" + slotIndex);
2694         if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
2695             slotIndex = getSlotIndex(getDefaultSubId());
2696         }
2697         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
2698             if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- invalid slotIndex");
2699             return null;
2700         }
2701 
2702         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
2703                 null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
2704                 new String[]{String.valueOf(slotIndex)}, null);
2705         ArrayList<SubscriptionInfo> subList = null;
2706         try {
2707             if (cursor != null) {
2708                 while (cursor.moveToNext()) {
2709                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
2710                     if (subInfo != null) {
2711                         if (subList == null) {
2712                             subList = new ArrayList<SubscriptionInfo>();
2713                         }
2714                         subList.add(subInfo);
2715                     }
2716                 }
2717             }
2718         } finally {
2719             if (cursor != null) {
2720                 cursor.close();
2721             }
2722         }
2723         if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
2724 
2725         return subList;
2726     }
2727 
2728     @UnsupportedAppUsage
validateSubId(int subId)2729     private void validateSubId(int subId) {
2730         if (DBG) logd("validateSubId subId: " + subId);
2731         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
2732             throw new RuntimeException("Invalid sub id passed as parameter");
2733         } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
2734             throw new RuntimeException("Default sub id passed as parameter");
2735         }
2736     }
2737 
getActiveSubIdArrayList()2738     private synchronized ArrayList<Integer> getActiveSubIdArrayList() {
2739         // Clone the sub id list so it can't change out from under us while iterating
2740         List<Entry<Integer, ArrayList<Integer>>> simInfoList =
2741                 new ArrayList<>(sSlotIndexToSubIds.entrySet());
2742 
2743         // Put the set of sub ids in slot index order
2744         Collections.sort(simInfoList, (x, y) -> x.getKey().compareTo(y.getKey()));
2745 
2746         // Collect the sub ids for each slot in turn
2747         ArrayList<Integer> allSubs = new ArrayList<>();
2748         for (Entry<Integer, ArrayList<Integer>> slot : simInfoList) {
2749             allSubs.addAll(slot.getValue());
2750         }
2751         return allSubs;
2752     }
2753 
isSubscriptionVisible(int subId)2754     private boolean isSubscriptionVisible(int subId) {
2755         for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
2756             if (info.getSubscriptionId() == subId) {
2757                 // If group UUID is null, it's stand alone opportunistic profile. So it's visible.
2758                 // otherwise, it's bundled opportunistic profile, and is not visible.
2759                 return info.getGroupUuid() == null;
2760             }
2761         }
2762 
2763         return true;
2764     }
2765 
2766     /**
2767      * @return the list of subId's that are active, is never null but the length maybe 0.
2768      */
2769     @Override
getActiveSubIdList(boolean visibleOnly)2770     public int[] getActiveSubIdList(boolean visibleOnly) {
2771         List<Integer> allSubs = getActiveSubIdArrayList();
2772 
2773         if (visibleOnly) {
2774             // Grouped opportunistic subscriptions should be hidden.
2775             allSubs = allSubs.stream().filter(subId -> isSubscriptionVisible(subId))
2776                     .collect(Collectors.toList());
2777         }
2778 
2779         int[] subIdArr = new int[allSubs.size()];
2780         int i = 0;
2781         for (int sub : allSubs) {
2782             subIdArr[i] = sub;
2783             i++;
2784         }
2785 
2786         if (VDBG) {
2787             logdl("[getActiveSubIdList] allSubs=" + allSubs + " subIdArr.length="
2788                     + subIdArr.length);
2789         }
2790         return subIdArr;
2791     }
2792 
2793     @Override
isActiveSubId(int subId, String callingPackage, String callingFeatureId)2794     public boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId) {
2795         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
2796                 callingFeatureId, "isActiveSubId")) {
2797             throw new SecurityException("Requires READ_PHONE_STATE permission.");
2798         }
2799         final long identity = Binder.clearCallingIdentity();
2800         try {
2801             return isActiveSubId(subId);
2802         } finally {
2803             Binder.restoreCallingIdentity(identity);
2804         }
2805     }
2806 
2807     @UnsupportedAppUsage
2808     @Deprecated // This should be moved into isActiveSubId(int, String)
isActiveSubId(int subId)2809     public boolean isActiveSubId(int subId) {
2810         boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
2811                 && getActiveSubIdArrayList().contains(subId);
2812 
2813         if (VDBG) logdl("[isActiveSubId]- " + retVal);
2814         return retVal;
2815     }
2816 
2817     /**
2818      * Get the SIM state for the slot index.
2819      * For Remote-SIMs, this method returns {@link #IccCardConstants.State.UNKNOWN}
2820      * @return SIM state as the ordinal of {@See IccCardConstants.State}
2821      */
2822     @Override
getSimStateForSlotIndex(int slotIndex)2823     public int getSimStateForSlotIndex(int slotIndex) {
2824         State simState;
2825         String err;
2826         if (slotIndex < 0) {
2827             simState = IccCardConstants.State.UNKNOWN;
2828             err = "invalid slotIndex";
2829         } else {
2830             Phone phone = null;
2831             try {
2832                 phone = PhoneFactory.getPhone(slotIndex);
2833             } catch (IllegalStateException e) {
2834                 // ignore
2835             }
2836             if (phone == null) {
2837                 simState = IccCardConstants.State.UNKNOWN;
2838                 err = "phone == null";
2839             } else {
2840                 IccCard icc = phone.getIccCard();
2841                 if (icc == null) {
2842                     simState = IccCardConstants.State.UNKNOWN;
2843                     err = "icc == null";
2844                 } else {
2845                     simState = icc.getState();
2846                     err = "";
2847                 }
2848             }
2849         }
2850         if (VDBG) {
2851             logd("getSimStateForSlotIndex: " + err + " simState=" + simState
2852                     + " ordinal=" + simState.ordinal() + " slotIndex=" + slotIndex);
2853         }
2854         return simState.ordinal();
2855     }
2856 
2857     /**
2858      * Store properties associated with SubscriptionInfo in database
2859      * @param subId Subscription Id of Subscription
2860      * @param propKey Column name in database associated with SubscriptionInfo
2861      * @param propValue Value to store in DB for particular subId & column name
2862      *
2863      * @return number of rows updated.
2864      * @hide
2865      */
2866     @Override
setSubscriptionProperty(int subId, String propKey, String propValue)2867     public int setSubscriptionProperty(int subId, String propKey, String propValue) {
2868         enforceModifyPhoneState("setSubscriptionProperty");
2869         final long token = Binder.clearCallingIdentity();
2870 
2871         try {
2872             validateSubId(subId);
2873             ContentResolver resolver = mContext.getContentResolver();
2874             int result = setSubscriptionPropertyIntoContentResolver(
2875                     subId, propKey, propValue, resolver);
2876             // Refresh the Cache of Active Subscription Info List
2877             refreshCachedActiveSubscriptionInfoList();
2878 
2879             return result;
2880         } finally {
2881             Binder.restoreCallingIdentity(token);
2882         }
2883     }
2884 
setSubscriptionPropertyIntoContentResolver( int subId, String propKey, String propValue, ContentResolver resolver)2885     private int setSubscriptionPropertyIntoContentResolver(
2886             int subId, String propKey, String propValue, ContentResolver resolver) {
2887         ContentValues value = new ContentValues();
2888         boolean updateEntireGroup = GROUP_SHARING_PROPERTIES.contains(propKey);
2889         switch (propKey) {
2890             case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
2891             case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
2892             case SubscriptionManager.CB_AMBER_ALERT:
2893             case SubscriptionManager.CB_EMERGENCY_ALERT:
2894             case SubscriptionManager.CB_ALERT_SOUND_DURATION:
2895             case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
2896             case SubscriptionManager.CB_ALERT_VIBRATE:
2897             case SubscriptionManager.CB_ALERT_SPEECH:
2898             case SubscriptionManager.CB_ETWS_TEST_ALERT:
2899             case SubscriptionManager.CB_CHANNEL_50_ALERT:
2900             case SubscriptionManager.CB_CMAS_TEST_ALERT:
2901             case SubscriptionManager.CB_OPT_OUT_DIALOG:
2902             case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
2903             case SubscriptionManager.IS_OPPORTUNISTIC:
2904             case SubscriptionManager.VT_IMS_ENABLED:
2905             case SubscriptionManager.WFC_IMS_ENABLED:
2906             case SubscriptionManager.WFC_IMS_MODE:
2907             case SubscriptionManager.WFC_IMS_ROAMING_MODE:
2908             case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
2909             case SubscriptionManager.IMS_RCS_UCE_ENABLED:
2910                 value.put(propKey, Integer.parseInt(propValue));
2911                 break;
2912             case SubscriptionManager.ALLOWED_NETWORK_TYPES:
2913                 value.put(propKey, Long.parseLong(propValue));
2914                 break;
2915             default:
2916                 if (DBG) logd("Invalid column name");
2917                 break;
2918         }
2919 
2920         return updateDatabase(value, subId, updateEntireGroup);
2921     }
2922 
2923     /**
2924      * Get properties associated with SubscriptionInfo from database
2925      *
2926      * @param subId Subscription Id of Subscription
2927      * @param propKey Column name in SubscriptionInfo database
2928      * @return Value associated with subId and propKey column in database
2929      */
2930     @Override
getSubscriptionProperty(int subId, String propKey, String callingPackage, String callingFeatureId)2931     public String getSubscriptionProperty(int subId, String propKey, String callingPackage,
2932             String callingFeatureId) {
2933         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId, callingPackage,
2934                 callingFeatureId, "getSubscriptionProperty")) {
2935             return null;
2936         }
2937 
2938         final long identity = Binder.clearCallingIdentity();
2939         try {
2940             return getSubscriptionProperty(subId, propKey);
2941         } finally {
2942             Binder.restoreCallingIdentity(identity);
2943         }
2944     }
2945 
2946     /**
2947      * Get properties associated with SubscriptionInfo from database. Note this is the version
2948      * without permission check for telephony internal use only.
2949      *
2950      * @param subId Subscription Id of Subscription
2951      * @param propKey Column name in SubscriptionInfo database
2952      * @return Value associated with subId and propKey column in database
2953      */
getSubscriptionProperty(int subId, String propKey)2954     public String getSubscriptionProperty(int subId, String propKey) {
2955         String resultValue = null;
2956         try (Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
2957                 new String[]{propKey},
2958                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
2959                 new String[]{subId + ""}, null)) {
2960             if (cursor != null) {
2961                 if (cursor.moveToFirst()) {
2962                     switch (propKey) {
2963                         case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
2964                         case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
2965                         case SubscriptionManager.CB_AMBER_ALERT:
2966                         case SubscriptionManager.CB_EMERGENCY_ALERT:
2967                         case SubscriptionManager.CB_ALERT_SOUND_DURATION:
2968                         case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
2969                         case SubscriptionManager.CB_ALERT_VIBRATE:
2970                         case SubscriptionManager.CB_ALERT_SPEECH:
2971                         case SubscriptionManager.CB_ETWS_TEST_ALERT:
2972                         case SubscriptionManager.CB_CHANNEL_50_ALERT:
2973                         case SubscriptionManager.CB_CMAS_TEST_ALERT:
2974                         case SubscriptionManager.CB_OPT_OUT_DIALOG:
2975                         case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
2976                         case SubscriptionManager.VT_IMS_ENABLED:
2977                         case SubscriptionManager.WFC_IMS_ENABLED:
2978                         case SubscriptionManager.WFC_IMS_MODE:
2979                         case SubscriptionManager.WFC_IMS_ROAMING_MODE:
2980                         case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
2981                         case SubscriptionManager.IMS_RCS_UCE_ENABLED:
2982                         case SubscriptionManager.IS_OPPORTUNISTIC:
2983                         case SubscriptionManager.GROUP_UUID:
2984                         case SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES:
2985                         case SubscriptionManager.ALLOWED_NETWORK_TYPES:
2986                             resultValue = cursor.getString(0);
2987                             break;
2988                         default:
2989                             if(DBG) logd("Invalid column name");
2990                             break;
2991                     }
2992                 } else {
2993                     if(DBG) logd("Valid row not present in db");
2994                 }
2995             } else {
2996                 if(DBG) logd("Query failed");
2997             }
2998         }
2999 
3000         if (DBG) logd("getSubscriptionProperty Query value = " + resultValue);
3001         return resultValue;
3002     }
3003 
printStackTrace(String msg)3004     private void printStackTrace(String msg) {
3005         RuntimeException re = new RuntimeException();
3006         logd("StackTrace - " + msg);
3007         StackTraceElement[] st = re.getStackTrace();
3008         boolean first = true;
3009         for (StackTraceElement ste : st) {
3010             if (first) {
3011                 first = false;
3012             } else {
3013                 logd(ste.toString());
3014             }
3015         }
3016     }
3017 
3018     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)3019     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3020         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
3021                 "Requires DUMP");
3022         final long token = Binder.clearCallingIdentity();
3023         try {
3024             pw.println("SubscriptionController:");
3025             pw.println(" mLastISubServiceRegTime=" + mLastISubServiceRegTime);
3026             pw.println(" defaultSubId=" + getDefaultSubId());
3027             pw.println(" defaultDataSubId=" + getDefaultDataSubId());
3028             pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
3029             pw.println(" defaultSmsSubId=" + getDefaultSmsSubId());
3030 
3031             pw.println(" defaultDataPhoneId=" + SubscriptionManager
3032                     .from(mContext).getDefaultDataPhoneId());
3033             pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
3034             pw.println(" defaultSmsPhoneId=" + SubscriptionManager
3035                     .from(mContext).getDefaultSmsPhoneId());
3036             pw.flush();
3037 
3038             for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
3039                 pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subIds=" + entry);
3040             }
3041             pw.flush();
3042             pw.println("++++++++++++++++++++++++++++++++");
3043 
3044             List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList(
3045                     mContext.getOpPackageName(), mContext.getAttributionTag());
3046             if (sirl != null) {
3047                 pw.println(" ActiveSubInfoList:");
3048                 for (SubscriptionInfo entry : sirl) {
3049                     pw.println("  " + entry.toString());
3050                 }
3051             } else {
3052                 pw.println(" ActiveSubInfoList: is null");
3053             }
3054             pw.flush();
3055             pw.println("++++++++++++++++++++++++++++++++");
3056 
3057             sirl = getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag());
3058             if (sirl != null) {
3059                 pw.println(" AllSubInfoList:");
3060                 for (SubscriptionInfo entry : sirl) {
3061                     pw.println("  " + entry.toString());
3062                 }
3063             } else {
3064                 pw.println(" AllSubInfoList: is null");
3065             }
3066             pw.flush();
3067             pw.println("++++++++++++++++++++++++++++++++");
3068 
3069             mLocalLog.dump(fd, pw, args);
3070             pw.flush();
3071             pw.println("++++++++++++++++++++++++++++++++");
3072             pw.flush();
3073         } finally {
3074             Binder.restoreCallingIdentity(token);
3075         }
3076     }
3077 
3078     /**
3079      * Migrating Ims settings from global setting to subscription DB, if not already done.
3080      */
3081     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
migrateImsSettings()3082     public void migrateImsSettings() {
3083         migrateImsSettingHelper(
3084                 Settings.Global.ENHANCED_4G_MODE_ENABLED,
3085                 SubscriptionManager.ENHANCED_4G_MODE_ENABLED);
3086         migrateImsSettingHelper(
3087                 Settings.Global.VT_IMS_ENABLED,
3088                 SubscriptionManager.VT_IMS_ENABLED);
3089         migrateImsSettingHelper(
3090                 Settings.Global.WFC_IMS_ENABLED,
3091                 SubscriptionManager.WFC_IMS_ENABLED);
3092         migrateImsSettingHelper(
3093                 Settings.Global.WFC_IMS_MODE,
3094                 SubscriptionManager.WFC_IMS_MODE);
3095         migrateImsSettingHelper(
3096                 Settings.Global.WFC_IMS_ROAMING_MODE,
3097                 SubscriptionManager.WFC_IMS_ROAMING_MODE);
3098         migrateImsSettingHelper(
3099                 Settings.Global.WFC_IMS_ROAMING_ENABLED,
3100                 SubscriptionManager.WFC_IMS_ROAMING_ENABLED);
3101     }
3102 
migrateImsSettingHelper(String settingGlobal, String subscriptionProperty)3103     private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) {
3104         ContentResolver resolver = mContext.getContentResolver();
3105         int defaultSubId = getDefaultVoiceSubId();
3106         if (defaultSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
3107             return;
3108         }
3109         try {
3110             int prevSetting = Settings.Global.getInt(resolver, settingGlobal);
3111 
3112             if (prevSetting != DEPRECATED_SETTING) {
3113                 // Write previous setting into Subscription DB.
3114                 setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty,
3115                         Integer.toString(prevSetting), resolver);
3116                 // Write global setting value with DEPRECATED_SETTING making sure
3117                 // migration only happen once.
3118                 Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING);
3119             }
3120         } catch (Settings.SettingNotFoundException e) {
3121         }
3122     }
3123 
3124     /**
3125      * Set whether a subscription is opportunistic.
3126      *
3127      * Throws SecurityException if doesn't have required permission.
3128      *
3129      * @param opportunistic whether it’s opportunistic subscription.
3130      * @param subId the unique SubscriptionInfo index in database
3131      * @param callingPackage The package making the IPC.
3132      * @return the number of records updated
3133      */
3134     @Override
setOpportunistic(boolean opportunistic, int subId, String callingPackage)3135     public int setOpportunistic(boolean opportunistic, int subId, String callingPackage) {
3136         try {
3137             TelephonyPermissions.enforceCallingOrSelfModifyPermissionOrCarrierPrivilege(
3138                     mContext, subId, callingPackage);
3139         } catch (SecurityException e) {
3140             // The subscription may be inactive eSIM profile. If so, check the access rule in
3141             // database.
3142             enforceCarrierPrivilegeOnInactiveSub(subId, callingPackage,
3143                     "Caller requires permission on sub " + subId);
3144         }
3145 
3146         long token = Binder.clearCallingIdentity();
3147         try {
3148             int ret = setSubscriptionProperty(subId, SubscriptionManager.IS_OPPORTUNISTIC,
3149                     String.valueOf(opportunistic ? 1 : 0));
3150 
3151             if (ret != 0) notifySubscriptionInfoChanged();
3152 
3153             return ret;
3154         } finally {
3155             Binder.restoreCallingIdentity(token);
3156         }
3157     }
3158 
3159     /**
3160      * Get subscription info from database, and check whether caller has carrier privilege
3161      * permission with it. If checking fails, throws SecurityException.
3162      */
enforceCarrierPrivilegeOnInactiveSub(int subId, String callingPackage, String message)3163     private void enforceCarrierPrivilegeOnInactiveSub(int subId, String callingPackage,
3164             String message) {
3165         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
3166 
3167         SubscriptionManager subManager = (SubscriptionManager)
3168                 mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
3169         List<SubscriptionInfo> subInfo = getSubInfo(
3170                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
3171 
3172         try {
3173             if (!isActiveSubId(subId) && subInfo != null && subInfo.size() == 1
3174                     && subManager.canManageSubscription(subInfo.get(0), callingPackage)) {
3175                 return;
3176             }
3177             throw new SecurityException(message);
3178         } catch (IllegalArgumentException e) {
3179             // canManageSubscription will throw IllegalArgumentException if sub is not embedded
3180             // or package name is unknown. In this case, we also see it as permission check failure
3181             // and throw a SecurityException.
3182             throw new SecurityException(message);
3183         }
3184     }
3185 
3186     @Override
setPreferredDataSubscriptionId(int subId, boolean needValidation, ISetOpportunisticDataCallback callback)3187     public void setPreferredDataSubscriptionId(int subId, boolean needValidation,
3188             ISetOpportunisticDataCallback callback) {
3189         enforceModifyPhoneState("setPreferredDataSubscriptionId");
3190         final long token = Binder.clearCallingIdentity();
3191 
3192         try {
3193             PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
3194             if (phoneSwitcher == null) {
3195                 logd("Set preferred data sub: phoneSwitcher is null.");
3196                 AnomalyReporter.reportAnomaly(
3197                         UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
3198                         "Set preferred data sub: phoneSwitcher is null.");
3199                 if (callback != null) {
3200                     try {
3201                         callback.onComplete(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
3202                     } catch (RemoteException exception) {
3203                         logd("RemoteException " + exception);
3204                     }
3205                 }
3206                 return;
3207             }
3208 
3209             phoneSwitcher.trySetOpportunisticDataSubscription(subId, needValidation, callback);
3210         } finally {
3211             Binder.restoreCallingIdentity(token);
3212         }
3213     }
3214 
3215     @Override
getPreferredDataSubscriptionId()3216     public int getPreferredDataSubscriptionId() {
3217         enforceReadPrivilegedPhoneState("getPreferredDataSubscriptionId");
3218         final long token = Binder.clearCallingIdentity();
3219 
3220         try {
3221             PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
3222             if (phoneSwitcher == null) {
3223                 AnomalyReporter.reportAnomaly(
3224                         UUID.fromString("a3ab0b9d-f2aa-4baf-911d-7096c0d4645a"),
3225                         "Get preferred data sub: phoneSwitcher is null.");
3226                 return SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
3227             }
3228 
3229             return phoneSwitcher.getOpportunisticDataSubscriptionId();
3230         } finally {
3231             Binder.restoreCallingIdentity(token);
3232         }
3233     }
3234 
3235     @Override
getOpportunisticSubscriptions(String callingPackage, String callingFeatureId)3236     public List<SubscriptionInfo> getOpportunisticSubscriptions(String callingPackage,
3237             String callingFeatureId) {
3238         return getSubscriptionInfoListFromCacheHelper(
3239                 callingPackage, callingFeatureId, mCacheOpportunisticSubInfoList);
3240     }
3241 
3242     /**
3243      * Inform SubscriptionManager that subscriptions in the list are bundled
3244      * as a group. Typically it's a primary subscription and an opportunistic
3245      * subscription. It should only affect multi-SIM scenarios where primary
3246      * and opportunistic subscriptions can be activated together.
3247      * Being in the same group means they might be activated or deactivated
3248      * together, some of them may be invisible to the users, etc.
3249      *
3250      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
3251      * permission or had carrier privilege permission on the subscriptions:
3252      * {@link TelephonyManager#hasCarrierPrivileges(int)} or
3253      * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
3254      *
3255      * @throws SecurityException if the caller doesn't meet the requirements
3256      *             outlined above.
3257      * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist.
3258      *
3259      * @param subIdList list of subId that will be in the same group
3260      * @return groupUUID a UUID assigned to the subscription group. It returns
3261      * null if fails.
3262      *
3263      */
3264     @Override
createSubscriptionGroup(int[] subIdList, String callingPackage)3265     public ParcelUuid createSubscriptionGroup(int[] subIdList, String callingPackage) {
3266         if (subIdList == null || subIdList.length == 0) {
3267             throw new IllegalArgumentException("Invalid subIdList " + subIdList);
3268         }
3269 
3270         // Makes sure calling package matches caller UID.
3271         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
3272         // If it doesn't have modify phone state permission, or carrier privilege permission,
3273         // a SecurityException will be thrown.
3274         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
3275                 != PERMISSION_GRANTED && !checkCarrierPrivilegeOnSubList(
3276                         subIdList, callingPackage)) {
3277             throw new SecurityException("CreateSubscriptionGroup needs MODIFY_PHONE_STATE or"
3278                     + " carrier privilege permission on all specified subscriptions");
3279         }
3280 
3281         long identity = Binder.clearCallingIdentity();
3282 
3283         try {
3284             // Generate a UUID.
3285             ParcelUuid groupUUID = new ParcelUuid(UUID.randomUUID());
3286 
3287             ContentValues value = new ContentValues();
3288             value.put(SubscriptionManager.GROUP_UUID, groupUUID.toString());
3289             value.put(SubscriptionManager.GROUP_OWNER, callingPackage);
3290             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
3291                     value, getSelectionForSubIdList(subIdList), null);
3292 
3293             if (DBG) logdl("createSubscriptionGroup update DB result: " + result);
3294 
3295             refreshCachedActiveSubscriptionInfoList();
3296 
3297             notifySubscriptionInfoChanged();
3298 
3299             MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUUID);
3300 
3301             return groupUUID;
3302         } finally {
3303             Binder.restoreCallingIdentity(identity);
3304         }
3305     }
3306 
getOwnerPackageOfSubGroup(ParcelUuid groupUuid)3307     private String getOwnerPackageOfSubGroup(ParcelUuid groupUuid) {
3308         if (groupUuid == null) return null;
3309 
3310         List<SubscriptionInfo> infoList = getSubInfo(SubscriptionManager.GROUP_UUID
3311                 + "=\'" + groupUuid.toString() + "\'", null);
3312 
3313         return ArrayUtils.isEmpty(infoList) ? null : infoList.get(0).getGroupOwner();
3314     }
3315 
3316     /**
3317      * @param groupUuid a UUID assigned to the subscription group.
3318      * @param callingPackage the package making the IPC.
3319      * @return if callingPackage has carrier privilege on sublist.
3320      *
3321      */
canPackageManageGroup(ParcelUuid groupUuid, String callingPackage)3322     public boolean canPackageManageGroup(ParcelUuid groupUuid, String callingPackage) {
3323         if (groupUuid == null) {
3324             throw new IllegalArgumentException("Invalid groupUuid");
3325         }
3326 
3327         if (TextUtils.isEmpty(callingPackage)) {
3328             throw new IllegalArgumentException("Empty callingPackage");
3329         }
3330 
3331         List<SubscriptionInfo> infoList;
3332 
3333         // Getting all subscriptions in the group.
3334         long identity = Binder.clearCallingIdentity();
3335         try {
3336             infoList = getSubInfo(SubscriptionManager.GROUP_UUID
3337                     + "=\'" + groupUuid.toString() + "\'", null);
3338         } finally {
3339             Binder.restoreCallingIdentity(identity);
3340         }
3341 
3342         // If the group does not exist, then by default the UUID is up for grabs so no need to
3343         // restrict management of a group (that someone may be attempting to create).
3344         if (ArrayUtils.isEmpty(infoList)) {
3345             return true;
3346         }
3347 
3348         // If the calling package is the group owner, skip carrier permission check and return
3349         // true as it was done before.
3350         if (callingPackage.equals(infoList.get(0).getGroupOwner())) return true;
3351 
3352         // Check carrier privilege for all subscriptions in the group.
3353         int[] subIdArray = infoList.stream().mapToInt(info -> info.getSubscriptionId())
3354                 .toArray();
3355         return (checkCarrierPrivilegeOnSubList(subIdArray, callingPackage));
3356     }
3357 
updateGroupOwner(ParcelUuid groupUuid, String groupOwner)3358     private int updateGroupOwner(ParcelUuid groupUuid, String groupOwner) {
3359         // If the existing group owner is different from current caller, make caller the new
3360         // owner of all subscriptions in group.
3361         // This is for use-case of:
3362         // 1) Both package1 and package2 has permission (MODIFY_PHONE_STATE or carrier
3363         // privilege permission) of all related subscriptions.
3364         // 2) Package 1 created a group.
3365         // 3) Package 2 wants to add a subscription into it.
3366         // Step 3 should be granted as all operations are permission based. Which means as
3367         // long as the package passes the permission check, it can modify the subscription
3368         // and the group. And package 2 becomes the new group owner as it's the last to pass
3369         // permission checks on all members.
3370         ContentValues value = new ContentValues(1);
3371         value.put(SubscriptionManager.GROUP_OWNER, groupOwner);
3372         return mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
3373                 value, SubscriptionManager.GROUP_UUID + "=\"" + groupUuid + "\"", null);
3374     }
3375 
3376     @Override
addSubscriptionsIntoGroup(int[] subIdList, ParcelUuid groupUuid, String callingPackage)3377     public void addSubscriptionsIntoGroup(int[] subIdList, ParcelUuid groupUuid,
3378             String callingPackage) {
3379         if (subIdList == null || subIdList.length == 0) {
3380             throw new IllegalArgumentException("Invalid subId list");
3381         }
3382 
3383         if (groupUuid == null || groupUuid.equals(INVALID_GROUP_UUID)) {
3384             throw new IllegalArgumentException("Invalid groupUuid");
3385         }
3386 
3387         // Makes sure calling package matches caller UID.
3388         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
3389         // If it doesn't have modify phone state permission, or carrier privilege permission,
3390         // a SecurityException will be thrown.
3391         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
3392                 != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
3393                 && canPackageManageGroup(groupUuid, callingPackage))) {
3394             throw new SecurityException("Requires MODIFY_PHONE_STATE or carrier privilege"
3395                     + " permissions on subscriptions and the group.");
3396         }
3397 
3398         long identity = Binder.clearCallingIdentity();
3399 
3400         try {
3401             if (DBG) {
3402                 logdl("addSubscriptionsIntoGroup sub list "
3403                         + Arrays.toString(subIdList) + " into group " + groupUuid);
3404             }
3405 
3406             ContentValues value = new ContentValues();
3407             value.put(SubscriptionManager.GROUP_UUID, groupUuid.toString());
3408             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
3409                     value, getSelectionForSubIdList(subIdList), null);
3410 
3411             if (DBG) logdl("addSubscriptionsIntoGroup update DB result: " + result);
3412 
3413             if (result > 0) {
3414                 updateGroupOwner(groupUuid, callingPackage);
3415                 refreshCachedActiveSubscriptionInfoList();
3416                 notifySubscriptionInfoChanged();
3417                 MultiSimSettingController.getInstance().notifySubscriptionGroupChanged(groupUuid);
3418             }
3419         } finally {
3420             Binder.restoreCallingIdentity(identity);
3421         }
3422     }
3423 
3424     /**
3425      * Remove a list of subscriptions from their subscription group.
3426      * See {@link SubscriptionManager#createSubscriptionGroup(List<Integer>)} for more details.
3427      *
3428      * Caller will either have {@link android.Manifest.permission#MODIFY_PHONE_STATE}
3429      * permission or had carrier privilege permission on the subscriptions:
3430      * {@link TelephonyManager#hasCarrierPrivileges()} or
3431      * {@link SubscriptionManager#canManageSubscription(SubscriptionInfo)}
3432      *
3433      * @throws SecurityException if the caller doesn't meet the requirements
3434      *             outlined above.
3435      * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong
3436      *             the specified group.
3437      *
3438      * @param subIdList list of subId that need removing from their groups.
3439      *
3440      */
removeSubscriptionsFromGroup(int[] subIdList, ParcelUuid groupUuid, String callingPackage)3441     public void removeSubscriptionsFromGroup(int[] subIdList, ParcelUuid groupUuid,
3442             String callingPackage) {
3443         if (subIdList == null || subIdList.length == 0) {
3444             return;
3445         }
3446 
3447         // Makes sure calling package matches caller UID.
3448         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
3449         // If it doesn't have modify phone state permission, or carrier privilege permission,
3450         // a SecurityException will be thrown. If it's due to invalid parameter or internal state,
3451         // it will return null.
3452         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
3453                 != PERMISSION_GRANTED && !(checkCarrierPrivilegeOnSubList(subIdList, callingPackage)
3454                 && canPackageManageGroup(groupUuid, callingPackage))) {
3455             throw new SecurityException("removeSubscriptionsFromGroup needs MODIFY_PHONE_STATE or"
3456                     + " carrier privilege permission on all specified subscriptions");
3457         }
3458 
3459         long identity = Binder.clearCallingIdentity();
3460 
3461         try {
3462             List<SubscriptionInfo> subInfoList = getSubInfo(getSelectionForSubIdList(subIdList),
3463                     null);
3464             for (SubscriptionInfo info : subInfoList) {
3465                 if (!groupUuid.equals(info.getGroupUuid())) {
3466                     throw new IllegalArgumentException("Subscription " + info.getSubscriptionId()
3467                         + " doesn't belong to group " + groupUuid);
3468                 }
3469             }
3470             ContentValues value = new ContentValues();
3471             value.put(SubscriptionManager.GROUP_UUID, (String) null);
3472             value.put(SubscriptionManager.GROUP_OWNER, (String) null);
3473             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
3474                     value, getSelectionForSubIdList(subIdList), null);
3475 
3476             if (DBG) logdl("removeSubscriptionsFromGroup update DB result: " + result);
3477 
3478             if (result > 0) {
3479                 updateGroupOwner(groupUuid, callingPackage);
3480                 refreshCachedActiveSubscriptionInfoList();
3481                 notifySubscriptionInfoChanged();
3482             }
3483         } finally {
3484             Binder.restoreCallingIdentity(identity);
3485         }
3486     }
3487 
3488     /**
3489      *  Helper function to check if the caller has carrier privilege permissions on a list of subId.
3490      *  The check can either be processed against access rules on currently active SIM cards, or
3491      *  the access rules we keep in our database for currently inactive eSIMs.
3492      *
3493      * @throws IllegalArgumentException if the some subId is invalid or doesn't exist.
3494      *
3495      *  @return true if checking passes on all subId, false otherwise.
3496      */
checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage)3497     private boolean checkCarrierPrivilegeOnSubList(int[] subIdList, String callingPackage) {
3498         // Check carrier privilege permission on active subscriptions first.
3499         // If it fails, they could be inactive. So keep them in a HashSet and later check
3500         // access rules in our database.
3501         Set<Integer> checkSubList = new HashSet<>();
3502         for (int subId : subIdList) {
3503             if (isActiveSubId(subId)) {
3504                 if (!mTelephonyManager.hasCarrierPrivileges(subId)) {
3505                     return false;
3506                 }
3507             } else {
3508                 checkSubList.add(subId);
3509             }
3510         }
3511 
3512         if (checkSubList.isEmpty()) {
3513             return true;
3514         }
3515 
3516         long identity = Binder.clearCallingIdentity();
3517 
3518         try {
3519             // Check access rules for each sub info.
3520             SubscriptionManager subscriptionManager = (SubscriptionManager)
3521                     mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
3522             List<SubscriptionInfo> subInfoList = getSubInfo(
3523                     getSelectionForSubIdList(subIdList), null);
3524 
3525             // Didn't find all the subscriptions specified in subIdList.
3526             if (subInfoList == null || subInfoList.size() != subIdList.length) {
3527                 throw new IllegalArgumentException("Invalid subInfoList.");
3528             }
3529 
3530             for (SubscriptionInfo subInfo : subInfoList) {
3531                 if (checkSubList.contains(subInfo.getSubscriptionId())) {
3532                     if (subInfo.isEmbedded() && subscriptionManager.canManageSubscription(
3533                             subInfo, callingPackage)) {
3534                         checkSubList.remove(subInfo.getSubscriptionId());
3535                     } else {
3536                         return false;
3537                     }
3538                 }
3539             }
3540 
3541             return checkSubList.isEmpty();
3542         } finally {
3543             Binder.restoreCallingIdentity(identity);
3544         }
3545     }
3546 
3547     /**
3548      * Helper function to create selection argument of a list of subId.
3549      * The result should be: "in (subId1, subId2, ...)".
3550      */
getSelectionForSubIdList(int[] subId)3551     public static String getSelectionForSubIdList(int[] subId) {
3552         StringBuilder selection = new StringBuilder();
3553         selection.append(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID);
3554         selection.append(" IN (");
3555         for (int i = 0; i < subId.length - 1; i++) {
3556             selection.append(subId[i] + ", ");
3557         }
3558         selection.append(subId[subId.length - 1]);
3559         selection.append(")");
3560 
3561         return selection.toString();
3562     }
3563 
3564     /**
3565      * Helper function to create selection argument of a list of subId.
3566      * The result should be: "in (iccId1, iccId2, ...)".
3567      */
getSelectionForIccIdList(String[] iccIds)3568     private String getSelectionForIccIdList(String[] iccIds) {
3569         StringBuilder selection = new StringBuilder();
3570         selection.append(SubscriptionManager.ICC_ID);
3571         selection.append(" IN (");
3572         for (int i = 0; i < iccIds.length - 1; i++) {
3573             selection.append("\"" + iccIds[i] + "\", ");
3574         }
3575         selection.append("\"" + iccIds[iccIds.length - 1] + "\"");
3576         selection.append(")");
3577 
3578         return selection.toString();
3579     }
3580 
3581     /**
3582      * Get subscriptionInfo list of subscriptions that are in the same group of given subId.
3583      * See {@link #createSubscriptionGroup(int[], String)} for more details.
3584      *
3585      * Caller will either have {@link android.Manifest.permission#READ_PHONE_STATE}
3586      * permission or had carrier privilege permission on the subscription.
3587      * {@link TelephonyManager#hasCarrierPrivileges(int)}
3588      *
3589      * @throws SecurityException if the caller doesn't meet the requirements
3590      *             outlined above.
3591      *
3592      * @param groupUuid of which list of subInfo will be returned.
3593      * @return list of subscriptionInfo that belong to the same group, including the given
3594      * subscription itself. It will return an empty list if no subscription belongs to the group.
3595      *
3596      */
3597     @Override
getSubscriptionsInGroup(ParcelUuid groupUuid, String callingPackage, String callingFeatureId)3598     public List<SubscriptionInfo> getSubscriptionsInGroup(ParcelUuid groupUuid,
3599             String callingPackage, String callingFeatureId) {
3600         long identity = Binder.clearCallingIdentity();
3601         List<SubscriptionInfo> subInfoList;
3602 
3603         try {
3604             subInfoList = getAllSubInfoList(mContext.getOpPackageName(),
3605                     mContext.getAttributionTag());
3606             if (groupUuid == null || subInfoList == null || subInfoList.isEmpty()) {
3607                 return new ArrayList<>();
3608             }
3609         } finally {
3610             Binder.restoreCallingIdentity(identity);
3611         }
3612 
3613         return subInfoList.stream().filter(info -> {
3614             if (!groupUuid.equals(info.getGroupUuid())) return false;
3615             int subId = info.getSubscriptionId();
3616             return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext, subId,
3617                     callingPackage, callingFeatureId, "getSubscriptionsInGroup")
3618                     || info.canManageSubscription(mContext, callingPackage);
3619         }).map(subscriptionInfo -> conditionallyRemoveIdentifiers(subscriptionInfo,
3620                 callingPackage, callingFeatureId, "getSubscriptionsInGroup"))
3621         .collect(Collectors.toList());
3622 
3623     }
3624 
getGroupUuid(int subId)3625     public ParcelUuid getGroupUuid(int subId) {
3626         ParcelUuid groupUuid;
3627         List<SubscriptionInfo> subInfo = getSubInfo(SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
3628                         + "=" + subId, null);
3629         if (subInfo == null || subInfo.size() == 0) {
3630             groupUuid = null;
3631         } else {
3632             groupUuid = subInfo.get(0).getGroupUuid();
3633         }
3634 
3635         return groupUuid;
3636     }
3637 
3638 
3639     /**
3640      * Enable/Disable a subscription
3641      * @param enable true if enabling, false if disabling
3642      * @param subId the unique SubInfoRecord index in database
3643      *
3644      * @return true if success, false if fails or the further action is
3645      * needed hence it's redirected to Euicc.
3646      */
3647     @Override
setSubscriptionEnabled(boolean enable, int subId)3648     public boolean setSubscriptionEnabled(boolean enable, int subId) {
3649         enforceModifyPhoneState("setSubscriptionEnabled");
3650 
3651         final long identity = Binder.clearCallingIdentity();
3652         try {
3653             logd("setSubscriptionEnabled" + (enable ? " enable " : " disable ")
3654                     + " subId " + subId);
3655 
3656             // Error checking.
3657             if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
3658                 throw new IllegalArgumentException(
3659                         "setSubscriptionEnabled not usable subId " + subId);
3660             }
3661 
3662             // Nothing to do if it's already active or inactive.
3663             if (enable == isActiveSubscriptionId(subId)) return true;
3664 
3665             SubscriptionInfo info = SubscriptionController.getInstance()
3666                     .getAllSubInfoList(mContext.getOpPackageName(), mContext.getAttributionTag())
3667                     .stream()
3668                     .filter(subInfo -> subInfo.getSubscriptionId() == subId)
3669                     .findFirst()
3670                     .get();
3671 
3672             if (info == null) {
3673                 logd("setSubscriptionEnabled subId " + subId + " doesn't exist.");
3674                 return false;
3675             }
3676 
3677             // TODO: make sure after slot mapping, we enable the uicc applications for the
3678             // subscription we are enabling.
3679             if (info.isEmbedded()) {
3680                 return enableEmbeddedSubscription(info, enable);
3681             } else {
3682                 return enablePhysicalSubscription(info, enable);
3683             }
3684         } finally {
3685             Binder.restoreCallingIdentity(identity);
3686         }
3687     }
3688 
enableEmbeddedSubscription(SubscriptionInfo info, boolean enable)3689     private boolean enableEmbeddedSubscription(SubscriptionInfo info, boolean enable) {
3690         // We need to send intents to Euicc for operations:
3691 
3692         // 1) In single SIM mode, turning on a eSIM subscription while pSIM is the active slot.
3693         //    Euicc will ask user to switch to DSDS if supported or to confirm SIM slot
3694         //    switching.
3695         // 2) In DSDS mode, turning on / off an eSIM profile. Euicc can ask user whether
3696         //    to turn on DSDS, or whether to switch from current active eSIM profile to it, or
3697         //    to simply show a progress dialog.
3698         // 3) In future, similar operations on triple SIM devices.
3699         enableSubscriptionOverEuiccManager(info.getSubscriptionId(), enable,
3700                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
3701         // returning false to indicate state is not changed. If changed, a subscriptionInfo
3702         // change will be filed separately.
3703         return false;
3704 
3705         // TODO: uncomment or clean up if we decide whether to support standalone CBRS for Q.
3706         // subId = enable ? subId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
3707         // updateEnabledSubscriptionGlobalSetting(subId, physicalSlotIndex);
3708     }
3709 
enablePhysicalSubscription(SubscriptionInfo info, boolean enable)3710     private boolean enablePhysicalSubscription(SubscriptionInfo info, boolean enable) {
3711         if (info == null || !SubscriptionManager.isValidSubscriptionId(info.getSubscriptionId())) {
3712             return false;
3713         }
3714 
3715         int subId = info.getSubscriptionId();
3716 
3717         UiccSlotInfo slotInfo = null;
3718         int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
3719         UiccSlotInfo[] slotsInfo = mTelephonyManager.getUiccSlotsInfo();
3720         if (slotsInfo == null) return false;
3721         for (int i = 0; i < slotsInfo.length; i++) {
3722             UiccSlotInfo curSlotInfo = slotsInfo[i];
3723             if (curSlotInfo.getCardStateInfo() == CARD_STATE_INFO_PRESENT) {
3724                 if (TextUtils.equals(IccUtils.stripTrailingFs(curSlotInfo.getCardId()),
3725                         IccUtils.stripTrailingFs(info.getCardString()))) {
3726                     slotInfo = curSlotInfo;
3727                     physicalSlotIndex = i;
3728                     break;
3729                 }
3730             }
3731         }
3732 
3733         // Can't find the existing SIM.
3734         if (slotInfo == null) return false;
3735 
3736         if (enable && !slotInfo.getIsActive()) {
3737             // We need to send intents to Euicc if we are turning on an inactive slot.
3738             // Euicc will decide whether to ask user to switch to DSDS, or change SIM
3739             // slot mapping.
3740             EuiccManager euiccManager =
3741                     (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
3742             if (euiccManager != null && euiccManager.isEnabled()) {
3743                 enableSubscriptionOverEuiccManager(subId, enable, physicalSlotIndex);
3744             } else {
3745                 // Enable / disable uicc applications.
3746                 if (!info.areUiccApplicationsEnabled()) setUiccApplicationsEnabled(enable, subId);
3747                 // If euiccManager is not enabled, we try to switch to DSDS if possible,
3748                 // or switch slot if not.
3749                 if (mTelephonyManager.isMultiSimSupported() == MULTISIM_ALLOWED) {
3750                     PhoneConfigurationManager.getInstance().switchMultiSimConfig(
3751                             mTelephonyManager.getSupportedModemCount());
3752                 } else {
3753                     UiccController.getInstance().switchSlots(new int[]{physicalSlotIndex}, null);
3754                 }
3755             }
3756             return true;
3757         } else {
3758             // Enable / disable uicc applications.
3759             setUiccApplicationsEnabled(enable, subId);
3760             return true;
3761         }
3762     }
3763 
enableSubscriptionOverEuiccManager(int subId, boolean enable, int physicalSlotIndex)3764     private void enableSubscriptionOverEuiccManager(int subId, boolean enable,
3765             int physicalSlotIndex) {
3766         logdl("enableSubscriptionOverEuiccManager" + (enable ? " enable " : " disable ")
3767                 + "subId " + subId + " on slotIndex " + physicalSlotIndex);
3768         Intent intent = new Intent(EuiccManager.ACTION_TOGGLE_SUBSCRIPTION_PRIVILEGED);
3769         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3770         intent.putExtra(EuiccManager.EXTRA_SUBSCRIPTION_ID, subId);
3771         intent.putExtra(EuiccManager.EXTRA_ENABLE_SUBSCRIPTION, enable);
3772         if (physicalSlotIndex != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
3773             intent.putExtra(EuiccManager.EXTRA_PHYSICAL_SLOT_ID, physicalSlotIndex);
3774         }
3775         mContext.startActivity(intent);
3776     }
3777 
updateEnabledSubscriptionGlobalSetting(int subId, int physicalSlotIndex)3778     private void updateEnabledSubscriptionGlobalSetting(int subId, int physicalSlotIndex) {
3779         // Write the value which subscription is enabled into global setting.
3780         Settings.Global.putInt(mContext.getContentResolver(),
3781                 Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex, subId);
3782     }
3783 
updateModemStackEnabledGlobalSetting(boolean enabled, int physicalSlotIndex)3784     private void updateModemStackEnabledGlobalSetting(boolean enabled, int physicalSlotIndex) {
3785         // Write the whether a modem stack is disabled into global setting.
3786         Settings.Global.putInt(mContext.getContentResolver(),
3787                 Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT
3788                         + physicalSlotIndex, enabled ? 1 : 0);
3789     }
3790 
getPhysicalSlotIndex(boolean isEmbedded, int subId)3791     private int getPhysicalSlotIndex(boolean isEmbedded, int subId) {
3792         UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
3793         int logicalSlotIndex = getSlotIndex(subId);
3794         int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
3795         boolean isLogicalSlotIndexValid = SubscriptionManager.isValidSlotIndex(logicalSlotIndex);
3796 
3797         for (int i = 0; i < slotInfos.length; i++) {
3798             // If we can know the logicalSlotIndex from subId, we should find the exact matching
3799             // physicalSlotIndex. However for some cases like inactive eSIM, the logicalSlotIndex
3800             // will be -1. In this case, we assume there's only one eSIM, and return the
3801             // physicalSlotIndex of that eSIM.
3802             if ((isLogicalSlotIndexValid && slotInfos[i].getLogicalSlotIdx() == logicalSlotIndex)
3803                     || (!isLogicalSlotIndexValid && slotInfos[i].getIsEuicc() && isEmbedded)) {
3804                 physicalSlotIndex = i;
3805                 break;
3806             }
3807         }
3808 
3809         return physicalSlotIndex;
3810     }
3811 
getPhysicalSlotIndexFromLogicalSlotIndex(int logicalSlotIndex)3812     private int getPhysicalSlotIndexFromLogicalSlotIndex(int logicalSlotIndex) {
3813         int physicalSlotIndex = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
3814         UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
3815         for (int i = 0; i < slotInfos.length; i++) {
3816             if (slotInfos[i].getLogicalSlotIdx() == logicalSlotIndex) {
3817                 physicalSlotIndex = i;
3818                 break;
3819             }
3820         }
3821 
3822         return physicalSlotIndex;
3823     }
3824 
3825     @Override
isSubscriptionEnabled(int subId)3826     public boolean isSubscriptionEnabled(int subId) {
3827         // TODO: b/123314365 support multi-eSIM and removable eSIM.
3828         enforceReadPrivilegedPhoneState("isSubscriptionEnabled");
3829 
3830         long identity = Binder.clearCallingIdentity();
3831         try {
3832             // Error checking.
3833             if (!SubscriptionManager.isUsableSubscriptionId(subId)) {
3834                 throw new IllegalArgumentException(
3835                         "isSubscriptionEnabled not usable subId " + subId);
3836             }
3837 
3838             List<SubscriptionInfo> infoList = getSubInfo(
3839                     SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
3840             if (infoList == null || infoList.isEmpty()) {
3841                 // Subscription doesn't exist.
3842                 return false;
3843             }
3844 
3845             boolean isEmbedded = infoList.get(0).isEmbedded();
3846 
3847             if (isEmbedded) {
3848                 return isActiveSubId(subId);
3849             } else {
3850                 // For pSIM, we also need to check if modem is disabled or not.
3851                 return isActiveSubId(subId) && PhoneConfigurationManager.getInstance()
3852                         .getPhoneStatus(PhoneFactory.getPhone(getPhoneId(subId)));
3853             }
3854 
3855         } finally {
3856             Binder.restoreCallingIdentity(identity);
3857         }
3858     }
3859 
3860     @Override
getEnabledSubscriptionId(int logicalSlotIndex)3861     public int getEnabledSubscriptionId(int logicalSlotIndex) {
3862         // TODO: b/123314365 support multi-eSIM and removable eSIM.
3863         enforceReadPrivilegedPhoneState("getEnabledSubscriptionId");
3864 
3865         long identity = Binder.clearCallingIdentity();
3866         try {
3867             if (!SubscriptionManager.isValidPhoneId(logicalSlotIndex)) {
3868                 throw new IllegalArgumentException(
3869                         "getEnabledSubscriptionId with invalid logicalSlotIndex "
3870                                 + logicalSlotIndex);
3871             }
3872 
3873             // Getting and validating the physicalSlotIndex.
3874             int physicalSlotIndex = getPhysicalSlotIndexFromLogicalSlotIndex(logicalSlotIndex);
3875             if (physicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
3876                 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
3877             }
3878 
3879             // if modem stack is disabled, return INVALID_SUBSCRIPTION_ID without reading
3880             // Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT.
3881             int modemStackEnabled = Settings.Global.getInt(mContext.getContentResolver(),
3882                     Settings.Global.MODEM_STACK_ENABLED_FOR_SLOT + physicalSlotIndex, 1);
3883             if (modemStackEnabled != 1) {
3884                 return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
3885             }
3886 
3887             int subId;
3888             try {
3889                 subId = Settings.Global.getInt(mContext.getContentResolver(),
3890                         Settings.Global.ENABLED_SUBSCRIPTION_FOR_SLOT + physicalSlotIndex);
3891             } catch (Settings.SettingNotFoundException e) {
3892                 // Value never set. Return whether it's currently active.
3893                 subId = getSubIdUsingPhoneId(logicalSlotIndex);
3894             }
3895 
3896             return subId;
3897         } finally {
3898             Binder.restoreCallingIdentity(identity);
3899         }
3900     }
3901 
3902     // Helper function of getOpportunisticSubscriptions and getActiveSubscriptionInfoList.
3903     // They are doing similar things except operating on different cache.
getSubscriptionInfoListFromCacheHelper( String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList)3904     private List<SubscriptionInfo> getSubscriptionInfoListFromCacheHelper(
3905             String callingPackage, String callingFeatureId, List<SubscriptionInfo> cacheSubList) {
3906         boolean canReadPhoneState = false;
3907         boolean canReadIdentifiers = false;
3908         boolean canReadPhoneNumber = false;
3909         try {
3910             canReadPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
3911                     SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
3912                     Binder.getCallingUid(), callingPackage, callingFeatureId,
3913                     "getSubscriptionInfoList");
3914             // If the calling package has the READ_PHONE_STATE permission then check if the caller
3915             // also has access to subscriber identifiers and the phone number to ensure that the ICC
3916             // ID and any other unique identifiers are removed if the caller should not have access.
3917             if (canReadPhoneState) {
3918                 canReadIdentifiers = hasSubscriberIdentifierAccess(
3919                         SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
3920                         callingFeatureId, "getSubscriptionInfoList");
3921                 canReadPhoneNumber = hasPhoneNumberAccess(
3922                         SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
3923                         callingFeatureId, "getSubscriptionInfoList");
3924             }
3925         } catch (SecurityException e) {
3926             // If a SecurityException is thrown during the READ_PHONE_STATE check then the only way
3927             // to access a subscription is to have carrier privileges for its subId; an app with
3928             // carrier privileges for a subscription is also granted access to all identifiers so
3929             // the identifier and phone number access checks are not required.
3930         }
3931 
3932         synchronized (mSubInfoListLock) {
3933             // If the caller can read all phone state, just return the full list.
3934             if (canReadIdentifiers && canReadPhoneNumber) {
3935                 return new ArrayList<>(cacheSubList);
3936             }
3937             // Filter the list to only include subscriptions which the caller can manage.
3938             List<SubscriptionInfo> subscriptions = new ArrayList<>(cacheSubList.size());
3939             for (SubscriptionInfo subscriptionInfo : cacheSubList) {
3940                 int subId = subscriptionInfo.getSubscriptionId();
3941                 boolean hasCarrierPrivileges = TelephonyPermissions.checkCarrierPrivilegeForSubId(
3942                         mContext, subId);
3943                 // If the caller does not have the READ_PHONE_STATE permission nor carrier
3944                 // privileges then they cannot access the current subscription.
3945                 if (!canReadPhoneState && !hasCarrierPrivileges) {
3946                     continue;
3947                 }
3948                 // If the caller has carrier privileges then they are granted access to all
3949                 // identifiers for their subscription.
3950                 if (hasCarrierPrivileges) {
3951                     subscriptions.add(subscriptionInfo);
3952                 } else {
3953                     // The caller does not have carrier privileges for this subId, filter the
3954                     // identifiers in the subscription based on the results of the initial
3955                     // permission checks.
3956                     subscriptions.add(
3957                             conditionallyRemoveIdentifiers(subscriptionInfo, canReadIdentifiers,
3958                                     canReadPhoneNumber));
3959                 }
3960             }
3961             return subscriptions;
3962         }
3963     }
3964 
3965     /**
3966      * Conditionally removes identifiers from the provided {@code subInfo} if the {@code
3967      * callingPackage} does not meet the access requirements for identifiers and returns the
3968      * potentially modified object..
3969      *
3970      * <p>If the caller does not meet the access requirements for identifiers a clone of the
3971      * provided SubscriptionInfo is created and modified to avoid altering SubscriptionInfo objects
3972      * in a cache.
3973      */
conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, String callingPackage, String callingFeatureId, String message)3974     private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
3975             String callingPackage, String callingFeatureId, String message) {
3976         SubscriptionInfo result = subInfo;
3977         int subId = subInfo.getSubscriptionId();
3978         boolean hasIdentifierAccess = hasSubscriberIdentifierAccess(subId, callingPackage,
3979                 callingFeatureId, message);
3980         boolean hasPhoneNumberAccess = hasPhoneNumberAccess(subId, callingPackage, callingFeatureId,
3981                 message);
3982         return conditionallyRemoveIdentifiers(subInfo, hasIdentifierAccess, hasPhoneNumberAccess);
3983     }
3984 
3985     /**
3986      * Conditionally removes identifiers from the provided {@code subInfo} based on if the calling
3987      * package {@code hasIdentifierAccess} and {@code hasPhoneNumberAccess} and returns the
3988      * potentially modified object.
3989      *
3990      * <p>If the caller specifies the package does not have identifier or phone number access
3991      * a clone of the provided SubscriptionInfo is created and modified to avoid altering
3992      * SubscriptionInfo objects in a cache.
3993      */
conditionallyRemoveIdentifiers(SubscriptionInfo subInfo, boolean hasIdentifierAccess, boolean hasPhoneNumberAccess)3994     private SubscriptionInfo conditionallyRemoveIdentifiers(SubscriptionInfo subInfo,
3995             boolean hasIdentifierAccess, boolean hasPhoneNumberAccess) {
3996         if (hasIdentifierAccess && hasPhoneNumberAccess) {
3997             return subInfo;
3998         }
3999         SubscriptionInfo result = new SubscriptionInfo(subInfo);
4000         if (!hasIdentifierAccess) {
4001             result.clearIccId();
4002             result.clearCardString();
4003         }
4004         if (!hasPhoneNumberAccess) {
4005             result.clearNumber();
4006         }
4007         return result;
4008     }
4009 
addToSubIdList(int slotIndex, int subId, int subscriptionType)4010     private synchronized boolean addToSubIdList(int slotIndex, int subId, int subscriptionType) {
4011         ArrayList<Integer> subIdsList = sSlotIndexToSubIds.getCopy(slotIndex);
4012         if (subIdsList == null) {
4013             subIdsList = new ArrayList<>();
4014             sSlotIndexToSubIds.put(slotIndex, subIdsList);
4015         }
4016 
4017         // add the given subId unless it already exists
4018         if (subIdsList.contains(subId)) {
4019             logdl("slotIndex, subId combo already exists in the map. Not adding it again.");
4020             return false;
4021         }
4022         if (isSubscriptionForRemoteSim(subscriptionType)) {
4023             // For Remote SIM subscriptions, a slot can have multiple subscriptions.
4024             sSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
4025         } else {
4026             // for all other types of subscriptions, a slot can have only one subscription at a time
4027             sSlotIndexToSubIds.clearSubIdList(slotIndex);
4028             sSlotIndexToSubIds.addToSubIdList(slotIndex, subId);
4029         }
4030 
4031 
4032         // Remove the slot from sSlotIndexToSubIds if it has the same sub id with the added slot
4033         for (Entry<Integer, ArrayList<Integer>> entry : sSlotIndexToSubIds.entrySet()) {
4034             if (entry.getKey() != slotIndex && entry.getValue() != null
4035                     && entry.getValue().contains(subId)) {
4036                 logdl("addToSubIdList - remove " + entry.getKey());
4037                 sSlotIndexToSubIds.remove(entry.getKey());
4038             }
4039         }
4040 
4041         if (DBG) logdl("slotIndex, subId combo is added to the map.");
4042         return true;
4043     }
4044 
isSubscriptionForRemoteSim(int subscriptionType)4045     private boolean isSubscriptionForRemoteSim(int subscriptionType) {
4046         return subscriptionType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
4047     }
4048 
4049     /**
4050      * This is only for testing
4051      * @hide
4052      */
4053     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
getSlotIndexToSubIdsMap()4054     public Map<Integer, ArrayList<Integer>> getSlotIndexToSubIdsMap() {
4055         return sSlotIndexToSubIds.getMap();
4056     }
4057 
4058     /**
4059      * This is only for testing
4060      * @hide
4061      */
4062     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
resetStaticMembers()4063     public void resetStaticMembers() {
4064         sDefaultFallbackSubId.set(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
4065         mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
4066     }
4067 
notifyOpportunisticSubscriptionInfoChanged()4068     private void notifyOpportunisticSubscriptionInfoChanged() {
4069         TelephonyRegistryManager trm =
4070                 (TelephonyRegistryManager)
4071                         mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
4072         if (DBG) logd("notifyOpptSubscriptionInfoChanged:");
4073         trm.notifyOpportunisticSubscriptionInfoChanged();
4074     }
4075 
refreshCachedOpportunisticSubscriptionInfoList()4076     private void refreshCachedOpportunisticSubscriptionInfoList() {
4077         synchronized (mSubInfoListLock) {
4078             List<SubscriptionInfo> oldOpptCachedList = mCacheOpportunisticSubInfoList;
4079 
4080             List<SubscriptionInfo> subList = getSubInfo(
4081                     SubscriptionManager.IS_OPPORTUNISTIC + "=1 AND ("
4082                             + SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
4083                             + SubscriptionManager.IS_EMBEDDED + "=1)", null);
4084 
4085             if (subList != null) {
4086                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
4087             } else {
4088                 subList = new ArrayList<>();
4089             }
4090 
4091             mCacheOpportunisticSubInfoList = subList;
4092 
4093             for (SubscriptionInfo info : mCacheOpportunisticSubInfoList) {
4094                 if (shouldDisableSubGroup(info.getGroupUuid())) {
4095                     info.setGroupDisabled(true);
4096                 }
4097             }
4098 
4099             if (DBG_CACHE) {
4100                 if (!mCacheOpportunisticSubInfoList.isEmpty()) {
4101                     for (SubscriptionInfo si : mCacheOpportunisticSubInfoList) {
4102                         logd("[refreshCachedOpptSubscriptionInfoList] Setting Cached info="
4103                                 + si);
4104                     }
4105                 } else {
4106                     logdl("[refreshCachedOpptSubscriptionInfoList]- no info return");
4107                 }
4108             }
4109 
4110             if (!oldOpptCachedList.equals(mCacheOpportunisticSubInfoList)) {
4111                 mOpptSubInfoListChangedDirtyBit.set(true);
4112             }
4113         }
4114     }
4115 
shouldDisableSubGroup(ParcelUuid groupUuid)4116     private boolean shouldDisableSubGroup(ParcelUuid groupUuid) {
4117         if (groupUuid == null) return false;
4118 
4119         for (SubscriptionInfo activeInfo : mCacheActiveSubInfoList) {
4120             if (!activeInfo.isOpportunistic() && groupUuid.equals(activeInfo.getGroupUuid())) {
4121                 return false;
4122             }
4123         }
4124 
4125         return true;
4126     }
4127 
4128     /**
4129      * Set allowing mobile data during voice call.
4130      *
4131      * @param subId Subscription index
4132      * @param rules Data enabled override rules in string format. See {@link DataEnabledOverride}
4133      * for details.
4134      * @return {@code true} if settings changed, otherwise {@code false}.
4135      */
setDataEnabledOverrideRules(int subId, @NonNull String rules)4136     public boolean setDataEnabledOverrideRules(int subId, @NonNull String rules) {
4137         if (DBG) logd("[setDataEnabledOverrideRules]+ rules:" + rules + " subId:" + subId);
4138 
4139         validateSubId(subId);
4140         ContentValues value = new ContentValues(1);
4141         value.put(SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES, rules);
4142 
4143         boolean result = updateDatabase(value, subId, true) > 0;
4144 
4145         if (result) {
4146             // Refresh the Cache of Active Subscription Info List
4147             refreshCachedActiveSubscriptionInfoList();
4148             notifySubscriptionInfoChanged();
4149         }
4150 
4151         return result;
4152     }
4153 
4154     /**
4155      * Get data enabled override rules.
4156      *
4157      * @param subId Subscription index
4158      * @return Data enabled override rules in string
4159      */
4160     @NonNull
getDataEnabledOverrideRules(int subId)4161     public String getDataEnabledOverrideRules(int subId) {
4162         return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId,
4163                 SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
4164     }
4165 
4166     /**
4167      * Get active data subscription id.
4168      *
4169      * @return Active data subscription id
4170      *
4171      * @hide
4172      */
4173     @Override
getActiveDataSubscriptionId()4174     public int getActiveDataSubscriptionId() {
4175         final long token = Binder.clearCallingIdentity();
4176 
4177         try {
4178             PhoneSwitcher phoneSwitcher = PhoneSwitcher.getInstance();
4179             if (phoneSwitcher != null) {
4180                 int activeDataSubId = phoneSwitcher.getActiveDataSubId();
4181                 if (SubscriptionManager.isUsableSubscriptionId(activeDataSubId)) {
4182                     return activeDataSubId;
4183                 }
4184             }
4185             // If phone switcher isn't ready, or active data sub id is not available, use default
4186             // sub id from settings.
4187             return getDefaultDataSubId();
4188         } finally {
4189             Binder.restoreCallingIdentity(token);
4190         }
4191     }
4192 
4193     /**
4194      * Whether it's supported to disable / re-enable a subscription on a physical (non-euicc) SIM.
4195      */
4196     @Override
canDisablePhysicalSubscription()4197     public boolean canDisablePhysicalSubscription() {
4198         enforceReadPrivilegedPhoneState("canToggleUiccApplicationsEnablement");
4199 
4200         final long identity = Binder.clearCallingIdentity();
4201         try {
4202             Phone phone = PhoneFactory.getDefaultPhone();
4203             return phone != null && phone.canDisablePhysicalSubscription();
4204         } finally {
4205             Binder.restoreCallingIdentity(identity);
4206         }
4207     }
4208 
4209     /**
4210      * @hide
4211      */
setGlobalSetting(String name, int value)4212     private void setGlobalSetting(String name, int value) {
4213         Settings.Global.putInt(mContext.getContentResolver(), name, value);
4214         if (name == Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION) {
4215             invalidateDefaultDataSubIdCaches();
4216             invalidateActiveDataSubIdCaches();
4217             invalidateDefaultSubIdCaches();
4218             invalidateSlotIndexCaches();
4219         } else if (name == Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION) {
4220             invalidateDefaultSubIdCaches();
4221             invalidateSlotIndexCaches();
4222         } else if (name == Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION) {
4223             invalidateDefaultSmsSubIdCaches();
4224         }
4225     }
4226 
4227     /**
4228      * @hide
4229      */
invalidateDefaultSubIdCaches()4230     private static void invalidateDefaultSubIdCaches() {
4231         if (sCachingEnabled) {
4232             SubscriptionManager.invalidateDefaultSubIdCaches();
4233         }
4234     }
4235 
4236     /**
4237      * @hide
4238      */
invalidateDefaultDataSubIdCaches()4239     private static void invalidateDefaultDataSubIdCaches() {
4240         if (sCachingEnabled) {
4241             SubscriptionManager.invalidateDefaultDataSubIdCaches();
4242         }
4243     }
4244 
4245     /**
4246      * @hide
4247      */
invalidateDefaultSmsSubIdCaches()4248     private static void invalidateDefaultSmsSubIdCaches() {
4249         if (sCachingEnabled) {
4250             SubscriptionManager.invalidateDefaultSmsSubIdCaches();
4251         }
4252     }
4253 
4254     /**
4255      * @hide
4256      */
invalidateActiveDataSubIdCaches()4257     protected static void invalidateActiveDataSubIdCaches() {
4258         if (sCachingEnabled) {
4259             SubscriptionManager.invalidateActiveDataSubIdCaches();
4260         }
4261     }
4262 
4263     /**
4264      * @hide
4265      */
invalidateSlotIndexCaches()4266     protected static void invalidateSlotIndexCaches() {
4267         if (sCachingEnabled) {
4268             SubscriptionManager.invalidateSlotIndexCaches();
4269         }
4270     }
4271 
4272     /**
4273      * @hide
4274      */
4275     @VisibleForTesting
disableCaching()4276     public static void disableCaching() {
4277         sCachingEnabled = false;
4278     }
4279 
4280     /**
4281      * @hide
4282      */
4283     @VisibleForTesting
enableCaching()4284     public static void enableCaching() {
4285         sCachingEnabled = true;
4286     }
4287 }
4288