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