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