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