SubscriptionController.java revision 374fb1f013fcddac3421720a7f2123e8f025c52a
1/*
2* Copyright (C) 2011-2014 MediaTek Inc.
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.Manifest;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.os.AsyncResult;
24import android.os.Binder;
25import android.os.Handler;
26import android.os.Message;
27import android.os.ServiceManager;
28import android.os.UserHandle;
29import android.telephony.Rlog;
30import android.util.Log;
31import android.net.Uri;
32import android.database.Cursor;
33import android.content.Intent;
34import android.provider.BaseColumns;
35import android.provider.Settings;
36import android.provider.Settings.SettingNotFoundException;
37import android.content.ContentResolver;
38import android.content.ContentValues;
39
40import com.android.internal.telephony.ISub;
41import com.android.internal.telephony.dataconnection.DctController;
42import com.android.internal.telephony.uicc.IccConstants;
43import com.android.internal.telephony.uicc.IccFileHandler;
44import android.telephony.SubscriptionManager;
45import android.telephony.SubInfoRecord;
46import android.telephony.TelephonyManager;
47
48import java.util.ArrayList;
49import java.util.Arrays;
50import java.util.List;
51import java.util.HashMap;
52import java.util.Iterator;
53import java.util.Map.Entry;
54/**
55 * SubscriptionController to provide an inter-process communication to
56 * access Sms in Icc.
57 */
58public class SubscriptionController extends ISub.Stub {
59    static final String LOG_TAG = "SUB";
60    static final boolean DBG = true;
61    static final boolean VDBG = false;
62
63    protected final Object mLock = new Object();
64    protected boolean mSuccess;
65
66    /** The singleton instance. */
67    private static SubscriptionController sInstance = null;
68    protected static Phone mPhone;
69    protected static Context mContext;
70
71    private DataConnectionHandler mDataConnectionHandler;
72
73    public static final Uri CONTENT_URI =
74            Uri.parse("content://telephony/siminfo");
75
76    public static final int DEFAULT_INT_VALUE = -100;
77
78    public static final String DEFAULT_STRING_VALUE = "N/A";
79
80    private static final int EVENT_SET_DEFAULT_DATA_DONE = 1;
81
82    public static final int EXTRA_VALUE_NEW_SIM = 1;
83    public static final int EXTRA_VALUE_REMOVE_SIM = 2;
84    public static final int EXTRA_VALUE_REPOSITION_SIM = 3;
85    public static final int EXTRA_VALUE_NOCHANGE = 4;
86
87    public static final String INTENT_KEY_DETECT_STATUS = "simDetectStatus";
88    public static final String INTENT_KEY_SIM_COUNT = "simCount";
89    public static final String INTENT_KEY_NEW_SIM_SLOT = "newSIMSlot";
90    public static final String INTENT_KEY_NEW_SIM_STATUS = "newSIMStatus";
91
92    /**
93     * The ICC ID of a SIM.
94     * <P>Type: TEXT (String)</P>
95     */
96    public static final String ICC_ID = "icc_id";
97
98    /**
99     * <P>Type: INTEGER (int)</P>
100     */
101    public static final String SIM_ID = "sim_id";
102
103    public static final int SIM_NOT_INSERTED = -1;
104
105    /**
106     * The display name of a SIM.
107     * <P>Type: TEXT (String)</P>
108     */
109    public static final String DISPLAY_NAME = "display_name";
110
111    public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName;
112
113    /**
114     * The display name source of a SIM.
115     * <P>Type: INT (int)</P>
116     */
117    public static final String NAME_SOURCE = "name_source";
118
119    public static final int DEFAULT_SOURCE = 0;
120
121    public static final int SIM_SOURCE = 1;
122
123    public static final int USER_INPUT = 2;
124
125    /**
126     * The color of a SIM.
127     * <P>Type: INTEGER (int)</P>
128     */
129    public static final String COLOR = "color";
130
131    public static final int COLOR_1 = 0;
132
133    public static final int COLOR_2 = 1;
134
135    public static final int COLOR_3 = 2;
136
137    public static final int COLOR_4 = 3;
138
139    public static final int COLOR_DEFAULT = COLOR_1;
140
141    /**
142     * The phone number of a SIM.
143     * <P>Type: TEXT (String)</P>
144     */
145    public static final String NUMBER = "number";
146
147    /**
148     * The number display format of a SIM.
149     * <P>Type: INTEGER (int)</P>
150     */
151    public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
152
153    public static final int DISPALY_NUMBER_NONE = 0;
154
155    public static final int DISPLAY_NUMBER_FIRST = 1;
156
157    public static final int DISPLAY_NUMBER_LAST = 2;
158
159    public static final int DISLPAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
160
161    /**
162     * Permission for data roaming of a SIM.
163     * <P>Type: INTEGER (int)</P>
164     */
165    public static final String DATA_ROAMING = "data_roaming";
166
167    public static final int DATA_ROAMING_ENABLE = 1;
168
169    public static final int DATA_ROAMING_DISABLE = 0;
170
171    public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
172
173    private static final int RES_TYPE_BACKGROUND_DARK = 0;
174
175    private static final int RES_TYPE_BACKGROUND_LIGHT = 1;
176
177    private static final int[] sSimBackgroundDarkRes = setSimResource(RES_TYPE_BACKGROUND_DARK);
178
179    private static final int[] sSimBackgroundLightRes = setSimResource(RES_TYPE_BACKGROUND_LIGHT);
180
181    private static HashMap<Integer, Long> mSimInfo = new HashMap<Integer, Long>();
182    // FIXME define an invalid SUB_ID, and use that below instead of "1".
183    private static long mDefaultVoiceSubId = 1;
184    private static int mDefaultPhoneId = 0;
185
186    public static SubscriptionController init(Phone phone) {
187        synchronized (SubscriptionController.class) {
188            if (sInstance == null) {
189                sInstance = new SubscriptionController(phone);
190            } else {
191                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
192            }
193            return sInstance;
194        }
195    }
196
197    public static SubscriptionController init(Context c, CommandsInterface[] ci) {
198        synchronized (SubscriptionController.class) {
199            if (sInstance == null) {
200                sInstance = new SubscriptionController(c);
201            } else {
202                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
203            }
204            return sInstance;
205        }
206    }
207
208    public static SubscriptionController getInstance() {
209        if (sInstance == null)
210        {
211           Log.wtf(LOG_TAG, "getInstance null");
212        }
213
214        return sInstance;
215    }
216
217    private SubscriptionController(Context c) {
218        mContext = c;
219
220        if(ServiceManager.getService("isub") == null) {
221                ServiceManager.addService("isub", this);
222        }
223
224        mDataConnectionHandler = new DataConnectionHandler();
225        logd("SubscriptionController init by Context");
226    }
227
228    private boolean isSubInfoReady() {
229        return (mSimInfo.size() > 0) ? true : false;
230    }
231
232    private SubscriptionController(Phone phone) {
233        mContext = phone.getContext();
234
235        if(ServiceManager.getService("isub") == null) {
236                ServiceManager.addService("isub", this);
237        }
238
239        logd("SubscriptionController init by Phone");
240    }
241
242    /**
243     * Broadcast when subinfo settings has chanded
244     * @SubId The unique SubInfoRecord index in database
245     * @param columnName The column that is updated
246     * @param intContent The updated integer value
247     * @param stringContent The updated string value
248     */
249     private void broadcastSimInfoContentChanged(long subId,
250            String columnName, int intContent, String stringContent) {
251        Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
252        intent.putExtra(BaseColumns._ID, subId);
253        intent.putExtra(TelephonyIntents.EXTRA_COLUMN_NAME, columnName);
254        intent.putExtra(TelephonyIntents.EXTRA_INT_CONTENT, intContent);
255        intent.putExtra(TelephonyIntents.EXTRA_STRING_CONTENT, stringContent);
256        if (intContent != DEFAULT_INT_VALUE) {
257            logd("SubInfoRecord" + subId + " changed, " + columnName + " -> " +  intContent);
258        } else {
259            logd("SubInfoRecord" + subId + " changed, " + columnName + " -> " +  stringContent);
260        }
261        mContext.sendBroadcast(intent);
262    }
263
264
265    /**
266     * New SubInfoRecord instance and fill in detail info
267     * @param cursor
268     * @return the query result of desired SubInfoRecord
269     */
270    private SubInfoRecord getSubInfoRecord(Cursor cursor) {
271            SubInfoRecord info = new SubInfoRecord();
272            info.mSubId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
273            info.mIccId = cursor.getString(cursor.getColumnIndexOrThrow(ICC_ID));
274            info.mSlotId = cursor.getInt(cursor.getColumnIndexOrThrow(SIM_ID));
275            info.mDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(DISPLAY_NAME));
276            info.mNameSource = cursor.getInt(cursor.getColumnIndexOrThrow(NAME_SOURCE));
277            info.mColor = cursor.getInt(cursor.getColumnIndexOrThrow(COLOR));
278            info.mNumber = cursor.getString(cursor.getColumnIndexOrThrow(NUMBER));
279            info.mDispalyNumberFormat = cursor.getInt(cursor.getColumnIndexOrThrow(DISPLAY_NUMBER_FORMAT));
280            info.mDataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(DATA_ROAMING));
281
282            int size = sSimBackgroundDarkRes.length;
283            if (info.mColor >= 0 && info.mColor < size) {
284                info.mSimIconRes[RES_TYPE_BACKGROUND_DARK] = sSimBackgroundDarkRes[info.mColor];
285                info.mSimIconRes[RES_TYPE_BACKGROUND_LIGHT] = sSimBackgroundLightRes[info.mColor];
286            }
287            logd("[getSubInfoRecord] SubId:" + info.mSubId + " iccid:" + info.mIccId + " slotId:" + info.mSlotId
288                    + " displayName:" + info.mDisplayName + " color:" + info.mColor);
289
290            return info;
291    }
292
293    /**
294     * Query SubInfoRecord(s) from subinfo database
295     * @param selection A filter declaring which rows to return
296     * @param queryKey query key content
297     * @return Array list of queried result from database
298     */
299     private List<SubInfoRecord> getSubInfo(String selection, Object queryKey) {
300        logd("selection:" + selection + " " + queryKey);
301        String[] selectionArgs = null;
302        if (queryKey != null) {
303            selectionArgs = new String[] {queryKey.toString()};
304        }
305        ArrayList<SubInfoRecord> subList = null;
306        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
307                null, selection, selectionArgs, null);
308        try {
309            if (cursor != null) {
310                while (cursor.moveToNext()) {
311                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
312                    if (subInfo != null)
313                    {
314                        if (subList == null)
315                        {
316                            subList = new ArrayList<SubInfoRecord>();
317                        }
318                        subList.add(subInfo);
319                }
320                }
321            } else {
322                logd("Query fail");
323            }
324        } finally {
325            if (cursor != null) {
326                cursor.close();
327            }
328        }
329
330        return subList;
331    }
332
333
334
335    /**
336     * Get the SubInfoRecord according to an index
337     * @param subId The unique SubInfoRecord index in database
338     * @return SubInfoRecord, maybe null
339     */
340    @Override
341    public SubInfoRecord getSubInfoUsingSubId(long subId) {
342        logd("[getSubInfoUsingSubIdx]+ subId:" + subId);
343        if (subId <= 0 || !isSubInfoReady()) {
344            logd("[getSubInfoUsingSubIdx]- subId <= 0 or not ready");
345            return null;
346        }
347        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
348                null, BaseColumns._ID + "=?", new String[] {Long.toString(subId)}, null);
349        try {
350            if (cursor != null) {
351                if (cursor.moveToFirst()) {
352                    logd("[getSubInfoUsingSubIdx]- Info detail:");
353                    return getSubInfoRecord(cursor);
354                }
355            }
356        } finally {
357            if (cursor != null) {
358                cursor.close();
359            }
360        }
361        logd("[getSubInfoUsingSubIdx]- null info return");
362
363        return null;
364    }
365
366    /**
367     * Get the SubInfoRecord according to an IccId
368     * @param iccId the IccId of SIM card
369     * @return SubInfoRecord, maybe null
370     */
371    @Override
372    public List<SubInfoRecord> getSubInfoUsingIccId(String iccId) {
373        logd("[getSubInfoUsingIccId]+ iccId:" + iccId);
374        if (iccId == null || !isSubInfoReady()) {
375            logd("[getSubInfoUsingIccId]- null iccid or not ready");
376            return null;
377        }
378        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
379                null, ICC_ID + "=?", new String[] {iccId}, null);
380        ArrayList<SubInfoRecord> subList = null;
381        try {
382            if (cursor != null) {
383                while (cursor.moveToNext()) {
384                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
385                    if (subInfo != null)
386                    {
387                        if (subList == null)
388                        {
389                            subList = new ArrayList<SubInfoRecord>();
390                        }
391                        subList.add(subInfo);
392                }
393                }
394            } else {
395                logd("Query fail");
396            }
397        } finally {
398            if (cursor != null) {
399                cursor.close();
400            }
401        }
402
403        return subList;
404    }
405
406    /**
407     * Get the SubInfoRecord according to slotId
408     * @param slotId the slot which the SIM is inserted
409     * @return SubInfoRecord, maybe null
410     */
411    @Override
412    public List<SubInfoRecord> getSubInfoUsingSlotId(int slotId) {
413        return getSubInfoUsingSlotIdWithCheck(slotId, true);
414    }
415
416    /**
417     * Get all the SubInfoRecord(s) in subinfo database
418     * @return Array list of all SubInfoRecords in database, include thsoe that were inserted before
419     */
420    @Override
421    public List<SubInfoRecord> getAllSubInfoList() {
422        logd("[getAllSubInfoList]+");
423        List<SubInfoRecord> subList = null;
424        subList = getSubInfo(null, null);
425        if (subList != null) {
426            logd("[getAllSubInfoList]- " + subList.size() + " infos return");
427        } else {
428            logd("[getAllSubInfoList]- no info return");
429        }
430
431        return subList;
432    }
433
434    /**
435     * Get the SubInfoRecord(s) of the currently inserted SIM(s)
436     * @return Array list of currently inserted SubInfoRecord(s)
437     */
438    @Override
439    public List<SubInfoRecord> getActivatedSubInfoList() {
440        logd("[getActivatedSubInfoList]+");
441        List<SubInfoRecord> subList = null;
442
443        if (!isSubInfoReady()) {
444            logd("[getActivatedSubInfoList] Sub Controller not ready");
445            return subList;
446        }
447
448        subList = getSubInfo(SIM_ID + "!=" + SIM_NOT_INSERTED, null);
449        if (subList != null) {
450            logd("[getActivatedSubInfoList]- " + subList.size() + " infos return");
451        } else {
452            logd("[getActivatedSubInfoList]- no info return");
453        }
454
455        return subList;
456    }
457
458    /**
459     * Get the SUB count of all SUB(s) in subinfo database
460     * @return all SIM count in database, include what was inserted before
461     */
462    @Override
463    public int getAllSubInfoCount() {
464        logd("[getAllSubInfoCount]+");
465        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
466                null, null, null, null);
467        try {
468            if (cursor != null) {
469                int count = cursor.getCount();
470                logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
471                return count;
472            }
473        } finally {
474            if (cursor != null) {
475                cursor.close();
476            }
477        }
478        logd("[getAllSubInfoCount]- no SUB in DB");
479
480        return 0;
481    }
482
483    /**
484     * Add a new SubInfoRecord to subinfo database if needed
485     * @param iccId the IccId of the SIM card
486     * @param slotId the slot which the SIM is inserted
487     * @return the URL of the newly created row or the updated row
488     */
489    @Override
490    public int addSubInfoRecord(String iccId, int slotId) {
491        logd("[addSubInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
492        if (iccId == null) {
493            logd("[addSubInfoRecord]- null iccId");
494        }
495        Uri uri = null;
496        String nameToSet;
497        nameToSet = "SUB 0"+Integer.toString(slotId+1);
498        ContentResolver resolver = mContext.getContentResolver();
499        Cursor cursor = resolver.query(CONTENT_URI, new String[] {BaseColumns._ID, SIM_ID, NAME_SOURCE},
500                ICC_ID + "=?", new String[] {iccId}, null);
501        try {
502            if (cursor == null || !cursor.moveToFirst()) {
503                ContentValues value = new ContentValues();
504                value.put(ICC_ID, iccId);
505                // default SIM color differs between slots
506                value.put(COLOR, slotId);
507                value.put(SIM_ID, slotId);
508                value.put(DISPLAY_NAME, nameToSet);
509                uri = resolver.insert(CONTENT_URI, value);
510                logd("[addSubInfoRecord] New record creating values=" + value);
511                logd("[addSubInfoRecord] New record result uri=" + uri);
512           } else {
513                long subId = cursor.getLong(0);
514                int oldSimInfoId = cursor.getInt(1);
515                int nameSource = cursor.getInt(2);
516                ContentValues value = new ContentValues();
517
518                if (slotId != oldSimInfoId) {
519                    value.put(SIM_ID, slotId);
520                }
521
522                if (nameSource != USER_INPUT) {
523                    value.put(DISPLAY_NAME, nameToSet);
524                }
525
526                if (value.size() > 0) {
527                    String where = BaseColumns._ID + "=" + Long.toString(subId);
528                    logd("[addSubInfoRecord] resolver.update value=" + value + " where=" + where);
529                    resolver.update(CONTENT_URI, value, where, null);
530                }
531
532                logd("[addSubInfoRecord] Record already exist");
533            }
534        } finally {
535            if (cursor != null) {
536                cursor.close();
537            }
538        }
539
540        cursor = resolver.query(CONTENT_URI,
541                null, SIM_ID + "=?", new String[] {String.valueOf(slotId)}, null);
542        if (cursor == null) logd("[addSubInfoRecord] 1 cursor is null");
543
544        try {
545            if (cursor != null && cursor.moveToFirst()) {
546                do {
547                    long subId = cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
548                    logd("[addSubInfoRecord] subId=" + subId + " mSimInfo.size" + mSimInfo.size());
549                    // If mSinInfo is already having a valid subId for a slotId/phoneId,
550                    // do not add another subId for same slotId/phoneId.
551                    long[] sub = getSubId(slotId);
552
553                    // FIXME: This is only adding records if there isn't any or sub is returning "dummy" values.
554                    // this should probably be updating if the recorddoesn't contain the specific slotId/subId mapping.
555                    // But that will be for another time and likely there will be other changes in the mean time.
556                    if ((mSimInfo.size() == 0) || (sub != null && sub.length > 0 && sub[0] <= 0)) {
557                        // set the first entry as default sub
558                        // TODO While two subs active, if user deactivats first
559                        // one, need to update the default subId with second
560                        // one.
561                        if (mSimInfo.size() == 0) {
562                            logd("[addSubInfoRecord] call setDefaultSubId subId=" + subId);
563                            setDefaultSubId(subId);
564                        }
565                        mSimInfo.put(slotId, subId);
566                        logd("[addSubInfoRecord] mSimInfo.size=" + mSimInfo.size()
567                                + " slotId = " + slotId + " subId = " + subId);
568                    } else {
569                        logd("[addSubInfoRecord] size != 0 && sub[] isn't valid, IGNORE");
570                    }
571                } while (cursor.moveToNext());
572            } else {
573                logd("[addSubInfoRecord] no records for " + BaseColumns._ID);
574            }
575        } finally {
576            if (cursor != null) {
577                cursor.close();
578            }
579        }
580
581        int size = mSimInfo.size();
582        logd("[addSubInfoRecord]- info size=" + size);
583
584        return 1;
585    }
586
587    /**
588     * Set SIM color by simInfo index
589     * @param color the color of the SIM
590     * @param subId the unique SubInfoRecord index in database
591     * @return the number of records updated
592     */
593    @Override
594    public int setColor(int color, long subId) {
595        logd("[setColor]+ color:" + color + " subId:" + subId);
596        int size = sSimBackgroundDarkRes.length;
597        if (subId <= 0 || color < 0 || color >= size) {
598            logd("[setColor]- fail");
599            return -1;
600        }
601        ContentValues value = new ContentValues(1);
602        value.put(COLOR, color);
603        logd("[setColor]- color:" + color + " set");
604
605        int result = mContext.getContentResolver().update(CONTENT_URI, value,
606                                                    BaseColumns._ID + "=" + Long.toString(subId), null);
607        broadcastSimInfoContentChanged(subId, COLOR, color, DEFAULT_STRING_VALUE);
608
609        return result;
610    }
611
612    /**
613     * Set display name by simInfo index
614     * @param displayName the display name of SIM card
615     * @param subId the unique SubInfoRecord index in database
616     * @return the number of records updated
617     */
618    @Override
619    public int setDisplayName(String displayName, long subId) {
620        return setDisplayNameUsingSrc(displayName, subId, -1);
621    }
622
623    /**
624     * Set display name by simInfo index with name source
625     * @param displayName the display name of SIM card
626     * @param subId the unique SubInfoRecord index in database
627     * @param nameSource 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT
628     * @return the number of records updated
629     */
630    @Override
631    public int setDisplayNameUsingSrc(String displayName, long subId, long nameSource) {
632        logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId + " nameSource:" + nameSource);
633        if (subId <= 0) {
634            logd("[setDisplayName]- fail");
635            return -1;
636        }
637        String nameToSet;
638        if (displayName == null) {
639            nameToSet = mContext.getString(DEFAULT_NAME_RES);
640        } else {
641            nameToSet = displayName;
642        }
643        ContentValues value = new ContentValues(1);
644        value.put(DISPLAY_NAME, nameToSet);
645        if (nameSource >= DEFAULT_SOURCE) {
646            logd("Set nameSource=" + nameSource);
647            value.put(NAME_SOURCE, nameSource);
648        }
649        logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
650
651        int result = mContext.getContentResolver().update(CONTENT_URI, value,
652                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
653        broadcastSimInfoContentChanged(subId, DISPLAY_NAME, DEFAULT_INT_VALUE, nameToSet);
654
655        return result;
656    }
657
658    /**
659     * Set phone number by subId
660     * @param number the phone number of the SIM
661     * @param subId the unique SubInfoRecord index in database
662     * @return the number of records updated
663     */
664    @Override
665    public int setDispalyNumber(String number, long subId) {
666        logd("[setDispalyNumber]+ number:" + number + " subId:" + subId);
667        if (number == null || subId <= 0) {
668            logd("[setDispalyNumber]- fail");
669            return -1;
670        }
671        ContentValues value = new ContentValues(1);
672        value.put(NUMBER, number);
673        logd("[setDispalyNumber]- number:" + number + " set");
674
675        int result = mContext.getContentResolver().update(CONTENT_URI, value,
676                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
677        broadcastSimInfoContentChanged(subId, NUMBER, DEFAULT_INT_VALUE, number);
678
679        return result;
680    }
681
682    /**
683     * Set number display format. 0: none, 1: the first four digits, 2: the last four digits
684     * @param format the display format of phone number
685     * @param subId the unique SubInfoRecord index in database
686     * @return the number of records updated
687     */
688    @Override
689    public int setDisplayNumberFormat(int format, long subId) {
690        logd("[setDisplayNumberFormat]+ format:" + format + " subId:" + subId);
691        if (format < 0 || subId <= 0) {
692            logd("[setDisplayNumberFormat]- fail, return -1");
693            return -1;
694        }
695        ContentValues value = new ContentValues(1);
696        value.put(DISPLAY_NUMBER_FORMAT, format);
697        logd("[setDisplayNumberFormat]- format:" + format + " set");
698
699        int result = mContext.getContentResolver().update(CONTENT_URI, value,
700                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
701        broadcastSimInfoContentChanged(subId, DISPLAY_NUMBER_FORMAT, format, DEFAULT_STRING_VALUE);
702
703        return result;
704    }
705
706    /**
707     * Set data roaming by simInfo index
708     * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
709     * @param subId the unique SubInfoRecord index in database
710     * @return the number of records updated
711     */
712    @Override
713    public int setDataRoaming(int roaming, long subId) {
714        logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
715        if (roaming < 0 || subId <= 0) {
716            logd("[setDataRoaming]- fail");
717            return -1;
718        }
719        ContentValues value = new ContentValues(1);
720        value.put(DATA_ROAMING, roaming);
721        logd("[setDataRoaming]- roaming:" + roaming + " set");
722
723        int result = mContext.getContentResolver().update(CONTENT_URI, value,
724                                                  BaseColumns._ID + "=" + Long.toString(subId), null);
725        broadcastSimInfoContentChanged(subId, DATA_ROAMING, roaming, DEFAULT_STRING_VALUE);
726
727        return result;
728    }
729
730    @Override
731    public int getSlotId(long subId) {
732        logd("[getSlotId]+ subId:" + subId);
733        if (subId <= 0) {
734            logd("[getSlotId]- subId <= 0");
735            return SIM_NOT_INSERTED;
736        }
737
738        int size = mSimInfo.size();
739        logd("[getSlotId]- info size="+size);
740
741        if (size == 0)
742        {
743            return SIM_NOT_INSERTED;
744        }
745
746        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
747            int sim = entry.getKey();
748            long sub = entry.getValue();
749            logd("[getSlotId]- entry, sim ="+sim+", sub="+ sub);
750
751            if (subId == sub)
752            {
753                logd("[getSlotId]- return ="+sim);
754                return sim;
755            }
756        }
757
758        logd("[getSlotId]- return fail");
759        return (int)(subId-1);
760
761    }
762
763    /**
764     * Return the subId for specified sim Id.
765     * @deprecated
766     */
767    @Override
768    @Deprecated
769    public long[] getSubId(int slotId) {
770        if (VDBG) logd("[getSubId]+ slotId:" + slotId);
771
772        // FIXME this should return the subIds associated with the PhoneIds
773        // using {1, 2} for now, to workaround problem with data classes
774        // not being able to find a subscription that has value set to
775        // default data during bootup, since SubscriptionController has not
776        // discovered all the subs when this query is made.
777
778        long[] subId = new long[] {-1-slotId, -1-slotId};
779
780        if (slotId < 0) {
781            logd("[getSubId]- slotId < 0, return dummy instead");
782            return subId;
783        }
784
785        int size = mSimInfo.size();
786        if (VDBG) logd("[getSubId]- info size="+size);
787
788        if (size == 0)
789        {
790            logd("[getSubId]- size == 0, return dummy instead");
791            return subId;
792        }
793
794        int subIdx = 0;
795
796        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
797            int sim = entry.getKey();
798            long sub = entry.getValue();
799
800            if (VDBG) logd("[getSubId]- entry, sim ="+sim+", sub="+ sub);
801            if (slotId == sim)
802            {
803                subId[subIdx] = sub;
804                if (VDBG) logd("[getSubId]- subId["+subIdx+"] = "+subId[subIdx]);
805                subIdx++;
806            }
807        }
808
809        if (VDBG) logd("[getSubId]-, subId = "+subId[0]);
810        return subId;
811    }
812
813    @Override
814    public int getPhoneId(long subId) {
815        if (VDBG) logd("[getPhoneId]+ subId:" + subId);
816        if (subId <= 0) {
817            // FIXME Do not auto map subId to phoneId
818            // May be we shoud add dummy subId's during
819            // initialization of subscription controller ??
820            if (subId == -1) {
821                logd("[getPhoneId]- subId == -1 return =" + 0);
822                return 0;
823            } else if (subId == -2) {
824                logd("[getPhoneId]- subId == -2 return =" + 1);
825                return 1;
826            }
827        }
828
829        int size = mSimInfo.size();
830
831        if (size == 0 || subId == SubscriptionManager.DEFAULT_SUB_ID) {
832            if (VDBG) logd("[getPhoneId]- subId == DEFAULT_SUB_ID returning defaultPhoneId=" +
833                    mDefaultPhoneId);
834            return mDefaultPhoneId;
835        }
836
837        for (Entry<Integer, Long> entry: mSimInfo.entrySet()) {
838            int sim = entry.getKey();
839            long sub = entry.getValue();
840            if (VDBG) logd("[getPhoneId]- entry, sim="+sim+", sub="+ sub);
841
842            if (subId == sub)
843            {
844                if (VDBG) logd("[getPhoneId]- return="+sim);
845                return sim;
846            }
847        }
848
849        if (VDBG) logd("[getPhoneId]- return=" + (int)(subId-1));
850        return (int)(subId-1);
851
852    }
853
854    @Override
855    public int clearSubInfo()
856    {
857        if (VDBG) logd("[clearSubInfo]+");
858
859        int size = mSimInfo.size();
860        if (VDBG) logd("[getSubId]- info size="+size);
861
862        if (size == 0)
863        {
864            return 0;
865        }
866
867        mSimInfo.clear();
868        if (VDBG) logd("[clearSubInfo]-");
869        return 0;
870
871    }
872
873    private static int[] setSimResource(int type) {
874        int[] simResource = null;
875
876        switch (type) {
877            case RES_TYPE_BACKGROUND_DARK:
878                simResource = new int[] {
879                    com.android.internal.R.drawable.sim_dark_blue,
880                    com.android.internal.R.drawable.sim_dark_orange,
881                    com.android.internal.R.drawable.sim_dark_green,
882                    com.android.internal.R.drawable.sim_dark_purple
883                };
884                break;
885            case RES_TYPE_BACKGROUND_LIGHT:
886                simResource = new int[] {
887                    com.android.internal.R.drawable.sim_light_blue,
888                    com.android.internal.R.drawable.sim_light_orange,
889                    com.android.internal.R.drawable.sim_light_green,
890                    com.android.internal.R.drawable.sim_light_purple
891                };
892                break;
893        }
894
895        return simResource;
896    }
897
898    private void logd(String msg) {
899        Rlog.d(LOG_TAG, "[SubController]" + msg);
900    }
901
902    private void loge(String msg) {
903        Rlog.e(LOG_TAG, "[SubController]" + msg);
904    }
905
906    @Override
907    public long getDefaultSubId() {
908        if (VDBG) logd("getDefaultSubId: value=" + mDefaultVoiceSubId);
909        return mDefaultVoiceSubId;
910    }
911
912    @Override
913    public void setDefaultVoiceSubId(long subId) {
914        Settings.Global.putLong(mContext.getContentResolver(),
915                Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
916        broadcastDefaultVoiceSubIdChanged(subId);
917        if (VDBG) logd("setDefaultVoiceSubId, subId=" + subId);
918    }
919
920    private static void broadcastDefaultVoiceSubIdChanged(long subId) {
921        // Broadcast an Intent for default voice sub change
922        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
923        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
924        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
925        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
926    }
927
928    @Override
929    public long getDefaultVoiceSubId() {
930       long subId = SubscriptionManager.INVALID_SUB_ID;
931
932        try {
933            subId = Settings.Global.getLong(mContext.getContentResolver(),
934                    Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION);
935        } catch (SettingNotFoundException snfe) {
936            loge("Settings Exception Reading Dual Sim Voice Call Values");
937        }
938
939        if (VDBG) logd("getDefaultVoiceSubId, value = " + subId);
940        return subId;
941    }
942
943    @Override
944    public long getDefaultDataSubId() {
945       long subId = SubscriptionManager.INVALID_SUB_ID;
946       try {
947           subId = Settings.Global.getLong(mContext.getContentResolver(),
948                   Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION);
949       } catch (SettingNotFoundException snfe) {
950           loge("Settings Exception Reading Dual Sim Data Call Values");
951       }
952
953       return subId;
954    }
955
956    public void setDefaultDataSubId(long subId) {
957        loge("setDataSubId: subId=" + subId + " FIXME NOP right now");
958        DctController dctController = DctController.getInstance();
959        dctController.setDataSubId(subId);
960        dctController.registerForDataSwitchInfo(mDataConnectionHandler,
961                EVENT_SET_DEFAULT_DATA_DONE, null);
962    }
963
964    private void updateDataSubId(long subId) {
965        if (VDBG) logd(" updateDataSubId,  subId=" + subId);
966        Settings.Global.putLong(mContext.getContentResolver(),
967                Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
968        broadcastDefaultDataSubIdChanged(subId);
969    }
970
971    private static void broadcastDefaultDataSubIdChanged(long subId) {
972        // Broadcast an Intent for default data sub change
973        Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
974        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
975        intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
976        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
977    }
978
979    /* Sets the default subscription. If only one sub is active that
980     * sub is set as default subId. If two or more  sub's are active
981     * the first sub is set as default subscription
982     */
983    // FIXME Modify/rename this method name as part of
984    // refactoring other subscription changes
985    public void setDefaultSubId(long subId) {
986        if (subId > 0 && subId != SubscriptionManager.INVALID_SUB_ID
987                && subId != SubscriptionManager.DEFAULT_SUB_ID) {
988            int phoneId = getPhoneId(subId);
989            if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount()) {
990                mDefaultVoiceSubId = subId; // returned by getFirstActiveSubId()
991                if (VDBG) logd("setDefaultSubId: mDefaultVoiceSubId=subId=" + subId);
992                // Update MCC MNC device configuration information
993                String defaultMccMnc = TelephonyManager.getDefault().getSimOperator(phoneId);
994                if (VDBG) logd("setDefaultSubId: call update mccmnc=" + defaultMccMnc);
995                MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
996
997                // Broadcast an Intent for default sub change
998                Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
999                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1000                SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
1001                if (VDBG) {
1002                    logd("setDefaultSubId: broadcast default subId changed phoneId=" + phoneId
1003                            + " subId=" + subId);
1004                }
1005                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
1006            } else {
1007                if (VDBG) {
1008                    logd("setDefaultSubId: not set invalid phoneId=" + phoneId + " subId=" + subId);
1009                }
1010            }
1011        } else {
1012            if (VDBG) logd("setDefaultSubId: not set invalid subId=" + subId);
1013        }
1014    }
1015
1016    // FIXME SubscriptionController should register (perhaps using Registrants)
1017    // with SimInfoUpdater for any updates to the subscriptions. When it is
1018    // notified of any updates, this API should be called. Rather than
1019    // SimInfoUpdater calling into updateDefaultSubId.
1020    public void updateDefaultSubId() {
1021        if (!isSubActive(getDefaultDataSubId())) {
1022            updateDataSubId(getFirstActiveSubId());
1023        }
1024
1025        if (!isSubActive(getDefaultVoiceSubId())) {
1026            setDefaultVoiceSubId(getFirstActiveSubId());
1027        }
1028    }
1029
1030    // FIXME As part of getDefaultSubId method cleanup, modify
1031    // the mDefaultVoiceSubId to mFirstActiveSubId.
1032    private long getFirstActiveSubId() {
1033        if (VDBG) logd("getFirstActiveSubId, value = " + mDefaultVoiceSubId);
1034        return mDefaultVoiceSubId;
1035    }
1036
1037    private boolean isSubActive(long subId) {
1038        boolean subActive = false;
1039        List<SubInfoRecord> activeSubList = getActivatedSubInfoList();
1040
1041        if (activeSubList != null) {
1042            for (SubInfoRecord subInfoRecord : activeSubList) {
1043                if (subInfoRecord.mSubId == subId) {
1044                    if (VDBG) logd("isSubActive, found active sub " + subId);
1045                    subActive = true;
1046                    break;
1047                }
1048            }
1049        }
1050        return subActive;
1051    }
1052
1053    private class DataConnectionHandler extends Handler {
1054        @Override
1055        public void handleMessage(Message msg) {
1056            AsyncResult ar = (AsyncResult) msg.obj;
1057            switch (msg.what) {
1058                case EVENT_SET_DEFAULT_DATA_DONE:
1059                    Long subId = (Long)ar.result;
1060                    if (VDBG) logd("EVENT_SET_DEFAULT_DATA_DONE subId:" + subId);
1061                    updateDataSubId(subId);
1062                    break;
1063            }
1064        }
1065    }
1066
1067    /* This should return long and not long [] since each phone has
1068     * exactly 1 sub id for now, it could return the 0th element
1069     * returned from getSubId()
1070     */
1071    // FIXME will design a mechanism to manage the relationship between PhoneId/SlotId/SubId
1072    // since phoneId = SlotId is not always true
1073    public long getSubIdUsingPhoneId(int phoneId) {
1074        long[] subId = getSubId(phoneId);
1075        return subId[0];
1076    }
1077
1078    public long[] getSubIdUsingSlotId(int slotId) {
1079        return getSubId(slotId);
1080    }
1081
1082    public List<SubInfoRecord> getSubInfoUsingSlotIdWithCheck(int slotId, boolean needCheck) {
1083        if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]+ slotId=" + slotId);
1084        if (slotId < 0 ) {
1085            if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]- return null, slotId < 0");
1086            return null;
1087        }
1088
1089        if (needCheck && !isSubInfoReady()) {
1090            if (VDBG) logd("[getSubInfoUsingSlotIdWithCheck]- not ready return null");
1091            return null;
1092        }
1093
1094        Cursor cursor = mContext.getContentResolver().query(CONTENT_URI,
1095                null, SIM_ID + "=?", new String[] {String.valueOf(slotId)}, null);
1096        ArrayList<SubInfoRecord> subList = null;
1097        try {
1098            if (cursor != null) {
1099                while (cursor.moveToNext()) {
1100                    SubInfoRecord subInfo = getSubInfoRecord(cursor);
1101                    if (subInfo != null)
1102                    {
1103                        if (subList == null)
1104                        {
1105                            subList = new ArrayList<SubInfoRecord>();
1106                        }
1107                        subList.add(subInfo);
1108                    }
1109                }
1110            }
1111        } finally {
1112            if (cursor != null) {
1113                cursor.close();
1114            }
1115        }
1116        if (VDBG) logd("[getSubInfoUsingSlotId]- subList=" + subList);
1117        return subList;
1118
1119    }
1120}
1121