/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony; import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.UserSwitchObserver; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.IPackageManager; import android.os.AsyncResult; import android.os.Handler; import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.preference.PreferenceManager; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.SettingNotFoundException; import android.service.euicc.EuiccProfileInfo; import android.service.euicc.EuiccService; import android.service.euicc.GetEuiccProfileInfoListResult; import android.telephony.CarrierConfigManager; import android.telephony.Rlog; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccAccessRule; import android.telephony.euicc.EuiccManager; import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.euicc.EuiccController; import com.android.internal.telephony.uicc.IccRecords; import com.android.internal.telephony.uicc.IccUtils; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** *@hide */ public class SubscriptionInfoUpdater extends Handler { private static final String LOG_TAG = "SubscriptionInfoUpdater"; private static final int PROJECT_SIM_NUM = TelephonyManager.getDefault().getPhoneCount(); private static final int EVENT_INVALID = -1; private static final int EVENT_GET_NETWORK_SELECTION_MODE_DONE = 2; private static final int EVENT_SIM_LOADED = 3; private static final int EVENT_SIM_ABSENT = 4; private static final int EVENT_SIM_LOCKED = 5; private static final int EVENT_SIM_IO_ERROR = 6; private static final int EVENT_SIM_UNKNOWN = 7; private static final int EVENT_SIM_RESTRICTED = 8; private static final int EVENT_SIM_NOT_READY = 9; private static final int EVENT_SIM_READY = 10; private static final int EVENT_SIM_IMSI = 11; private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 12; private static final String ICCID_STRING_FOR_NO_SIM = ""; /** * int[] sInsertSimState maintains all slots' SIM inserted status currently, * it may contain 4 kinds of values: * SIM_NOT_INSERT : no SIM inserted in slot i now * SIM_CHANGED : a valid SIM insert in slot i and is different SIM from last time * it will later become SIM_NEW or SIM_REPOSITION during update procedure * SIM_NOT_CHANGE : a valid SIM insert in slot i and is the same SIM as last time * SIM_NEW : a valid SIM insert in slot i and is a new SIM * SIM_REPOSITION : a valid SIM insert in slot i and is inserted in different slot last time * positive integer #: index to distinguish SIM cards with the same IccId */ public static final int SIM_NOT_CHANGE = 0; public static final int SIM_CHANGED = -1; public static final int SIM_NEW = -2; public static final int SIM_REPOSITION = -3; public static final int SIM_NOT_INSERT = -99; public static final int STATUS_NO_SIM_INSERTED = 0x00; public static final int STATUS_SIM1_INSERTED = 0x01; public static final int STATUS_SIM2_INSERTED = 0x02; public static final int STATUS_SIM3_INSERTED = 0x04; public static final int STATUS_SIM4_INSERTED = 0x08; // Key used to read/write the current IMSI. Updated on SIM_STATE_CHANGED - LOADED. public static final String CURR_SUBID = "curr_subid"; private static Phone[] mPhone; private static Context mContext = null; private static String mIccId[] = new String[PROJECT_SIM_NUM]; private static int[] mInsertSimState = new int[PROJECT_SIM_NUM]; private static int[] sSimCardState = new int[PROJECT_SIM_NUM]; private static int[] sSimApplicationState = new int[PROJECT_SIM_NUM]; private SubscriptionManager mSubscriptionManager = null; private EuiccManager mEuiccManager; private IPackageManager mPackageManager; // The current foreground user ID. private int mCurrentlyActiveUserId; private CarrierServiceBindHelper mCarrierServiceBindHelper; public SubscriptionInfoUpdater( Looper looper, Context context, Phone[] phone, CommandsInterface[] ci) { super(looper); logd("Constructor invoked"); mContext = context; mPhone = phone; mSubscriptionManager = SubscriptionManager.from(mContext); mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext); initializeCarrierApps(); } private void initializeCarrierApps() { // Initialize carrier apps: // -Now (on system startup) // -Whenever new carrier privilege rules might change (new SIM is loaded) // -Whenever we switch to a new user mCurrentlyActiveUserId = 0; try { ActivityManager.getService().registerUserSwitchObserver(new UserSwitchObserver() { @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { mCurrentlyActiveUserId = newUserId; CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), mPackageManager, TelephonyManager.getDefault(), mContext.getContentResolver(), mCurrentlyActiveUserId); if (reply != null) { try { reply.sendResult(null); } catch (RemoteException e) { } } } }, LOG_TAG); mCurrentlyActiveUserId = ActivityManager.getService().getCurrentUser().id; } catch (RemoteException e) { logd("Couldn't get current user ID; guessing it's 0: " + e.getMessage()); } CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), mPackageManager, TelephonyManager.getDefault(), mContext.getContentResolver(), mCurrentlyActiveUserId); } public void updateInternalIccState(String simStatus, String reason, int slotId) { logd("updateInternalIccState to simStatus " + simStatus + " reason " + reason + " slotId " + slotId); int message = internalIccStateToMessage(simStatus); if (message != EVENT_INVALID) { sendMessage(obtainMessage(message, slotId, -1, reason)); } } private int internalIccStateToMessage(String simStatus) { switch(simStatus) { case IccCardConstants.INTENT_VALUE_ICC_ABSENT: return EVENT_SIM_ABSENT; case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN: return EVENT_SIM_UNKNOWN; case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR: return EVENT_SIM_IO_ERROR; case IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED: return EVENT_SIM_RESTRICTED; case IccCardConstants.INTENT_VALUE_ICC_NOT_READY: return EVENT_SIM_NOT_READY; case IccCardConstants.INTENT_VALUE_ICC_LOCKED: return EVENT_SIM_LOCKED; case IccCardConstants.INTENT_VALUE_ICC_LOADED: return EVENT_SIM_LOADED; case IccCardConstants.INTENT_VALUE_ICC_READY: return EVENT_SIM_READY; case IccCardConstants.INTENT_VALUE_ICC_IMSI: return EVENT_SIM_IMSI; default: logd("Ignoring simStatus: " + simStatus); return EVENT_INVALID; } } private boolean isAllIccIdQueryDone() { for (int i = 0; i < PROJECT_SIM_NUM; i++) { if (mIccId[i] == null) { logd("Wait for SIM" + (i + 1) + " IccId"); return false; } } logd("All IccIds query complete"); return true; } @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_GET_NETWORK_SELECTION_MODE_DONE: { AsyncResult ar = (AsyncResult)msg.obj; Integer slotId = (Integer)ar.userObj; if (ar.exception == null && ar.result != null) { int[] modes = (int[])ar.result; if (modes[0] == 1) { // Manual mode. mPhone[slotId].setNetworkSelectionModeAutomatic(null); } } else { logd("EVENT_GET_NETWORK_SELECTION_MODE_DONE: error getting network mode."); } break; } case EVENT_SIM_LOADED: handleSimLoaded(msg.arg1); break; case EVENT_SIM_ABSENT: handleSimAbsent(msg.arg1); break; case EVENT_SIM_LOCKED: handleSimLocked(msg.arg1, (String) msg.obj); break; case EVENT_SIM_UNKNOWN: updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN); broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null); broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); break; case EVENT_SIM_IO_ERROR: handleSimError(msg.arg1); break; case EVENT_SIM_RESTRICTED: updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_CARD_RESTRICTED); broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY); break; case EVENT_SIM_READY: broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_READY, null); broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_PRESENT); broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY); break; case EVENT_SIM_IMSI: broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_IMSI, null); break; case EVENT_SIM_NOT_READY: broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_NOT_READY, null); broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_PRESENT); broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_NOT_READY); // intentional fall through // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this // phase, the subscription list is accessible. // TODO(b/64216093): Clean up this special case, likely by treating NOT_READY // as equivalent to ABSENT, once the rest of the system can handle it. Currently // this breaks SystemUI which shows a "No SIM" icon. case EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS: if (updateEmbeddedSubscriptions()) { SubscriptionController.getInstance().notifySubscriptionInfoChanged(); } if (msg.obj != null) { ((Runnable) msg.obj).run(); } break; default: logd("Unknown msg:" + msg.what); } } void requestEmbeddedSubscriptionInfoListRefresh(@Nullable Runnable callback) { sendMessage(obtainMessage(EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS, callback)); } private void handleSimLocked(int slotId, String reason) { if (mIccId[slotId] != null && mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) { logd("SIM" + (slotId + 1) + " hot plug in"); mIccId[slotId] = null; } String iccId = mIccId[slotId]; if (iccId == null) { IccCard iccCard = mPhone[slotId].getIccCard(); if (iccCard == null) { logd("handleSimLocked: IccCard null"); return; } IccRecords records = iccCard.getIccRecords(); if (records == null) { logd("handleSimLocked: IccRecords null"); return; } if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) { logd("handleSimLocked: IccID null"); return; } mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId()); } else { logd("NOT Querying IccId its already set sIccid[" + slotId + "]=" + iccId); } if (isAllIccIdQueryDone()) { updateSubscriptionInfoByIccId(); } updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED); broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason); broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT); broadcastSimApplicationStateChanged(slotId, getSimStateFromLockedReason(reason)); } private static int getSimStateFromLockedReason(String lockedReason) { switch (lockedReason) { case IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN: return TelephonyManager.SIM_STATE_PIN_REQUIRED; case IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK: return TelephonyManager.SIM_STATE_PUK_REQUIRED; case IccCardConstants.INTENT_VALUE_LOCKED_NETWORK: return TelephonyManager.SIM_STATE_NETWORK_LOCKED; case IccCardConstants.INTENT_VALUE_ABSENT_ON_PERM_DISABLED: return TelephonyManager.SIM_STATE_PERM_DISABLED; default: Rlog.e(LOG_TAG, "Unexpected SIM locked reason " + lockedReason); return TelephonyManager.SIM_STATE_UNKNOWN; } } private void handleSimLoaded(int slotId) { logd("handleSimLoaded: slotId: " + slotId); // The SIM should be loaded at this state, but it is possible in cases such as SIM being // removed or a refresh RESET that the IccRecords could be null. The right behavior is to // not broadcast the SIM loaded. int loadedSlotId = slotId; IccCard iccCard = mPhone[slotId].getIccCard(); if (iccCard == null) { // Possibly a race condition. logd("handleSimLoaded: IccCard null"); return; } IccRecords records = iccCard.getIccRecords(); if (records == null) { // Possibly a race condition. logd("handleSimLoaded: IccRecords null"); return; } if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) { logd("handleSimLoaded: IccID null"); return; } mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId()); if (isAllIccIdQueryDone()) { updateSubscriptionInfoByIccId(); int[] subIds = mSubscriptionManager.getActiveSubscriptionIdList(); for (int subId : subIds) { TelephonyManager tm = TelephonyManager.getDefault(); String operator = tm.getSimOperatorNumeric(subId); slotId = SubscriptionController.getInstance().getPhoneId(subId); if (!TextUtils.isEmpty(operator)) { if (subId == SubscriptionController.getInstance().getDefaultSubId()) { MccTable.updateMccMncConfiguration(mContext, operator, false); } SubscriptionController.getInstance().setMccMnc(operator, subId); } else { logd("EVENT_RECORDS_LOADED Operator name is null"); } String msisdn = tm.getLine1Number(subId); ContentResolver contentResolver = mContext.getContentResolver(); if (msisdn != null) { SubscriptionController.getInstance().setDisplayNumber(msisdn, subId); } SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId); String nameToSet; String simCarrierName = tm.getSimOperatorName(subId); if (subInfo != null && subInfo.getNameSource() != SubscriptionManager.NAME_SOURCE_USER_INPUT) { if (!TextUtils.isEmpty(simCarrierName)) { nameToSet = simCarrierName; } else { nameToSet = "CARD " + Integer.toString(slotId + 1); } logd("sim name = " + nameToSet); SubscriptionController.getInstance().setDisplayName(nameToSet, subId); } /* Update preferred network type and network selection mode on SIM change. * Storing last subId in SharedPreference for now to detect SIM change. */ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); int storedSubId = sp.getInt(CURR_SUBID + slotId, -1); if (storedSubId != subId) { int networkType = Settings.Global.getInt( mPhone[slotId].getContext().getContentResolver(), Settings.Global.PREFERRED_NETWORK_MODE + subId, -1 /* invalid network mode */); if (networkType == -1) { networkType = RILConstants.PREFERRED_NETWORK_MODE; try { networkType = TelephonyManager.getIntAtIndex( mContext.getContentResolver(), Settings.Global.PREFERRED_NETWORK_MODE, slotId); } catch (SettingNotFoundException retrySnfe) { Rlog.e(LOG_TAG, "Settings Exception Reading Value At Index for " + "Settings.Global.PREFERRED_NETWORK_MODE"); } Settings.Global.putInt( mPhone[slotId].getContext().getContentResolver(), Global.PREFERRED_NETWORK_MODE + subId, networkType); } // Set the modem network mode mPhone[slotId].setPreferredNetworkType(networkType, null); // Only support automatic selection mode on SIM change. mPhone[slotId].getNetworkSelectionMode( obtainMessage(EVENT_GET_NETWORK_SELECTION_MODE_DONE, new Integer(slotId))); // Update stored subId SharedPreferences.Editor editor = sp.edit(); editor.putInt(CURR_SUBID + slotId, subId); editor.apply(); } } } // Update set of enabled carrier apps now that the privilege rules may have changed. CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), mPackageManager, TelephonyManager.getDefault(), mContext.getContentResolver(), mCurrentlyActiveUserId); broadcastSimStateChanged(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null); broadcastSimCardStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_PRESENT); broadcastSimApplicationStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_LOADED); updateCarrierServices(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED); } private void updateCarrierServices(int slotId, String simState) { CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); configManager.updateConfigForPhoneId(slotId, simState); mCarrierServiceBindHelper.updateForPhoneId(slotId, simState); } private void handleSimAbsent(int slotId) { if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) { logd("SIM" + (slotId + 1) + " hot plug out"); } mIccId[slotId] = ICCID_STRING_FOR_NO_SIM; if (isAllIccIdQueryDone()) { updateSubscriptionInfoByIccId(); } updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT); broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null); broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_ABSENT); broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY); } private void handleSimError(int slotId) { if (mIccId[slotId] != null && !mIccId[slotId].equals(ICCID_STRING_FOR_NO_SIM)) { logd("SIM" + (slotId + 1) + " Error "); } mIccId[slotId] = ICCID_STRING_FOR_NO_SIM; if (isAllIccIdQueryDone()) { updateSubscriptionInfoByIccId(); } updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_CARD_IO_ERROR); broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY); } /** * TODO: Simplify more, as no one is interested in what happened * only what the current list contains. */ synchronized private void updateSubscriptionInfoByIccId() { logd("updateSubscriptionInfoByIccId:+ Start"); for (int i = 0; i < PROJECT_SIM_NUM; i++) { mInsertSimState[i] = SIM_NOT_CHANGE; } int insertedSimCount = PROJECT_SIM_NUM; for (int i = 0; i < PROJECT_SIM_NUM; i++) { if (ICCID_STRING_FOR_NO_SIM.equals(mIccId[i])) { insertedSimCount--; mInsertSimState[i] = SIM_NOT_INSERT; } } logd("insertedSimCount = " + insertedSimCount); // We only clear the slot-to-sub map when one/some SIM was removed. Note this is a // workaround for some race conditions that the empty map was accessed while we are // rebuilding the map. if (SubscriptionController.getInstance().getActiveSubIdList().length > insertedSimCount) { SubscriptionController.getInstance().clearSubInfo(); } int index = 0; for (int i = 0; i < PROJECT_SIM_NUM; i++) { if (mInsertSimState[i] == SIM_NOT_INSERT) { continue; } index = 2; for (int j = i + 1; j < PROJECT_SIM_NUM; j++) { if (mInsertSimState[j] == SIM_NOT_CHANGE && mIccId[i].equals(mIccId[j])) { mInsertSimState[i] = 1; mInsertSimState[j] = index; index++; } } } ContentResolver contentResolver = mContext.getContentResolver(); String[] oldIccId = new String[PROJECT_SIM_NUM]; String[] decIccId = new String[PROJECT_SIM_NUM]; for (int i = 0; i < PROJECT_SIM_NUM; i++) { oldIccId[i] = null; List oldSubInfo = SubscriptionController.getInstance() .getSubInfoUsingSlotIndexPrivileged(i, false); decIccId[i] = IccUtils.getDecimalSubstring(mIccId[i]); if (oldSubInfo != null && oldSubInfo.size() > 0) { oldIccId[i] = oldSubInfo.get(0).getIccId(); logd("updateSubscriptionInfoByIccId: oldSubId = " + oldSubInfo.get(0).getSubscriptionId()); if (mInsertSimState[i] == SIM_NOT_CHANGE && !(mIccId[i].equals(oldIccId[i]) || (decIccId[i] != null && decIccId[i].equals(oldIccId[i])))) { mInsertSimState[i] = SIM_CHANGED; } if (mInsertSimState[i] != SIM_NOT_CHANGE) { ContentValues value = new ContentValues(1); value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX); contentResolver.update(SubscriptionManager.CONTENT_URI, value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null); // refresh Cached Active Subscription Info List SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList(); } } else { if (mInsertSimState[i] == SIM_NOT_CHANGE) { // no SIM inserted last time, but there is one SIM inserted now mInsertSimState[i] = SIM_CHANGED; } oldIccId[i] = ICCID_STRING_FOR_NO_SIM; logd("updateSubscriptionInfoByIccId: No SIM in slot " + i + " last time"); } } for (int i = 0; i < PROJECT_SIM_NUM; i++) { logd("updateSubscriptionInfoByIccId: oldIccId[" + i + "] = " + oldIccId[i] + ", sIccId[" + i + "] = " + mIccId[i]); } //check if the inserted SIM is new SIM int nNewCardCount = 0; int nNewSimStatus = 0; for (int i = 0; i < PROJECT_SIM_NUM; i++) { if (mInsertSimState[i] == SIM_NOT_INSERT) { logd("updateSubscriptionInfoByIccId: No SIM inserted in slot " + i + " this time"); } else { if (mInsertSimState[i] > 0) { //some special SIMs may have the same IccIds, add suffix to distinguish them //FIXME: addSubInfoRecord can return an error. mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i] + Integer.toString(mInsertSimState[i]), i); logd("SUB" + (i + 1) + " has invalid IccId"); } else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ { logd("updateSubscriptionInfoByIccId: adding subscription info record: iccid: " + mIccId[i] + "slot: " + i); mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i); } if (isNewSim(mIccId[i], decIccId[i], oldIccId)) { nNewCardCount++; switch (i) { case PhoneConstants.SUB1: nNewSimStatus |= STATUS_SIM1_INSERTED; break; case PhoneConstants.SUB2: nNewSimStatus |= STATUS_SIM2_INSERTED; break; case PhoneConstants.SUB3: nNewSimStatus |= STATUS_SIM3_INSERTED; break; //case PhoneConstants.SUB3: // nNewSimStatus |= STATUS_SIM4_INSERTED; // break; } mInsertSimState[i] = SIM_NEW; } } } for (int i = 0; i < PROJECT_SIM_NUM; i++) { if (mInsertSimState[i] == SIM_CHANGED) { mInsertSimState[i] = SIM_REPOSITION; } logd("updateSubscriptionInfoByIccId: sInsertSimState[" + i + "] = " + mInsertSimState[i]); } List subInfos = mSubscriptionManager.getActiveSubscriptionInfoList(); int nSubCount = (subInfos == null) ? 0 : subInfos.size(); logd("updateSubscriptionInfoByIccId: nSubCount = " + nSubCount); for (int i=0; i < nSubCount; i++) { SubscriptionInfo temp = subInfos.get(i); String msisdn = TelephonyManager.getDefault().getLine1Number( temp.getSubscriptionId()); if (msisdn != null) { ContentValues value = new ContentValues(1); value.put(SubscriptionManager.NUMBER, msisdn); contentResolver.update(SubscriptionManager.CONTENT_URI, value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" + Integer.toString(temp.getSubscriptionId()), null); // refresh Cached Active Subscription Info List SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList(); } } // Ensure the modems are mapped correctly mSubscriptionManager.setDefaultDataSubId( mSubscriptionManager.getDefaultDataSubscriptionId()); // No need to check return value here as we notify for the above changes anyway. updateEmbeddedSubscriptions(); SubscriptionController.getInstance().notifySubscriptionInfoChanged(); logd("updateSubscriptionInfoByIccId:- SubscriptionInfo update complete"); } /** * Update the cached list of embedded subscriptions. * * @return true if changes may have been made. This is not a guarantee that changes were made, * but notifications about subscription changes may be skipped if this returns false as an * optimization to avoid spurious notifications. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public boolean updateEmbeddedSubscriptions() { // Do nothing if eUICCs are disabled. (Previous entries may remain in the cache, but they // are filtered out of list calls as long as EuiccManager.isEnabled returns false). if (!mEuiccManager.isEnabled()) { return false; } GetEuiccProfileInfoListResult result = EuiccController.get().blockingGetEuiccProfileInfoList(); if (result == null) { // IPC to the eUICC controller failed. return false; } final EuiccProfileInfo[] embeddedProfiles; if (result.getResult() == EuiccService.RESULT_OK) { List list = result.getProfiles(); if (list == null || list.size() == 0) { embeddedProfiles = new EuiccProfileInfo[0]; } else { embeddedProfiles = list.toArray(new EuiccProfileInfo[list.size()]); } } else { logd("updatedEmbeddedSubscriptions: error " + result.getResult() + " listing profiles"); // If there's an error listing profiles, treat it equivalently to a successful // listing which returned no profiles under the assumption that none are currently // accessible. embeddedProfiles = new EuiccProfileInfo[0]; } final boolean isRemovable = result.getIsRemovable(); final String[] embeddedIccids = new String[embeddedProfiles.length]; for (int i = 0; i < embeddedProfiles.length; i++) { embeddedIccids[i] = embeddedProfiles[i].getIccid(); } // Note that this only tracks whether we make any writes to the DB. It's possible this will // be set to true for an update even when the row contents remain exactly unchanged from // before, since we don't compare against the previous value. Since this is only intended to // avoid some spurious broadcasts (particularly for users who don't use eSIM at all), this // is fine. boolean hasChanges = false; // Update or insert records for all embedded subscriptions (except non-removable ones if the // current eUICC is non-removable, since we assume these are still accessible though not // returned by the eUICC controller). List existingSubscriptions = SubscriptionController.getInstance() .getSubscriptionInfoListForEmbeddedSubscriptionUpdate(embeddedIccids, isRemovable); ContentResolver contentResolver = mContext.getContentResolver(); for (EuiccProfileInfo embeddedProfile : embeddedProfiles) { int index = findSubscriptionInfoForIccid(existingSubscriptions, embeddedProfile.getIccid()); if (index < 0) { // No existing entry for this ICCID; create an empty one. SubscriptionController.getInstance().insertEmptySubInfoRecord( embeddedProfile.getIccid(), SubscriptionManager.SIM_NOT_INSERTED); } else { existingSubscriptions.remove(index); } ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 1); List ruleList = embeddedProfile.getUiccAccessRules(); boolean isRuleListEmpty = false; if (ruleList == null || ruleList.size() == 0) { isRuleListEmpty = true; } values.put(SubscriptionManager.ACCESS_RULES, isRuleListEmpty ? null : UiccAccessRule.encodeRules( ruleList.toArray(new UiccAccessRule[ruleList.size()]))); values.put(SubscriptionManager.IS_REMOVABLE, isRemovable); values.put(SubscriptionManager.DISPLAY_NAME, embeddedProfile.getNickname()); values.put(SubscriptionManager.NAME_SOURCE, SubscriptionManager.NAME_SOURCE_USER_INPUT); hasChanges = true; contentResolver.update(SubscriptionManager.CONTENT_URI, values, SubscriptionManager.ICC_ID + "=\"" + embeddedProfile.getIccid() + "\"", null); // refresh Cached Active Subscription Info List SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList(); } // Remove all remaining subscriptions which have embedded = true. We set embedded to false // to ensure they are not returned in the list of embedded subscriptions (but keep them // around in case the subscription is added back later, which is equivalent to a removable // SIM being removed and reinserted). if (!existingSubscriptions.isEmpty()) { List iccidsToRemove = new ArrayList<>(); for (int i = 0; i < existingSubscriptions.size(); i++) { SubscriptionInfo info = existingSubscriptions.get(i); if (info.isEmbedded()) { iccidsToRemove.add("\"" + info.getIccId() + "\""); } } String whereClause = SubscriptionManager.ICC_ID + " IN (" + TextUtils.join(",", iccidsToRemove) + ")"; ContentValues values = new ContentValues(); values.put(SubscriptionManager.IS_EMBEDDED, 0); hasChanges = true; contentResolver.update(SubscriptionManager.CONTENT_URI, values, whereClause, null); // refresh Cached Active Subscription Info List SubscriptionController.getInstance().refreshCachedActiveSubscriptionInfoList(); } return hasChanges; } private static int findSubscriptionInfoForIccid(List list, String iccid) { for (int i = 0; i < list.size(); i++) { if (TextUtils.equals(iccid, list.get(i).getIccId())) { return i; } } return -1; } private boolean isNewSim(String iccId, String decIccId, String[] oldIccId) { boolean newSim = true; for(int i = 0; i < PROJECT_SIM_NUM; i++) { if(iccId.equals(oldIccId[i])) { newSim = false; break; } else if (decIccId != null && decIccId.equals(oldIccId[i])) { newSim = false; break; } } logd("newSim = " + newSim); return newSim; } private void broadcastSimStateChanged(int slotId, String state, String reason) { Intent i = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); // TODO - we'd like this intent to have a single snapshot of all sim state, // but until then this should not use REPLACE_PENDING or we may lose // information // i.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING // | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); i.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone"); i.putExtra(IccCardConstants.INTENT_KEY_ICC_STATE, state); i.putExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON, reason); SubscriptionManager.putPhoneIdAndSubIdExtra(i, slotId); logd("Broadcasting intent ACTION_SIM_STATE_CHANGED " + state + " reason " + reason + " for mCardIndex: " + slotId); IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId); } private void broadcastSimCardStateChanged(int phoneId, int state) { if (state != sSimCardState[phoneId]) { sSimCardState[phoneId] = state; Intent i = new Intent(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state); SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId); logd("Broadcasting intent ACTION_SIM_CARD_STATE_CHANGED " + simStateString(state) + " for phone: " + phoneId); mContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); } } private void broadcastSimApplicationStateChanged(int phoneId, int state) { // Broadcast if the state has changed, except if old state was UNKNOWN and new is NOT_READY, // because that's the initial state and a broadcast should be sent only on a transition // after SIM is PRESENT if (!(state == sSimApplicationState[phoneId] || (state == TelephonyManager.SIM_STATE_NOT_READY && sSimApplicationState[phoneId] == TelephonyManager.SIM_STATE_UNKNOWN))) { sSimApplicationState[phoneId] = state; Intent i = new Intent(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED); i.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); i.putExtra(TelephonyManager.EXTRA_SIM_STATE, state); SubscriptionManager.putPhoneIdAndSubIdExtra(i, phoneId); logd("Broadcasting intent ACTION_SIM_APPLICATION_STATE_CHANGED " + simStateString(state) + " for phone: " + phoneId); mContext.sendBroadcast(i, Manifest.permission.READ_PRIVILEGED_PHONE_STATE); } } private static String simStateString(int state) { switch (state) { case TelephonyManager.SIM_STATE_UNKNOWN: return "UNKNOWN"; case TelephonyManager.SIM_STATE_ABSENT: return "ABSENT"; case TelephonyManager.SIM_STATE_PIN_REQUIRED: return "PIN_REQUIRED"; case TelephonyManager.SIM_STATE_PUK_REQUIRED: return "PUK_REQUIRED"; case TelephonyManager.SIM_STATE_NETWORK_LOCKED: return "NETWORK_LOCKED"; case TelephonyManager.SIM_STATE_READY: return "READY"; case TelephonyManager.SIM_STATE_NOT_READY: return "NOT_READY"; case TelephonyManager.SIM_STATE_PERM_DISABLED: return "PERM_DISABLED"; case TelephonyManager.SIM_STATE_CARD_IO_ERROR: return "CARD_IO_ERROR"; case TelephonyManager.SIM_STATE_CARD_RESTRICTED: return "CARD_RESTRICTED"; case TelephonyManager.SIM_STATE_LOADED: return "LOADED"; case TelephonyManager.SIM_STATE_PRESENT: return "PRESENT"; default: return "INVALID"; } } private void logd(String message) { Rlog.d(LOG_TAG, message); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("SubscriptionInfoUpdater:"); mCarrierServiceBindHelper.dump(fd, pw, args); } }