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.content.ContentResolver;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.database.Cursor;
25 import android.graphics.Bitmap;
26 import android.graphics.BitmapFactory;
27 import android.net.Uri;
28 import android.os.Binder;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.os.UserHandle;
32 import android.provider.Settings;
33 import android.telephony.RadioAccessFamily;
34 import android.telephony.Rlog;
35 import android.telephony.SubscriptionInfo;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.TelephonyManager;
38 import android.text.TextUtils;
39 import android.text.format.Time;
40 import android.util.Log;
41 
42 import com.android.internal.telephony.IccCardConstants.State;
43 
44 import java.io.FileDescriptor;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.HashMap;
50 import java.util.Iterator;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Map.Entry;
54 import java.util.Set;
55 
56 /**
57  * SubscriptionController to provide an inter-process communication to
58  * access Sms in Icc.
59  *
60  * Any setters which take subId, slotId or phoneId as a parameter will throw an exception if the
61  * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
62  *
63  * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling
64  * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()).
65  *
66  * Finally, any getters which perform the mapping between subscriptions, slots and phones will
67  * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters
68  * will fail and return the appropriate error value. Ie calling getSlotId(INVALID_SUBSCRIPTION_ID)
69  * will return INVALID_SLOT_ID and calling getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID)
70  * will return null.
71  *
72  */
73 public class SubscriptionController extends ISub.Stub {
74     static final String LOG_TAG = "SubscriptionController";
75     static final boolean DBG = true;
76     static final boolean VDBG = false;
77     static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
78     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
79 
80     /**
81      * Copied from android.util.LocalLog with flush() adding flush and line number
82      * TODO: Update LocalLog
83      */
84     static class ScLocalLog {
85 
86         private LinkedList<String> mLog;
87         private int mMaxLines;
88         private Time mNow;
89 
ScLocalLog(int maxLines)90         public ScLocalLog(int maxLines) {
91             mLog = new LinkedList<String>();
92             mMaxLines = maxLines;
93             mNow = new Time();
94         }
95 
log(String msg)96         public synchronized void log(String msg) {
97             if (mMaxLines > 0) {
98                 int pid = android.os.Process.myPid();
99                 int tid = android.os.Process.myTid();
100                 mNow.setToNow();
101                 mLog.add(mNow.format("%m-%d %H:%M:%S") + " pid=" + pid + " tid=" + tid + " " + msg);
102                 while (mLog.size() > mMaxLines) mLog.remove();
103             }
104         }
105 
dump(FileDescriptor fd, PrintWriter pw, String[] args)106         public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
107             final int LOOPS_PER_FLUSH = 10; // Flush every N loops.
108             Iterator<String> itr = mLog.listIterator(0);
109             int i = 0;
110             while (itr.hasNext()) {
111                 pw.println(Integer.toString(i++) + ": " + itr.next());
112                 // Flush periodically so we don't drop lines
113                 if ((i % LOOPS_PER_FLUSH) == 0) pw.flush();
114             }
115         }
116     }
117 
118     protected final Object mLock = new Object();
119 
120     /** The singleton instance. */
121     private static SubscriptionController sInstance = null;
122     protected static PhoneProxy[] sProxyPhones;
123     protected Context mContext;
124     protected TelephonyManager mTelephonyManager;
125     protected CallManager mCM;
126 
127     // FIXME: Does not allow for multiple subs in a slot and change to SparseArray
128     private static HashMap<Integer, Integer> mSlotIdxToSubId = new HashMap<Integer, Integer>();
129     private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
130     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
131 
132     private int[] colorArr;
133 
init(Phone phone)134     public static SubscriptionController init(Phone phone) {
135         synchronized (SubscriptionController.class) {
136             if (sInstance == null) {
137                 sInstance = new SubscriptionController(phone);
138             } else {
139                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
140             }
141             return sInstance;
142         }
143     }
144 
init(Context c, CommandsInterface[] ci)145     public static SubscriptionController init(Context c, CommandsInterface[] ci) {
146         synchronized (SubscriptionController.class) {
147             if (sInstance == null) {
148                 sInstance = new SubscriptionController(c);
149             } else {
150                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
151             }
152             return sInstance;
153         }
154     }
155 
getInstance()156     public static SubscriptionController getInstance() {
157         if (sInstance == null)
158         {
159            Log.wtf(LOG_TAG, "getInstance null");
160         }
161 
162         return sInstance;
163     }
164 
SubscriptionController(Context c)165     private SubscriptionController(Context c) {
166         mContext = c;
167         mCM = CallManager.getInstance();
168         mTelephonyManager = TelephonyManager.from(mContext);
169 
170         if(ServiceManager.getService("isub") == null) {
171                 ServiceManager.addService("isub", this);
172         }
173 
174         if (DBG) logdl("[SubscriptionController] init by Context");
175     }
176 
isSubInfoReady()177     private boolean isSubInfoReady() {
178         return mSlotIdxToSubId.size() > 0;
179     }
180 
SubscriptionController(Phone phone)181     private SubscriptionController(Phone phone) {
182         mContext = phone.getContext();
183         mCM = CallManager.getInstance();
184 
185         if(ServiceManager.getService("isub") == null) {
186                 ServiceManager.addService("isub", this);
187         }
188 
189         if (DBG) logdl("[SubscriptionController] init by Phone");
190     }
191 
192     /**
193      * Make sure the caller has the READ_PHONE_STATE permission.
194      *
195      * @throws SecurityException if the caller does not have the required permission
196      */
enforceSubscriptionPermission()197     private void enforceSubscriptionPermission() {
198         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE,
199                 "Requires READ_PHONE_STATE");
200     }
201 
202     /**
203      * Broadcast when SubscriptionInfo has changed
204      * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
205      */
broadcastSimInfoContentChanged()206      private void broadcastSimInfoContentChanged() {
207         Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
208         mContext.sendBroadcast(intent);
209         intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
210         mContext.sendBroadcast(intent);
211      }
212 
checkNotifyPermission(String method)213      private boolean checkNotifyPermission(String method) {
214          if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
215                      == PackageManager.PERMISSION_GRANTED) {
216              return true;
217          }
218          if (DBG) {
219              logd("checkNotifyPermission Permission Denial: " + method + " from pid="
220                      + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
221          }
222          return false;
223      }
224 
notifySubscriptionInfoChanged()225      public void notifySubscriptionInfoChanged() {
226          if (!checkNotifyPermission("notifySubscriptionInfoChanged")) {
227              return;
228          }
229          ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
230                  "telephony.registry"));
231          try {
232              if (DBG) logd("notifySubscriptionInfoChanged:");
233              tr.notifySubscriptionInfoChanged();
234          } catch (RemoteException ex) {
235              // Should never happen because its always available.
236          }
237 
238          // FIXME: Remove if listener technique accepted.
239          broadcastSimInfoContentChanged();
240      }
241 
242     /**
243      * New SubInfoRecord instance and fill in detail info
244      * @param cursor
245      * @return the query result of desired SubInfoRecord
246      */
getSubInfoRecord(Cursor cursor)247     private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
248         int id = cursor.getInt(cursor.getColumnIndexOrThrow(
249                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
250         String iccId = cursor.getString(cursor.getColumnIndexOrThrow(
251                 SubscriptionManager.ICC_ID));
252         int simSlotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
253                 SubscriptionManager.SIM_SLOT_INDEX));
254         String displayName = cursor.getString(cursor.getColumnIndexOrThrow(
255                 SubscriptionManager.DISPLAY_NAME));
256         String carrierName = cursor.getString(cursor.getColumnIndexOrThrow(
257                 SubscriptionManager.CARRIER_NAME));
258         int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
259                 SubscriptionManager.NAME_SOURCE));
260         int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
261                 SubscriptionManager.COLOR));
262         String number = cursor.getString(cursor.getColumnIndexOrThrow(
263                 SubscriptionManager.NUMBER));
264         int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
265                 SubscriptionManager.DATA_ROAMING));
266         // Get the blank bitmap for this SubInfoRecord
267         Bitmap iconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
268                 com.android.internal.R.drawable.ic_sim_card_multi_24px_clr);
269         int mcc = cursor.getInt(cursor.getColumnIndexOrThrow(
270                 SubscriptionManager.MCC));
271         int mnc = cursor.getInt(cursor.getColumnIndexOrThrow(
272                 SubscriptionManager.MNC));
273         // FIXME: consider stick this into database too
274         String countryIso = getSubscriptionCountryIso(id);
275 
276         if (DBG) {
277             logd("[getSubInfoRecord] id:" + id + " iccid:" + iccId + " simSlotIndex:" + simSlotIndex
278                 + " displayName:" + displayName + " nameSource:" + nameSource
279                 + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
280                 + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso);
281         }
282 
283         String line1Number = mTelephonyManager.getLine1NumberForSubscriber(id);
284         if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
285             logd("Line1Number is different: " + line1Number);
286             number = line1Number;
287         }
288         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
289                 nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso);
290     }
291 
292     /**
293      * Get ISO country code for the subscription's provider
294      *
295      * @param subId The subscription ID
296      * @return The ISO country code for the subscription's provider
297      */
getSubscriptionCountryIso(int subId)298     private String getSubscriptionCountryIso(int subId) {
299         final int phoneId = getPhoneId(subId);
300         if (phoneId < 0) {
301             return "";
302         }
303         return mTelephonyManager.getSimCountryIsoForPhone(phoneId);
304     }
305 
306     /**
307      * Query SubInfoRecord(s) from subinfo database
308      * @param selection A filter declaring which rows to return
309      * @param queryKey query key content
310      * @return Array list of queried result from database
311      */
getSubInfo(String selection, Object queryKey)312      private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
313         if (DBG) logd("selection:" + selection + " " + queryKey);
314         String[] selectionArgs = null;
315         if (queryKey != null) {
316             selectionArgs = new String[] {queryKey.toString()};
317         }
318         ArrayList<SubscriptionInfo> subList = null;
319         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
320                 null, selection, selectionArgs, null);
321         try {
322             if (cursor != null) {
323                 while (cursor.moveToNext()) {
324                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
325                     if (subInfo != null)
326                     {
327                         if (subList == null)
328                         {
329                             subList = new ArrayList<SubscriptionInfo>();
330                         }
331                         subList.add(subInfo);
332                 }
333                 }
334             } else {
335                 if (DBG) logd("Query fail");
336             }
337         } finally {
338             if (cursor != null) {
339                 cursor.close();
340             }
341         }
342 
343         return subList;
344     }
345 
346     /**
347      * Find unused color to be set for new SubInfoRecord
348      * @return RGB integer value of color
349      */
getUnusedColor()350     private int getUnusedColor() {
351         List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList();
352         colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
353         int colorIdx = 0;
354 
355         if (availableSubInfos != null) {
356             for (int i = 0; i < colorArr.length; i++) {
357                 int j;
358                 for (j = 0; j < availableSubInfos.size(); j++) {
359                     if (colorArr[i] == availableSubInfos.get(j).getIconTint()) {
360                         break;
361                     }
362                 }
363                 if (j == availableSubInfos.size()) {
364                     return colorArr[i];
365                 }
366             }
367             colorIdx = availableSubInfos.size() % colorArr.length;
368         }
369         return colorArr[colorIdx];
370     }
371 
372     /**
373      * Get the active SubscriptionInfo with the subId key
374      * @param subId The unique SubscriptionInfo key in database
375      * @return SubscriptionInfo, maybe null if its not active
376      */
377     @Override
getActiveSubscriptionInfo(int subId)378     public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
379         enforceSubscriptionPermission();
380 
381         List<SubscriptionInfo> subList = getActiveSubscriptionInfoList();
382         if (subList != null) {
383             for (SubscriptionInfo si : subList) {
384                 if (si.getSubscriptionId() == subId) {
385                     if (DBG) logd("[getActiveSubInfoForSubscriber]+ subId=" + subId + " subInfo=" + si);
386                     return si;
387                 }
388             }
389         }
390         if (DBG) {
391             logd("[getActiveSubInfoForSubscriber]- subId=" + subId
392                     + " subList=" + subList + " subInfo=null");
393         }
394         return null;
395     }
396 
397     /**
398      * Get the active SubscriptionInfo associated with the iccId
399      * @param iccId the IccId of SIM card
400      * @return SubscriptionInfo, maybe null if its not active
401      */
402     @Override
getActiveSubscriptionInfoForIccId(String iccId)403     public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId) {
404         enforceSubscriptionPermission();
405 
406         List<SubscriptionInfo> subList = getActiveSubscriptionInfoList();
407         if (subList != null) {
408             for (SubscriptionInfo si : subList) {
409                 if (si.getIccId() == iccId) {
410                     if (DBG) logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si);
411                     return si;
412                 }
413             }
414         }
415         if (DBG) {
416             logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId
417                     + " subList=" + subList + " subInfo=null");
418         }
419         return null;
420     }
421 
422     /**
423      * Get the active SubscriptionInfo associated with the slotIdx
424      * @param slotIdx the slot which the subscription is inserted
425      * @return SubscriptionInfo, maybe null if its not active
426      */
427     @Override
getActiveSubscriptionInfoForSimSlotIndex(int slotIdx)428     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx) {
429         enforceSubscriptionPermission();
430 
431         List<SubscriptionInfo> subList = getActiveSubscriptionInfoList();
432         if (subList != null) {
433             for (SubscriptionInfo si : subList) {
434                 if (si.getSimSlotIndex() == slotIdx) {
435                     if (DBG) {
436                         logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIdx=" + slotIdx
437                             + " subId=" + si);
438                     }
439                     return si;
440                 }
441             }
442             if (DBG) {
443                 logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIdx=" + slotIdx
444                     + " subId=null");
445             }
446         } else {
447             if (DBG) {
448                 logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
449             }
450         }
451         return null;
452     }
453 
454     /**
455      * @return List of all SubscriptionInfo records in database,
456      * include those that were inserted before, maybe empty but not null.
457      * @hide
458      */
459     @Override
getAllSubInfoList()460     public List<SubscriptionInfo> getAllSubInfoList() {
461         if (DBG) logd("[getAllSubInfoList]+");
462         enforceSubscriptionPermission();
463 
464         List<SubscriptionInfo> subList = null;
465         subList = getSubInfo(null, null);
466         if (subList != null) {
467             if (DBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
468         } else {
469             if (DBG) logd("[getAllSubInfoList]- no info return");
470         }
471 
472         return subList;
473     }
474 
475     /**
476      * Get the SubInfoRecord(s) of the currently inserted SIM(s)
477      * @return Array list of currently inserted SubInfoRecord(s)
478      */
479     @Override
getActiveSubscriptionInfoList()480     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
481         enforceSubscriptionPermission();
482         if (DBG) logdl("[getActiveSubInfoList]+");
483 
484         List<SubscriptionInfo> subList = null;
485 
486         if (!isSubInfoReady()) {
487             if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
488             return subList;
489         }
490 
491         subList = getSubInfo(SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
492         if (subList != null) {
493             // FIXME: Unnecessary when an insertion sort is used!
494             Collections.sort(subList, new Comparator<SubscriptionInfo>() {
495                 @Override
496                 public int compare(SubscriptionInfo arg0, SubscriptionInfo arg1) {
497                     // Primary sort key on SimSlotIndex
498                     int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
499                     if (flag == 0) {
500                         // Secondary sort on SubscriptionId
501                         return arg0.getSubscriptionId() - arg1.getSubscriptionId();
502                     }
503                     return flag;
504                 }
505             });
506 
507             if (DBG) logdl("[getActiveSubInfoList]- " + subList.size() + " infos return");
508         } else {
509             if (DBG) logdl("[getActiveSubInfoList]- no info return");
510         }
511 
512         return subList;
513     }
514 
515     /**
516      * Get the SUB count of active SUB(s)
517      * @return active SIM count
518      */
519     @Override
getActiveSubInfoCount()520     public int getActiveSubInfoCount() {
521         if (DBG) logd("[getActiveSubInfoCount]+");
522         List<SubscriptionInfo> records = getActiveSubscriptionInfoList();
523         if (records == null) {
524             if (DBG) logd("[getActiveSubInfoCount] records null");
525             return 0;
526         }
527         if (DBG) logd("[getActiveSubInfoCount]- count: " + records.size());
528         return records.size();
529     }
530 
531     /**
532      * Get the SUB count of all SUB(s) in SubscriptoinInfo database
533      * @return all SIM count in database, include what was inserted before
534      */
535     @Override
getAllSubInfoCount()536     public int getAllSubInfoCount() {
537         if (DBG) logd("[getAllSubInfoCount]+");
538         enforceSubscriptionPermission();
539 
540         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
541                 null, null, null, null);
542         try {
543             if (cursor != null) {
544                 int count = cursor.getCount();
545                 if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
546                 return count;
547             }
548         } finally {
549             if (cursor != null) {
550                 cursor.close();
551             }
552         }
553         if (DBG) logd("[getAllSubInfoCount]- no SUB in DB");
554 
555         return 0;
556     }
557 
558     /**
559      * @return the maximum number of subscriptions this device will support at any one time.
560      */
561     @Override
getActiveSubInfoCountMax()562     public int getActiveSubInfoCountMax() {
563         // FIXME: This valid now but change to use TelephonyDevController in the future
564         return mTelephonyManager.getSimCount();
565     }
566 
567     /**
568      * Add a new SubInfoRecord to subinfo database if needed
569      * @param iccId the IccId of the SIM card
570      * @param slotId the slot which the SIM is inserted
571      * @return 0 if success, < 0 on error.
572      */
573     @Override
addSubInfoRecord(String iccId, int slotId)574     public int addSubInfoRecord(String iccId, int slotId) {
575         if (DBG) logdl("[addSubInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
576         enforceSubscriptionPermission();
577 
578         if (iccId == null) {
579             if (DBG) logdl("[addSubInfoRecord]- null iccId");
580             return -1;
581         }
582 
583         int[] subIds = getSubId(slotId);
584         if (subIds == null || subIds.length == 0) {
585             if (DBG) {
586                 logdl("[addSubInfoRecord]- getSubId failed subIds == null || length == 0 subIds="
587                     + subIds);
588             }
589             return -1;
590         }
591 
592         String nameToSet;
593         String CarrierName = mTelephonyManager.getSimOperatorNumericForSubscription(subIds[0]);
594         if (DBG) logdl("[addSubInfoRecord] CarrierName = " + CarrierName);
595         String simCarrierName = mTelephonyManager.getSimOperatorNameForSubscription(subIds[0]);
596 
597         if (!TextUtils.isEmpty(simCarrierName)) {
598             nameToSet = simCarrierName;
599         } else {
600             nameToSet = "CARD " + Integer.toString(slotId + 1);
601         }
602         if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
603         if (DBG) logdl("[addSubInfoRecord] carrier name = " + simCarrierName);
604 
605         ContentResolver resolver = mContext.getContentResolver();
606         Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
607                 new String[] {SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
608                 SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
609                 SubscriptionManager.ICC_ID + "=?", new String[] {iccId}, null);
610 
611         int color = getUnusedColor();
612 
613         try {
614             if (cursor == null || !cursor.moveToFirst()) {
615                 ContentValues value = new ContentValues();
616                 value.put(SubscriptionManager.ICC_ID, iccId);
617                 // default SIM color differs between slots
618                 value.put(SubscriptionManager.COLOR, color);
619                 value.put(SubscriptionManager.SIM_SLOT_INDEX, slotId);
620                 value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
621                 value.put(SubscriptionManager.CARRIER_NAME,
622                         !TextUtils.isEmpty(simCarrierName) ? simCarrierName :
623                         mContext.getString(com.android.internal.R.string.unknownName));
624                 Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
625                 if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
626             } else {
627                 int subId = cursor.getInt(0);
628                 int oldSimInfoId = cursor.getInt(1);
629                 int nameSource = cursor.getInt(2);
630                 ContentValues value = new ContentValues();
631 
632                 if (slotId != oldSimInfoId) {
633                     value.put(SubscriptionManager.SIM_SLOT_INDEX, slotId);
634                 }
635 
636                 if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
637                     value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
638                 }
639 
640                 if (!TextUtils.isEmpty(simCarrierName)) {
641                     value.put(SubscriptionManager.CARRIER_NAME, simCarrierName);
642                 }
643 
644                 if (value.size() > 0) {
645                     resolver.update(SubscriptionManager.CONTENT_URI, value,
646                             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
647                             "=" + Long.toString(subId), null);
648                 }
649 
650                 if (DBG) logdl("[addSubInfoRecord] Record already exists");
651             }
652         } finally {
653             if (cursor != null) {
654                 cursor.close();
655             }
656         }
657 
658         cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
659                 SubscriptionManager.SIM_SLOT_INDEX + "=?",
660                 new String[] {String.valueOf(slotId)}, null);
661         try {
662             if (cursor != null && cursor.moveToFirst()) {
663                 do {
664                     int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
665                             SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
666                     // If mSlotIdToSubIdMap already has a valid subId for a slotId/phoneId,
667                     // do not add another subId for same slotId/phoneId.
668                     Integer currentSubId = mSlotIdxToSubId.get(slotId);
669                     if (currentSubId == null
670                             || !SubscriptionManager.isValidSubscriptionId(currentSubId)) {
671                         // TODO While two subs active, if user deactivats first
672                         // one, need to update the default subId with second one.
673 
674                         // FIXME: Currently we assume phoneId == slotId which in the future
675                         // may not be true, for instance with multiple subs per slot.
676                         // But is true at the moment.
677                         mSlotIdxToSubId.put(slotId, subId);
678                         int subIdCountMax = getActiveSubInfoCountMax();
679                         int defaultSubId = getDefaultSubId();
680                         if (DBG) {
681                             logdl("[addSubInfoRecord]"
682                                 + " mSlotIdxToSubId.size=" + mSlotIdxToSubId.size()
683                                 + " slotId=" + slotId + " subId=" + subId
684                                 + " defaultSubId=" + defaultSubId + " simCount=" + subIdCountMax);
685                         }
686 
687                         // Set the default sub if not set or if single sim device
688                         if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
689                                 || subIdCountMax == 1) {
690                             setDefaultFallbackSubId(subId);
691                         }
692                         // If single sim device, set this subscription as the default for everything
693                         if (subIdCountMax == 1) {
694                             if (DBG) {
695                                 logdl("[addSubInfoRecord] one sim set defaults to subId=" + subId);
696                             }
697                             setDefaultDataSubId(subId);
698                             setDefaultSmsSubId(subId);
699                             setDefaultVoiceSubId(subId);
700                         }
701                     } else {
702                         if (DBG) {
703                             logdl("[addSubInfoRecord] currentSubId != null"
704                                 + " && currentSubId is valid, IGNORE");
705                         }
706                     }
707                     if (DBG) logdl("[addSubInfoRecord] hashmap(" + slotId + "," + subId + ")");
708                 } while (cursor.moveToNext());
709             }
710         } finally {
711             if (cursor != null) {
712                 cursor.close();
713             }
714         }
715 
716         // Once the records are loaded, notify DcTracker
717         updateAllDataConnectionTrackers();
718 
719         if (DBG) logdl("[addSubInfoRecord]- info size=" + mSlotIdxToSubId.size());
720         return 0;
721     }
722 
723     /**
724      * Generate and set carrier text based on input parameters
725      * @param showPlmn flag to indicate if plmn should be included in carrier text
726      * @param plmn plmn to be included in carrier text
727      * @param showSpn flag to indicate if spn should be included in carrier text
728      * @param spn spn to be included in carrier text
729      * @return true if carrier text is set, false otherwise
730      */
setPlmnSpn(int slotId, boolean showPlmn, String plmn, boolean showSpn, String spn)731     public boolean setPlmnSpn(int slotId, boolean showPlmn, String plmn, boolean showSpn,
732                               String spn) {
733         synchronized (mLock) {
734             int[] subIds = getSubId(slotId);
735             if (mContext.getPackageManager().resolveContentProvider(
736                     SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
737                     subIds == null ||
738                     !SubscriptionManager.isValidSubscriptionId(subIds[0])) {
739                 // No place to store this info, we are done.
740                 // TODO: This can be removed once SubscriptionController is not running on devices
741                 // that don't need it, such as TVs.
742                 return false;
743             }
744             String carrierText = "";
745             if (showPlmn) {
746                 carrierText = plmn;
747                 if (showSpn) {
748                     // Need to show both plmn and spn.
749                     String separator = mContext.getString(
750                             com.android.internal.R.string.kg_text_message_separator).toString();
751                     carrierText = new StringBuilder().append(carrierText).append(separator)
752                             .append(spn).toString();
753                 }
754             } else if (showSpn) {
755                 carrierText = spn;
756             }
757             for (int i = 0; i < subIds.length; i++) {
758                 setCarrierText(carrierText, subIds[i]);
759             }
760             return true;
761         }
762     }
763 
764     /**
765      * Set carrier text by simInfo index
766      * @param text new carrier text
767      * @param subId the unique SubInfoRecord index in database
768      * @return the number of records updated
769      */
setCarrierText(String text, int subId)770     private int setCarrierText(String text, int subId) {
771         if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);
772         enforceSubscriptionPermission();
773 
774         ContentValues value = new ContentValues(1);
775         value.put(SubscriptionManager.CARRIER_NAME, text);
776 
777         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
778                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
779         notifySubscriptionInfoChanged();
780 
781         return result;
782     }
783 
784     /**
785      * Set SIM color tint by simInfo index
786      * @param tint the tint color of the SIM
787      * @param subId the unique SubInfoRecord index in database
788      * @return the number of records updated
789      */
790     @Override
setIconTint(int tint, int subId)791     public int setIconTint(int tint, int subId) {
792         if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
793         enforceSubscriptionPermission();
794 
795         validateSubId(subId);
796         ContentValues value = new ContentValues(1);
797         value.put(SubscriptionManager.COLOR, tint);
798         if (DBG) logd("[setIconTint]- tint:" + tint + " set");
799 
800         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
801                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
802         notifySubscriptionInfoChanged();
803 
804         return result;
805     }
806 
807     /**
808      * Set display name by simInfo index
809      * @param displayName the display name of SIM card
810      * @param subId the unique SubInfoRecord index in database
811      * @return the number of records updated
812      */
813     @Override
setDisplayName(String displayName, int subId)814     public int setDisplayName(String displayName, int subId) {
815         return setDisplayNameUsingSrc(displayName, subId, -1);
816     }
817 
818     /**
819      * Set display name by simInfo index with name source
820      * @param displayName the display name of SIM card
821      * @param subId the unique SubInfoRecord index in database
822      * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
823      *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED
824      * @return the number of records updated
825      */
826     @Override
setDisplayNameUsingSrc(String displayName, int subId, long nameSource)827     public int setDisplayNameUsingSrc(String displayName, int subId, long nameSource) {
828         if (DBG) {
829             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
830                 + " nameSource:" + nameSource);
831         }
832         enforceSubscriptionPermission();
833 
834         validateSubId(subId);
835         String nameToSet;
836         if (displayName == null) {
837             nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
838         } else {
839             nameToSet = displayName;
840         }
841         ContentValues value = new ContentValues(1);
842         value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
843         if (nameSource >= SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE) {
844             if (DBG) logd("Set nameSource=" + nameSource);
845             value.put(SubscriptionManager.NAME_SOURCE, nameSource);
846         }
847         if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
848 
849         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
850                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
851         notifySubscriptionInfoChanged();
852 
853         return result;
854     }
855 
856     /**
857      * Set phone number by subId
858      * @param number the phone number of the SIM
859      * @param subId the unique SubInfoRecord index in database
860      * @return the number of records updated
861      */
862     @Override
setDisplayNumber(String number, int subId)863     public int setDisplayNumber(String number, int subId) {
864         if (DBG) logd("[setDisplayNumber]+ subId:" + subId);
865         enforceSubscriptionPermission();
866 
867         validateSubId(subId);
868         int result;
869         int phoneId = getPhoneId(subId);
870 
871         if (number == null || phoneId < 0 ||
872                 phoneId >= mTelephonyManager.getPhoneCount()) {
873             if (DBG) logd("[setDispalyNumber]- fail");
874             return -1;
875         }
876         ContentValues value = new ContentValues(1);
877         value.put(SubscriptionManager.NUMBER, number);
878 
879         // This function had a call to update number on the SIM (Phone.setLine1Number()) but that
880         // was removed as there doesn't seem to be a reason for that. If it is added back, watch out
881         // for deadlocks.
882 
883         result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
884                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
885                     + "=" + Long.toString(subId), null);
886         if (DBG) logd("[setDisplayNumber]- update result :" + result);
887         notifySubscriptionInfoChanged();
888 
889         return result;
890     }
891 
892     /**
893      * Set data roaming by simInfo index
894      * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
895      * @param subId the unique SubInfoRecord index in database
896      * @return the number of records updated
897      */
898     @Override
setDataRoaming(int roaming, int subId)899     public int setDataRoaming(int roaming, int subId) {
900         if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
901         enforceSubscriptionPermission();
902 
903         validateSubId(subId);
904         if (roaming < 0) {
905             if (DBG) logd("[setDataRoaming]- fail");
906             return -1;
907         }
908         ContentValues value = new ContentValues(1);
909         value.put(SubscriptionManager.DATA_ROAMING, roaming);
910         if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
911 
912         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
913                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
914         notifySubscriptionInfoChanged();
915 
916         return result;
917     }
918 
919     /**
920      * Set MCC/MNC by subscription ID
921      * @param mccMnc MCC/MNC associated with the subscription
922      * @param subId the unique SubInfoRecord index in database
923      * @return the number of records updated
924      */
setMccMnc(String mccMnc, int subId)925     public int setMccMnc(String mccMnc, int subId) {
926         int mcc = 0;
927         int mnc = 0;
928         try {
929             mcc = Integer.parseInt(mccMnc.substring(0,3));
930             mnc = Integer.parseInt(mccMnc.substring(3));
931         } catch (NumberFormatException e) {
932             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
933         }
934         if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
935         ContentValues value = new ContentValues(2);
936         value.put(SubscriptionManager.MCC, mcc);
937         value.put(SubscriptionManager.MNC, mnc);
938 
939         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
940                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
941         notifySubscriptionInfoChanged();
942 
943         return result;
944     }
945 
946 
947     @Override
getSlotId(int subId)948     public int getSlotId(int subId) {
949         if (VDBG) printStackTrace("[getSlotId] subId=" + subId);
950 
951         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
952             subId = getDefaultSubId();
953         }
954         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
955             if (DBG) logd("[getSlotId]- subId invalid");
956             return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
957         }
958 
959         int size = mSlotIdxToSubId.size();
960 
961         if (size == 0)
962         {
963             if (DBG) logd("[getSlotId]- size == 0, return SIM_NOT_INSERTED instead");
964             return SubscriptionManager.SIM_NOT_INSERTED;
965         }
966 
967         for (Entry<Integer, Integer> entry: mSlotIdxToSubId.entrySet()) {
968             int sim = entry.getKey();
969             int sub = entry.getValue();
970 
971             if (subId == sub)
972             {
973                 if (VDBG) logv("[getSlotId]- return = " + sim);
974                 return sim;
975             }
976         }
977 
978         if (DBG) logd("[getSlotId]- return fail");
979         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
980     }
981 
982     /**
983      * Return the subId for specified slot Id.
984      * @deprecated
985      */
986     @Override
987     @Deprecated
getSubId(int slotIdx)988     public int[] getSubId(int slotIdx) {
989         if (VDBG) printStackTrace("[getSubId]+ slotIdx=" + slotIdx);
990 
991         // Map default slotIdx to the current default subId.
992         // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous
993         // as a slot maybe used for multiple different type of "connections"
994         // such as: voice, data and sms. But we're doing the best we can and using
995         // getDefaultSubId which makes a best guess.
996         if (slotIdx == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
997             slotIdx = getSlotId(getDefaultSubId());
998             if (DBG) logd("[getSubId] map default slotIdx=" + slotIdx);
999         }
1000 
1001         // Check that we have a valid SlotIdx
1002         if (!SubscriptionManager.isValidSlotId(slotIdx)) {
1003             if (DBG) logd("[getSubId]- invalid slotIdx=" + slotIdx);
1004             return null;
1005         }
1006 
1007         // Check if we've got any SubscriptionInfo records using slotIdToSubId as a surrogate.
1008         int size = mSlotIdxToSubId.size();
1009         if (size == 0) {
1010             if (DBG) {
1011                 logd("[getSubId]- mSlotIdToSubIdMap.size == 0, return DummySubIds slotIdx="
1012                         + slotIdx);
1013             }
1014             return getDummySubIds(slotIdx);
1015         }
1016 
1017         // Create an array of subIds that are in this slot?
1018         ArrayList<Integer> subIds = new ArrayList<Integer>();
1019         for (Entry<Integer, Integer> entry: mSlotIdxToSubId.entrySet()) {
1020             int slot = entry.getKey();
1021             int sub = entry.getValue();
1022             if (slotIdx == slot) {
1023                 subIds.add(sub);
1024             }
1025         }
1026 
1027         // Convert ArrayList to array
1028         int numSubIds = subIds.size();
1029         if (numSubIds > 0) {
1030             int[] subIdArr = new int[numSubIds];
1031             for (int i = 0; i < numSubIds; i++) {
1032                 subIdArr[i] = subIds.get(i);
1033             }
1034             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
1035             return subIdArr;
1036         } else {
1037             if (DBG) logd("[getSubId]- numSubIds == 0, return DummySubIds slotIdx=" + slotIdx);
1038             return getDummySubIds(slotIdx);
1039         }
1040     }
1041 
1042     @Override
getPhoneId(int subId)1043     public int getPhoneId(int subId) {
1044         if (VDBG) printStackTrace("[getPhoneId] subId=" + subId);
1045         int phoneId;
1046 
1047         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1048             subId = getDefaultSubId();
1049             if (DBG) logdl("[getPhoneId] asked for default subId=" + subId);
1050         }
1051 
1052         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1053             if (DBG) {
1054                 logdl("[getPhoneId]- invalid subId return="
1055                         + SubscriptionManager.INVALID_PHONE_INDEX);
1056             }
1057             return SubscriptionManager.INVALID_PHONE_INDEX;
1058         }
1059 
1060         int size = mSlotIdxToSubId.size();
1061         if (size == 0) {
1062             phoneId = mDefaultPhoneId;
1063             if (DBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
1064             return phoneId;
1065         }
1066 
1067         // FIXME: Assumes phoneId == slotId
1068         for (Entry<Integer, Integer> entry: mSlotIdxToSubId.entrySet()) {
1069             int sim = entry.getKey();
1070             int sub = entry.getValue();
1071 
1072             if (subId == sub) {
1073                 if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
1074                 return sim;
1075             }
1076         }
1077 
1078         phoneId = mDefaultPhoneId;
1079         if (DBG) {
1080             logdl("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId);
1081         }
1082         return phoneId;
1083 
1084     }
1085 
getDummySubIds(int slotIdx)1086     private int[] getDummySubIds(int slotIdx) {
1087         // FIXME: Remove notion of Dummy SUBSCRIPTION_ID.
1088         // I tested this returning null as no one appears to care,
1089         // but no connection came up on sprout with two sims.
1090         // We need to figure out why and hopefully remove DummySubsIds!!!
1091         int numSubs = getActiveSubInfoCountMax();
1092         if (numSubs > 0) {
1093             int[] dummyValues = new int[numSubs];
1094             for (int i = 0; i < numSubs; i++) {
1095                 dummyValues[i] = SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE - slotIdx;
1096             }
1097             if (DBG) {
1098                 logd("getDummySubIds: slotIdx=" + slotIdx
1099                     + " return " + numSubs + " DummySubIds with each subId=" + dummyValues[0]);
1100             }
1101             return dummyValues;
1102         } else {
1103             return null;
1104         }
1105     }
1106 
1107     /**
1108      * @return the number of records cleared
1109      */
1110     @Override
clearSubInfo()1111     public int clearSubInfo() {
1112         enforceSubscriptionPermission();
1113         if (DBG) logd("[clearSubInfo]+");
1114 
1115         int size = mSlotIdxToSubId.size();
1116 
1117         if (size == 0) {
1118             if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
1119             return 0;
1120         }
1121 
1122         mSlotIdxToSubId.clear();
1123         if (DBG) logdl("[clearSubInfo]- clear size=" + size);
1124         return size;
1125     }
1126 
logvl(String msg)1127     private void logvl(String msg) {
1128         logv(msg);
1129         mLocalLog.log(msg);
1130     }
1131 
logv(String msg)1132     private void logv(String msg) {
1133         Rlog.v(LOG_TAG, msg);
1134     }
1135 
logdl(String msg)1136     private void logdl(String msg) {
1137         logd(msg);
1138         mLocalLog.log(msg);
1139     }
1140 
slogd(String msg)1141     private static void slogd(String msg) {
1142         Rlog.d(LOG_TAG, msg);
1143     }
1144 
logd(String msg)1145     private void logd(String msg) {
1146         Rlog.d(LOG_TAG, msg);
1147     }
1148 
logel(String msg)1149     private void logel(String msg) {
1150         loge(msg);
1151         mLocalLog.log(msg);
1152     }
1153 
loge(String msg)1154     private void loge(String msg) {
1155         Rlog.e(LOG_TAG, msg);
1156     }
1157 
1158     @Override
getDefaultSubId()1159     public int getDefaultSubId() {
1160         int subId;
1161         boolean isVoiceCapable = mContext.getResources().getBoolean(
1162                 com.android.internal.R.bool.config_voice_capable);
1163         if (isVoiceCapable) {
1164             subId = getDefaultVoiceSubId();
1165             if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
1166         } else {
1167             subId = getDefaultDataSubId();
1168             if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
1169         }
1170         if ( ! isActiveSubId(subId)) {
1171             subId = mDefaultFallbackSubId;
1172             if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
1173         }
1174         if (VDBG) logv("[getDefaultSubId]- value = " + subId);
1175         return subId;
1176     }
1177 
1178     @Override
setDefaultSmsSubId(int subId)1179     public void setDefaultSmsSubId(int subId) {
1180         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1181             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
1182         }
1183         if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
1184         Settings.Global.putInt(mContext.getContentResolver(),
1185                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
1186         broadcastDefaultSmsSubIdChanged(subId);
1187     }
1188 
broadcastDefaultSmsSubIdChanged(int subId)1189     private void broadcastDefaultSmsSubIdChanged(int subId) {
1190         // Broadcast an Intent for default sms sub change
1191         if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
1192         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
1193         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1194         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1195         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1196     }
1197 
1198     @Override
getDefaultSmsSubId()1199     public int getDefaultSmsSubId() {
1200         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1201                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
1202                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1203         if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId);
1204         return subId;
1205     }
1206 
1207     @Override
setDefaultVoiceSubId(int subId)1208     public void setDefaultVoiceSubId(int subId) {
1209         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1210             throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
1211         }
1212         if (DBG) logdl("[setDefaultVoiceSubId] subId=" + subId);
1213         Settings.Global.putInt(mContext.getContentResolver(),
1214                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
1215         broadcastDefaultVoiceSubIdChanged(subId);
1216     }
1217 
broadcastDefaultVoiceSubIdChanged(int subId)1218     private void broadcastDefaultVoiceSubIdChanged(int subId) {
1219         // Broadcast an Intent for default voice sub change
1220         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
1221         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
1222         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1223         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1224         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1225     }
1226 
1227     @Override
getDefaultVoiceSubId()1228     public int getDefaultVoiceSubId() {
1229         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1230                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
1231                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1232         if (VDBG) logd("[getDefaultVoiceSubId] subId=" + subId);
1233         return subId;
1234     }
1235 
1236     @Override
getDefaultDataSubId()1237     public int getDefaultDataSubId() {
1238         int subId = Settings.Global.getInt(mContext.getContentResolver(),
1239                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
1240                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1241         if (VDBG) logd("[getDefaultDataSubId] subId= " + subId);
1242         return subId;
1243     }
1244 
1245     @Override
setDefaultDataSubId(int subId)1246     public void setDefaultDataSubId(int subId) {
1247         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1248             throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
1249         }
1250         if (DBG) logdl("[setDefaultDataSubId] subId=" + subId);
1251 
1252         int len = sProxyPhones.length;
1253         logdl("[setDefaultDataSubId] num phones=" + len);
1254 
1255         RadioAccessFamily[] rafs = new RadioAccessFamily[len];
1256         for (int phoneId = 0; phoneId < len; phoneId++) {
1257             PhoneProxy phone = sProxyPhones[phoneId];
1258             int raf = phone.getRadioAccessFamily();
1259             int id = phone.getSubId();
1260             logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF=" + raf);
1261             raf |= RadioAccessFamily.RAF_GSM;
1262             if (id == subId) {
1263                 raf |= RadioAccessFamily.RAF_UMTS;
1264             } else {
1265                 raf &= ~RadioAccessFamily.RAF_UMTS;
1266             }
1267             logdl("[setDefaultDataSubId] reqRAF=" + raf);
1268 
1269             // Set the raf to the maximum of the requested and the user's preferred.
1270             int networkType = PhoneFactory.calculatePreferredNetworkType(mContext, id);
1271             logdl("[setDefaultDataSubId] networkType=" + networkType);
1272             raf &= RadioAccessFamily.getRafFromNetworkType(networkType);
1273 
1274             logdl("[setDefaultDataSubId] newRAF=" + raf);
1275             rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
1276         }
1277         ProxyController.getInstance().setRadioCapability(rafs);
1278 
1279         // FIXME is this still needed?
1280         updateAllDataConnectionTrackers();
1281 
1282         Settings.Global.putInt(mContext.getContentResolver(),
1283                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
1284         broadcastDefaultDataSubIdChanged(subId);
1285     }
1286 
updateAllDataConnectionTrackers()1287     private void updateAllDataConnectionTrackers() {
1288         // Tell Phone Proxies to update data connection tracker
1289         int len = sProxyPhones.length;
1290         if (DBG) logdl("[updateAllDataConnectionTrackers] sProxyPhones.length=" + len);
1291         for (int phoneId = 0; phoneId < len; phoneId++) {
1292             if (DBG) logdl("[updateAllDataConnectionTrackers] phoneId=" + phoneId);
1293             sProxyPhones[phoneId].updateDataConnectionTracker();
1294         }
1295     }
1296 
broadcastDefaultDataSubIdChanged(int subId)1297     private void broadcastDefaultDataSubIdChanged(int subId) {
1298         // Broadcast an Intent for default data sub change
1299         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
1300         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
1301         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1302         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1303         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1304     }
1305 
1306     /* Sets the default subscription. If only one sub is active that
1307      * sub is set as default subId. If two or more  sub's are active
1308      * the first sub is set as default subscription
1309      */
setDefaultFallbackSubId(int subId)1310     private void setDefaultFallbackSubId(int subId) {
1311         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1312             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
1313         }
1314         if (DBG) logdl("[setDefaultFallbackSubId] subId=" + subId);
1315         if (SubscriptionManager.isValidSubscriptionId(subId)) {
1316             int phoneId = getPhoneId(subId);
1317             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
1318                     || mTelephonyManager.getSimCount() == 1)) {
1319                 if (DBG) logdl("[setDefaultFallbackSubId] set mDefaultFallbackSubId=" + subId);
1320                 mDefaultFallbackSubId = subId;
1321                 // Update MCC MNC device configuration information
1322                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
1323                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
1324 
1325                 // Broadcast an Intent for default sub change
1326                 Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
1327                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1328                 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
1329                 if (DBG) {
1330                     logdl("[setDefaultFallbackSubId] broadcast default subId changed phoneId=" + phoneId
1331                             + " subId=" + subId);
1332                 }
1333                 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1334             } else {
1335                 if (DBG) {
1336                     logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
1337                             + " subId=" + subId);
1338                 }
1339             }
1340         }
1341     }
1342 
1343     @Override
clearDefaultsForInactiveSubIds()1344     public void clearDefaultsForInactiveSubIds() {
1345         final List<SubscriptionInfo> records = getActiveSubscriptionInfoList();
1346         if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records);
1347         if (shouldDefaultBeCleared(records, getDefaultDataSubId())) {
1348             if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id");
1349             setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1350         }
1351         if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) {
1352             if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id");
1353             setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1354         }
1355         if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) {
1356             if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id");
1357             setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
1358         }
1359     }
1360 
shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId)1361     private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) {
1362         if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId);
1363         if (records == null) {
1364             if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId);
1365             return true;
1366         }
1367         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1368             // If the subId parameter is not valid its already cleared so return false.
1369             if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId);
1370             return false;
1371         }
1372         for (SubscriptionInfo record : records) {
1373             int id = record.getSubscriptionId();
1374             if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id);
1375             if (id == subId) {
1376                 logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId);
1377                 return false;
1378             }
1379         }
1380         if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId);
1381         return true;
1382     }
1383 
1384     // FIXME: We need we should not be assuming phoneId == slotId as it will not be true
1385     // when there are multiple subscriptions per sim and probably for other reasons.
getSubIdUsingPhoneId(int phoneId)1386     public int getSubIdUsingPhoneId(int phoneId) {
1387         int[] subIds = getSubId(phoneId);
1388         if (subIds == null || subIds.length == 0) {
1389             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
1390         }
1391         return subIds[0];
1392     }
1393 
getSubIdUsingSlotId(int slotId)1394     public int[] getSubIdUsingSlotId(int slotId) {
1395         return getSubId(slotId);
1396     }
1397 
getSubInfoUsingSlotIdWithCheck(int slotId, boolean needCheck)1398     public List<SubscriptionInfo> getSubInfoUsingSlotIdWithCheck(int slotId, boolean needCheck) {
1399         if (DBG) logd("[getSubInfoUsingSlotIdWithCheck]+ slotId:" + slotId);
1400         enforceSubscriptionPermission();
1401 
1402         if (slotId == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
1403             slotId = getSlotId(getDefaultSubId());
1404         }
1405         if (!SubscriptionManager.isValidSlotId(slotId)) {
1406             if (DBG) logd("[getSubInfoUsingSlotIdWithCheck]- invalid slotId");
1407             return null;
1408         }
1409 
1410         if (needCheck && !isSubInfoReady()) {
1411             if (DBG) logd("[getSubInfoUsingSlotIdWithCheck]- not ready");
1412             return null;
1413         }
1414 
1415         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
1416                 null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
1417                 new String[] {String.valueOf(slotId)}, null);
1418         ArrayList<SubscriptionInfo> subList = null;
1419         try {
1420             if (cursor != null) {
1421                 while (cursor.moveToNext()) {
1422                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
1423                     if (subInfo != null)
1424                     {
1425                         if (subList == null)
1426                         {
1427                             subList = new ArrayList<SubscriptionInfo>();
1428                         }
1429                         subList.add(subInfo);
1430                     }
1431                 }
1432             }
1433         } finally {
1434             if (cursor != null) {
1435                 cursor.close();
1436             }
1437         }
1438         if (DBG) logd("[getSubInfoUsingSlotId]- null info return");
1439 
1440         return subList;
1441     }
1442 
validateSubId(int subId)1443     private void validateSubId(int subId) {
1444         if (DBG) logd("validateSubId subId: " + subId);
1445         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
1446             throw new RuntimeException("Invalid sub id passed as parameter");
1447         } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
1448             throw new RuntimeException("Default sub id passed as parameter");
1449         }
1450     }
1451 
updatePhonesAvailability(PhoneProxy[] phones)1452     public void updatePhonesAvailability(PhoneProxy[] phones) {
1453         sProxyPhones = phones;
1454     }
1455 
1456     /**
1457      * @return the list of subId's that are active, is never null but the length maybe 0.
1458      */
1459     @Override
getActiveSubIdList()1460     public int[] getActiveSubIdList() {
1461         Set<Entry<Integer, Integer>> simInfoSet = mSlotIdxToSubId.entrySet();
1462         if (DBG) logdl("[getActiveSubIdList] simInfoSet=" + simInfoSet);
1463 
1464         int[] subIdArr = new int[simInfoSet.size()];
1465         int i = 0;
1466         for (Entry<Integer, Integer> entry: simInfoSet) {
1467             int sub = entry.getValue();
1468             subIdArr[i] = sub;
1469             i++;
1470         }
1471 
1472         if (DBG) logdl("[getActiveSubIdList] X subIdArr.length=" + subIdArr.length);
1473         return subIdArr;
1474     }
1475 
isActiveSubId(int subId)1476     private boolean isActiveSubId(int subId) {
1477         boolean retVal = false;
1478 
1479         if (SubscriptionManager.isValidSubscriptionId(subId)) {
1480             Set<Entry<Integer, Integer>> simInfoSet = mSlotIdxToSubId.entrySet();
1481             if (VDBG) logdl("[isActiveSubId] simInfoSet=" + simInfoSet);
1482 
1483             for (Entry<Integer, Integer> entry: simInfoSet) {
1484                 if (subId == entry.getValue()) {
1485                     retVal = true;
1486                     break;
1487                 }
1488             }
1489         }
1490 
1491         if (VDBG) logdl("[isActiveSubId]- " + retVal);
1492         return retVal;
1493     }
1494 
1495     /**
1496      * Get the SIM state for the subscriber
1497      * @return SIM state as the ordinal of {@See IccCardConstants.State}
1498      */
1499     @Override
getSimStateForSubscriber(int subId)1500     public int getSimStateForSubscriber(int subId) {
1501         State simState;
1502         String err;
1503         int phoneIdx = getPhoneId(subId);
1504         if (phoneIdx < 0) {
1505             simState = IccCardConstants.State.UNKNOWN;
1506             err = "invalid PhoneIdx";
1507         } else {
1508             Phone phone = PhoneFactory.getPhone(phoneIdx);
1509             if (phone == null) {
1510                 simState = IccCardConstants.State.UNKNOWN;
1511                 err = "phone == null";
1512             } else {
1513                 IccCard icc = phone.getIccCard();
1514                 if (icc == null) {
1515                     simState = IccCardConstants.State.UNKNOWN;
1516                     err = "icc == null";
1517                 } else {
1518                     simState = icc.getState();
1519                     err = "";
1520                 }
1521             }
1522         }
1523         if (DBG) logd("getSimStateForSubscriber: " + err + " simState=" + simState
1524                 + " ordinal=" + simState.ordinal());
1525         return simState.ordinal();
1526     }
1527 
printStackTrace(String msg)1528     private static void printStackTrace(String msg) {
1529         RuntimeException re = new RuntimeException();
1530         slogd("StackTrace - " + msg);
1531         StackTraceElement[] st = re.getStackTrace();
1532         boolean first = true;
1533         for (StackTraceElement ste : st) {
1534             if (first) {
1535                 first = false;
1536             } else {
1537                 slogd(ste.toString());
1538             }
1539         }
1540     }
1541 
1542     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1543     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1544         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
1545                 "Requires DUMP");
1546         final long token = Binder.clearCallingIdentity();
1547         try {
1548             pw.println("SubscriptionController:");
1549             pw.println(" defaultSubId=" + getDefaultSubId());
1550             pw.println(" defaultDataSubId=" + getDefaultDataSubId());
1551             pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
1552             pw.println(" defaultSmsSubId=" + getDefaultSmsSubId());
1553 
1554             pw.println(" defaultDataPhoneId=" + SubscriptionManager
1555                     .from(mContext).getDefaultDataPhoneId());
1556             pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
1557             pw.println(" defaultSmsPhoneId=" + SubscriptionManager
1558                     .from(mContext).getDefaultSmsPhoneId());
1559             pw.flush();
1560 
1561             for (Entry<Integer, Integer> entry : mSlotIdxToSubId.entrySet()) {
1562                 pw.println(" mSlotIdToSubIdMap[" + entry.getKey() + "]: subId=" + entry.getValue());
1563             }
1564             pw.flush();
1565             pw.println("++++++++++++++++++++++++++++++++");
1566 
1567             List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList();
1568             if (sirl != null) {
1569                 pw.println(" ActiveSubInfoList:");
1570                 for (SubscriptionInfo entry : sirl) {
1571                     pw.println("  " + entry.toString());
1572                 }
1573             } else {
1574                 pw.println(" ActiveSubInfoList: is null");
1575             }
1576             pw.flush();
1577             pw.println("++++++++++++++++++++++++++++++++");
1578 
1579             sirl = getAllSubInfoList();
1580             if (sirl != null) {
1581                 pw.println(" AllSubInfoList:");
1582                 for (SubscriptionInfo entry : sirl) {
1583                     pw.println("  " + entry.toString());
1584                 }
1585             } else {
1586                 pw.println(" AllSubInfoList: is null");
1587             }
1588             pw.flush();
1589             pw.println("++++++++++++++++++++++++++++++++");
1590 
1591             mLocalLog.dump(fd, pw, args);
1592             pw.flush();
1593             pw.println("++++++++++++++++++++++++++++++++");
1594             pw.flush();
1595         } finally {
1596             Binder.restoreCallingIdentity(token);
1597         }
1598     }
1599 }
1600