| /* |
| * 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 boolean DBG = true; |
| |
| 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) { |
| this(looper, context, phone, ci, |
| IPackageManager.Stub.asInterface(ServiceManager.getService("package"))); |
| } |
| |
| @VisibleForTesting public SubscriptionInfoUpdater( |
| Looper looper, Context context, Phone[] phone, |
| CommandsInterface[] ci, IPackageManager packageMgr) { |
| super(looper); |
| logd("Constructor invoked"); |
| |
| mContext = context; |
| mPhone = phone; |
| mSubscriptionManager = SubscriptionManager.from(mContext); |
| mEuiccManager = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE); |
| mPackageManager = packageMgr; |
| |
| 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: |
| broadcastSimStateChanged(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null); |
| broadcastSimCardStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); |
| broadcastSimApplicationStateChanged(msg.arg1, TelephonyManager.SIM_STATE_UNKNOWN); |
| updateSubscriptionCarrierId(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN); |
| updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_UNKNOWN); |
| break; |
| |
| case EVENT_SIM_IO_ERROR: |
| handleSimError(msg.arg1); |
| break; |
| |
| case EVENT_SIM_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); |
| updateSubscriptionCarrierId(msg.arg1, |
| IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); |
| updateCarrierServices(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED); |
| 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: |
| handleSimNotReady(msg.arg1); |
| // intentional fall through |
| |
| 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(); |
| } |
| |
| broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED, reason); |
| broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT); |
| broadcastSimApplicationStateChanged(slotId, getSimStateFromLockedReason(reason)); |
| updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED); |
| updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED); |
| } |
| |
| 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 handleSimNotReady(int slotId) { |
| logd("handleSimNotReady: slotId: " + slotId); |
| |
| IccCard iccCard = mPhone[slotId].getIccCard(); |
| if (iccCard.isEmptyProfile()) { |
| // ICC_NOT_READY is a terminal state for an eSIM on the boot profile. At this |
| // phase, the subscription list is accessible. Treating NOT_READY |
| // as equivalent to ABSENT, once the rest of the system can handle it. |
| mIccId[slotId] = ICCID_STRING_FOR_NO_SIM; |
| if (isAllIccIdQueryDone()) { |
| updateSubscriptionInfoByIccId(); |
| } |
| } |
| |
| broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_NOT_READY, |
| null); |
| broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_PRESENT); |
| broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY); |
| } |
| |
| 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); |
| } |
| SubscriptionController.getInstance().setMccMnc(operator, subId); |
| } else { |
| logd("EVENT_RECORDS_LOADED Operator name is null"); |
| } |
| |
| String iso = tm.getSimCountryIsoForPhone(slotId); |
| |
| if (!TextUtils.isEmpty(iso)) { |
| SubscriptionController.getInstance().setCountryIso(iso, subId); |
| } else { |
| logd("EVENT_RECORDS_LOADED sim country iso 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); |
| |
| /** |
| * The sim loading sequence will be |
| * 1. ACTION_SUBINFO_CONTENT_CHANGE happens through updateSubscriptionInfoByIccId() above. |
| * 2. ACTION_SIM_STATE_CHANGED/ACTION_SIM_CARD_STATE_CHANGED |
| * /ACTION_SIM_APPLICATION_STATE_CHANGED |
| * 3. ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED |
| * 4. ACTION_CARRIER_CONFIG_CHANGED |
| */ |
| broadcastSimStateChanged(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_LOADED, null); |
| broadcastSimCardStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_PRESENT); |
| broadcastSimApplicationStateChanged(loadedSlotId, TelephonyManager.SIM_STATE_LOADED); |
| updateSubscriptionCarrierId(loadedSlotId, IccCardConstants.INTENT_VALUE_ICC_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 updateSubscriptionCarrierId(int slotId, String simState) { |
| if (mPhone != null && mPhone[slotId] != null) { |
| mPhone[slotId].resolveSubscriptionCarrierId(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(); |
| } |
| broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT, null); |
| broadcastSimCardStateChanged(slotId, TelephonyManager.SIM_STATE_ABSENT); |
| broadcastSimApplicationStateChanged(slotId, TelephonyManager.SIM_STATE_NOT_READY); |
| updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT); |
| updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_ABSENT); |
| } |
| |
| 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(); |
| } |
| 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); |
| updateSubscriptionCarrierId(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); |
| updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR); |
| } |
| |
| /** |
| * 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<SubscriptionInfo> 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.getUriForSubscriptionId( |
| oldSubInfo.get(0).getSubscriptionId()), value, null, 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 |
| 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)) { |
| 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<SubscriptionInfo> 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.getUriForSubscriptionId( |
| temp.getSubscriptionId()), value, null, 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() { |
| if (DBG) logd("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<EuiccProfileInfo> 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(); |
| } |
| |
| if (DBG) logd("Get eUICC profile list of size " + embeddedProfiles.length); |
| |
| // 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<SubscriptionInfo> 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); |
| } |
| |
| if (DBG) { |
| logd("embeddedProfile " + embeddedProfile + " existing record " |
| + (index < 0 ? "not found" : "found")); |
| } |
| |
| ContentValues values = new ContentValues(); |
| values.put(SubscriptionManager.IS_EMBEDDED, 1); |
| List<UiccAccessRule> 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()) { |
| if (DBG) { |
| logd("Removing existing embedded subscriptions of size" |
| + existingSubscriptions.size()); |
| } |
| List<String> iccidsToRemove = new ArrayList<>(); |
| for (int i = 0; i < existingSubscriptions.size(); i++) { |
| SubscriptionInfo info = existingSubscriptions.get(i); |
| if (info.isEmbedded()) { |
| if (DBG) logd("Removing embedded subscription of IccId " + info.getIccId()); |
| 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(); |
| } |
| |
| if (DBG) logd("updateEmbeddedSubscriptions done hasChanges=" + hasChanges); |
| return hasChanges; |
| } |
| |
| private static int findSubscriptionInfoForIccid(List<SubscriptionInfo> 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); |
| } |
| } |