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