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