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.Manifest;
     20 import android.annotation.Nullable;
     21 import android.app.AppOpsManager;
     22 import android.content.ContentResolver;
     23 import android.content.ContentValues;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.database.Cursor;
     27 import android.graphics.Bitmap;
     28 import android.graphics.BitmapFactory;
     29 import android.net.Uri;
     30 import android.os.Binder;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.os.UserHandle;
     34 import android.provider.Settings;
     35 import android.telephony.RadioAccessFamily;
     36 import android.telephony.Rlog;
     37 import android.telephony.SubscriptionInfo;
     38 import android.telephony.SubscriptionManager;
     39 import android.telephony.TelephonyManager;
     40 import android.telephony.UiccAccessRule;
     41 import android.telephony.euicc.EuiccManager;
     42 import android.text.TextUtils;
     43 import android.text.format.Time;
     44 import android.util.Log;
     45 
     46 import com.android.internal.annotations.VisibleForTesting;
     47 import com.android.internal.telephony.IccCardConstants.State;
     48 import com.android.internal.telephony.uicc.IccUtils;
     49 import com.android.internal.telephony.uicc.UiccCard;
     50 import com.android.internal.telephony.uicc.UiccController;
     51 
     52 import java.io.FileDescriptor;
     53 import java.io.PrintWriter;
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.Collections;
     57 import java.util.Comparator;
     58 import java.util.HashSet;
     59 import java.util.Iterator;
     60 import java.util.LinkedList;
     61 import java.util.List;
     62 import java.util.Map;
     63 import java.util.Map.Entry;
     64 import java.util.Objects;
     65 import java.util.Set;
     66 import java.util.concurrent.ConcurrentHashMap;
     67 import java.util.stream.Collectors;
     68 
     69 /**
     70  * SubscriptionController to provide an inter-process communication to
     71  * access Sms in Icc.
     72  *
     73  * Any setters which take subId, slotIndex or phoneId as a parameter will throw an exception if the
     74  * parameter equals the corresponding INVALID_XXX_ID or DEFAULT_XXX_ID.
     75  *
     76  * All getters will lookup the corresponding default if the parameter is DEFAULT_XXX_ID. Ie calling
     77  * getPhoneId(DEFAULT_SUB_ID) will return the same as getPhoneId(getDefaultSubId()).
     78  *
     79  * Finally, any getters which perform the mapping between subscriptions, slots and phones will
     80  * return the corresponding INVALID_XXX_ID if the parameter is INVALID_XXX_ID. All other getters
     81  * will fail and return the appropriate error value. Ie calling
     82  * getSlotIndex(INVALID_SUBSCRIPTION_ID) will return INVALID_SIM_SLOT_INDEX and calling
     83  * getSubInfoForSubscriber(INVALID_SUBSCRIPTION_ID) will return null.
     84  *
     85  */
     86 public class SubscriptionController extends ISub.Stub {
     87     static final String LOG_TAG = "SubscriptionController";
     88     static final boolean DBG = true;
     89     static final boolean VDBG = false;
     90     static final boolean DBG_CACHE = false;
     91     static final int MAX_LOCAL_LOG_LINES = 500; // TODO: Reduce to 100 when 17678050 is fixed
     92     private static final int DEPRECATED_SETTING = -1;
     93     private ScLocalLog mLocalLog = new ScLocalLog(MAX_LOCAL_LOG_LINES);
     94 
     95     /* The Cache of Active SubInfoRecord(s) list of currently in use SubInfoRecord(s) */
     96     private final List<SubscriptionInfo> mCacheActiveSubInfoList = new ArrayList<>();
     97 
     98     /**
     99      * Copied from android.util.LocalLog with flush() adding flush and line number
    100      * TODO: Update LocalLog
    101      */
    102     static class ScLocalLog {
    103 
    104         private LinkedList<String> mLog;
    105         private int mMaxLines;
    106         private Time mNow;
    107 
    108         public ScLocalLog(int maxLines) {
    109             mLog = new LinkedList<String>();
    110             mMaxLines = maxLines;
    111             mNow = new Time();
    112         }
    113 
    114         public synchronized void log(String msg) {
    115             if (mMaxLines > 0) {
    116                 int pid = android.os.Process.myPid();
    117                 int tid = android.os.Process.myTid();
    118                 mNow.setToNow();
    119                 mLog.add(mNow.format("%m-%d %H:%M:%S") + " pid=" + pid + " tid=" + tid + " " + msg);
    120                 while (mLog.size() > mMaxLines) mLog.remove();
    121             }
    122         }
    123 
    124         public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    125             final int LOOPS_PER_FLUSH = 10; // Flush every N loops.
    126             Iterator<String> itr = mLog.listIterator(0);
    127             int i = 0;
    128             while (itr.hasNext()) {
    129                 pw.println(Integer.toString(i++) + ": " + itr.next());
    130                 // Flush periodically so we don't drop lines
    131                 if ((i % LOOPS_PER_FLUSH) == 0) pw.flush();
    132             }
    133         }
    134     }
    135 
    136     private static final Comparator<SubscriptionInfo> SUBSCRIPTION_INFO_COMPARATOR =
    137             (arg0, arg1) -> {
    138                 // Primary sort key on SimSlotIndex
    139                 int flag = arg0.getSimSlotIndex() - arg1.getSimSlotIndex();
    140                 if (flag == 0) {
    141                     // Secondary sort on SubscriptionId
    142                     return arg0.getSubscriptionId() - arg1.getSubscriptionId();
    143                 }
    144                 return flag;
    145             };
    146 
    147     protected final Object mLock = new Object();
    148 
    149     /** The singleton instance. */
    150     private static SubscriptionController sInstance = null;
    151     protected static Phone[] sPhones;
    152     protected Context mContext;
    153     protected TelephonyManager mTelephonyManager;
    154     protected CallManager mCM;
    155 
    156     private AppOpsManager mAppOps;
    157 
    158     // FIXME: Does not allow for multiple subs in a slot and change to SparseArray
    159     private static Map<Integer, Integer> sSlotIndexToSubId =
    160             new ConcurrentHashMap<Integer, Integer>();
    161     private static int mDefaultFallbackSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    162     private static int mDefaultPhoneId = SubscriptionManager.DEFAULT_PHONE_INDEX;
    163 
    164     private int[] colorArr;
    165     private long mLastISubServiceRegTime;
    166 
    167     public static SubscriptionController init(Phone phone) {
    168         synchronized (SubscriptionController.class) {
    169             if (sInstance == null) {
    170                 sInstance = new SubscriptionController(phone);
    171             } else {
    172                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
    173             }
    174             return sInstance;
    175         }
    176     }
    177 
    178     public static SubscriptionController init(Context c, CommandsInterface[] ci) {
    179         synchronized (SubscriptionController.class) {
    180             if (sInstance == null) {
    181                 sInstance = new SubscriptionController(c);
    182             } else {
    183                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
    184             }
    185             return sInstance;
    186         }
    187     }
    188 
    189     public static SubscriptionController getInstance() {
    190         if (sInstance == null)
    191         {
    192            Log.wtf(LOG_TAG, "getInstance null");
    193         }
    194 
    195         return sInstance;
    196     }
    197 
    198     protected SubscriptionController(Context c) {
    199         init(c);
    200         migrateImsSettings();
    201     }
    202 
    203     protected void init(Context c) {
    204         mContext = c;
    205         mCM = CallManager.getInstance();
    206         mTelephonyManager = TelephonyManager.from(mContext);
    207 
    208         mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
    209 
    210         if(ServiceManager.getService("isub") == null) {
    211             ServiceManager.addService("isub", this);
    212             mLastISubServiceRegTime = System.currentTimeMillis();
    213         }
    214 
    215         if (DBG) logdl("[SubscriptionController] init by Context");
    216     }
    217 
    218     private boolean isSubInfoReady() {
    219         return sSlotIndexToSubId.size() > 0;
    220     }
    221 
    222     private SubscriptionController(Phone phone) {
    223         mContext = phone.getContext();
    224         mCM = CallManager.getInstance();
    225         mAppOps = mContext.getSystemService(AppOpsManager.class);
    226 
    227         if(ServiceManager.getService("isub") == null) {
    228                 ServiceManager.addService("isub", this);
    229         }
    230 
    231         migrateImsSettings();
    232 
    233         if (DBG) logdl("[SubscriptionController] init by Phone");
    234     }
    235 
    236     private void enforceModifyPhoneState(String message) {
    237         mContext.enforceCallingOrSelfPermission(
    238                 android.Manifest.permission.MODIFY_PHONE_STATE, message);
    239     }
    240 
    241     /**
    242      * Broadcast when SubscriptionInfo has changed
    243      * FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
    244      */
    245      private void broadcastSimInfoContentChanged() {
    246         Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
    247         mContext.sendBroadcast(intent);
    248         intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
    249         mContext.sendBroadcast(intent);
    250      }
    251 
    252      public void notifySubscriptionInfoChanged() {
    253          ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
    254                  "telephony.registry"));
    255          try {
    256              if (DBG) logd("notifySubscriptionInfoChanged:");
    257              tr.notifySubscriptionInfoChanged();
    258          } catch (RemoteException ex) {
    259              // Should never happen because its always available.
    260          }
    261 
    262          // FIXME: Remove if listener technique accepted.
    263          broadcastSimInfoContentChanged();
    264      }
    265 
    266     /**
    267      * New SubInfoRecord instance and fill in detail info
    268      * @param cursor
    269      * @return the query result of desired SubInfoRecord
    270      */
    271     private SubscriptionInfo getSubInfoRecord(Cursor cursor) {
    272         int id = cursor.getInt(cursor.getColumnIndexOrThrow(
    273                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
    274         String iccId = cursor.getString(cursor.getColumnIndexOrThrow(
    275                 SubscriptionManager.ICC_ID));
    276         int simSlotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(
    277                 SubscriptionManager.SIM_SLOT_INDEX));
    278         String displayName = cursor.getString(cursor.getColumnIndexOrThrow(
    279                 SubscriptionManager.DISPLAY_NAME));
    280         String carrierName = cursor.getString(cursor.getColumnIndexOrThrow(
    281                 SubscriptionManager.CARRIER_NAME));
    282         int nameSource = cursor.getInt(cursor.getColumnIndexOrThrow(
    283                 SubscriptionManager.NAME_SOURCE));
    284         int iconTint = cursor.getInt(cursor.getColumnIndexOrThrow(
    285                 SubscriptionManager.COLOR));
    286         String number = cursor.getString(cursor.getColumnIndexOrThrow(
    287                 SubscriptionManager.NUMBER));
    288         int dataRoaming = cursor.getInt(cursor.getColumnIndexOrThrow(
    289                 SubscriptionManager.DATA_ROAMING));
    290         // Get the blank bitmap for this SubInfoRecord
    291         Bitmap iconBitmap = BitmapFactory.decodeResource(mContext.getResources(),
    292                 com.android.internal.R.drawable.ic_sim_card_multi_24px_clr);
    293         int mcc = cursor.getInt(cursor.getColumnIndexOrThrow(
    294                 SubscriptionManager.MCC));
    295         int mnc = cursor.getInt(cursor.getColumnIndexOrThrow(
    296                 SubscriptionManager.MNC));
    297         String cardId = cursor.getString(cursor.getColumnIndexOrThrow(
    298                 SubscriptionManager.CARD_ID));
    299         // FIXME: consider stick this into database too
    300         String countryIso = getSubscriptionCountryIso(id);
    301         boolean isEmbedded = cursor.getInt(cursor.getColumnIndexOrThrow(
    302                 SubscriptionManager.IS_EMBEDDED)) == 1;
    303         UiccAccessRule[] accessRules;
    304         if (isEmbedded) {
    305             accessRules = UiccAccessRule.decodeRules(cursor.getBlob(
    306                     cursor.getColumnIndexOrThrow(SubscriptionManager.ACCESS_RULES)));
    307         } else {
    308             accessRules = null;
    309         }
    310 
    311         if (VDBG) {
    312             String iccIdToPrint = SubscriptionInfo.givePrintableIccid(iccId);
    313             String cardIdToPrint = SubscriptionInfo.givePrintableIccid(cardId);
    314             logd("[getSubInfoRecord] id:" + id + " iccid:" + iccIdToPrint + " simSlotIndex:"
    315                     + simSlotIndex + " displayName:" + displayName + " nameSource:" + nameSource
    316                     + " iconTint:" + iconTint + " dataRoaming:" + dataRoaming
    317                     + " mcc:" + mcc + " mnc:" + mnc + " countIso:" + countryIso + " isEmbedded:"
    318                     + isEmbedded + " accessRules:" + Arrays.toString(accessRules)
    319                     + " cardId:" + cardIdToPrint);
    320         }
    321 
    322         // If line1number has been set to a different number, use it instead.
    323         String line1Number = mTelephonyManager.getLine1Number(id);
    324         if (!TextUtils.isEmpty(line1Number) && !line1Number.equals(number)) {
    325             number = line1Number;
    326         }
    327         return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
    328                 nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
    329                 isEmbedded, accessRules, cardId);
    330     }
    331 
    332     /**
    333      * Get ISO country code for the subscription's provider
    334      *
    335      * @param subId The subscription ID
    336      * @return The ISO country code for the subscription's provider
    337      */
    338     private String getSubscriptionCountryIso(int subId) {
    339         final int phoneId = getPhoneId(subId);
    340         if (phoneId < 0) {
    341             return "";
    342         }
    343         return mTelephonyManager.getSimCountryIsoForPhone(phoneId);
    344     }
    345 
    346     /**
    347      * Query SubInfoRecord(s) from subinfo database
    348      * @param selection A filter declaring which rows to return
    349      * @param queryKey query key content
    350      * @return Array list of queried result from database
    351      */
    352      private List<SubscriptionInfo> getSubInfo(String selection, Object queryKey) {
    353         if (VDBG) logd("selection:" + selection + " " + queryKey);
    354         String[] selectionArgs = null;
    355         if (queryKey != null) {
    356             selectionArgs = new String[] {queryKey.toString()};
    357         }
    358         ArrayList<SubscriptionInfo> subList = null;
    359         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
    360                 null, selection, selectionArgs, null);
    361         try {
    362             if (cursor != null) {
    363                 while (cursor.moveToNext()) {
    364                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
    365                     if (subInfo != null)
    366                     {
    367                         if (subList == null)
    368                         {
    369                             subList = new ArrayList<SubscriptionInfo>();
    370                         }
    371                         subList.add(subInfo);
    372                     }
    373                 }
    374             } else {
    375                 if (DBG) logd("Query fail");
    376             }
    377         } finally {
    378             if (cursor != null) {
    379                 cursor.close();
    380             }
    381         }
    382 
    383         return subList;
    384     }
    385 
    386     /**
    387      * Find unused color to be set for new SubInfoRecord
    388      * @param callingPackage The package making the IPC.
    389      * @return RGB integer value of color
    390      */
    391     private int getUnusedColor(String callingPackage) {
    392         List<SubscriptionInfo> availableSubInfos = getActiveSubscriptionInfoList(callingPackage);
    393         colorArr = mContext.getResources().getIntArray(com.android.internal.R.array.sim_colors);
    394         int colorIdx = 0;
    395 
    396         if (availableSubInfos != null) {
    397             for (int i = 0; i < colorArr.length; i++) {
    398                 int j;
    399                 for (j = 0; j < availableSubInfos.size(); j++) {
    400                     if (colorArr[i] == availableSubInfos.get(j).getIconTint()) {
    401                         break;
    402                     }
    403                 }
    404                 if (j == availableSubInfos.size()) {
    405                     return colorArr[i];
    406                 }
    407             }
    408             colorIdx = availableSubInfos.size() % colorArr.length;
    409         }
    410         return colorArr[colorIdx];
    411     }
    412 
    413     /**
    414      * Get the active SubscriptionInfo with the subId key
    415      * @param subId The unique SubscriptionInfo key in database
    416      * @param callingPackage The package making the IPC.
    417      * @return SubscriptionInfo, maybe null if its not active
    418      */
    419     @Override
    420     public SubscriptionInfo getActiveSubscriptionInfo(int subId, String callingPackage) {
    421         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    422                 mContext, subId, callingPackage, "getActiveSubscriptionInfo")) {
    423             return null;
    424         }
    425 
    426         // Now that all security checks passes, perform the operation as ourselves.
    427         final long identity = Binder.clearCallingIdentity();
    428         try {
    429             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
    430                     mContext.getOpPackageName());
    431             if (subList != null) {
    432                 for (SubscriptionInfo si : subList) {
    433                     if (si.getSubscriptionId() == subId) {
    434                         if (DBG) {
    435                             logd("[getActiveSubscriptionInfo]+ subId=" + subId + " subInfo=" + si);
    436                         }
    437 
    438                         return si;
    439                     }
    440                 }
    441             }
    442             if (DBG) {
    443                 logd("[getActiveSubInfoForSubscriber]- subId=" + subId
    444                         + " subList=" + subList + " subInfo=null");
    445             }
    446         } finally {
    447             Binder.restoreCallingIdentity(identity);
    448         }
    449 
    450         return null;
    451     }
    452 
    453     /**
    454      * Get the active SubscriptionInfo associated with the iccId
    455      * @param iccId the IccId of SIM card
    456      * @param callingPackage The package making the IPC.
    457      * @return SubscriptionInfo, maybe null if its not active
    458      */
    459     @Override
    460     public SubscriptionInfo getActiveSubscriptionInfoForIccId(String iccId, String callingPackage) {
    461         // Query the subscriptions unconditionally, and then check whether the caller has access to
    462         // the given subscription.
    463         final SubscriptionInfo si = getActiveSubscriptionInfoForIccIdInternal(iccId);
    464 
    465         final int subId = si != null
    466                 ? si.getSubscriptionId() : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
    467         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    468                 mContext, subId, callingPackage, "getActiveSubscriptionInfoForIccId")) {
    469             return null;
    470         }
    471 
    472         return si;
    473     }
    474 
    475     /**
    476      * Get the active SubscriptionInfo associated with the given iccId. The caller *must* perform
    477      * permission checks when using this method.
    478      */
    479     private SubscriptionInfo getActiveSubscriptionInfoForIccIdInternal(String iccId) {
    480         if (iccId == null) {
    481             return null;
    482         }
    483 
    484         final long identity = Binder.clearCallingIdentity();
    485         try {
    486             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
    487                     mContext.getOpPackageName());
    488             if (subList != null) {
    489                 for (SubscriptionInfo si : subList) {
    490                     if (iccId.equals(si.getIccId())) {
    491                         if (DBG)
    492                             logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId + " subInfo=" + si);
    493                         return si;
    494                     }
    495                 }
    496             }
    497             if (DBG) {
    498                 logd("[getActiveSubInfoUsingIccId]+ iccId=" + iccId
    499                         + " subList=" + subList + " subInfo=null");
    500             }
    501         } finally {
    502             Binder.restoreCallingIdentity(identity);
    503         }
    504 
    505         return null;
    506     }
    507 
    508     /**
    509      * Get the active SubscriptionInfo associated with the slotIndex
    510      * @param slotIndex the slot which the subscription is inserted
    511      * @param callingPackage The package making the IPC.
    512      * @return SubscriptionInfo, maybe null if its not active
    513      */
    514     @Override
    515     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex,
    516             String callingPackage) {
    517         Phone phone = PhoneFactory.getPhone(slotIndex);
    518         if (phone == null) {
    519             if (DBG) {
    520                 loge("[getActiveSubscriptionInfoForSimSlotIndex] no phone, slotIndex=" + slotIndex);
    521             }
    522             return null;
    523         }
    524         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    525                 mContext, phone.getSubId(), callingPackage,
    526                 "getActiveSubscriptionInfoForSimSlotIndex")) {
    527             return null;
    528         }
    529 
    530         // Now that all security checks passes, perform the operation as ourselves.
    531         final long identity = Binder.clearCallingIdentity();
    532         try {
    533             List<SubscriptionInfo> subList = getActiveSubscriptionInfoList(
    534                     mContext.getOpPackageName());
    535             if (subList != null) {
    536                 for (SubscriptionInfo si : subList) {
    537                     if (si.getSimSlotIndex() == slotIndex) {
    538                         if (DBG) {
    539                             logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex="
    540                                     + slotIndex + " subId=" + si);
    541                         }
    542                         return si;
    543                     }
    544                 }
    545                 if (DBG) {
    546                     logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIndex=" + slotIndex
    547                             + " subId=null");
    548                 }
    549             } else {
    550                 if (DBG) {
    551                     logd("[getActiveSubscriptionInfoForSimSlotIndex]+ subList=null");
    552                 }
    553             }
    554         } finally {
    555             Binder.restoreCallingIdentity(identity);
    556         }
    557 
    558         return null;
    559     }
    560 
    561     /**
    562      * @param callingPackage The package making the IPC.
    563      * @return List of all SubscriptionInfo records in database,
    564      * include those that were inserted before, maybe empty but not null.
    565      * @hide
    566      */
    567     @Override
    568     public List<SubscriptionInfo> getAllSubInfoList(String callingPackage) {
    569         if (DBG) logd("[getAllSubInfoList]+");
    570 
    571         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
    572         // about carrier-privileged callers not having access.
    573         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    574                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
    575                 "getAllSubInfoList")) {
    576             return null;
    577         }
    578 
    579         // Now that all security checks passes, perform the operation as ourselves.
    580         final long identity = Binder.clearCallingIdentity();
    581         try {
    582             List<SubscriptionInfo> subList = null;
    583             subList = getSubInfo(null, null);
    584             if (subList != null) {
    585                 if (DBG) logd("[getAllSubInfoList]- " + subList.size() + " infos return");
    586             } else {
    587                 if (DBG) logd("[getAllSubInfoList]- no info return");
    588             }
    589             return subList;
    590         } finally {
    591             Binder.restoreCallingIdentity(identity);
    592         }
    593     }
    594 
    595     /**
    596      * Get the SubInfoRecord(s) of the currently inserted SIM(s)
    597      * @param callingPackage The package making the IPC.
    598      * @return Array list of currently inserted SubInfoRecord(s)
    599      */
    600     @Override
    601     public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {
    602         if (!isSubInfoReady()) {
    603             if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
    604             return null;
    605         }
    606 
    607         boolean canReadAllPhoneState;
    608         try {
    609             canReadAllPhoneState = TelephonyPermissions.checkReadPhoneState(mContext,
    610                     SubscriptionManager.INVALID_SUBSCRIPTION_ID, Binder.getCallingPid(),
    611                     Binder.getCallingUid(), callingPackage, "getActiveSubscriptionInfoList");
    612         } catch (SecurityException e) {
    613             canReadAllPhoneState = false;
    614         }
    615 
    616         synchronized (mCacheActiveSubInfoList) {
    617             // If the caller can read all phone state, just return the full list.
    618             if (canReadAllPhoneState) {
    619                 return new ArrayList<>(mCacheActiveSubInfoList);
    620             }
    621 
    622             // Filter the list to only include subscriptions which the caller can manage.
    623             return mCacheActiveSubInfoList.stream()
    624                     .filter(subscriptionInfo -> {
    625                         try {
    626                             return TelephonyPermissions.checkCallingOrSelfReadPhoneState(mContext,
    627                                     subscriptionInfo.getSubscriptionId(), callingPackage,
    628                                     "getActiveSubscriptionInfoList");
    629                         } catch (SecurityException e) {
    630                             return false;
    631                         }
    632                     })
    633                     .collect(Collectors.toList());
    634         }
    635     }
    636 
    637     /**
    638      * Refresh the cache of SubInfoRecord(s) of the currently inserted SIM(s)
    639      */
    640     @VisibleForTesting  // For mockito to mock this method
    641     public void refreshCachedActiveSubscriptionInfoList() {
    642         if (!isSubInfoReady()) {
    643             if (DBG_CACHE) {
    644                 logdl("[refreshCachedActiveSubscriptionInfoList] "
    645                         + "Sub Controller not ready ");
    646             }
    647             return;
    648         }
    649 
    650         synchronized (mCacheActiveSubInfoList) {
    651             mCacheActiveSubInfoList.clear();
    652             List<SubscriptionInfo> activeSubscriptionInfoList = getSubInfo(
    653                     SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);
    654             if (activeSubscriptionInfoList != null) {
    655                 mCacheActiveSubInfoList.addAll(activeSubscriptionInfoList);
    656             }
    657             if (DBG_CACHE) {
    658                 if (!mCacheActiveSubInfoList.isEmpty()) {
    659                     for (SubscriptionInfo si : mCacheActiveSubInfoList) {
    660                         logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached info="
    661                                 + si);
    662                     }
    663                 } else {
    664                     logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
    665                 }
    666             }
    667         }
    668     }
    669 
    670     /**
    671      * Get the SUB count of active SUB(s)
    672      * @param callingPackage The package making the IPC.
    673      * @return active SIM count
    674      */
    675     @Override
    676     public int getActiveSubInfoCount(String callingPackage) {
    677         // Let getActiveSubscriptionInfoList perform permission checks / filtering.
    678         List<SubscriptionInfo> records = getActiveSubscriptionInfoList(callingPackage);
    679         if (records == null) {
    680             if (VDBG) logd("[getActiveSubInfoCount] records null");
    681             return 0;
    682         }
    683         if (VDBG) logd("[getActiveSubInfoCount]- count: " + records.size());
    684         return records.size();
    685     }
    686 
    687     /**
    688      * Get the SUB count of all SUB(s) in SubscriptoinInfo database
    689      * @param callingPackage The package making the IPC.
    690      * @return all SIM count in database, include what was inserted before
    691      */
    692     @Override
    693     public int getAllSubInfoCount(String callingPackage) {
    694         if (DBG) logd("[getAllSubInfoCount]+");
    695 
    696         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
    697         // about carrier-privileged callers not having access.
    698         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    699                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
    700                 "getAllSubInfoCount")) {
    701             return 0;
    702         }
    703 
    704         // Now that all security checks passes, perform the operation as ourselves.
    705         final long identity = Binder.clearCallingIdentity();
    706         try {
    707             Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
    708                     null, null, null, null);
    709             try {
    710                 if (cursor != null) {
    711                     int count = cursor.getCount();
    712                     if (DBG) logd("[getAllSubInfoCount]- " + count + " SUB(s) in DB");
    713                     return count;
    714                 }
    715             } finally {
    716                 if (cursor != null) {
    717                     cursor.close();
    718                 }
    719             }
    720             if (DBG) logd("[getAllSubInfoCount]- no SUB in DB");
    721 
    722             return 0;
    723         } finally {
    724             Binder.restoreCallingIdentity(identity);
    725         }
    726     }
    727 
    728     /**
    729      * @return the maximum number of subscriptions this device will support at any one time.
    730      */
    731     @Override
    732     public int getActiveSubInfoCountMax() {
    733         // FIXME: This valid now but change to use TelephonyDevController in the future
    734         return mTelephonyManager.getSimCount();
    735     }
    736 
    737     @Override
    738     public List<SubscriptionInfo> getAvailableSubscriptionInfoList(String callingPackage) {
    739         // This API isn't public, so no need to provide a valid subscription ID - we're not worried
    740         // about carrier-privileged callers not having access.
    741         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
    742                 mContext, SubscriptionManager.INVALID_SUBSCRIPTION_ID, callingPackage,
    743                 "getAvailableSubscriptionInfoList")) {
    744             throw new SecurityException("Need READ_PHONE_STATE to call "
    745                     + " getAvailableSubscriptionInfoList");
    746         }
    747 
    748         // Now that all security checks pass, perform the operation as ourselves.
    749         final long identity = Binder.clearCallingIdentity();
    750         try {
    751             EuiccManager euiccManager =
    752                     (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
    753             if (!euiccManager.isEnabled()) {
    754                 if (DBG) logdl("[getAvailableSubInfoList] Embedded subscriptions are disabled");
    755                 return null;
    756             }
    757 
    758             List<SubscriptionInfo> subList = getSubInfo(
    759                     SubscriptionManager.SIM_SLOT_INDEX + ">=0 OR "
    760                             + SubscriptionManager.IS_EMBEDDED + "=1", null);
    761 
    762             if (subList != null) {
    763                 subList.sort(SUBSCRIPTION_INFO_COMPARATOR);
    764 
    765                 if (VDBG) logdl("[getAvailableSubInfoList]- " + subList.size() + " infos return");
    766             } else {
    767                 if (DBG) logdl("[getAvailableSubInfoList]- no info return");
    768             }
    769 
    770             return subList;
    771         } finally {
    772             Binder.restoreCallingIdentity(identity);
    773         }
    774     }
    775 
    776     @Override
    777     public List<SubscriptionInfo> getAccessibleSubscriptionInfoList(String callingPackage) {
    778         EuiccManager euiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE);
    779         if (!euiccManager.isEnabled()) {
    780             if (DBG) {
    781                 logdl("[getAccessibleSubInfoList] Embedded subscriptions are disabled");
    782             }
    783             return null;
    784         }
    785 
    786         // Verify that the given package belongs to the calling UID.
    787         mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
    788 
    789         // Perform the operation as ourselves. If the caller cannot read phone state, they may still
    790         // have carrier privileges per the subscription metadata, so we always need to make the
    791         // query and then filter the results.
    792         final long identity = Binder.clearCallingIdentity();
    793         List<SubscriptionInfo> subList;
    794         try {
    795             subList = getSubInfo(SubscriptionManager.IS_EMBEDDED + "=1", null);
    796         } finally {
    797             Binder.restoreCallingIdentity(identity);
    798         }
    799 
    800         if (subList == null) {
    801             if (DBG) logdl("[getAccessibleSubInfoList] No info returned");
    802             return null;
    803         }
    804 
    805         // Filter the list to only include subscriptions which the (restored) caller can manage.
    806         List<SubscriptionInfo> filteredList = subList.stream()
    807                 .filter(subscriptionInfo ->
    808                         subscriptionInfo.canManageSubscription(mContext, callingPackage))
    809                 .sorted(SUBSCRIPTION_INFO_COMPARATOR)
    810                 .collect(Collectors.toList());
    811         if (VDBG) {
    812             logdl("[getAccessibleSubInfoList] " + filteredList.size() + " infos returned");
    813         }
    814         return filteredList;
    815     }
    816 
    817     /**
    818      * Return the list of subscriptions in the database which are either:
    819      * <ul>
    820      * <li>Embedded (but see note about {@code includeNonRemovableSubscriptions}, or
    821      * <li>In the given list of current embedded ICCIDs (which may not yet be in the database, or
    822      *     which may not currently be marked as embedded).
    823      * </ul>
    824      *
    825      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
    826      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
    827      *
    828      * @param embeddedIccids all ICCIDs of available embedded subscriptions. This is used to surface
    829      *     entries for profiles which had been previously deleted.
    830      * @param isEuiccRemovable whether the current ICCID is removable. Non-removable subscriptions
    831      *     will only be returned if the current ICCID is not removable; otherwise, they are left
    832      *     alone (not returned here unless in the embeddedIccids list) under the assumption that
    833      *     they will still be accessible when the eUICC containing them is activated.
    834      */
    835     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    836     public List<SubscriptionInfo> getSubscriptionInfoListForEmbeddedSubscriptionUpdate(
    837             String[] embeddedIccids, boolean isEuiccRemovable) {
    838         StringBuilder whereClause = new StringBuilder();
    839         whereClause.append("(").append(SubscriptionManager.IS_EMBEDDED).append("=1");
    840         if (isEuiccRemovable) {
    841             // Current eUICC is removable, so don't return non-removable subscriptions (which would
    842             // be deleted), as these are expected to still be present on a different, non-removable
    843             // eUICC.
    844             whereClause.append(" AND ").append(SubscriptionManager.IS_REMOVABLE).append("=1");
    845         }
    846         // Else, return both removable and non-removable subscriptions. This is expected to delete
    847         // all removable subscriptions, which is desired as they may not be accessible.
    848 
    849         whereClause.append(") OR ").append(SubscriptionManager.ICC_ID).append(" IN (");
    850         // ICCIDs are validated to contain only numbers when passed in, and come from a trusted
    851         // app, so no need to escape.
    852         for (int i = 0; i < embeddedIccids.length; i++) {
    853             if (i > 0) {
    854                 whereClause.append(",");
    855             }
    856             whereClause.append("\"").append(embeddedIccids[i]).append("\"");
    857         }
    858         whereClause.append(")");
    859 
    860         List<SubscriptionInfo> list = getSubInfo(whereClause.toString(), null);
    861         if (list == null) {
    862             return Collections.emptyList();
    863         }
    864         return list;
    865     }
    866 
    867     @Override
    868     public void requestEmbeddedSubscriptionInfoListRefresh() {
    869         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS,
    870                 "requestEmbeddedSubscriptionInfoListRefresh");
    871         long token = Binder.clearCallingIdentity();
    872         try {
    873             PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(null /* callback */);
    874         } finally {
    875             Binder.restoreCallingIdentity(token);
    876         }
    877     }
    878 
    879     /**
    880      * Asynchronously refresh the embedded subscription info list.
    881      *
    882      * @param callback Optional callback to execute after the refresh completes. Must terminate
    883      *     quickly as it will be called from SubscriptionInfoUpdater's handler thread.
    884      */
    885     // No permission check needed as this is not exposed via AIDL.
    886     public void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) {
    887         PhoneFactory.requestEmbeddedSubscriptionInfoListRefresh(callback);
    888     }
    889 
    890     /**
    891      * Add a new SubInfoRecord to subinfo database if needed
    892      * @param iccId the IccId of the SIM card
    893      * @param slotIndex the slot which the SIM is inserted
    894      * @return 0 if success, < 0 on error.
    895      */
    896     @Override
    897     public int addSubInfoRecord(String iccId, int slotIndex) {
    898         if (DBG) logdl("[addSubInfoRecord]+ iccId:" + SubscriptionInfo.givePrintableIccid(iccId) +
    899                 " slotIndex:" + slotIndex);
    900 
    901         enforceModifyPhoneState("addSubInfoRecord");
    902 
    903         // Now that all security checks passes, perform the operation as ourselves.
    904         final long identity = Binder.clearCallingIdentity();
    905         try {
    906             if (iccId == null) {
    907                 if (DBG) logdl("[addSubInfoRecord]- null iccId");
    908                 return -1;
    909             }
    910 
    911             ContentResolver resolver = mContext.getContentResolver();
    912             Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
    913                     new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
    914                             SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE,
    915                             SubscriptionManager.ICC_ID, SubscriptionManager.CARD_ID},
    916                     SubscriptionManager.ICC_ID + "=?" + " OR " + SubscriptionManager.ICC_ID + "=?",
    917                             new String[]{iccId, IccUtils.getDecimalSubstring(iccId)}, null);
    918 
    919             boolean setDisplayName = false;
    920             try {
    921                 if (cursor == null || !cursor.moveToFirst()) {
    922                     setDisplayName = true;
    923                     Uri uri = insertEmptySubInfoRecord(iccId, slotIndex);
    924                     if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
    925                 } else {
    926                     int subId = cursor.getInt(0);
    927                     int oldSimInfoId = cursor.getInt(1);
    928                     int nameSource = cursor.getInt(2);
    929                     String oldIccId = cursor.getString(3);
    930                     String oldCardId = cursor.getString(4);
    931                     ContentValues value = new ContentValues();
    932 
    933                     if (slotIndex != oldSimInfoId) {
    934                         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
    935                     }
    936 
    937                     if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
    938                         setDisplayName = true;
    939                     }
    940 
    941                     if (oldIccId != null && oldIccId.length() < iccId.length()
    942                             && (oldIccId.equals(IccUtils.getDecimalSubstring(iccId)))) {
    943                         value.put(SubscriptionManager.ICC_ID, iccId);
    944                     }
    945 
    946                     UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
    947                     if (card != null) {
    948                         String cardId = card.getCardId();
    949                         if (cardId != null && cardId != oldCardId) {
    950                             value.put(SubscriptionManager.CARD_ID, cardId);
    951                         }
    952                     }
    953 
    954                     if (value.size() > 0) {
    955                         resolver.update(SubscriptionManager.CONTENT_URI, value,
    956                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
    957                                         "=" + Long.toString(subId), null);
    958 
    959                         // Refresh the Cache of Active Subscription Info List
    960                         refreshCachedActiveSubscriptionInfoList();
    961                     }
    962 
    963                     if (DBG) logdl("[addSubInfoRecord] Record already exists");
    964                 }
    965             } finally {
    966                 if (cursor != null) {
    967                     cursor.close();
    968                 }
    969             }
    970 
    971             cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
    972                     SubscriptionManager.SIM_SLOT_INDEX + "=?",
    973                     new String[] {String.valueOf(slotIndex)}, null);
    974             try {
    975                 if (cursor != null && cursor.moveToFirst()) {
    976                     do {
    977                         int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
    978                                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
    979                         // If sSlotIndexToSubId already has the same subId for a slotIndex/phoneId,
    980                         // do not add it.
    981                         Integer currentSubId = sSlotIndexToSubId.get(slotIndex);
    982                         if (currentSubId == null
    983                                 || currentSubId != subId
    984                                 || !SubscriptionManager.isValidSubscriptionId(currentSubId)) {
    985                             // TODO While two subs active, if user deactivats first
    986                             // one, need to update the default subId with second one.
    987 
    988                             // FIXME: Currently we assume phoneId == slotIndex which in the future
    989                             // may not be true, for instance with multiple subs per slot.
    990                             // But is true at the moment.
    991                             sSlotIndexToSubId.put(slotIndex, subId);
    992                             int subIdCountMax = getActiveSubInfoCountMax();
    993                             int defaultSubId = getDefaultSubId();
    994                             if (DBG) {
    995                                 logdl("[addSubInfoRecord]"
    996                                         + " sSlotIndexToSubId.size=" + sSlotIndexToSubId.size()
    997                                         + " slotIndex=" + slotIndex + " subId=" + subId
    998                                         + " defaultSubId=" + defaultSubId + " simCount=" + subIdCountMax);
    999                             }
   1000 
   1001                             // Set the default sub if not set or if single sim device
   1002                             if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
   1003                                     || subIdCountMax == 1) {
   1004                                 setDefaultFallbackSubId(subId);
   1005                             }
   1006                             // If single sim device, set this subscription as the default for everything
   1007                             if (subIdCountMax == 1) {
   1008                                 if (DBG) {
   1009                                     logdl("[addSubInfoRecord] one sim set defaults to subId=" + subId);
   1010                                 }
   1011                                 setDefaultDataSubId(subId);
   1012                                 setDefaultSmsSubId(subId);
   1013                                 setDefaultVoiceSubId(subId);
   1014                             }
   1015                         } else {
   1016                             if (DBG) {
   1017                                 logdl("[addSubInfoRecord] currentSubId != null"
   1018                                         + " && currentSubId is valid, IGNORE");
   1019                             }
   1020                         }
   1021                         if (DBG) logdl("[addSubInfoRecord] hashmap(" + slotIndex + "," + subId + ")");
   1022                     } while (cursor.moveToNext());
   1023                 }
   1024             } finally {
   1025                 if (cursor != null) {
   1026                     cursor.close();
   1027                 }
   1028             }
   1029 
   1030             // Set Display name after sub id is set above so as to get valid simCarrierName
   1031             int subId = getSubIdUsingPhoneId(slotIndex);
   1032             if (!SubscriptionManager.isValidSubscriptionId(subId)) {
   1033                 if (DBG) {
   1034                     logdl("[addSubInfoRecord]- getSubId failed invalid subId = " + subId);
   1035                 }
   1036                 return -1;
   1037             }
   1038             if (setDisplayName) {
   1039                 String simCarrierName = mTelephonyManager.getSimOperatorName(subId);
   1040                 String nameToSet;
   1041 
   1042                 if (!TextUtils.isEmpty(simCarrierName)) {
   1043                     nameToSet = simCarrierName;
   1044                 } else {
   1045                     nameToSet = "CARD " + Integer.toString(slotIndex + 1);
   1046                 }
   1047 
   1048                 ContentValues value = new ContentValues();
   1049                 value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
   1050                 resolver.update(SubscriptionManager.CONTENT_URI, value,
   1051                         SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
   1052                                 "=" + Long.toString(subId), null);
   1053 
   1054                 // Refresh the Cache of Active Subscription Info List
   1055                 refreshCachedActiveSubscriptionInfoList();
   1056 
   1057                 if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
   1058             }
   1059 
   1060             // Once the records are loaded, notify DcTracker
   1061             sPhones[slotIndex].updateDataConnectionTracker();
   1062 
   1063             if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIndexToSubId.size());
   1064 
   1065         } finally {
   1066             Binder.restoreCallingIdentity(identity);
   1067         }
   1068         return 0;
   1069     }
   1070 
   1071     /**
   1072      * Insert an empty SubInfo record into the database.
   1073      *
   1074      * <p>NOTE: This is not accessible to external processes, so it does not need a permission
   1075      * check. It is only intended for use by {@link SubscriptionInfoUpdater}.
   1076      *
   1077      * <p>Precondition: No record exists with this iccId.
   1078      */
   1079     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
   1080     public Uri insertEmptySubInfoRecord(String iccId, int slotIndex) {
   1081         ContentResolver resolver = mContext.getContentResolver();
   1082         ContentValues value = new ContentValues();
   1083         value.put(SubscriptionManager.ICC_ID, iccId);
   1084         int color = getUnusedColor(mContext.getOpPackageName());
   1085         // default SIM color differs between slots
   1086         value.put(SubscriptionManager.COLOR, color);
   1087         value.put(SubscriptionManager.SIM_SLOT_INDEX, slotIndex);
   1088         value.put(SubscriptionManager.CARRIER_NAME, "");
   1089         UiccCard card = UiccController.getInstance().getUiccCardForPhone(slotIndex);
   1090         if (card != null) {
   1091             String cardId = card.getCardId();
   1092             if (cardId != null) {
   1093                 value.put(SubscriptionManager.CARD_ID, cardId);
   1094             } else {
   1095                 value.put(SubscriptionManager.CARD_ID, iccId);
   1096             }
   1097         } else {
   1098             value.put(SubscriptionManager.CARD_ID, iccId);
   1099         }
   1100 
   1101         Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);
   1102 
   1103         // Refresh the Cache of Active Subscription Info List
   1104         refreshCachedActiveSubscriptionInfoList();
   1105 
   1106         return uri;
   1107     }
   1108 
   1109     /**
   1110      * Generate and set carrier text based on input parameters
   1111      * @param showPlmn flag to indicate if plmn should be included in carrier text
   1112      * @param plmn plmn to be included in carrier text
   1113      * @param showSpn flag to indicate if spn should be included in carrier text
   1114      * @param spn spn to be included in carrier text
   1115      * @return true if carrier text is set, false otherwise
   1116      */
   1117     public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,
   1118                               String spn) {
   1119         synchronized (mLock) {
   1120             int subId = getSubIdUsingPhoneId(slotIndex);
   1121             if (mContext.getPackageManager().resolveContentProvider(
   1122                     SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||
   1123                     !SubscriptionManager.isValidSubscriptionId(subId)) {
   1124                 // No place to store this info. Notify registrants of the change anyway as they
   1125                 // might retrieve the SPN/PLMN text from the SST sticky broadcast.
   1126                 // TODO: This can be removed once SubscriptionController is not running on devices
   1127                 // that don't need it, such as TVs.
   1128                 if (DBG) logd("[setPlmnSpn] No valid subscription to store info");
   1129                 notifySubscriptionInfoChanged();
   1130                 return false;
   1131             }
   1132             String carrierText = "";
   1133             if (showPlmn) {
   1134                 carrierText = plmn;
   1135                 if (showSpn) {
   1136                     // Need to show both plmn and spn if both are not same.
   1137                     if(!Objects.equals(spn, plmn)) {
   1138                         String separator = mContext.getString(
   1139                                 com.android.internal.R.string.kg_text_message_separator).toString();
   1140                         carrierText = new StringBuilder().append(carrierText).append(separator)
   1141                                 .append(spn).toString();
   1142                     }
   1143                 }
   1144             } else if (showSpn) {
   1145                 carrierText = spn;
   1146             }
   1147             setCarrierText(carrierText, subId);
   1148             return true;
   1149         }
   1150     }
   1151 
   1152     /**
   1153      * Set carrier text by simInfo index
   1154      * @param text new carrier text
   1155      * @param subId the unique SubInfoRecord index in database
   1156      * @return the number of records updated
   1157      */
   1158     private int setCarrierText(String text, int subId) {
   1159         if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);
   1160 
   1161         enforceModifyPhoneState("setCarrierText");
   1162 
   1163         // Now that all security checks passes, perform the operation as ourselves.
   1164         final long identity = Binder.clearCallingIdentity();
   1165         try {
   1166             ContentValues value = new ContentValues(1);
   1167             value.put(SubscriptionManager.CARRIER_NAME, text);
   1168 
   1169             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
   1170                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
   1171                     Long.toString(subId), null);
   1172 
   1173             // Refresh the Cache of Active Subscription Info List
   1174             refreshCachedActiveSubscriptionInfoList();
   1175 
   1176             notifySubscriptionInfoChanged();
   1177 
   1178             return result;
   1179         } finally {
   1180             Binder.restoreCallingIdentity(identity);
   1181         }
   1182     }
   1183 
   1184     /**
   1185      * Set SIM color tint by simInfo index
   1186      * @param tint the tint color of the SIM
   1187      * @param subId the unique SubInfoRecord index in database
   1188      * @return the number of records updated
   1189      */
   1190     @Override
   1191     public int setIconTint(int tint, int subId) {
   1192         if (DBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
   1193 
   1194         enforceModifyPhoneState("setIconTint");
   1195 
   1196         // Now that all security checks passes, perform the operation as ourselves.
   1197         final long identity = Binder.clearCallingIdentity();
   1198         try {
   1199             validateSubId(subId);
   1200             ContentValues value = new ContentValues(1);
   1201             value.put(SubscriptionManager.COLOR, tint);
   1202             if (DBG) logd("[setIconTint]- tint:" + tint + " set");
   1203 
   1204             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
   1205                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
   1206                             Long.toString(subId), null);
   1207 
   1208             // Refresh the Cache of Active Subscription Info List
   1209             refreshCachedActiveSubscriptionInfoList();
   1210 
   1211             notifySubscriptionInfoChanged();
   1212 
   1213             return result;
   1214         } finally {
   1215             Binder.restoreCallingIdentity(identity);
   1216         }
   1217     }
   1218 
   1219     /**
   1220      * Set display name by simInfo index
   1221      * @param displayName the display name of SIM card
   1222      * @param subId the unique SubInfoRecord index in database
   1223      * @return the number of records updated
   1224      */
   1225     @Override
   1226     public int setDisplayName(String displayName, int subId) {
   1227         return setDisplayNameUsingSrc(displayName, subId, -1);
   1228     }
   1229 
   1230     /**
   1231      * Set display name by simInfo index with name source
   1232      * @param displayName the display name of SIM card
   1233      * @param subId the unique SubInfoRecord index in database
   1234      * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
   1235      *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED
   1236      * @return the number of records updated
   1237      */
   1238     @Override
   1239     public int setDisplayNameUsingSrc(String displayName, int subId, long nameSource) {
   1240         if (DBG) {
   1241             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
   1242                 + " nameSource:" + nameSource);
   1243         }
   1244 
   1245         enforceModifyPhoneState("setDisplayNameUsingSrc");
   1246 
   1247         // Now that all security checks passes, perform the operation as ourselves.
   1248         final long identity = Binder.clearCallingIdentity();
   1249         try {
   1250             validateSubId(subId);
   1251             String nameToSet;
   1252             if (displayName == null) {
   1253                 nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
   1254             } else {
   1255                 nameToSet = displayName;
   1256             }
   1257             ContentValues value = new ContentValues(1);
   1258             value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
   1259             if (nameSource >= SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE) {
   1260                 if (DBG) logd("Set nameSource=" + nameSource);
   1261                 value.put(SubscriptionManager.NAME_SOURCE, nameSource);
   1262             }
   1263             if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
   1264             // TODO(b/33075886): If this is an embedded subscription, we must also save the new name
   1265             // to the eSIM itself. Currently it will be blown away the next time the subscription
   1266             // list is updated.
   1267 
   1268             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
   1269                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
   1270                     Long.toString(subId), null);
   1271 
   1272             // Refresh the Cache of Active Subscription Info List
   1273             refreshCachedActiveSubscriptionInfoList();
   1274 
   1275             notifySubscriptionInfoChanged();
   1276 
   1277             return result;
   1278         } finally {
   1279             Binder.restoreCallingIdentity(identity);
   1280         }
   1281     }
   1282 
   1283     /**
   1284      * Set phone number by subId
   1285      * @param number the phone number of the SIM
   1286      * @param subId the unique SubInfoRecord index in database
   1287      * @return the number of records updated
   1288      */
   1289     @Override
   1290     public int setDisplayNumber(String number, int subId) {
   1291         if (DBG) logd("[setDisplayNumber]+ subId:" + subId);
   1292 
   1293         enforceModifyPhoneState("setDisplayNumber");
   1294 
   1295         // Now that all security checks passes, perform the operation as ourselves.
   1296         final long identity = Binder.clearCallingIdentity();
   1297         try {
   1298             validateSubId(subId);
   1299             int result;
   1300             int phoneId = getPhoneId(subId);
   1301 
   1302             if (number == null || phoneId < 0 ||
   1303                     phoneId >= mTelephonyManager.getPhoneCount()) {
   1304                 if (DBG) logd("[setDispalyNumber]- fail");
   1305                 return -1;
   1306             }
   1307             ContentValues value = new ContentValues(1);
   1308             value.put(SubscriptionManager.NUMBER, number);
   1309 
   1310             // This function had a call to update number on the SIM (Phone.setLine1Number()) but
   1311             // that was removed as there doesn't seem to be a reason for that. If it is added
   1312             // back, watch out for deadlocks.
   1313 
   1314             result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
   1315                     SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
   1316                             + "=" + Long.toString(subId), null);
   1317 
   1318             // Refresh the Cache of Active Subscription Info List
   1319             refreshCachedActiveSubscriptionInfoList();
   1320 
   1321             if (DBG) logd("[setDisplayNumber]- update result :" + result);
   1322             notifySubscriptionInfoChanged();
   1323 
   1324             return result;
   1325         } finally {
   1326             Binder.restoreCallingIdentity(identity);
   1327         }
   1328     }
   1329 
   1330     /**
   1331      * Set data roaming by simInfo index
   1332      * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
   1333      * @param subId the unique SubInfoRecord index in database
   1334      * @return the number of records updated
   1335      */
   1336     @Override
   1337     public int setDataRoaming(int roaming, int subId) {
   1338         if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
   1339 
   1340         enforceModifyPhoneState("setDataRoaming");
   1341 
   1342         // Now that all security checks passes, perform the operation as ourselves.
   1343         final long identity = Binder.clearCallingIdentity();
   1344         try {
   1345             validateSubId(subId);
   1346             if (roaming < 0) {
   1347                 if (DBG) logd("[setDataRoaming]- fail");
   1348                 return -1;
   1349             }
   1350             ContentValues value = new ContentValues(1);
   1351             value.put(SubscriptionManager.DATA_ROAMING, roaming);
   1352             if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");
   1353 
   1354             int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
   1355                     value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
   1356                     Long.toString(subId), null);
   1357 
   1358             // Refresh the Cache of Active Subscription Info List
   1359             refreshCachedActiveSubscriptionInfoList();
   1360 
   1361             notifySubscriptionInfoChanged();
   1362 
   1363             return result;
   1364         } finally {
   1365             Binder.restoreCallingIdentity(identity);
   1366         }
   1367     }
   1368 
   1369     /**
   1370      * Set MCC/MNC by subscription ID
   1371      * @param mccMnc MCC/MNC associated with the subscription
   1372      * @param subId the unique SubInfoRecord index in database
   1373      * @return the number of records updated
   1374      */
   1375     public int setMccMnc(String mccMnc, int subId) {
   1376         int mcc = 0;
   1377         int mnc = 0;
   1378         try {
   1379             mcc = Integer.parseInt(mccMnc.substring(0,3));
   1380             mnc = Integer.parseInt(mccMnc.substring(3));
   1381         } catch (NumberFormatException e) {
   1382             loge("[setMccMnc] - couldn't parse mcc/mnc: " + mccMnc);
   1383         }
   1384         if (DBG) logd("[setMccMnc]+ mcc/mnc:" + mcc + "/" + mnc + " subId:" + subId);
   1385         ContentValues value = new ContentValues(2);
   1386         value.put(SubscriptionManager.MCC, mcc);
   1387         value.put(SubscriptionManager.MNC, mnc);
   1388 
   1389         int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI, value,
   1390                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Long.toString(subId), null);
   1391 
   1392         // Refresh the Cache of Active Subscription Info List
   1393         refreshCachedActiveSubscriptionInfoList();
   1394 
   1395         notifySubscriptionInfoChanged();
   1396 
   1397         return result;
   1398     }
   1399 
   1400     @Override
   1401     public int getSlotIndex(int subId) {
   1402         if (VDBG) printStackTrace("[getSlotIndex] subId=" + subId);
   1403 
   1404         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1405             subId = getDefaultSubId();
   1406         }
   1407         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
   1408             if (DBG) logd("[getSlotIndex]- subId invalid");
   1409             return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
   1410         }
   1411 
   1412         int size = sSlotIndexToSubId.size();
   1413 
   1414         if (size == 0)
   1415         {
   1416             if (DBG) logd("[getSlotIndex]- size == 0, return SIM_NOT_INSERTED instead");
   1417             return SubscriptionManager.SIM_NOT_INSERTED;
   1418         }
   1419 
   1420         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
   1421             int sim = entry.getKey();
   1422             int sub = entry.getValue();
   1423 
   1424             if (subId == sub)
   1425             {
   1426                 if (VDBG) logv("[getSlotIndex]- return = " + sim);
   1427                 return sim;
   1428             }
   1429         }
   1430 
   1431         if (DBG) logd("[getSlotIndex]- return fail");
   1432         return SubscriptionManager.INVALID_SIM_SLOT_INDEX;
   1433     }
   1434 
   1435     /**
   1436      * Return the subId for specified slot Id.
   1437      * @deprecated
   1438      */
   1439     @Override
   1440     @Deprecated
   1441     public int[] getSubId(int slotIndex) {
   1442         if (VDBG) printStackTrace("[getSubId]+ slotIndex=" + slotIndex);
   1443 
   1444         // Map default slotIndex to the current default subId.
   1445         // TODO: Not used anywhere sp consider deleting as it's somewhat nebulous
   1446         // as a slot maybe used for multiple different type of "connections"
   1447         // such as: voice, data and sms. But we're doing the best we can and using
   1448         // getDefaultSubId which makes a best guess.
   1449         if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
   1450             slotIndex = getSlotIndex(getDefaultSubId());
   1451             if (VDBG) logd("[getSubId] map default slotIndex=" + slotIndex);
   1452         }
   1453 
   1454         // Check that we have a valid slotIndex
   1455         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
   1456             if (DBG) logd("[getSubId]- invalid slotIndex=" + slotIndex);
   1457             return null;
   1458         }
   1459 
   1460         // Check if we've got any SubscriptionInfo records using slotIndexToSubId as a surrogate.
   1461         int size = sSlotIndexToSubId.size();
   1462         if (size == 0) {
   1463             if (VDBG) {
   1464                 logd("[getSubId]- sSlotIndexToSubId.size == 0, return DummySubIds slotIndex="
   1465                         + slotIndex);
   1466             }
   1467             return getDummySubIds(slotIndex);
   1468         }
   1469 
   1470         // Create an array of subIds that are in this slot?
   1471         ArrayList<Integer> subIds = new ArrayList<Integer>();
   1472         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
   1473             int slot = entry.getKey();
   1474             int sub = entry.getValue();
   1475             if (slotIndex == slot) {
   1476                 subIds.add(sub);
   1477             }
   1478         }
   1479 
   1480         // Convert ArrayList to array
   1481         int numSubIds = subIds.size();
   1482         if (numSubIds > 0) {
   1483             int[] subIdArr = new int[numSubIds];
   1484             for (int i = 0; i < numSubIds; i++) {
   1485                 subIdArr[i] = subIds.get(i);
   1486             }
   1487             if (VDBG) logd("[getSubId]- subIdArr=" + subIdArr);
   1488             return subIdArr;
   1489         } else {
   1490             if (DBG) logd("[getSubId]- numSubIds == 0, return DummySubIds slotIndex=" + slotIndex);
   1491             return getDummySubIds(slotIndex);
   1492         }
   1493     }
   1494 
   1495     @Override
   1496     public int getPhoneId(int subId) {
   1497         if (VDBG) printStackTrace("[getPhoneId] subId=" + subId);
   1498         int phoneId;
   1499 
   1500         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1501             subId = getDefaultSubId();
   1502             if (DBG) logdl("[getPhoneId] asked for default subId=" + subId);
   1503         }
   1504 
   1505         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
   1506             if (VDBG) {
   1507                 logdl("[getPhoneId]- invalid subId return="
   1508                         + SubscriptionManager.INVALID_PHONE_INDEX);
   1509             }
   1510             return SubscriptionManager.INVALID_PHONE_INDEX;
   1511         }
   1512 
   1513         int size = sSlotIndexToSubId.size();
   1514         if (size == 0) {
   1515             phoneId = mDefaultPhoneId;
   1516             if (DBG) logdl("[getPhoneId]- no sims, returning default phoneId=" + phoneId);
   1517             return phoneId;
   1518         }
   1519 
   1520         // FIXME: Assumes phoneId == slotIndex
   1521         for (Entry<Integer, Integer> entry: sSlotIndexToSubId.entrySet()) {
   1522             int sim = entry.getKey();
   1523             int sub = entry.getValue();
   1524 
   1525             if (subId == sub) {
   1526                 if (VDBG) logdl("[getPhoneId]- found subId=" + subId + " phoneId=" + sim);
   1527                 return sim;
   1528             }
   1529         }
   1530 
   1531         phoneId = mDefaultPhoneId;
   1532         if (DBG) {
   1533             logdl("[getPhoneId]- subId=" + subId + " not found return default phoneId=" + phoneId);
   1534         }
   1535         return phoneId;
   1536 
   1537     }
   1538 
   1539     private int[] getDummySubIds(int slotIndex) {
   1540         // FIXME: Remove notion of Dummy SUBSCRIPTION_ID.
   1541         // I tested this returning null as no one appears to care,
   1542         // but no connection came up on sprout with two sims.
   1543         // We need to figure out why and hopefully remove DummySubsIds!!!
   1544         int numSubs = getActiveSubInfoCountMax();
   1545         if (numSubs > 0) {
   1546             int[] dummyValues = new int[numSubs];
   1547             for (int i = 0; i < numSubs; i++) {
   1548                 dummyValues[i] = SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE - slotIndex;
   1549             }
   1550             if (VDBG) {
   1551                 logd("getDummySubIds: slotIndex=" + slotIndex
   1552                     + " return " + numSubs + " DummySubIds with each subId=" + dummyValues[0]);
   1553             }
   1554             return dummyValues;
   1555         } else {
   1556             return null;
   1557         }
   1558     }
   1559 
   1560     /**
   1561      * @return the number of records cleared
   1562      */
   1563     @Override
   1564     public int clearSubInfo() {
   1565         enforceModifyPhoneState("clearSubInfo");
   1566 
   1567         // Now that all security checks passes, perform the operation as ourselves.
   1568         final long identity = Binder.clearCallingIdentity();
   1569         try {
   1570             int size = sSlotIndexToSubId.size();
   1571 
   1572             if (size == 0) {
   1573                 if (DBG) logdl("[clearSubInfo]- no simInfo size=" + size);
   1574                 return 0;
   1575             }
   1576 
   1577             sSlotIndexToSubId.clear();
   1578             if (DBG) logdl("[clearSubInfo]- clear size=" + size);
   1579             return size;
   1580         } finally {
   1581             Binder.restoreCallingIdentity(identity);
   1582         }
   1583     }
   1584 
   1585     private void logvl(String msg) {
   1586         logv(msg);
   1587         mLocalLog.log(msg);
   1588     }
   1589 
   1590     private void logv(String msg) {
   1591         Rlog.v(LOG_TAG, msg);
   1592     }
   1593 
   1594     private void logdl(String msg) {
   1595         logd(msg);
   1596         mLocalLog.log(msg);
   1597     }
   1598 
   1599     private static void slogd(String msg) {
   1600         Rlog.d(LOG_TAG, msg);
   1601     }
   1602 
   1603     private void logd(String msg) {
   1604         Rlog.d(LOG_TAG, msg);
   1605     }
   1606 
   1607     private void logel(String msg) {
   1608         loge(msg);
   1609         mLocalLog.log(msg);
   1610     }
   1611 
   1612     private void loge(String msg) {
   1613         Rlog.e(LOG_TAG, msg);
   1614     }
   1615 
   1616     @Override
   1617     public int getDefaultSubId() {
   1618         int subId;
   1619         boolean isVoiceCapable = mContext.getResources().getBoolean(
   1620                 com.android.internal.R.bool.config_voice_capable);
   1621         if (isVoiceCapable) {
   1622             subId = getDefaultVoiceSubId();
   1623             if (VDBG) logdl("[getDefaultSubId] isVoiceCapable subId=" + subId);
   1624         } else {
   1625             subId = getDefaultDataSubId();
   1626             if (VDBG) logdl("[getDefaultSubId] NOT VoiceCapable subId=" + subId);
   1627         }
   1628         if (!isActiveSubId(subId)) {
   1629             subId = mDefaultFallbackSubId;
   1630             if (VDBG) logdl("[getDefaultSubId] NOT active use fall back subId=" + subId);
   1631         }
   1632         if (VDBG) logv("[getDefaultSubId]- value = " + subId);
   1633         return subId;
   1634     }
   1635 
   1636     @Override
   1637     public void setDefaultSmsSubId(int subId) {
   1638         enforceModifyPhoneState("setDefaultSmsSubId");
   1639 
   1640         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1641             throw new RuntimeException("setDefaultSmsSubId called with DEFAULT_SUB_ID");
   1642         }
   1643         if (DBG) logdl("[setDefaultSmsSubId] subId=" + subId);
   1644         Settings.Global.putInt(mContext.getContentResolver(),
   1645                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION, subId);
   1646         broadcastDefaultSmsSubIdChanged(subId);
   1647     }
   1648 
   1649     private void broadcastDefaultSmsSubIdChanged(int subId) {
   1650         // Broadcast an Intent for default sms sub change
   1651         if (DBG) logdl("[broadcastDefaultSmsSubIdChanged] subId=" + subId);
   1652         Intent intent = new Intent(SubscriptionManager.ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED);
   1653         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
   1654                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
   1655         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
   1656         intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
   1657         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
   1658     }
   1659 
   1660     @Override
   1661     public int getDefaultSmsSubId() {
   1662         int subId = Settings.Global.getInt(mContext.getContentResolver(),
   1663                 Settings.Global.MULTI_SIM_SMS_SUBSCRIPTION,
   1664                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1665         if (VDBG) logd("[getDefaultSmsSubId] subId=" + subId);
   1666         return subId;
   1667     }
   1668 
   1669     @Override
   1670     public void setDefaultVoiceSubId(int subId) {
   1671         enforceModifyPhoneState("setDefaultVoiceSubId");
   1672 
   1673         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1674             throw new RuntimeException("setDefaultVoiceSubId called with DEFAULT_SUB_ID");
   1675         }
   1676         if (DBG) logdl("[setDefaultVoiceSubId] subId=" + subId);
   1677         Settings.Global.putInt(mContext.getContentResolver(),
   1678                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION, subId);
   1679         broadcastDefaultVoiceSubIdChanged(subId);
   1680     }
   1681 
   1682     /**
   1683      * Broadcast intent of ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
   1684      * @hide
   1685      */
   1686     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
   1687     public void broadcastDefaultVoiceSubIdChanged(int subId) {
   1688         // Broadcast an Intent for default voice sub change
   1689         if (DBG) logdl("[broadcastDefaultVoiceSubIdChanged] subId=" + subId);
   1690         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
   1691         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
   1692                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
   1693         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
   1694         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
   1695     }
   1696 
   1697     @Override
   1698     public int getDefaultVoiceSubId() {
   1699         int subId = Settings.Global.getInt(mContext.getContentResolver(),
   1700                 Settings.Global.MULTI_SIM_VOICE_CALL_SUBSCRIPTION,
   1701                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1702         if (VDBG) slogd("[getDefaultVoiceSubId] subId=" + subId);
   1703         return subId;
   1704     }
   1705 
   1706     @Override
   1707     public int getDefaultDataSubId() {
   1708         int subId = Settings.Global.getInt(mContext.getContentResolver(),
   1709                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION,
   1710                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1711         if (VDBG) logd("[getDefaultDataSubId] subId= " + subId);
   1712         return subId;
   1713     }
   1714 
   1715     @Override
   1716     public void setDefaultDataSubId(int subId) {
   1717         enforceModifyPhoneState("setDefaultDataSubId");
   1718 
   1719         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1720             throw new RuntimeException("setDefaultDataSubId called with DEFAULT_SUB_ID");
   1721         }
   1722 
   1723         ProxyController proxyController = ProxyController.getInstance();
   1724         int len = sPhones.length;
   1725         logdl("[setDefaultDataSubId] num phones=" + len + ", subId=" + subId);
   1726 
   1727         if (SubscriptionManager.isValidSubscriptionId(subId)) {
   1728             // Only re-map modems if the new default data sub is valid
   1729             RadioAccessFamily[] rafs = new RadioAccessFamily[len];
   1730             boolean atLeastOneMatch = false;
   1731             for (int phoneId = 0; phoneId < len; phoneId++) {
   1732                 Phone phone = sPhones[phoneId];
   1733                 int raf;
   1734                 int id = phone.getSubId();
   1735                 if (id == subId) {
   1736                     // TODO Handle the general case of N modems and M subscriptions.
   1737                     raf = proxyController.getMaxRafSupported();
   1738                     atLeastOneMatch = true;
   1739                 } else {
   1740                     // TODO Handle the general case of N modems and M subscriptions.
   1741                     raf = proxyController.getMinRafSupported();
   1742                 }
   1743                 logdl("[setDefaultDataSubId] phoneId=" + phoneId + " subId=" + id + " RAF=" + raf);
   1744                 rafs[phoneId] = new RadioAccessFamily(phoneId, raf);
   1745             }
   1746             if (atLeastOneMatch) {
   1747                 proxyController.setRadioCapability(rafs);
   1748             } else {
   1749                 if (DBG) logdl("[setDefaultDataSubId] no valid subId's found - not updating.");
   1750             }
   1751         }
   1752 
   1753         // FIXME is this still needed?
   1754         updateAllDataConnectionTrackers();
   1755 
   1756         Settings.Global.putInt(mContext.getContentResolver(),
   1757                 Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, subId);
   1758         broadcastDefaultDataSubIdChanged(subId);
   1759     }
   1760 
   1761     private void updateAllDataConnectionTrackers() {
   1762         // Tell Phone Proxies to update data connection tracker
   1763         int len = sPhones.length;
   1764         if (DBG) logdl("[updateAllDataConnectionTrackers] sPhones.length=" + len);
   1765         for (int phoneId = 0; phoneId < len; phoneId++) {
   1766             if (DBG) logdl("[updateAllDataConnectionTrackers] phoneId=" + phoneId);
   1767             sPhones[phoneId].updateDataConnectionTracker();
   1768         }
   1769     }
   1770 
   1771     private void broadcastDefaultDataSubIdChanged(int subId) {
   1772         // Broadcast an Intent for default data sub change
   1773         if (DBG) logdl("[broadcastDefaultDataSubIdChanged] subId=" + subId);
   1774         Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
   1775         intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
   1776                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
   1777         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
   1778         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
   1779     }
   1780 
   1781     /* Sets the default subscription. If only one sub is active that
   1782      * sub is set as default subId. If two or more  sub's are active
   1783      * the first sub is set as default subscription
   1784      */
   1785     private void setDefaultFallbackSubId(int subId) {
   1786         if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1787             throw new RuntimeException("setDefaultSubId called with DEFAULT_SUB_ID");
   1788         }
   1789         if (DBG) logdl("[setDefaultFallbackSubId] subId=" + subId);
   1790         if (SubscriptionManager.isValidSubscriptionId(subId)) {
   1791             int phoneId = getPhoneId(subId);
   1792             if (phoneId >= 0 && (phoneId < mTelephonyManager.getPhoneCount()
   1793                     || mTelephonyManager.getSimCount() == 1)) {
   1794                 if (DBG) logdl("[setDefaultFallbackSubId] set mDefaultFallbackSubId=" + subId);
   1795                 mDefaultFallbackSubId = subId;
   1796                 // Update MCC MNC device configuration information
   1797                 String defaultMccMnc = mTelephonyManager.getSimOperatorNumericForPhone(phoneId);
   1798                 MccTable.updateMccMncConfiguration(mContext, defaultMccMnc, false);
   1799 
   1800                 // Broadcast an Intent for default sub change
   1801                 Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_SUBSCRIPTION_CHANGED);
   1802                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
   1803                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
   1804                 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId, subId);
   1805                 if (DBG) {
   1806                     logdl("[setDefaultFallbackSubId] broadcast default subId changed phoneId=" +
   1807                             phoneId + " subId=" + subId);
   1808                 }
   1809                 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
   1810             } else {
   1811                 if (DBG) {
   1812                     logdl("[setDefaultFallbackSubId] not set invalid phoneId=" + phoneId
   1813                             + " subId=" + subId);
   1814                 }
   1815             }
   1816         }
   1817     }
   1818 
   1819     @Override
   1820     public void clearDefaultsForInactiveSubIds() {
   1821         enforceModifyPhoneState("clearDefaultsForInactiveSubIds");
   1822 
   1823         // Now that all security checks passes, perform the operation as ourselves.
   1824         final long identity = Binder.clearCallingIdentity();
   1825         try {
   1826             final List<SubscriptionInfo> records = getActiveSubscriptionInfoList(
   1827                     mContext.getOpPackageName());
   1828             if (DBG) logdl("[clearDefaultsForInactiveSubIds] records: " + records);
   1829             if (shouldDefaultBeCleared(records, getDefaultDataSubId())) {
   1830                 if (DBG) logd("[clearDefaultsForInactiveSubIds] clearing default data sub id");
   1831                 setDefaultDataSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1832             }
   1833             if (shouldDefaultBeCleared(records, getDefaultSmsSubId())) {
   1834                 if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default sms sub id");
   1835                 setDefaultSmsSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1836             }
   1837             if (shouldDefaultBeCleared(records, getDefaultVoiceSubId())) {
   1838                 if (DBG) logdl("[clearDefaultsForInactiveSubIds] clearing default voice sub id");
   1839                 setDefaultVoiceSubId(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
   1840             }
   1841         } finally {
   1842             Binder.restoreCallingIdentity(identity);
   1843         }
   1844     }
   1845 
   1846     private boolean shouldDefaultBeCleared(List<SubscriptionInfo> records, int subId) {
   1847         if (DBG) logdl("[shouldDefaultBeCleared: subId] " + subId);
   1848         if (records == null) {
   1849             if (DBG) logdl("[shouldDefaultBeCleared] return true no records subId=" + subId);
   1850             return true;
   1851         }
   1852         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
   1853             // If the subId parameter is not valid its already cleared so return false.
   1854             if (DBG) logdl("[shouldDefaultBeCleared] return false only one subId, subId=" + subId);
   1855             return false;
   1856         }
   1857         for (SubscriptionInfo record : records) {
   1858             int id = record.getSubscriptionId();
   1859             if (DBG) logdl("[shouldDefaultBeCleared] Record.id: " + id);
   1860             if (id == subId) {
   1861                 logdl("[shouldDefaultBeCleared] return false subId is active, subId=" + subId);
   1862                 return false;
   1863             }
   1864         }
   1865         if (DBG) logdl("[shouldDefaultBeCleared] return true not active subId=" + subId);
   1866         return true;
   1867     }
   1868 
   1869     // FIXME: We need we should not be assuming phoneId == slotIndex as it will not be true
   1870     // when there are multiple subscriptions per sim and probably for other reasons.
   1871     public int getSubIdUsingPhoneId(int phoneId) {
   1872         int[] subIds = getSubId(phoneId);
   1873         if (subIds == null || subIds.length == 0) {
   1874             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
   1875         }
   1876         return subIds[0];
   1877     }
   1878 
   1879     /** Must be public for access from instrumentation tests. */
   1880     @VisibleForTesting
   1881     public List<SubscriptionInfo> getSubInfoUsingSlotIndexPrivileged(int slotIndex,
   1882             boolean needCheck) {
   1883         if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]+ slotIndex:" + slotIndex);
   1884         if (slotIndex == SubscriptionManager.DEFAULT_SIM_SLOT_INDEX) {
   1885             slotIndex = getSlotIndex(getDefaultSubId());
   1886         }
   1887         if (!SubscriptionManager.isValidSlotIndex(slotIndex)) {
   1888             if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- invalid slotIndex");
   1889             return null;
   1890         }
   1891 
   1892         if (needCheck && !isSubInfoReady()) {
   1893             if (DBG) logd("[getSubInfoUsingSlotIndexPrivileged]- not ready");
   1894             return null;
   1895         }
   1896 
   1897         Cursor cursor = mContext.getContentResolver().query(SubscriptionManager.CONTENT_URI,
   1898                 null, SubscriptionManager.SIM_SLOT_INDEX + "=?",
   1899                 new String[]{String.valueOf(slotIndex)}, null);
   1900         ArrayList<SubscriptionInfo> subList = null;
   1901         try {
   1902             if (cursor != null) {
   1903                 while (cursor.moveToNext()) {
   1904                     SubscriptionInfo subInfo = getSubInfoRecord(cursor);
   1905                     if (subInfo != null) {
   1906                         if (subList == null) {
   1907                             subList = new ArrayList<SubscriptionInfo>();
   1908                         }
   1909                         subList.add(subInfo);
   1910                     }
   1911                 }
   1912             }
   1913         } finally {
   1914             if (cursor != null) {
   1915                 cursor.close();
   1916             }
   1917         }
   1918         if (DBG) logd("[getSubInfoUsingSlotIndex]- null info return");
   1919 
   1920         return subList;
   1921     }
   1922 
   1923     private void validateSubId(int subId) {
   1924         if (DBG) logd("validateSubId subId: " + subId);
   1925         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
   1926             throw new RuntimeException("Invalid sub id passed as parameter");
   1927         } else if (subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
   1928             throw new RuntimeException("Default sub id passed as parameter");
   1929         }
   1930     }
   1931 
   1932     public void updatePhonesAvailability(Phone[] phones) {
   1933         sPhones = phones;
   1934     }
   1935 
   1936     /**
   1937      * @return the list of subId's that are active, is never null but the length maybe 0.
   1938      */
   1939     @Override
   1940     public int[] getActiveSubIdList() {
   1941         Set<Entry<Integer, Integer>> simInfoSet = new HashSet<>(sSlotIndexToSubId.entrySet());
   1942 
   1943         int[] subIdArr = new int[simInfoSet.size()];
   1944         int i = 0;
   1945         for (Entry<Integer, Integer> entry: simInfoSet) {
   1946             int sub = entry.getValue();
   1947             subIdArr[i] = sub;
   1948             i++;
   1949         }
   1950 
   1951         if (VDBG) {
   1952             logdl("[getActiveSubIdList] simInfoSet=" + simInfoSet + " subIdArr.length="
   1953                     + subIdArr.length);
   1954         }
   1955         return subIdArr;
   1956     }
   1957 
   1958     @Override
   1959     public boolean isActiveSubId(int subId) {
   1960         boolean retVal = SubscriptionManager.isValidSubscriptionId(subId)
   1961                 && sSlotIndexToSubId.containsValue(subId);
   1962 
   1963         if (VDBG) logdl("[isActiveSubId]- " + retVal);
   1964         return retVal;
   1965     }
   1966 
   1967     /**
   1968      * Get the SIM state for the slot index
   1969      * @return SIM state as the ordinal of {@See IccCardConstants.State}
   1970      */
   1971     @Override
   1972     public int getSimStateForSlotIndex(int slotIndex) {
   1973         State simState;
   1974         String err;
   1975         if (slotIndex < 0) {
   1976             simState = IccCardConstants.State.UNKNOWN;
   1977             err = "invalid slotIndex";
   1978         } else {
   1979             Phone phone = PhoneFactory.getPhone(slotIndex);
   1980             if (phone == null) {
   1981                 simState = IccCardConstants.State.UNKNOWN;
   1982                 err = "phone == null";
   1983             } else {
   1984                 IccCard icc = phone.getIccCard();
   1985                 if (icc == null) {
   1986                     simState = IccCardConstants.State.UNKNOWN;
   1987                     err = "icc == null";
   1988                 } else {
   1989                     simState = icc.getState();
   1990                     err = "";
   1991                 }
   1992             }
   1993         }
   1994         if (VDBG) {
   1995             logd("getSimStateForSlotIndex: " + err + " simState=" + simState
   1996                     + " ordinal=" + simState.ordinal() + " slotIndex=" + slotIndex);
   1997         }
   1998         return simState.ordinal();
   1999     }
   2000 
   2001     /**
   2002      * Store properties associated with SubscriptionInfo in database
   2003      * @param subId Subscription Id of Subscription
   2004      * @param propKey Column name in database associated with SubscriptionInfo
   2005      * @param propValue Value to store in DB for particular subId & column name
   2006      * @hide
   2007      */
   2008     @Override
   2009     public void setSubscriptionProperty(int subId, String propKey, String propValue) {
   2010         enforceModifyPhoneState("setSubscriptionProperty");
   2011         final long token = Binder.clearCallingIdentity();
   2012         ContentResolver resolver = mContext.getContentResolver();
   2013 
   2014         setSubscriptionPropertyIntoContentResolver(subId, propKey, propValue, resolver);
   2015 
   2016         // Refresh the Cache of Active Subscription Info List
   2017         refreshCachedActiveSubscriptionInfoList();
   2018 
   2019         Binder.restoreCallingIdentity(token);
   2020     }
   2021 
   2022     private static void setSubscriptionPropertyIntoContentResolver(
   2023             int subId, String propKey, String propValue, ContentResolver resolver) {
   2024         ContentValues value = new ContentValues();
   2025         switch (propKey) {
   2026             case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
   2027             case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
   2028             case SubscriptionManager.CB_AMBER_ALERT:
   2029             case SubscriptionManager.CB_EMERGENCY_ALERT:
   2030             case SubscriptionManager.CB_ALERT_SOUND_DURATION:
   2031             case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
   2032             case SubscriptionManager.CB_ALERT_VIBRATE:
   2033             case SubscriptionManager.CB_ALERT_SPEECH:
   2034             case SubscriptionManager.CB_ETWS_TEST_ALERT:
   2035             case SubscriptionManager.CB_CHANNEL_50_ALERT:
   2036             case SubscriptionManager.CB_CMAS_TEST_ALERT:
   2037             case SubscriptionManager.CB_OPT_OUT_DIALOG:
   2038             case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
   2039             case SubscriptionManager.VT_IMS_ENABLED:
   2040             case SubscriptionManager.WFC_IMS_ENABLED:
   2041             case SubscriptionManager.WFC_IMS_MODE:
   2042             case SubscriptionManager.WFC_IMS_ROAMING_MODE:
   2043             case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
   2044                 value.put(propKey, Integer.parseInt(propValue));
   2045                 break;
   2046             default:
   2047                 if (DBG) slogd("Invalid column name");
   2048                 break;
   2049         }
   2050 
   2051         resolver.update(SubscriptionManager.CONTENT_URI, value,
   2052                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
   2053                         "=" + Integer.toString(subId), null);
   2054     }
   2055 
   2056     /**
   2057      * Store properties associated with SubscriptionInfo in database
   2058      * @param subId Subscription Id of Subscription
   2059      * @param propKey Column name in SubscriptionInfo database
   2060      * @return Value associated with subId and propKey column in database
   2061      * @hide
   2062      */
   2063     @Override
   2064     public String getSubscriptionProperty(int subId, String propKey, String callingPackage) {
   2065         if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState(
   2066                 mContext, subId, callingPackage, "getSubscriptionProperty")) {
   2067             return null;
   2068         }
   2069         String resultValue = null;
   2070         ContentResolver resolver = mContext.getContentResolver();
   2071         Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
   2072                 new String[]{propKey},
   2073                 SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=?",
   2074                 new String[]{subId + ""}, null);
   2075 
   2076         try {
   2077             if (cursor != null) {
   2078                 if (cursor.moveToFirst()) {
   2079                     switch (propKey) {
   2080                         case SubscriptionManager.CB_EXTREME_THREAT_ALERT:
   2081                         case SubscriptionManager.CB_SEVERE_THREAT_ALERT:
   2082                         case SubscriptionManager.CB_AMBER_ALERT:
   2083                         case SubscriptionManager.CB_EMERGENCY_ALERT:
   2084                         case SubscriptionManager.CB_ALERT_SOUND_DURATION:
   2085                         case SubscriptionManager.CB_ALERT_REMINDER_INTERVAL:
   2086                         case SubscriptionManager.CB_ALERT_VIBRATE:
   2087                         case SubscriptionManager.CB_ALERT_SPEECH:
   2088                         case SubscriptionManager.CB_ETWS_TEST_ALERT:
   2089                         case SubscriptionManager.CB_CHANNEL_50_ALERT:
   2090                         case SubscriptionManager.CB_CMAS_TEST_ALERT:
   2091                         case SubscriptionManager.CB_OPT_OUT_DIALOG:
   2092                         case SubscriptionManager.ENHANCED_4G_MODE_ENABLED:
   2093                         case SubscriptionManager.VT_IMS_ENABLED:
   2094                         case SubscriptionManager.WFC_IMS_ENABLED:
   2095                         case SubscriptionManager.WFC_IMS_MODE:
   2096                         case SubscriptionManager.WFC_IMS_ROAMING_MODE:
   2097                         case SubscriptionManager.WFC_IMS_ROAMING_ENABLED:
   2098                             resultValue = cursor.getInt(0) + "";
   2099                             break;
   2100                         default:
   2101                             if(DBG) logd("Invalid column name");
   2102                             break;
   2103                     }
   2104                 } else {
   2105                     if(DBG) logd("Valid row not present in db");
   2106                 }
   2107             } else {
   2108                 if(DBG) logd("Query failed");
   2109             }
   2110         } finally {
   2111             if (cursor != null) {
   2112                 cursor.close();
   2113             }
   2114         }
   2115         if (DBG) logd("getSubscriptionProperty Query value = " + resultValue);
   2116         return resultValue;
   2117     }
   2118 
   2119     private static void printStackTrace(String msg) {
   2120         RuntimeException re = new RuntimeException();
   2121         slogd("StackTrace - " + msg);
   2122         StackTraceElement[] st = re.getStackTrace();
   2123         boolean first = true;
   2124         for (StackTraceElement ste : st) {
   2125             if (first) {
   2126                 first = false;
   2127             } else {
   2128                 slogd(ste.toString());
   2129             }
   2130         }
   2131     }
   2132 
   2133     @Override
   2134     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   2135         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
   2136                 "Requires DUMP");
   2137         final long token = Binder.clearCallingIdentity();
   2138         try {
   2139             pw.println("SubscriptionController:");
   2140             pw.println(" mLastISubServiceRegTime=" + mLastISubServiceRegTime);
   2141             pw.println(" defaultSubId=" + getDefaultSubId());
   2142             pw.println(" defaultDataSubId=" + getDefaultDataSubId());
   2143             pw.println(" defaultVoiceSubId=" + getDefaultVoiceSubId());
   2144             pw.println(" defaultSmsSubId=" + getDefaultSmsSubId());
   2145 
   2146             pw.println(" defaultDataPhoneId=" + SubscriptionManager
   2147                     .from(mContext).getDefaultDataPhoneId());
   2148             pw.println(" defaultVoicePhoneId=" + SubscriptionManager.getDefaultVoicePhoneId());
   2149             pw.println(" defaultSmsPhoneId=" + SubscriptionManager
   2150                     .from(mContext).getDefaultSmsPhoneId());
   2151             pw.flush();
   2152 
   2153             for (Entry<Integer, Integer> entry : sSlotIndexToSubId.entrySet()) {
   2154                 pw.println(" sSlotIndexToSubId[" + entry.getKey() + "]: subId=" + entry.getValue());
   2155             }
   2156             pw.flush();
   2157             pw.println("++++++++++++++++++++++++++++++++");
   2158 
   2159             List<SubscriptionInfo> sirl = getActiveSubscriptionInfoList(
   2160                     mContext.getOpPackageName());
   2161             if (sirl != null) {
   2162                 pw.println(" ActiveSubInfoList:");
   2163                 for (SubscriptionInfo entry : sirl) {
   2164                     pw.println("  " + entry.toString());
   2165                 }
   2166             } else {
   2167                 pw.println(" ActiveSubInfoList: is null");
   2168             }
   2169             pw.flush();
   2170             pw.println("++++++++++++++++++++++++++++++++");
   2171 
   2172             sirl = getAllSubInfoList(mContext.getOpPackageName());
   2173             if (sirl != null) {
   2174                 pw.println(" AllSubInfoList:");
   2175                 for (SubscriptionInfo entry : sirl) {
   2176                     pw.println("  " + entry.toString());
   2177                 }
   2178             } else {
   2179                 pw.println(" AllSubInfoList: is null");
   2180             }
   2181             pw.flush();
   2182             pw.println("++++++++++++++++++++++++++++++++");
   2183 
   2184             mLocalLog.dump(fd, pw, args);
   2185             pw.flush();
   2186             pw.println("++++++++++++++++++++++++++++++++");
   2187             pw.flush();
   2188         } finally {
   2189             Binder.restoreCallingIdentity(token);
   2190         }
   2191     }
   2192 
   2193     /**
   2194      * Migrating Ims settings from global setting to subscription DB, if not already done.
   2195      */
   2196     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
   2197     public void migrateImsSettings() {
   2198         migrateImsSettingHelper(
   2199                 Settings.Global.ENHANCED_4G_MODE_ENABLED,
   2200                 SubscriptionManager.ENHANCED_4G_MODE_ENABLED);
   2201         migrateImsSettingHelper(
   2202                 Settings.Global.VT_IMS_ENABLED,
   2203                 SubscriptionManager.VT_IMS_ENABLED);
   2204         migrateImsSettingHelper(
   2205                 Settings.Global.WFC_IMS_ENABLED,
   2206                 SubscriptionManager.WFC_IMS_ENABLED);
   2207         migrateImsSettingHelper(
   2208                 Settings.Global.WFC_IMS_MODE,
   2209                 SubscriptionManager.WFC_IMS_MODE);
   2210         migrateImsSettingHelper(
   2211                 Settings.Global.WFC_IMS_ROAMING_MODE,
   2212                 SubscriptionManager.WFC_IMS_ROAMING_MODE);
   2213         migrateImsSettingHelper(
   2214                 Settings.Global.WFC_IMS_ROAMING_ENABLED,
   2215                 SubscriptionManager.WFC_IMS_ROAMING_ENABLED);
   2216     }
   2217 
   2218     private void migrateImsSettingHelper(String settingGlobal, String subscriptionProperty) {
   2219         ContentResolver resolver = mContext.getContentResolver();
   2220         int defaultSubId = getDefaultVoiceSubId();
   2221         try {
   2222             int prevSetting = Settings.Global.getInt(resolver, settingGlobal);
   2223 
   2224             if (prevSetting != DEPRECATED_SETTING) {
   2225                 // Write previous setting into Subscription DB.
   2226                 setSubscriptionPropertyIntoContentResolver(defaultSubId, subscriptionProperty,
   2227                         Integer.toString(prevSetting), resolver);
   2228                 // Write global setting value with DEPRECATED_SETTING making sure
   2229                 // migration only happen once.
   2230                 Settings.Global.putInt(resolver, settingGlobal, DEPRECATED_SETTING);
   2231             }
   2232         } catch (Settings.SettingNotFoundException e) {
   2233         }
   2234     }
   2235 }
   2236