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