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