• 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 android.Manifest;
20 import android.annotation.Nullable;
21 import android.app.AppOpsManager;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.net.Uri;
30 import android.os.Binder;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.provider.Settings;
35 import android.telephony.RadioAccessFamily;
36 import android.telephony.Rlog;
37 import android.telephony.SubscriptionInfo;
38 import android.telephony.SubscriptionManager;
39 import android.telephony.TelephonyManager;
40 import android.telephony.UiccAccessRule;
41 import android.telephony.euicc.EuiccManager;
42 import android.text.TextUtils;
43 import android.text.format.Time;
44 import android.util.Log;
45 
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.telephony.IccCardConstants.State;
48 
49 import java.io.FileDescriptor;
50 import java.io.PrintWriter;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.HashSet;
56 import java.util.Iterator;
57 import java.util.LinkedList;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Map.Entry;
61 import java.util.Objects;
62 import java.util.Set;
63 import java.util.concurrent.ConcurrentHashMap;
64 import java.util.concurrent.atomic.AtomicReference;
65 import java.util.stream.Collectors;
66 
67 /**
68  * SubscriptionController to provide an inter-process communication to
69  * access Sms in Icc.
70  *
71  * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the
72  * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
73  *
74  * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling
75  * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()).
76  *
77  * Finally, any getters which perform the mapping between subscriptions, slots and phones will
78  * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters
79  * will fail and return the appropriate error value. Ie calling
80  * getSlotIndex(INVALID_SUBSCRIPTION_ID) will return INVALID_SIM_SLOT_INDEX and calling
81  * getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID) will return null.
82  *
83  */
84 public class SubscriptionController extends ISub.Stub {
85     static final String LOG_TAG = "SubscriptionController";
86     static final boolean DBG = true;
87     static final boolean VDBG = false;
88     static final boolean DBG_CACHE = false;
89     static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
90     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
91 
92     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
93     private AtomicReference<List<SubscriptionInfo>> mCacheActiveSubInfoList = new AtomicReference();
94 
95     /**
96      * Copied from android.util.LocalLog with flush() adding flush and line number
97      * TODO: Update LocalLog
98      */
99     static class ScLocalLog {
100 
101         private LinkedList<String> mLog;
102         private int mMaxLines;
103         private Time mNow;
104 
ScLocalLog(int maxLines)105         public ScLocalLog(int maxLines) {
106             mLog = new LinkedList<String>();
107             mMaxLines = maxLines;
108             mNow = new Time();
109         }
110 
log(String msg)111         public synchronized void log(String msg) {
112             if (mMaxLines > 0) {
113                 int pid = android.os.Process.myPid();
114                 int tid = android.os.Process.myTid();
115                 mNow.setToNow();
116                 mLog.add(mNow.format("%m-%d %H:%M:%S") + " pid=" + pid + " tid=" + tid + " " + msg);
117                 while (mLog.size() > mMaxLines) mLog.remove();
118             }
119         }
120 
dump(FileDescriptor fd, PrintWriter pw, String[] args)121         public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
122             final int LOOPS_PER_FLUSH = 10; // Flush every N loops.
123             Iterator<String> itr = mLog.listIterator(0);
124             int i = 0;
125             while (itr.hasNext()) {
126                 pw.println(Integer.toString(i++) + ": " + itr.next());
127                 // Flush periodically so we don't drop lines
128                 if ((i % LOOPS_PER_FLUSH) == 0) pw.flush();
129             }
130         }
131     }
132 
133     private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
134             (arg0, arg1) -> {
135                 // Primary sort key on SimSlotIndex
136                 int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
137                 if (flag == 0) {
138                     // Secondary sort on SubscriptionId
139                     return arg0.getSubscriptionId() - arg1.getSubscriptionId();
140                 }
141                 return flag;
142             };
143 
144     protected final Object mLock = new Object();
145 
146     /** The singleton instance. */
147     private static SubscriptionController sInstance = null;
148     protected static Phone[] sPhones;
149     protected Context mContext;
150     protected TelephonyManager mTelephonyManager;
151     protected CallManager mCM;
152 
153     private AppOpsManager mAppOps;
154 
155     // FIXME: Does not allow for multiple subs in a slot and change to SparseArray
156     private static Map<Integer, Integer> sSlotIndexToSubId =
157             new ConcurrentHashMap<Integer, Integer>();
158     private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
159     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
160 
161     private int[] colorArr;
162 
init(Phone phone)163     public static SubscriptionController init(Phone phone) {
164         synchronized (SubscriptionController.class) {
165             if (sInstance == null) {
166                 sInstance = new SubscriptionController(phone);
167             } else {
168                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
169             }
170             return sInstance;
171         }
172     }
173 
init(Context c, CommandsInterface[] ci)174     public static SubscriptionController init(Context c, CommandsInterface[] ci) {
175         synchronized (SubscriptionController.class) {
176             if (sInstance == null) {
177                 sInstance = new SubscriptionController(c);
178             } else {
179                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
180             }
181             return sInstance;
182         }
183     }
184 
getInstance()185     public static SubscriptionController getInstance() {
186         if (sInstance == null)
187         {
188            Log.wtf(LOG_TAG, "getInstance null");
189         }
190 
191         return sInstance;
192     }
193 
SubscriptionController(Context c)194     protected SubscriptionController(Context c) {
195         init(c);
196     }
197 
init(Context c)198     protected void init(Context c) {
199         mContext = c;
200         mCM = CallManager.getInstance();
201         mTelephonyManager = TelephonyManager.from(mContext);
202 
203         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
204 
205         if(ServiceManager.getService("isub") == null) {
206                 ServiceManager.addService("isub", this);
207         }
208 
209         if (DBG) logdl("[SubscriptionController] init by Context");
210     }
211 
isSubInfoReady()212     private boolean isSubInfoReady() {
213         return sSlotIndexToSubId.size() > 0;
214     }
215 
SubscriptionController(Phone phone)216     private SubscriptionController(Phone phone) {
217         mContext = phone.getContext();
218         mCM = CallManager.getInstance();
219         mAppOps = mContext.getSystemService(AppOpsManager.class);
220 
221         if(ServiceManager.getService("isub") == null) {
222                 ServiceManager.addService("isub", this);
223         }
224 
225         if (DBG) logdl("[SubscriptionController] init by Phone");
226     }
227 
228     /**
229      * Make sure the caller can read phone state which requires holding the
230      * READ_PHONE_STATE permission and the OP_READ_PHONE_STATE app op being
231      * set to MODE_ALLOWED.
232      *
233      * @param callingPackage The package claiming to make the IPC.
234      * @param message The name of the access protected method.
235      *
236      * @throws SecurityException if the caller does not have READ_PHONE_STATE permission.
237      */
canReadPhoneState(String callingPackage, String message)238     private boolean canReadPhoneState(String callingPackage, String message) {
239         try {
240             mContext.enforceCallingOrSelfPermission(
241                     android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message);
242 
243             // SKIP checking run-time permission since self or using PRIVILEDGED permission
244             return true;
245         } catch (SecurityException e) {
246             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
247                     message);
248         }
249 
250         return mAppOps.noteOp(AppOpsManager.OP_READ_PHONE_STATE, Binder.getCallingUid(),
251                 callingPackage) == AppOpsManager.MODE_ALLOWED;
252     }
253 
enforceModifyPhoneState(String message)254     private void enforceModifyPhoneState(String message) {
255         mContext.enforceCallingOrSelfPermission(
256                 android.Manifest.permission.MODIFY_PHONE_STATE, message);
257     }
258 
259     /**
260      * Broadcast when SubscriptionInfo has changed
261      * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
262      */
broadcastSimInfoContentChanged()263      private void broadcastSimInfoContentChanged() {
264         Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
265         mContext.sendBroadcast(intent);
266         intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
267         mContext.sendBroadcast(intent);
268      }
269 
notifySubscriptionInfoChanged()270      public void notifySubscriptionInfoChanged() {
271          ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
272                  "telephony.registry"));
273          try {
274              if (DBG) logd("notifySubscriptionInfoChanged:");
275              tr.notifySubscriptionInfoChanged();
276          } catch (RemoteException ex) {
277              // Should never happen because its always available.
278          }
279 
280          // FIXME: Remove if listener technique accepted.
281          broadcastSimInfoContentChanged();
282      }
283 
284     /**
285      * New SubInfoRecord instance and fill in detail info
286      * @param cursor
287      * @return the query result of desired SubInfoRecord
288      */
getSubInfoRecord(Cursor cursor)289     private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
290         int id = cursor.getInt(cursor.getColumnIndexOrThrow(
291                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
292         String iccId = cursor.getString(cursor.getColumnIndexOrThrow(
293                 SubscriptionManager.ICC_ID));
294         int simSlotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
295                 SubscriptionManager.SIM_SLOT_INDEX));
296         String displayName = cursor.getString(cursor.getColumnIndexOrThrow(
297                 SubscriptionManager.DISPLAY_NAME));
298         String carrierName = cursor.getString(cursor.getColumnIndexOrThrow(
299                 SubscriptionManager.CARRIER_NAME));
300         int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
301                 SubscriptionManager.NAME_SOURCE));
302         int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
303                 SubscriptionManager.COLOR));
304         String number = cursor.getString(cursor.getColumnIndexOrThrow(
305                 SubscriptionManager.NUMBER));
306         int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
307                 SubscriptionManager.DATA_ROAMING));
308         // Get the blank bitmap for this SubInfoRecord
309         Bitmap iconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
310                 com.android.internal.R.drawable.ic_sim_card_multi_24px_clr);
311         int mcc = cursor.getInt(cursor.getColumnIndexOrThrow(
312                 SubscriptionManager.MCC));
313         int mnc = cursor.getInt(cursor.getColumnIndexOrThrow(
314                 SubscriptionManager.MNC));
315         // FIXME: consider stick this into database too
316         String countryIso = getSubscriptionCountryIso(id);
317         boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
318                 SubscriptionManager.IS_EMBEDDED)) == 1;
319         UiccAccessRule[] accessRules;
320         if (isEmbedded) {
321             accessRules = UiccAccessRule.decodeRules(cursor.getBlob(
322                     cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)));
323         } else {
324             accessRules = null;
325         }
326 
327         if (VDBG) {
328             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
329             logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
330                     + simSlotIndex + " displayName:" + displayName + " nameSource:" + nameSource
331                     + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
332                     + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
333                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules));
334         }
335 
336         // If line1number has been set to a different number, use it instead.
337         String line1Number = mTelephonyManager.getLine1Number(id);
338         if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
339             number = line1Number;
340         }
341         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
342                 nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
343                 isEmbedded, accessRules);
344     }
345 
346     /**
347      * Get ISO country code for the subscription's provider
348      *
349      * @param subId The subscription ID
350      * @return The ISO country code for the subscription's provider
351      */
getSubscriptionCountryIso(int subId)352     private String getSubscriptionCountryIso(int subId) {
353         final int phoneId = getPhoneId(subId);
354         if (phoneId < 0) {
355             return "";
356         }
357         return mTelephonyManager.getSimCountryIsoForPhone(phoneId);
358     }
359 
360     /**
361      * Query SubInfoRecord(s) from subinfo database
362      * @param selection A filter declaring which rows to return
363      * @param queryKey query key content
364      * @return Array list of queried result from database
365      */
getSubInfo(String selection, Object queryKey)366      private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
367         if (VDBG) logd("selection:" + selection + " " + queryKey);
368         String[] selectionArgs = null;
369         if (queryKey != null) {
370             selectionArgs = new String[] {queryKey.toString()};
371         }
372         ArrayList<SubscriptionInfo> subList = null;
373         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
374                 null, selection, selectionArgs, null);
375         try {
376             if (cursor != null) {
377                 while (cursor.moveToNext()) {
378                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
379                     if (subInfo != null)
380                     {
381                         if (subList == null)
382                         {
383                             subList = new ArrayList<SubscriptionInfo>();
384                         }
385                         subList.add(subInfo);
386                 }
387                 }
388             } else {
389                 if (DBG) logd("Query fail");
390             }
391         } finally {
392             if (cursor != null) {
393                 cursor.close();
394             }
395         }
396 
397         return subList;
398     }
399 
400     /**
401      * Find unused color to be set for new SubInfoRecord
402      * @param callingPackage The package making the IPC.
403      * @return RGB integer value of color
404      */
getUnusedColor(String callingPackage)405     private int getUnusedColor(String callingPackage) {
406         List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage);
407         colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
408         int colorIdx = 0;
409 
410         if (availableSubInfos != null) {
411             for (int i = 0; i < colorArr.length; i++) {
412                 int j;
413                 for (j = 0; j < availableSubInfos.size(); j++) {
414                     if (colorArr[i] == availableSubInfos.get(j).getIconTint()) {
415                         break;
416                     }
417                 }
418                 if (j == availableSubInfos.size()) {
419                     return colorArr[i];
420                 }
421             }
422             colorIdx = availableSubInfos.size() % colorArr.length;
423         }
424         return colorArr[colorIdx];
425     }
426 
427     /**
428      * Get the active SubscriptionInfo with the subId key
429      * @param subId The unique SubscriptionInfo key in database
430      * @param callingPackage The package making the IPC.
431      * @return SubscriptionInfo, maybe null if its not active
432      */
433     @Override
getActiveSubscriptionInfo(int subId, String callingPackage)434     public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
435         if (!canReadPhoneState(callingPackage, "getActiveSubscriptionInfo")) {
436             return null;
437         }
438 
439         // Now that all security checks passes, perform the operation as ourselves.
440         final long identity = Binder.clearCallingIdentity();
441         try {
442             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
443                     mContext.getOpPackageName());
444             if (subList != null) {
445                 for (SubscriptionInfo si : subList) {
446                     if (si.getSubscriptionId() == subId) {
447                         if (DBG) {
448                             logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
449                         }
450 
451                         return si;
452                     }
453                 }
454             }
455             if (DBG) {
456                 logd("[getActiveSubInfoForSubscriber]- subId=" + subId
457                         + " subList=" + subList + " subInfo=null");
458             }
459         } finally {
460             Binder.restoreCallingIdentity(identity);
461         }
462 
463         return null;
464     }
465 
466     /**
467      * Get the active SubscriptionInfo associated with the iccId
468      * @param iccId the IccId of SIM card
469      * @param callingPackage The package making the IPC.
470      * @return SubscriptionInfo, maybe null if its not active
471      */
472     @Override
getActiveSubscriptionInfoForIccId(String iccId, String callingPackage)473     public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage) {
474         if (!canReadPhoneState(callingPackage, "getActiveSubscriptionInfoForIccId") ||
475                 iccId == null) {
476             return null;
477         }
478 
479         // Now that all security checks passes, perform the operation as ourselves.
480         final long identity = Binder.clearCallingIdentity();
481         try {
482             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
483                     mContext.getOpPackageName());
484             if (subList != null) {
485                 for (SubscriptionInfo si : subList) {
486                     if (iccId.equals(si.getIccId())) {
487                         if (DBG)
488                             logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si);
489                         return si;
490                     }
491                 }
492             }
493             if (DBG) {
494                 logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId
495                         + " subList=" + subList + " subInfo=null");
496             }
497         } finally {
498             Binder.restoreCallingIdentity(identity);
499         }
500 
501         return null;
502     }
503 
504     /**
505      * Get the active SubscriptionInfo associated with the slotIndex
506      * @param slotIndex the slot which the subscription is inserted
507      * @param callingPackage The package making the IPC.
508      * @return SubscriptionInfo, maybe null if its not active
509      */
510     @Override
getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage)511     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
512             String callingPackage) {
513         if (!canReadPhoneState(callingPackage, "getActiveSubscriptionInfoForSimSlotIndex")) {
514             return null;
515         }
516 
517         // Now that all security checks passes, perform the operation as ourselves.
518         final long identity = Binder.clearCallingIdentity();
519         try {
520             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
521                     mContext.getOpPackageName());
522             if (subList != null) {
523                 for (SubscriptionInfo si : subList) {
524                     if (si.getSimSlotIndex() == slotIndex) {
525                         if (DBG) {
526                             logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
527                                     + slotIndex + " subId=" + si);
528                         }
529                         return si;
530                     }
531                 }
532                 if (DBG) {
533                     logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex
534                             + " subId=null");
535                 }
536             } else {
537                 if (DBG) {
538                     logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
539                 }
540             }
541         } finally {
542             Binder.restoreCallingIdentity(identity);
543         }
544 
545         return null;
546     }
547 
548     /**
549      * @param callingPackage The package making the IPC.
550      * @return List of all SubscriptionInfo records in database,
551      * include those that were inserted before, maybe empty but not null.
552      * @hide
553      */
554     @Override
getAllSubInfoList(String callingPackage)555     public List<SubscriptionInfo> getAllSubInfoList(String callingPackage) {
556         if (DBG) logd("[getAllSubInfoList]+");
557 
558         if (!canReadPhoneState(callingPackage, "getAllSubInfoList")) {
559             return null;
560         }
561 
562         // Now that all security checks passes, perform the operation as ourselves.
563         final long identity = Binder.clearCallingIdentity();
564         try {
565             List<SubscriptionInfo> subList = null;
566             subList = getSubInfo(null, null);
567             if (subList != null) {
568                 if (DBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
569             } else {
570                 if (DBG) logd("[getAllSubInfoList]- no info return");
571             }
572             return subList;
573         } finally {
574             Binder.restoreCallingIdentity(identity);
575         }
576     }
577 
578     /**
579      * Get the SubInfoRecord(s) of the currently inserted SIM(s)
580      * @param callingPackage The package making the IPC.
581      * @return Array list of currently inserted SubInfoRecord(s)
582      */
583     @Override
getActiveSubscriptionInfoList(String callingPackage)584     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
585 
586         if (!canReadPhoneState(callingPackage, "getActiveSubscriptionInfoList")) {
587             return null;
588         }
589 
590         // Now that all security checks passes, perform the operation as ourselves.
591         final long identity = Binder.clearCallingIdentity();
592         try {
593             if (!isSubInfoReady()) {
594                 if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
595                 return null;
596             }
597 
598             // Get the active subscription info list from the cache if the cache is not null
599             List<SubscriptionInfo> tmpCachedSubList = mCacheActiveSubInfoList.get();
600             if (tmpCachedSubList != null) {
601                 if (DBG_CACHE) {
602                     for (SubscriptionInfo si : tmpCachedSubList) {
603                         logd("[getActiveSubscriptionInfoList] Getting Cached subInfo=" + si);
604                     }
605                 }
606                 return new ArrayList<SubscriptionInfo>(tmpCachedSubList);
607             } else {
608                 if (DBG_CACHE) {
609                     logd("[getActiveSubscriptionInfoList] Cached subInfo is null");
610                 }
611                 return null;
612             }
613         } finally {
614             Binder.restoreCallingIdentity(identity);
615         }
616     }
617 
618     /**
619      * Refresh the cache of SubInfoRecord(s) of the currently inserted SIM(s)
620      */
621     @VisibleForTesting
refreshCachedActiveSubscriptionInfoList()622     protected void refreshCachedActiveSubscriptionInfoList() {
623 
624         // Now that all security checks passes, perform the operation as ourselves.
625         final long identity = Binder.clearCallingIdentity();
626         try {
627             if (!isSubInfoReady()) {
628                 if (DBG_CACHE) {
629                     logdl("[refreshCachedActiveSubscriptionInfoList] "
630                             + "Sub Controller not ready ");
631                 }
632                 return;
633             }
634 
635             List<SubscriptionInfo> subList = getSubInfo(
636                     SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
637 
638             if (subList != null) {
639                 // FIXME: Unnecessary when an insertion sort is used!
640                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
641 
642                 if (DBG_CACHE) {
643                     logdl("[refreshCachedActiveSubscriptionInfoList]- " + subList.size()
644                             + " infos return");
645                 }
646             } else {
647                 if (DBG_CACHE) logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
648             }
649 
650             if (DBG_CACHE) {
651                 for (SubscriptionInfo si : subList) {
652                     logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached subInfo=" + si);
653                 }
654             }
655             mCacheActiveSubInfoList.set(subList);
656 
657         } finally {
658             Binder.restoreCallingIdentity(identity);
659         }
660     }
661 
662     /**
663      * Get the SUB count of active SUB(s)
664      * @param callingPackage The package making the IPC.
665      * @return active SIM count
666      */
667     @Override
getActiveSubInfoCount(String callingPackage)668     public int getActiveSubInfoCount(String callingPackage) {
669         if (!canReadPhoneState(callingPackage, "getActiveSubInfoCount")) {
670             return 0;
671         }
672 
673         // Now that all security checks passes, perform the operation as ourselves.
674         final long identity = Binder.clearCallingIdentity();
675         try {
676             List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
677                     mContext.getOpPackageName());
678             if (records == null) {
679                 if (VDBG) logd("[getActiveSubInfoCount] records null");
680                 return 0;
681             }
682             if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
683             return records.size();
684         } finally {
685             Binder.restoreCallingIdentity(identity);
686         }
687     }
688 
689     /**
690      * Get the SUB count of all SUB(s) in SubscriptoinInfo database
691      * @param callingPackage The package making the IPC.
692      * @return all SIM count in database, include what was inserted before
693      */
694     @Override
getAllSubInfoCount(String callingPackage)695     public int getAllSubInfoCount(String callingPackage) {
696         if (DBG) logd("[getAllSubInfoCount]+");
697 
698         if (!canReadPhoneState(callingPackage, "getAllSubInfoCount")) {
699             return 0;
700         }
701 
702         // Now that all security checks passes, perform the operation as ourselves.
703         final long identity = Binder.clearCallingIdentity();
704         try {
705             Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
706                     null, null, null, null);
707             try {
708                 if (cursor != null) {
709                     int count = cursor.getCount();
710                     if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
711                     return count;
712                 }
713             } finally {
714                 if (cursor != null) {
715                     cursor.close();
716                 }
717             }
718             if (DBG) logd("[getAllSubInfoCount]- no SUB in DB");
719 
720             return 0;
721         } finally {
722             Binder.restoreCallingIdentity(identity);
723         }
724     }
725 
726     /**
727      * @return the maximum number of subscriptions this device will support at any one time.
728      */
729     @Override
getActiveSubInfoCountMax()730     public int getActiveSubInfoCountMax() {
731         // FIXME: This valid now but change to use TelephonyDevController in the future
732         return mTelephonyManager.getSimCount();
733     }
734 
735     @Override
getAvailableSubscriptionInfoList(String callingPackage)736     public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage) {
737         if (!canReadPhoneState(callingPackage, "getAvailableSubscriptionInfoList")) {
738             throw new SecurityException("Need READ_PHONE_STATE to call "
739                     + " getAvailableSubscriptionInfoList");
740         }
741 
742         // Now that all security checks pass, perform the operation as ourselves.
743         final long identity = Binder.clearCallingIdentity();
744         try {
745             EuiccManager euiccManager =
746                     (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
747             if (!euiccManager.isEnabled()) {
748                 if (DBG) logdl("[getAvailableSubInfoList] Embedded subscriptions are disabled");
749                 return null;
750             }
751 
752             List<SubscriptionInfo> subList = getSubInfo(
753                     SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
754                             + SubscriptionManager.IS_EMBEDDED + "=1", null);
755 
756             if (subList != null) {
757                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
758 
759                 if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return");
760             } else {
761                 if (DBG) logdl("[getAvailableSubInfoList]- no info return");
762             }
763 
764             return subList;
765         } finally {
766             Binder.restoreCallingIdentity(identity);
767         }
768     }
769 
770     @Override
getAccessibleSubscriptionInfoList(String callingPackage)771     public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
772         EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
773         if (!euiccManager.isEnabled()) {
774             if (DBG) {
775                 logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled");
776             }
777             return null;
778         }
779 
780         // Verify that the given package belongs to the calling UID.
781         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
782 
783         // Perform the operation as ourselves. If the caller cannot read phone state, they may still
784         // have carrier privileges per the subscription metadata, so we always need to make the
785         // query and then filter the results.
786         final long identity = Binder.clearCallingIdentity();
787         List<SubscriptionInfo> subList;
788         try {
789             subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null);
790         } finally {
791             Binder.restoreCallingIdentity(identity);
792         }
793 
794         if (subList == null) {
795             if (DBG) logdl("[getAccessibleSubInfoList] No info returned");
796             return null;
797         }
798 
799         // Filter the list to only include subscriptions which the (restored) caller can manage.
800         List<SubscriptionInfo> filteredList = subList.stream()
801                 .filter(subscriptionInfo ->
802                         subscriptionInfo.canManageSubscription(mContext, callingPackage))
803                 .sorted(SUBSCRIPTION_INFO_COMPARATOR)
804                 .collect(Collectors.toList());
805         if (VDBG) {
806             logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned");
807         }
808         return filteredList;
809     }
810 
811     /**
812      * Return the list of subscriptions in the database which are either:
813      * <ul>
814      * <li>Embedded (but see note about {@code includeNonRemovableSubscriptions}, or
815      * <li>In the given list of current embedded ICCIDs (which may not yet be in the database, or
816      *     which may not currently be marked as embedded).
817      * </ul>
818      *
819      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
820      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
821      *
822      * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface
823      *     entries for profiles which had been previously deleted.
824      * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions
825      *     will only be returned if the current ICCID is not removable; otherwise, they are left
826      *     alone (not returned here unless in the embeddedIccids list) under the assumption that
827      *     they will still be accessible when the eUICC containing them is activated.
828      */
829     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getSubscriptionInfoListForEmbeddedSubscriptionUpdate( String[] embeddedIccids, boolean isEuiccRemovable)830     public List<SubscriptionInfo> getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
831             String[] embeddedIccids, boolean isEuiccRemovable) {
832         StringBuilder whereClause = new StringBuilder();
833         whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1");
834         if (isEuiccRemovable) {
835             // Current eUICC is removable, so don't return non-removable subscriptions (which would
836             // be deleted), as these are expected to still be present on a different, non-removable
837             // eUICC.
838             whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1");
839         }
840         // Else, return both removable and non-removable subscriptions. This is expected to delete
841         // all removable subscriptions, which is desired as they may not be accessible.
842 
843         whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN (");
844         // ICCIDs are validated to contain only numbers when passed in, and come from a trusted
845         // app, so no need to escape.
846         for (int i = 0; i < embeddedIccids.length; i++) {
847             if (i > 0) {
848                 whereClause.append(",");
849             }
850             whereClause.append("\"").append(embeddedIccids[i]).append("\"");
851         }
852         whereClause.append(")");
853 
854         List<SubscriptionInfo> list = getSubInfo(whereClause.toString(), null);
855         if (list == null) {
856             return Collections.emptyList();
857         }
858         return list;
859     }
860 
861     @Override
requestEmbeddedSubscriptionInfoListRefresh()862     public void requestEmbeddedSubscriptionInfoListRefresh() {
863         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
864                 "requestEmbeddedSubscriptionInfoListRefresh");
865         long token = Binder.clearCallingIdentity();
866         try {
867             PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(null /* callback */);
868         } finally {
869             Binder.restoreCallingIdentity(token);
870         }
871     }
872 
873     /**
874      * Asynchronously refresh the embedded subscription info list.
875      *
876      * @param callback Optional callback to execute after the refresh completes. Must terminate
877      *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
878      */
879     // No permission check needed as this is not exposed via AIDL.
requestEmbeddedSubscriptionInfoListRefresh(@ullable Runnable callback)880     public void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) {
881         PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(callback);
882     }
883 
884     /**
885      * Add a new SubInfoRecord to subinfo database if needed
886      * @param iccId the IccId of the SIM card
887      * @param slotIndex the slot which the SIM is inserted
888      * @return 0 if success, < 0 on error.
889      */
890     @Override
addSubInfoRecord(String iccId, int slotIndex)891     public int addSubInfoRecord(String iccId, int slotIndex) {
892         if (DBG) logdl("[addSubInfoRecord]+ iccId:" + SubscriptionInfo.givePrintableIccid(iccId) +
893                 " slotIndex:" + slotIndex);
894 
895         enforceModifyPhoneState("addSubInfoRecord");
896 
897         // Now that all security checks passes, perform the operation as ourselves.
898         final long identity = Binder.clearCallingIdentity();
899         try {
900             if (iccId == null) {
901                 if (DBG) logdl("[addSubInfoRecord]- null iccId");
902                 return -1;
903             }
904 
905             ContentResolver resolver = mContext.getContentResolver();
906             Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
907                     new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
908                             SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
909                     SubscriptionManager.ICC_ID + "=?", new String[]{iccId}, null);
910 
911             boolean setDisplayName = false;
912             try {
913                 if (cursor == null || !cursor.moveToFirst()) {
914                     setDisplayName = true;
915                     Uri uri = insertEmptySubInfoRecord(iccId, slotIndex);
916                     if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
917                 } else {
918                     int subId = cursor.getInt(0);
919                     int oldSimInfoId = cursor.getInt(1);
920                     int nameSource = cursor.getInt(2);
921                     ContentValues value = new ContentValues();
922 
923                     if (slotIndex != oldSimInfoId) {
924                         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
925                     }
926 
927                     if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
928                         setDisplayName = true;
929                     }
930 
931                     if (value.size() > 0) {
932                         resolver.update(SubscriptionManager.CONTENT_URI, value,
933                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
934                                         "=" + Long.toString(subId), null);
935 
936                         // Refresh the Cache of Active Subscription Info List
937                         refreshCachedActiveSubscriptionInfoList();
938                     }
939 
940                     if (DBG) logdl("[addSubInfoRecord] Record already exists");
941                 }
942             } finally {
943                 if (cursor != null) {
944                     cursor.close();
945                 }
946             }
947 
948             cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
949                     SubscriptionManager.SIM_SLOT_INDEX + "=?",
950                     new String[] {String.valueOf(slotIndex)}, null);
951             try {
952                 if (cursor != null && cursor.moveToFirst()) {
953                     do {
954                         int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
955                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
956                         // If sSlotIndexToSubId already has the same subId for a slotIndex/phoneId,
957                         // do not add it.
958                         Integer currentSubId = sSlotIndexToSubId.get(slotIndex);
959                         if (currentSubId == null
960                                 || currentSubId != subId
961                                 || !SubscriptionManager.isValidSubscriptionId(currentSubId)) {
962                             // TODO While two subs active, if user deactivats first
963                             // one, need to update the default subId with second one.
964 
965                             // FIXME: Currently we assume phoneId == slotIndex which in the future
966                             // may not be true, for instance with multiple subs per slot.
967                             // But is true at the moment.
968                             sSlotIndexToSubId.put(slotIndex, subId);
969                             int subIdCountMax = getActiveSubInfoCountMax();
970                             int defaultSubId = getDefaultSubId();
971                             if (DBG) {
972                                 logdl("[addSubInfoRecord]"
973                                         + " sSlotIndexToSubId.size=" + sSlotIndexToSubId.size()
974                                         + " slotIndex=" + slotIndex + " subId=" + subId
975                                         + " defaultSubId=" + defaultSubId + " simCount=" + subIdCountMax);
976                             }
977 
978                             // Set the default sub if not set or if single sim device
979                             if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
980                                     || subIdCountMax == 1) {
981                                 setDefaultFallbackSubId(subId);
982                             }
983                             // If single sim device, set this subscription as the default for everything
984                             if (subIdCountMax == 1) {
985                                 if (DBG) {
986                                     logdl("[addSubInfoRecord] one sim set defaults to subId=" + subId);
987                                 }
988                                 setDefaultDataSubId(subId);
989                                 setDefaultSmsSubId(subId);
990                                 setDefaultVoiceSubId(subId);
991                             }
992                         } else {
993                             if (DBG) {
994                                 logdl("[addSubInfoRecord] currentSubId != null"
995                                         + " && currentSubId is valid, IGNORE");
996                             }
997                         }
998                         if (DBG) logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
999                     } while (cursor.moveToNext());
1000                 }
1001             } finally {
1002                 if (cursor != null) {
1003                     cursor.close();
1004                 }
1005             }
1006 
1007             // Set Display name after sub id is set above so as to get valid simCarrierName
1008             int subId = getSubIdUsingPhoneId(slotIndex);
1009             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1010                 if (DBG) {
1011                     logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
1012                 }
1013                 return -1;
1014             }
1015             if (setDisplayName) {
1016                 String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
1017                 String nameToSet;
1018 
1019                 if (!TextUtils.isEmpty(simCarrierName)) {
1020                     nameToSet = simCarrierName;
1021                 } else {
1022                     nameToSet = "CARD " + Integer.toString(slotIndex + 1);
1023                 }
1024 
1025                 ContentValues value = new ContentValues();
1026                 value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
1027                 resolver.update(SubscriptionManager.CONTENT_URI, value,
1028                         SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
1029                                 "=" + Long.toString(subId), null);
1030 
1031                 // Refresh the Cache of Active Subscription Info List
1032                 refreshCachedActiveSubscriptionInfoList();
1033 
1034                 if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
1035             }
1036 
1037             // Once the records are loaded, notify DcTracker
1038             sPhones[slotIndex].updateDataConnectionTracker();
1039 
1040             if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubId.size());
1041 
1042         } finally {
1043             Binder.restoreCallingIdentity(identity);
1044         }
1045         return 0;
1046     }
1047 
1048     /**
1049      * Insert an empty SubInfo record into the database.
1050      *
1051      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
1052      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
1053      *
1054      * <p>Precondition: No record exists with this iccId.
1055      */
1056     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
insertEmptySubInfoRecord(String iccId, int slotIndex)1057     public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
1058         ContentResolver resolver = mContext.getContentResolver();
1059         ContentValues value = new ContentValues();
1060         value.put(SubscriptionManager.ICC_ID, iccId);
1061         int color = getUnusedColor(mContext.getOpPackageName());
1062         // default SIM color differs between slots
1063         value.put(SubscriptionManager.COLOR, color);
1064         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
1065         value.put(SubscriptionManager.CARRIER_NAME, "");
1066 
1067         Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
1068 
1069         // Refresh the Cache of Active Subscription Info List
1070         refreshCachedActiveSubscriptionInfoList();
1071 
1072         return uri;
1073     }
1074 
1075     /**
1076      * Generate and set carrier text based on input parameters
1077      * @param showPlmn flag to indicate if plmn should be included in carrier text
1078      * @param plmn plmn to be included in carrier text
1079      * @param showSpn flag to indicate if spn should be included in carrier text
1080      * @param spn spn to be included in carrier text
1081      * @return true if carrier text is set, false otherwise
1082      */
setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn, String spn)1083     public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
1084                               String spn) {
1085         synchronized (mLock) {
1086             int subId = getSubIdUsingPhoneId(slotIndex);
1087             if (mContext.getPackageManager().resolveContentProvider(
1088                     SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
1089                     !SubscriptionManager.isValidSubscriptionId(subId)) {
1090                 // No place to store this info. Notify registrants of the change anyway as they
1091                 // might retrieve the SPN/PLMN text from the SST sticky broadcast.
1092                 // TODO: This can be removed once SubscriptionController is not running on devices
1093                 // that don't need it, such as TVs.
1094                 if (DBG) logd("[setPlmnSpn] No valid subscription to store info");
1095                 notifySubscriptionInfoChanged();
1096                 return false;
1097             }
1098             String carrierText = "";
1099             if (showPlmn) {
1100                 carrierText = plmn;
1101                 if (showSpn) {
1102                     // Need to show both plmn and spn if both are not same.
1103                     if(!Objects.equals(spn, plmn)) {
1104                         String separator = mContext.getString(
1105                                 com.android.internal.R.string.kg_text_message_separator).toString();
1106                         carrierText = new StringBuilder().append(carrierText).append(separator)
1107                                 .append(spn).toString();
1108                     }
1109                 }
1110             } else if (showSpn) {
1111                 carrierText = spn;
1112             }
1113             setCarrierText(carrierText, subId);
1114             return true;
1115         }
1116     }
1117 
1118     /**
1119      * Set carrier text by simInfo index
1120      * @param text new carrier text
1121      * @param subId the unique SubInfoRecord index in database
1122      * @return the number of records updated
1123      */
setCarrierText(String text, int subId)1124     private int setCarrierText(String text, int subId) {
1125         if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);
1126 
1127         enforceModifyPhoneState("setCarrierText");
1128 
1129         // Now that all security checks passes, perform the operation as ourselves.
1130         final long identity = Binder.clearCallingIdentity();
1131         try {
1132             ContentValues value = new ContentValues(1);
1133             value.put(SubscriptionManager.CARRIER_NAME, text);
1134 
1135             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
1136                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
1137                     Long.toString(subId), null);
1138 
1139             // Refresh the Cache of Active Subscription Info List
1140             refreshCachedActiveSubscriptionInfoList();
1141 
1142             notifySubscriptionInfoChanged();
1143 
1144             return result;
1145         } finally {
1146             Binder.restoreCallingIdentity(identity);
1147         }
1148     }
1149 
1150     /**
1151      * Set SIM color tint by simInfo index
1152      * @param tint the tint color of the SIM
1153      * @param subId the unique SubInfoRecord index in database
1154      * @return the number of records updated
1155      */
1156     @Override
setIconTint(int tint, int subId)1157     public int setIconTint(int tint, int subId) {
1158         if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
1159 
1160         enforceModifyPhoneState("setIconTint");
1161 
1162         // Now that all security checks passes, perform the operation as ourselves.
1163         final long identity = Binder.clearCallingIdentity();
1164         try {
1165             validateSubId(subId);
1166             ContentValues value = new ContentValues(1);
1167             value.put(SubscriptionManager.COLOR, tint);
1168             if (DBG) logd("[setIconTint]- tint:" + tint + " set");
1169 
1170             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
1171                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
1172                             Long.toString(subId), null);
1173 
1174             // Refresh the Cache of Active Subscription Info List
1175             refreshCachedActiveSubscriptionInfoList();
1176 
1177             notifySubscriptionInfoChanged();
1178 
1179             return result;
1180         } finally {
1181             Binder.restoreCallingIdentity(identity);
1182         }
1183     }
1184 
1185     /**
1186      * Set display name by simInfo index
1187      * @param displayName the display name of SIM card
1188      * @param subId the unique SubInfoRecord index in database
1189      * @return the number of records updated
1190      */
1191     @Override
setDisplayName(String displayName, int subId)1192     public int setDisplayName(String displayName, int subId) {
1193         return setDisplayNameUsingSrc(displayName, subId, -1);
1194     }
1195 
1196     /**
1197      * Set display name by simInfo index with name source
1198      * @param displayName the display name of SIM card
1199      * @param subId the unique SubInfoRecord index in database
1200      * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
1201      *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED
1202      * @return the number of records updated
1203      */
1204     @Override
setDisplayNameUsingSrc(String displayName, int subId, long nameSource)1205     public int setDisplayNameUsingSrc(String displayName, int subId, long nameSource) {
1206         if (DBG) {
1207             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
1208                 + " nameSource:" + nameSource);
1209         }
1210 
1211         enforceModifyPhoneState("setDisplayNameUsingSrc");
1212 
1213         // Now that all security checks passes, perform the operation as ourselves.
1214         final long identity = Binder.clearCallingIdentity();
1215         try {
1216             validateSubId(subId);
1217             String nameToSet;
1218             if (displayName == null) {
1219                 nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
1220             } else {
1221                 nameToSet = displayName;
1222             }
1223             ContentValues value = new ContentValues(1);
1224             value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
1225             if (nameSource >= SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE) {
1226                 if (DBG) logd("Set nameSource=" + nameSource);
1227                 value.put(SubscriptionManager.NAME_SOURCE, nameSource);
1228             }
1229             if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
1230             // TODO(b/33075886): If this is an embedded subscription, we must also save the new name
1231             // to the eSIM itself. Currently it will be blown away the next time the subscription
1232             // list is updated.
1233 
1234             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
1235                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
1236                     Long.toString(subId), null);
1237 
1238             // Refresh the Cache of Active Subscription Info List
1239             refreshCachedActiveSubscriptionInfoList();
1240 
1241             notifySubscriptionInfoChanged();
1242 
1243             return result;
1244         } finally {
1245             Binder.restoreCallingIdentity(identity);
1246         }
1247     }
1248 
1249     /**
1250      * Set phone number by subId
1251      * @param number the phone number of the SIM
1252      * @param subId the unique SubInfoRecord index in database
1253      * @return the number of records updated
1254      */
1255     @Override
setDisplayNumber(String number, int subId)1256     public int setDisplayNumber(String number, int subId) {
1257         if (DBG) logd("[setDisplayNumber]+ subId:" + subId);
1258 
1259         enforceModifyPhoneState("setDisplayNumber");
1260 
1261         // Now that all security checks passes, perform the operation as ourselves.
1262         final long identity = Binder.clearCallingIdentity();
1263         try {
1264             validateSubId(subId);
1265             int result;
1266             int phoneId = getPhoneId(subId);
1267 
1268             if (number == null || phoneId < 0 ||
1269                     phoneId >= mTelephonyManager.getPhoneCount()) {
1270                 if (DBG) logd("[setDispalyNumber]- fail");
1271                 return -1;
1272             }
1273             ContentValues value = new ContentValues(1);
1274             value.put(SubscriptionManager.NUMBER, number);
1275 
1276             // This function had a call to update number on the SIM (Phone.setLine1Number()) but
1277             // that was removed as there doesn't seem to be a reason for that. If it is added
1278             // back, watch out for deadlocks.
1279 
1280             result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
1281                     SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
1282                             + "=" + Long.toString(subId), null);
1283 
1284             // Refresh the Cache of Active Subscription Info List
1285             refreshCachedActiveSubscriptionInfoList();
1286 
1287             if (DBG) logd("[setDisplayNumber]- update result :" + result);
1288             notifySubscriptionInfoChanged();
1289 
1290             return result;
1291         } finally {
1292             Binder.restoreCallingIdentity(identity);
1293         }
1294     }
1295 
1296     /**
1297      * Set data roaming by simInfo index
1298      * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
1299      * @param subId the unique SubInfoRecord index in database
1300      * @return the number of records updated
1301      */
1302     @Override
setDataRoaming(int roaming, int subId)1303     public int setDataRoaming(int roaming, int subId) {
1304         if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
1305 
1306         enforceModifyPhoneState("setDataRoaming");
1307 
1308         // Now that all security checks passes, perform the operation as ourselves.
1309         final long identity = Binder.clearCallingIdentity();
1310         try {
1311             validateSubId(subId);
1312             if (roaming < 0) {
1313                 if (DBG) logd("[setDataRoaming]- fail");
1314                 return -1;
1315             }
1316             ContentValues value = new ContentValues(1);
1317             value.put(SubscriptionManager.DATA_ROAMING, roaming);
1318             if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
1319 
1320             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
1321                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
1322                     Long.toString(subId), null);
1323 
1324             // Refresh the Cache of Active Subscription Info List
1325             refreshCachedActiveSubscriptionInfoList();
1326 
1327             notifySubscriptionInfoChanged();
1328 
1329             return result;
1330         } finally {
1331             Binder.restoreCallingIdentity(identity);
1332         }
1333     }
1334 
1335     /**
1336      * Set MCC/MNC by subscription ID
1337      * @param mccMnc MCC/MNC associated with the subscription
1338      * @param subId the unique SubInfoRecord index in database
1339      * @return the number of records updated
1340      */
setMccMnc(String mccMnc, int subId)1341     public int setMccMnc(String mccMnc, int subId) {
1342         int mcc = 0;
1343         int mnc = 0;
1344         try {
1345             mcc = Integer.parseInt(mccMnc.substring(0,3));
1346             mnc = Integer.parseInt(mccMnc.substring(3));
1347         } catch (NumberFormatException e) {
1348             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
1349         }
1350         if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
1351         ContentValues value = new ContentValues(2);
1352         value.put(SubscriptionManager.MCC, mcc);
1353         value.put(SubscriptionManager.MNC, mnc);
1354 
1355         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
1356                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
1357 
1358         // Refresh the Cache of Active Subscription Info List
1359         refreshCachedActiveSubscriptionInfoList();
1360 
1361         notifySubscriptionInfoChanged();
1362 
1363         return result;
1364     }
1365 
1366     @Override
getSlotIndex(int subId)1367     public int getSlotIndex(int subId) {
1368         if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId);
1369 
1370         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1371             subId = getDefaultSubId();
1372         }
1373         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1374             if (DBG) logd("[getSlotIndex]- subId invalid");
1375             return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
1376         }
1377 
1378         int size = sSlotIndexToSubId.size();
1379 
1380         if (size == 0)
1381         {
1382             if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead");
1383             return SubscriptionManager.SIM_NOT_INSERTED;
1384         }
1385 
1386         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
1387             int sim = entry.getKey();
1388             int sub = entry.getValue();
1389 
1390             if (subId == sub)
1391             {
1392                 if (VDBG) logv("[getSlotIndex]- return = " + sim);
1393                 return sim;
1394             }
1395         }
1396 
1397         if (DBG) logd("[getSlotIndex]- return fail");
1398         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
1399     }
1400 
1401     /**
1402      * Return the subId for specified slot Id.
1403      * @deprecated
1404      */
1405     @Override
1406     @Deprecated
getSubId(int slotIndex)1407     public int[] getSubId(int slotIndex) {
1408         if (VDBG) printStackTrace("[getSubId]+ slotIndex=" + slotIndex);
1409 
1410         // Map default slotIndex to the current default subId.
1411         // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous
1412         // as a slot maybe used for multiple different type of "connections"
1413         // such as: voice, data and sms. But we're doing the best we can and using
1414         // getDefaultSubId which makes a best guess.
1415         if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
1416             slotIndex = getSlotIndex(getDefaultSubId());
1417             if (VDBG) logd("[getSubId] map default slotIndex=" + slotIndex);
1418         }
1419 
1420         // Check that we have a valid slotIndex
1421         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
1422             if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex);
1423             return null;
1424         }
1425 
1426         // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate.
1427         int size = sSlotIndexToSubId.size();
1428         if (size == 0) {
1429             if (VDBG) {
1430                 logd("[getSubId]- sSlotIndexToSubId.size == 0, return DummySubIds slotIndex="
1431                         + slotIndex);
1432             }
1433             return getDummySubIds(slotIndex);
1434         }
1435 
1436         // Create an array of subIds that are in this slot?
1437         ArrayList<Integer> subIds = new ArrayList<Integer>();
1438         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
1439             int slot = entry.getKey();
1440             int sub = entry.getValue();
1441             if (slotIndex == slot) {
1442                 subIds.add(sub);
1443             }
1444         }
1445 
1446         // Convert ArrayList to array
1447         int numSubIds = subIds.size();
1448         if (numSubIds > 0) {
1449             int[] subIdArr = new int[numSubIds];
1450             for (int i = 0; i < numSubIds; i++) {
1451                 subIdArr[i] = subIds.get(i);
1452             }
1453             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
1454             return subIdArr;
1455         } else {
1456             if (DBG) logd("[getSubId]- numSubIds == 0, return DummySubIds slotIndex=" + slotIndex);
1457             return getDummySubIds(slotIndex);
1458         }
1459     }
1460 
1461     @Override
getPhoneId(int subId)1462     public int getPhoneId(int subId) {
1463         if (VDBG) printStackTrace("[getPhoneId] subId=" + subId);
1464         int phoneId;
1465 
1466         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1467             subId = getDefaultSubId();
1468             if (DBG) logdl("[getPhoneId] asked for default subId=" + subId);
1469         }
1470 
1471         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1472             if (VDBG) {
1473                 logdl("[getPhoneId]- invalid subId return="
1474                         + SubscriptionManager.INVALID_PHONE_INDEX);
1475             }
1476             return SubscriptionManager.INVALID_PHONE_INDEX;
1477         }
1478 
1479         int size = sSlotIndexToSubId.size();
1480         if (size == 0) {
1481             phoneId = mDefaultPhoneId;
1482             if (DBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
1483             return phoneId;
1484         }
1485 
1486         // FIXME: Assumes phoneId == slotIndex
1487         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
1488             int sim = entry.getKey();
1489             int sub = entry.getValue();
1490 
1491             if (subId == sub) {
1492                 if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
1493                 return sim;
1494             }
1495         }
1496 
1497         phoneId = mDefaultPhoneId;
1498         if (DBG) {
1499             logdl("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId);
1500         }
1501         return phoneId;
1502 
1503     }
1504 
getDummySubIds(int slotIndex)1505     private int[] getDummySubIds(int slotIndex) {
1506         // FIXME: Remove notion of Dummy SUBSCRIPTION_ID.
1507         // I tested this returning null as no one appears to care,
1508         // but no connection came up on sprout with two sims.
1509         // We need to figure out why and hopefully remove DummySubsIds!!!
1510         int numSubs = getActiveSubInfoCountMax();
1511         if (numSubs > 0) {
1512             int[] dummyValues = new int[numSubs];
1513             for (int i = 0; i < numSubs; i++) {
1514                 dummyValues[i] = SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE - slotIndex;
1515             }
1516             if (VDBG) {
1517                 logd("getDummySubIds: slotIndex=" + slotIndex
1518                     + " return " + numSubs + " DummySubIds with each subId=" + dummyValues[0]);
1519             }
1520             return dummyValues;
1521         } else {
1522             return null;
1523         }
1524     }
1525 
1526     /**
1527      * @return the number of records cleared
1528      */
1529     @Override
clearSubInfo()1530     public int clearSubInfo() {
1531         enforceModifyPhoneState("clearSubInfo");
1532 
1533         // Now that all security checks passes, perform the operation as ourselves.
1534         final long identity = Binder.clearCallingIdentity();
1535         try {
1536             int size = sSlotIndexToSubId.size();
1537 
1538             if (size == 0) {
1539                 if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
1540                 return 0;
1541             }
1542 
1543             sSlotIndexToSubId.clear();
1544             if (DBG) logdl("[clearSubInfo]- clear size=" + size);
1545             return size;
1546         } finally {
1547             Binder.restoreCallingIdentity(identity);
1548         }
1549     }
1550 
logvl(String msg)1551     private void logvl(String msg) {
1552         logv(msg);
1553         mLocalLog.log(msg);
1554     }
1555 
logv(String msg)1556     private void logv(String msg) {
1557         Rlog.v(LOG_TAG, msg);
1558     }
1559 
logdl(String msg)1560     private void logdl(String msg) {
1561         logd(msg);
1562         mLocalLog.log(msg);
1563     }
1564 
slogd(String msg)1565     private static void slogd(String msg) {
1566         Rlog.d(LOG_TAG, msg);
1567     }
1568 
logd(String msg)1569     private void logd(String msg) {
1570         Rlog.d(LOG_TAG, msg);
1571     }
1572 
logel(String msg)1573     private void logel(String msg) {
1574         loge(msg);
1575         mLocalLog.log(msg);
1576     }
1577 
loge(String msg)1578     private void loge(String msg) {
1579         Rlog.e(LOG_TAG, msg);
1580     }
1581 
1582     @Override
getDefaultSubId()1583     public int getDefaultSubId() {
1584         int subId;
1585         boolean isVoiceCapable = mContext.getResources().getBoolean(
1586                 com.android.internal.R.bool.config_voice_capable);
1587         if (isVoiceCapable) {
1588             subId = getDefaultVoiceSubId();
1589             if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
1590         } else {
1591             subId = getDefaultDataSubId();
1592             if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
1593         }
1594         if (!isActiveSubId(subId)) {
1595             subId = mDefaultFallbackSubId;
1596             if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
1597         }
1598         if (VDBG) logv("[getDefaultSubId]- value = " + subId);
1599         return subId;
1600     }
1601 
1602     @Override
setDefaultSmsSubId(int subId)1603     public void setDefaultSmsSubId(int subId) {
1604         enforceModifyPhoneState("setDefaultSmsSubId");
1605 
1606         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1607             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
1608         }
1609         if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
1610         Settings.Global.putInt(mContext.getContentResolver(),
1611                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
1612         broadcastDefaultSmsSubIdChanged(subId);
1613     }
1614 
broadcastDefaultSmsSubIdChanged(int subId)1615     private void broadcastDefaultSmsSubIdChanged(int subId) {
1616         // Broadcast an Intent for default sms sub change
1617         if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
1618         Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
1619         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
1620                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1621         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1622         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
1623         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1624     }
1625 
1626     @Override
getDefaultSmsSubId()1627     public int getDefaultSmsSubId() {
1628         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1629                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
1630                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1631         if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId);
1632         return subId;
1633     }
1634 
1635     @Override
setDefaultVoiceSubId(int subId)1636     public void setDefaultVoiceSubId(int subId) {
1637         enforceModifyPhoneState("setDefaultVoiceSubId");
1638 
1639         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1640             throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
1641         }
1642         if (DBG) logdl("[setDefaultVoiceSubId] subId=" + subId);
1643         Settings.Global.putInt(mContext.getContentResolver(),
1644                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
1645         broadcastDefaultVoiceSubIdChanged(subId);
1646     }
1647 
broadcastDefaultVoiceSubIdChanged(int subId)1648     private void broadcastDefaultVoiceSubIdChanged(int subId) {
1649         // Broadcast an Intent for default voice sub change
1650         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
1651         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
1652         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
1653                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1654         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1655         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1656     }
1657 
1658     @Override
getDefaultVoiceSubId()1659     public int getDefaultVoiceSubId() {
1660         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1661                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
1662                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1663         if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
1664         return subId;
1665     }
1666 
1667     @Override
getDefaultDataSubId()1668     public int getDefaultDataSubId() {
1669         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1670                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
1671                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1672         if (VDBG) logd("[getDefaultDataSubId] subId= " + subId);
1673         return subId;
1674     }
1675 
1676     @Override
setDefaultDataSubId(int subId)1677     public void setDefaultDataSubId(int subId) {
1678         enforceModifyPhoneState("setDefaultDataSubId");
1679 
1680         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1681             throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
1682         }
1683 
1684         ProxyController proxyController = ProxyController.getInstance();
1685         int len = sPhones.length;
1686         logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId);
1687 
1688         if (SubscriptionManager.isValidSubscriptionId(subId)) {
1689             // Only re-map modems if the new default data sub is valid
1690             RadioAccessFamily[] rafs = new RadioAccessFamily[len];
1691             boolean atLeastOneMatch = false;
1692             for (int phoneId = 0; phoneId < len; phoneId++) {
1693                 Phone phone = sPhones[phoneId];
1694                 int raf;
1695                 int id = phone.getSubId();
1696                 if (id == subId) {
1697                     // TODO Handle the general case of N modems and M subscriptions.
1698                     raf = proxyController.getMaxRafSupported();
1699                     atLeastOneMatch = true;
1700                 } else {
1701                     // TODO Handle the general case of N modems and M subscriptions.
1702                     raf = proxyController.getMinRafSupported();
1703                 }
1704                 logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF=" + raf);
1705                 rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
1706             }
1707             if (atLeastOneMatch) {
1708                 proxyController.setRadioCapability(rafs);
1709             } else {
1710                 if (DBG) logdl("[setDefaultDataSubId] no valid subId's found - not updating.");
1711             }
1712         }
1713 
1714         // FIXME is this still needed?
1715         updateAllDataConnectionTrackers();
1716 
1717         Settings.Global.putInt(mContext.getContentResolver(),
1718                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
1719         broadcastDefaultDataSubIdChanged(subId);
1720     }
1721 
updateAllDataConnectionTrackers()1722     private void updateAllDataConnectionTrackers() {
1723         // Tell Phone Proxies to update data connection tracker
1724         int len = sPhones.length;
1725         if (DBG) logdl("[updateAllDataConnectionTrackers] sPhones.length=" + len);
1726         for (int phoneId = 0; phoneId < len; phoneId++) {
1727             if (DBG) logdl("[updateAllDataConnectionTrackers] phoneId=" + phoneId);
1728             sPhones[phoneId].updateDataConnectionTracker();
1729         }
1730     }
1731 
broadcastDefaultDataSubIdChanged(int subId)1732     private void broadcastDefaultDataSubIdChanged(int subId) {
1733         // Broadcast an Intent for default data sub change
1734         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
1735         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
1736         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
1737                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1738         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1739         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1740     }
1741 
1742     /* Sets the default subscription. If only one sub is active that
1743      * sub is set as default subId. If two or more  sub's are active
1744      * the first sub is set as default subscription
1745      */
setDefaultFallbackSubId(int subId)1746     private void setDefaultFallbackSubId(int subId) {
1747         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1748             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
1749         }
1750         if (DBG) logdl("[setDefaultFallbackSubId] subId=" + subId);
1751         if (SubscriptionManager.isValidSubscriptionId(subId)) {
1752             int phoneId = getPhoneId(subId);
1753             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
1754                     || mTelephonyManager.getSimCount() == 1)) {
1755                 if (DBG) logdl("[setDefaultFallbackSubId] set mDefaultFallbackSubId=" + subId);
1756                 mDefaultFallbackSubId = subId;
1757                 // Update MCC MNC device configuration information
1758                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
1759                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
1760 
1761                 // Broadcast an Intent for default sub change
1762                 Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
1763                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
1764                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1765                 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
1766                 if (DBG) {
1767                     logdl("[setDefaultFallbackSubId] broadcast default subId changed phoneId=" +
1768                             phoneId + " subId=" + subId);
1769                 }
1770                 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1771             } else {
1772                 if (DBG) {
1773                     logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
1774                             + " subId=" + subId);
1775                 }
1776             }
1777         }
1778     }
1779 
1780     @Override
clearDefaultsForInactiveSubIds()1781     public void clearDefaultsForInactiveSubIds() {
1782         enforceModifyPhoneState("clearDefaultsForInactiveSubIds");
1783 
1784         // Now that all security checks passes, perform the operation as ourselves.
1785         final long identity = Binder.clearCallingIdentity();
1786         try {
1787             final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
1788                     mContext.getOpPackageName());
1789             if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records);
1790             if (shouldDefaultBeCleared(records, getDefaultDataSubId())) {
1791                 if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id");
1792                 setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1793             }
1794             if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) {
1795                 if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id");
1796                 setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1797             }
1798             if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) {
1799                 if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id");
1800                 setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1801             }
1802         } finally {
1803             Binder.restoreCallingIdentity(identity);
1804         }
1805     }
1806 
shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId)1807     private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) {
1808         if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId);
1809         if (records == null) {
1810             if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId);
1811             return true;
1812         }
1813         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1814             // If the subId parameter is not valid its already cleared so return false.
1815             if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId);
1816             return false;
1817         }
1818         for (SubscriptionInfo record : records) {
1819             int id = record.getSubscriptionId();
1820             if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id);
1821             if (id == subId) {
1822                 logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId);
1823                 return false;
1824             }
1825         }
1826         if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId);
1827         return true;
1828     }
1829 
1830     // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true
1831     // when there are multiple subscriptions per sim and probably for other reasons.
getSubIdUsingPhoneId(int phoneId)1832     public int getSubIdUsingPhoneId(int phoneId) {
1833         int[] subIds = getSubId(phoneId);
1834         if (subIds == null || subIds.length == 0) {
1835             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1836         }
1837         return subIds[0];
1838     }
1839 
getSubInfoUsingSlotIndexWithCheck(int slotIndex, boolean needCheck, String callingPackage)1840     public List<SubscriptionInfo> getSubInfoUsingSlotIndexWithCheck(int slotIndex,
1841                                                                     boolean needCheck,
1842                                                                     String callingPackage) {
1843         if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]+ slotIndex:" + slotIndex);
1844         if (!canReadPhoneState(callingPackage, "getSubInfoUsingSlotIndexWithCheck")) {
1845             return null;
1846         }
1847 
1848         // Now that all security checks passes, perform the operation as ourselves.
1849         final long identity = Binder.clearCallingIdentity();
1850         try {
1851             if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
1852                 slotIndex = getSlotIndex(getDefaultSubId());
1853             }
1854             if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
1855                 if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]- invalid slotIndex");
1856                 return null;
1857             }
1858 
1859             if (needCheck && !isSubInfoReady()) {
1860                 if (DBG) logd("[getSubInfoUsingSlotIndexWithCheck]- not ready");
1861                 return null;
1862             }
1863 
1864             Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
1865                     null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
1866                     new String[]{String.valueOf(slotIndex)}, null);
1867             ArrayList<SubscriptionInfo> subList = null;
1868             try {
1869                 if (cursor != null) {
1870                     while (cursor.moveToNext()) {
1871                         SubscriptionInfo subInfo = getSubInfoRecord(cursor);
1872                         if (subInfo != null) {
1873                             if (subList == null) {
1874                                 subList = new ArrayList<SubscriptionInfo>();
1875                             }
1876                             subList.add(subInfo);
1877                         }
1878                     }
1879                 }
1880             } finally {
1881                 if (cursor != null) {
1882                     cursor.close();
1883                 }
1884             }
1885             if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
1886 
1887             return subList;
1888         } finally {
1889             Binder.restoreCallingIdentity(identity);
1890         }
1891     }
1892 
validateSubId(int subId)1893     private void validateSubId(int subId) {
1894         if (DBG) logd("validateSubId subId: " + subId);
1895         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1896             throw new RuntimeException("Invalid sub id passed as parameter");
1897         } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1898             throw new RuntimeException("Default sub id passed as parameter");
1899         }
1900     }
1901 
updatePhonesAvailability(Phone[] phones)1902     public void updatePhonesAvailability(Phone[] phones) {
1903         sPhones = phones;
1904     }
1905 
1906     /**
1907      * @return the list of subId's that are active, is never null but the length maybe 0.
1908      */
1909     @Override
getActiveSubIdList()1910     public int[] getActiveSubIdList() {
1911         Set<Entry<Integer, Integer>> simInfoSet = new HashSet<>(sSlotIndexToSubId.entrySet());
1912 
1913         int[] subIdArr = new int[simInfoSet.size()];
1914         int i = 0;
1915         for (Entry<Integer, Integer> entry: simInfoSet) {
1916             int sub = entry.getValue();
1917             subIdArr[i] = sub;
1918             i++;
1919         }
1920 
1921         if (VDBG) {
1922             logdl("[getActiveSubIdList] simInfoSet=" + simInfoSet + " subIdArr.length="
1923                     + subIdArr.length);
1924         }
1925         return subIdArr;
1926     }
1927 
1928     @Override
isActiveSubId(int subId)1929     public boolean isActiveSubId(int subId) {
1930         boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
1931                 && sSlotIndexToSubId.containsValue(subId);
1932 
1933         if (VDBG) logdl("[isActiveSubId]- " + retVal);
1934         return retVal;
1935     }
1936 
1937     /**
1938      * Get the SIM state for the slot index
1939      * @return SIM state as the ordinal of {@See IccCardConstants.State}
1940      */
1941     @Override
getSimStateForSlotIndex(int slotIndex)1942     public int getSimStateForSlotIndex(int slotIndex) {
1943         State simState;
1944         String err;
1945         if (slotIndex < 0) {
1946             simState = IccCardConstants.State.UNKNOWN;
1947             err = "invalid slotIndex";
1948         } else {
1949             Phone phone = PhoneFactory.getPhone(slotIndex);
1950             if (phone == null) {
1951                 simState = IccCardConstants.State.UNKNOWN;
1952                 err = "phone == null";
1953             } else {
1954                 IccCard icc = phone.getIccCard();
1955                 if (icc == null) {
1956                     simState = IccCardConstants.State.UNKNOWN;
1957                     err = "icc == null";
1958                 } else {
1959                     simState = icc.getState();
1960                     err = "";
1961                 }
1962             }
1963         }
1964         if (VDBG) {
1965             logd("getSimStateForSlotIndex: " + err + " simState=" + simState
1966                     + " ordinal=" + simState.ordinal() + " slotIndex=" + slotIndex);
1967         }
1968         return simState.ordinal();
1969     }
1970 
1971     /**
1972      * Store properties associated with SubscriptionInfo in database
1973      * @param subId Subscription Id of Subscription
1974      * @param propKey Column name in database associated with SubscriptionInfo
1975      * @param propValue Value to store in DB for particular subId & column name
1976      * @hide
1977      */
1978     @Override
setSubscriptionProperty(int subId, String propKey, String propValue)1979     public void setSubscriptionProperty(int subId, String propKey, String propValue) {
1980         enforceModifyPhoneState("setSubscriptionProperty");
1981         final long token = Binder.clearCallingIdentity();
1982         ContentResolver resolver = mContext.getContentResolver();
1983         ContentValues value = new ContentValues();
1984         switch (propKey) {
1985             case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
1986             case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
1987             case SubscriptionManager.CB_AMBER_ALERT:
1988             case SubscriptionManager.CB_EMERGENCY_ALERT:
1989             case SubscriptionManager.CB_ALERT_SOUND_DURATION:
1990             case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
1991             case SubscriptionManager.CB_ALERT_VIBRATE:
1992             case SubscriptionManager.CB_ALERT_SPEECH:
1993             case SubscriptionManager.CB_ETWS_TEST_ALERT:
1994             case SubscriptionManager.CB_CHANNEL_50_ALERT:
1995             case SubscriptionManager.CB_CMAS_TEST_ALERT:
1996             case SubscriptionManager.CB_OPT_OUT_DIALOG:
1997                 value.put(propKey, Integer.parseInt(propValue));
1998                 break;
1999             default:
2000                 if(DBG) logd("Invalid column name");
2001                 break;
2002         }
2003 
2004         resolver.update(SubscriptionManager.CONTENT_URI, value,
2005                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
2006                         "=" + Integer.toString(subId), null);
2007 
2008         // Refresh the Cache of Active Subscription Info List
2009         refreshCachedActiveSubscriptionInfoList();
2010 
2011         Binder.restoreCallingIdentity(token);
2012     }
2013 
2014     /**
2015      * Store properties associated with SubscriptionInfo in database
2016      * @param subId Subscription Id of Subscription
2017      * @param propKey Column name in SubscriptionInfo database
2018      * @return Value associated with subId and propKey column in database
2019      * @hide
2020      */
2021     @Override
getSubscriptionProperty(int subId, String propKey, String callingPackage)2022     public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
2023         if (!canReadPhoneState(callingPackage, "getSubInfoUsingSlotIndexWithCheck")) {
2024             return null;
2025         }
2026         String resultValue = null;
2027         ContentResolver resolver = mContext.getContentResolver();
2028         Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
2029                 new String[]{propKey},
2030                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
2031                 new String[]{subId + ""}, null);
2032 
2033         try {
2034             if (cursor != null) {
2035                 if (cursor.moveToFirst()) {
2036                     switch (propKey) {
2037                         case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
2038                         case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
2039                         case SubscriptionManager.CB_AMBER_ALERT:
2040                         case SubscriptionManager.CB_EMERGENCY_ALERT:
2041                         case SubscriptionManager.CB_ALERT_SOUND_DURATION:
2042                         case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
2043                         case SubscriptionManager.CB_ALERT_VIBRATE:
2044                         case SubscriptionManager.CB_ALERT_SPEECH:
2045                         case SubscriptionManager.CB_ETWS_TEST_ALERT:
2046                         case SubscriptionManager.CB_CHANNEL_50_ALERT:
2047                         case SubscriptionManager.CB_CMAS_TEST_ALERT:
2048                         case SubscriptionManager.CB_OPT_OUT_DIALOG:
2049                             resultValue = cursor.getInt(0) + "";
2050                             break;
2051                         default:
2052                             if(DBG) logd("Invalid column name");
2053                             break;
2054                     }
2055                 } else {
2056                     if(DBG) logd("Valid row not present in db");
2057                 }
2058             } else {
2059                 if(DBG) logd("Query failed");
2060             }
2061         } finally {
2062             if (cursor != null) {
2063                 cursor.close();
2064             }
2065         }
2066         if (DBG) logd("getSubscriptionProperty Query value = " + resultValue);
2067         return resultValue;
2068     }
2069 
printStackTrace(String msg)2070     private static void printStackTrace(String msg) {
2071         RuntimeException re = new RuntimeException();
2072         slogd("StackTrace - " + msg);
2073         StackTraceElement[] st = re.getStackTrace();
2074         boolean first = true;
2075         for (StackTraceElement ste : st) {
2076             if (first) {
2077                 first = false;
2078             } else {
2079                 slogd(ste.toString());
2080             }
2081         }
2082     }
2083 
2084     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)2085     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2086         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
2087                 "Requires DUMP");
2088         final long token = Binder.clearCallingIdentity();
2089         try {
2090             pw.println("SubscriptionController:");
2091             pw.println(" defaultSubId=" + getDefaultSubId());
2092             pw.println(" defaultDataSubId=" + getDefaultDataSubId());
2093             pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
2094             pw.println(" defaultSmsSubId=" + getDefaultSmsSubId());
2095 
2096             pw.println(" defaultDataPhoneId=" + SubscriptionManager
2097                     .from(mContext).getDefaultDataPhoneId());
2098             pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
2099             pw.println(" defaultSmsPhoneId=" + SubscriptionManager
2100                     .from(mContext).getDefaultSmsPhoneId());
2101             pw.flush();
2102 
2103             for (Entry<Integer, Integer> entry : sSlotIndexToSubId.entrySet()) {
2104                 pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subId=" + entry.getValue());
2105             }
2106             pw.flush();
2107             pw.println("++++++++++++++++++++++++++++++++");
2108 
2109             List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList(
2110                     mContext.getOpPackageName());
2111             if (sirl != null) {
2112                 pw.println(" ActiveSubInfoList:");
2113                 for (SubscriptionInfo entry : sirl) {
2114                     pw.println("  " + entry.toString());
2115                 }
2116             } else {
2117                 pw.println(" ActiveSubInfoList: is null");
2118             }
2119             pw.flush();
2120             pw.println("++++++++++++++++++++++++++++++++");
2121 
2122             sirl = getAllSubInfoList(mContext.getOpPackageName());
2123             if (sirl != null) {
2124                 pw.println(" AllSubInfoList:");
2125                 for (SubscriptionInfo entry : sirl) {
2126                     pw.println("  " + entry.toString());
2127                 }
2128             } else {
2129                 pw.println(" AllSubInfoList: is null");
2130             }
2131             pw.flush();
2132             pw.println("++++++++++++++++++++++++++++++++");
2133 
2134             mLocalLog.dump(fd, pw, args);
2135             pw.flush();
2136             pw.println("++++++++++++++++++++++++++++++++");
2137             pw.flush();
2138         } finally {
2139             Binder.restoreCallingIdentity(token);
2140         }
2141     }
2142 }
2143