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