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