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