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