| /* |
| * Copyright 2017 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.uicc; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.app.AlertDialog; |
| import android.content.ActivityNotFoundException; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.UserHandle; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.IndentingPrintWriter; |
| import android.util.Log; |
| import android.view.WindowManager; |
| |
| import com.android.internal.R; |
| import com.android.internal.telephony.CommandsInterface; |
| import com.android.internal.telephony.IccCardConstants; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.PhoneFactory; |
| import com.android.internal.telephony.uicc.IccCardStatus.CardState; |
| import com.android.internal.telephony.uicc.IccSlotStatus.MultipleEnabledProfilesMode; |
| import com.android.internal.telephony.uicc.euicc.EuiccCard; |
| import com.android.internal.telephony.util.ArrayUtils; |
| import com.android.internal.telephony.util.TelephonyUtils; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| /** |
| * This class represents a physical slot on the device. |
| */ |
| public class UiccSlot extends Handler { |
| private static final String TAG = "UiccSlot"; |
| private static final boolean DBG = true; |
| |
| public static final String EXTRA_ICC_CARD_ADDED = |
| "com.android.internal.telephony.uicc.ICC_CARD_ADDED"; |
| public static final int INVALID_PHONE_ID = -1; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef( |
| prefix = {"VOLTAGE_CLASS_"}, |
| value = {VOLTAGE_CLASS_UNKNOWN, VOLTAGE_CLASS_A, VOLTAGE_CLASS_B, VOLTAGE_CLASS_C}) |
| public @interface VoltageClass {} |
| |
| public static final int VOLTAGE_CLASS_UNKNOWN = 0; |
| public static final int VOLTAGE_CLASS_A = 1; |
| public static final int VOLTAGE_CLASS_B = 2; |
| public static final int VOLTAGE_CLASS_C = 3; |
| |
| private final Object mLock = new Object(); |
| private boolean mActive; |
| private boolean mStateIsUnknown = true; |
| private Context mContext; |
| private UiccCard mUiccCard; |
| private boolean mIsEuicc; |
| private @VoltageClass int mMinimumVoltageClass; |
| private String mEid; |
| private AnswerToReset mAtr; |
| private boolean mIsRemovable; |
| private MultipleEnabledProfilesMode mSupportedMepMode; |
| |
| // Map each available portIdx to phoneId |
| private HashMap<Integer, Integer> mPortIdxToPhoneId = new HashMap<>(); |
| //Map each available portIdx with old radio state for state checking |
| private HashMap<Integer, Integer> mLastRadioState = new HashMap<>(); |
| // Store iccId of each port. |
| private HashMap<Integer, String> mIccIds = new HashMap<>(); |
| // IccCardStatus and IccSlotStatus events order is not guaranteed. Inorder to handle MEP mode, |
| // map each available portIdx with CardState for card state checking |
| private HashMap<Integer, CardState> mCardState = new HashMap<>(); |
| |
| private static final int EVENT_CARD_REMOVED = 13; |
| private static final int EVENT_CARD_ADDED = 14; |
| |
| public UiccSlot(Context c, boolean isActive) { |
| if (DBG) log("Creating"); |
| mContext = c; |
| mActive = isActive; |
| mSupportedMepMode = MultipleEnabledProfilesMode.NONE; |
| } |
| |
| /** |
| * Update slot. The main trigger for this is a change in the ICC Card status. |
| */ |
| public void update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex) { |
| synchronized (mLock) { |
| mPortIdxToPhoneId.put(ics.mSlotPortMapping.mPortIndex, phoneId); |
| CardState oldState = mCardState.get(ics.mSlotPortMapping.mPortIndex); |
| mCardState.put(ics.mSlotPortMapping.mPortIndex, ics.mCardState); |
| mIccIds.put(ics.mSlotPortMapping.mPortIndex, ics.iccid); |
| parseAtr(ics.atr); |
| mIsRemovable = isSlotRemovable(slotIndex); |
| // Update supported MEP mode in IccCardStatus if the CardState is present. |
| if (ics.mCardState.isCardPresent()) { |
| updateSupportedMepMode(ics.mSupportedMepMode); |
| } |
| |
| int radioState = ci.getRadioState(); |
| if (DBG) { |
| log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState); |
| } |
| |
| if (absentStateUpdateNeeded(oldState, ics.mSlotPortMapping.mPortIndex)) { |
| updateCardStateAbsent(ci.getRadioState(), phoneId, |
| ics.mSlotPortMapping.mPortIndex); |
| // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to |
| // create a new UiccCard instance in two scenarios: |
| // 1. mCardState is changing from ABSENT to non ABSENT. |
| // 2. The latest mCardState is not ABSENT, but there is no UiccCard instance. |
| } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT |
| || mUiccCard == null) && mCardState.get(ics.mSlotPortMapping.mPortIndex) |
| != CardState.CARDSTATE_ABSENT) { |
| // No notification while we are just powering up |
| if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE |
| && mLastRadioState.getOrDefault(ics.mSlotPortMapping.mPortIndex, |
| TelephonyManager.RADIO_POWER_UNAVAILABLE) |
| != TelephonyManager.RADIO_POWER_UNAVAILABLE) { |
| if (DBG) log("update: notify card added"); |
| sendMessage(obtainMessage(EVENT_CARD_ADDED, null)); |
| } |
| |
| // card is present in the slot now; create new mUiccCard |
| if (mUiccCard != null && (!mIsEuicc |
| || ArrayUtils.isEmpty(mUiccCard.getUiccPortList()))) { |
| loge("update: mUiccCard != null when card was present; disposing it now"); |
| mUiccCard.dispose(); |
| mUiccCard = null; |
| } |
| |
| if (!mIsEuicc) { |
| // Uicc does not support MEP, passing false by default. |
| mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock, |
| MultipleEnabledProfilesMode.NONE); |
| } else { |
| // The EID should be reported with the card status, but in case it's not we want |
| // to catch that here |
| if (TextUtils.isEmpty(ics.eid)) { |
| loge("update: eid is missing. ics.eid=" |
| + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, ics.eid)); |
| } |
| if (mUiccCard == null) { |
| mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock, |
| getSupportedMepMode()); |
| } else { |
| // In MEP case, UiccCard instance is already created, just call update API. |
| // UiccPort initialization is handled inside UiccCard. |
| mUiccCard.update(mContext, ci, ics, phoneId); |
| } |
| } |
| } else { |
| if (mUiccCard != null) { |
| mUiccCard.update(mContext, ci, ics, phoneId); |
| } |
| } |
| mLastRadioState.put(ics.mSlotPortMapping.mPortIndex, radioState); |
| } |
| } |
| |
| /** |
| * Update slot based on IccSlotStatus. |
| */ |
| public void update(CommandsInterface[] ci, IccSlotStatus iss, int slotIndex) { |
| synchronized (mLock) { |
| IccSimPortInfo[] simPortInfos = iss.mSimPortInfos; |
| parseAtr(iss.atr); |
| mEid = iss.eid; |
| mIsRemovable = isSlotRemovable(slotIndex); |
| |
| for (int i = 0; i < simPortInfos.length; i++) { |
| int phoneId = iss.mSimPortInfos[i].mLogicalSlotIndex; |
| CardState oldState = mCardState.get(i); |
| mCardState.put(i, iss.cardState); |
| mIccIds.put(i, simPortInfos[i].mIccId); |
| if (!iss.mSimPortInfos[i].mPortActive) { |
| // TODO: (b/79432584) evaluate whether should broadcast card state change |
| // even if it's inactive. |
| if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { |
| UiccController.getInstance().updateSimStateForInactivePort( |
| mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), |
| iss.mSimPortInfos[i].mIccId); |
| } else { |
| UiccController.updateInternalIccStateForInactivePort(mContext, |
| mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), |
| iss.mSimPortInfos[i].mIccId); |
| } |
| mLastRadioState.put(i, TelephonyManager.RADIO_POWER_UNAVAILABLE); |
| if (mUiccCard != null) { |
| // Dispose the port |
| mUiccCard.disposePort(i); |
| } |
| } else { |
| if (absentStateUpdateNeeded(oldState, i)) { |
| int radioState = SubscriptionManager.isValidPhoneId(phoneId) ? |
| ci[phoneId].getRadioState() : |
| TelephonyManager.RADIO_POWER_UNAVAILABLE; |
| updateCardStateAbsent(radioState, phoneId, i); |
| } |
| // TODO: (b/79432584) Create UiccCard or EuiccCard object here. |
| // Right now It's OK not creating it because Card status update will do it. |
| // But we should really make them symmetric. |
| } |
| } |
| // From MEP, Card can have multiple ports. So dispose UiccCard only when all the |
| // ports are inactive. |
| if (!hasActivePort(simPortInfos)) { |
| if (mActive) { |
| mActive = false; |
| nullifyUiccCard(true /* sim state is unknown */); |
| } |
| } else { |
| mActive = true; |
| } |
| mPortIdxToPhoneId.clear(); |
| for (int i = 0; i < simPortInfos.length; i++) { |
| // If port is not active, update with invalid phone id(i.e. -1) |
| mPortIdxToPhoneId.put(i, simPortInfos[i].mPortActive ? |
| simPortInfos[i].mLogicalSlotIndex : INVALID_PHONE_ID); |
| } |
| updateSupportedMepMode(iss.mSupportedMepMode); |
| // Since the MEP capability is related to supported MEP mode, thus need to |
| // update the flag after UiccCard creation. |
| if (mUiccCard != null) { |
| mUiccCard.updateSupportedMepMode(getSupportedMepMode()); |
| } |
| } |
| } |
| |
| private void updateSupportedMepMode(MultipleEnabledProfilesMode mode) { |
| mSupportedMepMode = mode; |
| // If SupportedMepMode is MultipleEnabledProfilesMode.NONE, validate ATR and |
| // num of ports to handle backward compatibility for < RADIO_HAL_VERSION_2_1. |
| if (mode == MultipleEnabledProfilesMode.NONE) { |
| // Even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per |
| // carrier restrictions, so checking the real number of ports reported from modem is |
| // necessary. |
| if (mPortIdxToPhoneId.size() > 1 |
| && mAtr != null && mAtr.isMultipleEnabledProfilesSupported()) { |
| // Set MEP-B mode in case if modem sends wrong mode even though supports MEP. |
| Log.i(TAG, "Modem does not send proper supported MEP mode or older HAL version"); |
| mSupportedMepMode = MultipleEnabledProfilesMode.MEP_B; |
| } |
| } |
| } |
| |
| private boolean hasActivePort(IccSimPortInfo[] simPortInfos) { |
| for (IccSimPortInfo simPortInfo : simPortInfos) { |
| if (simPortInfo.mPortActive) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /* Return valid phoneId if possible from the portIdx mapping*/ |
| private int getAnyValidPhoneId() { |
| for (int phoneId : mPortIdxToPhoneId.values()) { |
| if (SubscriptionManager.isValidPhoneId(phoneId)) { |
| return phoneId; |
| } |
| } |
| return INVALID_PHONE_ID; |
| } |
| |
| @NonNull |
| public int[] getPortList() { |
| synchronized (mLock) { |
| return mPortIdxToPhoneId.keySet().stream().mapToInt(Integer::valueOf).toArray(); |
| } |
| } |
| |
| /** Return whether the passing portIndex belong to this physical slot */ |
| public boolean isValidPortIndex(int portIndex) { |
| return mPortIdxToPhoneId.containsKey(portIndex); |
| } |
| |
| public int getPortIndexFromPhoneId(int phoneId) { |
| synchronized (mLock) { |
| for (Map.Entry<Integer, Integer> entry : mPortIdxToPhoneId.entrySet()) { |
| if (entry.getValue() == phoneId) { |
| return entry.getKey(); |
| } |
| } |
| return TelephonyManager.DEFAULT_PORT_INDEX; |
| } |
| } |
| |
| public int getPortIndexFromIccId(String iccId) { |
| synchronized (mLock) { |
| for (Map.Entry<Integer, String> entry : mIccIds.entrySet()) { |
| if (IccUtils.compareIgnoreTrailingFs(entry.getValue(), iccId)) { |
| return entry.getKey(); |
| } |
| } |
| // If iccId is not found, return invalid port index. |
| return TelephonyManager.INVALID_PORT_INDEX; |
| } |
| } |
| |
| public int getPhoneIdFromPortIndex(int portIndex) { |
| synchronized (mLock) { |
| return mPortIdxToPhoneId.getOrDefault(portIndex, INVALID_PHONE_ID); |
| } |
| } |
| |
| public boolean isPortActive(int portIdx) { |
| synchronized (mLock) { |
| return SubscriptionManager.isValidPhoneId( |
| mPortIdxToPhoneId.getOrDefault(portIdx, INVALID_PHONE_ID)); |
| } |
| } |
| |
| /* Returns true if multiple enabled profiles are supported */ |
| public boolean isMultipleEnabledProfileSupported() { |
| synchronized (mLock) { |
| return mSupportedMepMode.isMepMode(); |
| } |
| } |
| |
| private boolean absentStateUpdateNeeded(CardState oldState, int portIndex) { |
| return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null) |
| && mCardState.get(portIndex) == CardState.CARDSTATE_ABSENT; |
| } |
| |
| private void updateCardStateAbsent(int radioState, int phoneId, int portIndex) { |
| // No notification while we are just powering up |
| if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE |
| && mLastRadioState.getOrDefault( |
| portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE) |
| != TelephonyManager.RADIO_POWER_UNAVAILABLE) { |
| if (DBG) log("update: notify card removed"); |
| sendMessage(obtainMessage(EVENT_CARD_REMOVED, null)); |
| } |
| |
| if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { |
| UiccController.getInstance().updateSimState(phoneId, IccCardConstants.State.ABSENT, |
| null); |
| } else { |
| UiccController.updateInternalIccState(mContext, IccCardConstants.State.ABSENT, |
| null, phoneId); |
| } |
| // no card present in the slot now; dispose port and then card if needed. |
| disposeUiccCardIfNeeded(false /* sim state is not unknown */, portIndex); |
| mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE); |
| } |
| |
| // whenever we set mUiccCard to null, we lose the ability to differentiate between absent and |
| // unknown states. To mitigate this, we will us mStateIsUnknown to keep track. The sim is only |
| // unknown if we haven't heard from the radio or if the radio has become unavailable. |
| private void nullifyUiccCard(boolean stateUnknown) { |
| if (mUiccCard != null) { |
| mUiccCard.dispose(); |
| } |
| mStateIsUnknown = stateUnknown; |
| mUiccCard = null; |
| } |
| |
| private void disposeUiccCardIfNeeded(boolean isStateUnknown, int portIndex) { |
| // First dispose UiccPort corresponding to the portIndex |
| if (mUiccCard != null) { |
| mUiccCard.disposePort(portIndex); |
| if (ArrayUtils.isEmpty(mUiccCard.getUiccPortList())) { |
| // No UiccPort objects are found, safe to dispose the card |
| nullifyUiccCard(isStateUnknown); |
| } |
| } |
| } |
| |
| public boolean isStateUnknown() { |
| // CardState is not specific to any port index, use default port. |
| CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX); |
| if (cardState == null || cardState == CardState.CARDSTATE_ABSENT) { |
| // mStateIsUnknown is valid only in this scenario. |
| return mStateIsUnknown; |
| } |
| // if mUiccCard is null, assume the state to be UNKNOWN for now. |
| // The state may be known but since the actual card object is not available, |
| // it is safer to return UNKNOWN. |
| return mUiccCard == null; |
| } |
| |
| // Return true if a slot index is for removable UICCs or eUICCs |
| private boolean isSlotRemovable(int slotIndex) { |
| int[] euiccSlots = mContext.getResources() |
| .getIntArray(com.android.internal.R.array.non_removable_euicc_slots); |
| if (euiccSlots == null) { |
| return true; |
| } |
| for (int euiccSlot : euiccSlots) { |
| if (euiccSlot == slotIndex) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| private void checkIsEuiccSupported() { |
| if (mAtr == null) { |
| mIsEuicc = false; |
| return; |
| } |
| mIsEuicc = mAtr.isEuiccSupported(); |
| log(" checkIsEuiccSupported : " + mIsEuicc); |
| } |
| |
| private void checkMinimumVoltageClass() { |
| mMinimumVoltageClass = VOLTAGE_CLASS_UNKNOWN; |
| if (mAtr == null) { |
| return; |
| } |
| // Supported voltage classes are stored in the 5 least significant bits of the TA byte for |
| // global interface. |
| List<AnswerToReset.InterfaceByte> interfaceBytes = mAtr.getInterfaceBytes(); |
| for (int i = 0; i < interfaceBytes.size() - 1; i++) { |
| if (interfaceBytes.get(i).getTD() != null |
| && (interfaceBytes.get(i).getTD() & AnswerToReset.T_MASK) |
| == AnswerToReset.T_VALUE_FOR_GLOBAL_INTERFACE |
| && interfaceBytes.get(i + 1).getTA() != null) { |
| byte ta = interfaceBytes.get(i + 1).getTA(); |
| if ((ta & 0x01) != 0) { |
| mMinimumVoltageClass = VOLTAGE_CLASS_A; |
| } |
| if ((ta & 0x02) != 0) { |
| mMinimumVoltageClass = VOLTAGE_CLASS_B; |
| } |
| if ((ta & 0x04) != 0) { |
| mMinimumVoltageClass = VOLTAGE_CLASS_C; |
| } |
| return; |
| } |
| } |
| // Use default value - only class A |
| mMinimumVoltageClass = VOLTAGE_CLASS_A; |
| } |
| |
| private void parseAtr(String atr) { |
| mAtr = AnswerToReset.parseAtr(atr); |
| checkIsEuiccSupported(); |
| checkMinimumVoltageClass(); |
| } |
| |
| public boolean isEuicc() { |
| return mIsEuicc; |
| } |
| |
| @VoltageClass |
| public int getMinimumVoltageClass() { |
| return mMinimumVoltageClass; |
| } |
| |
| public boolean isActive() { |
| return mActive; |
| } |
| |
| public boolean isRemovable() { |
| return mIsRemovable; |
| } |
| |
| /** |
| * Returns the iccId specific to the port index. |
| * Always use {@link com.android.internal.telephony.uicc.UiccPort#getIccId} to get the iccId. |
| * Use this API to get the iccId of the inactive port only. |
| */ |
| public String getIccId(int portIdx) { |
| return mIccIds.get(portIdx); |
| } |
| |
| public String getEid() { |
| return mEid; |
| } |
| |
| public boolean isExtendedApduSupported() { |
| return (mAtr != null && mAtr.isExtendedApduSupported()); |
| } |
| |
| @Override |
| protected void finalize() { |
| if (DBG) log("UiccSlot finalized"); |
| } |
| |
| private void onIccSwap(boolean isAdded) { |
| |
| boolean isHotSwapSupported = mContext.getResources().getBoolean( |
| R.bool.config_hotswapCapable); |
| |
| if (isHotSwapSupported) { |
| log("onIccSwap: isHotSwapSupported is true, don't prompt for rebooting"); |
| return; |
| } |
| // As this check is for shutdown status check, use any phoneId |
| Phone phone = PhoneFactory.getPhone(getAnyValidPhoneId()); |
| if (phone != null && phone.isShuttingDown()) { |
| log("onIccSwap: already doing shutdown, no need to prompt"); |
| return; |
| } |
| |
| log("onIccSwap: isHotSwapSupported is false, prompt for rebooting"); |
| |
| promptForRestart(isAdded); |
| } |
| |
| private void promptForRestart(boolean isAdded) { |
| synchronized (mLock) { |
| final Resources res = mContext.getResources(); |
| final ComponentName dialogComponent = ComponentName.unflattenFromString( |
| res.getString(R.string.config_iccHotswapPromptForRestartDialogComponent)); |
| if (dialogComponent != null) { |
| Intent intent = new Intent().setComponent(dialogComponent) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
| .putExtra(EXTRA_ICC_CARD_ADDED, isAdded); |
| try { |
| mContext.startActivityAsUser(intent, UserHandle.CURRENT); |
| return; |
| } catch (ActivityNotFoundException e) { |
| loge("Unable to find ICC hotswap prompt for restart activity: " + e); |
| } |
| } |
| |
| // TODO: Here we assume the device can't handle SIM hot-swap |
| // and has to reboot. We may want to add a property, |
| // e.g. REBOOT_ON_SIM_SWAP, to indicate if modem support |
| // hot-swap. |
| DialogInterface.OnClickListener listener = null; |
| |
| |
| // TODO: SimRecords is not reset while SIM ABSENT (only reset while |
| // Radio_off_or_not_available). Have to reset in both both |
| // added or removed situation. |
| listener = new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| synchronized (mLock) { |
| if (which == DialogInterface.BUTTON_POSITIVE) { |
| if (DBG) log("Reboot due to SIM swap"); |
| PowerManager pm = (PowerManager) mContext |
| .getSystemService(Context.POWER_SERVICE); |
| pm.reboot("SIM is added."); |
| } |
| } |
| } |
| |
| }; |
| |
| Resources r = Resources.getSystem(); |
| |
| String title = (isAdded) ? r.getString(R.string.sim_added_title) : |
| r.getString(R.string.sim_removed_title); |
| String message = (isAdded) ? r.getString(R.string.sim_added_message) : |
| r.getString(R.string.sim_removed_message); |
| String buttonTxt = r.getString(R.string.sim_restart_button); |
| |
| AlertDialog dialog = new AlertDialog.Builder(mContext) |
| .setTitle(title) |
| .setMessage(message) |
| .setPositiveButton(buttonTxt, listener) |
| .create(); |
| dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); |
| dialog.show(); |
| } |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_CARD_REMOVED: |
| onIccSwap(false); |
| break; |
| case EVENT_CARD_ADDED: |
| onIccSwap(true); |
| break; |
| default: |
| loge("Unknown Event " + msg.what); |
| } |
| } |
| |
| /** |
| * Returns the state of the UiccCard in the slot. |
| * @return |
| */ |
| public CardState getCardState() { |
| synchronized (mLock) { |
| // CardState is not specific to any port index, use default port. |
| CardState cardState = mCardState.get(TelephonyManager.DEFAULT_PORT_INDEX); |
| return cardState == null ? CardState.CARDSTATE_ABSENT : cardState; |
| } |
| } |
| |
| /** |
| * Returns the UiccCard in the slot. |
| */ |
| public UiccCard getUiccCard() { |
| synchronized (mLock) { |
| return mUiccCard; |
| } |
| } |
| |
| /** |
| * Returns the supported MEP mode. |
| */ |
| public MultipleEnabledProfilesMode getSupportedMepMode() { |
| synchronized (mLock) { |
| return mSupportedMepMode; |
| } |
| } |
| /** |
| * Processes radio state unavailable event |
| */ |
| public void onRadioStateUnavailable(int phoneId) { |
| int portIndex = getPortIndexFromPhoneId(phoneId); |
| disposeUiccCardIfNeeded(true /* sim state is unknown */, portIndex); |
| |
| if (phoneId != INVALID_PHONE_ID) { |
| if (PhoneFactory.isSubscriptionManagerServiceEnabled()) { |
| UiccController.getInstance().updateSimState(phoneId, |
| IccCardConstants.State.UNKNOWN, null); |
| } else { |
| UiccController.updateInternalIccState( |
| mContext, IccCardConstants.State.UNKNOWN, null, phoneId); |
| } |
| } |
| mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE); |
| // Reset CardState |
| mCardState.put(portIndex, null); |
| } |
| |
| private void log(String msg) { |
| Rlog.d(TAG, msg); |
| } |
| |
| private void loge(String msg) { |
| Rlog.e(TAG, msg); |
| } |
| |
| private Map<Integer, String> getPrintableIccIds() { |
| Map<Integer, String> printableIccIds = mIccIds.entrySet().stream() |
| .collect(Collectors.toMap(Map.Entry::getKey, |
| e -> SubscriptionInfo.givePrintableIccid(e.getValue()))); |
| return printableIccIds; |
| } |
| |
| /** |
| * Dump |
| */ |
| public void dump(FileDescriptor fd, PrintWriter printWriter, String[] args) { |
| IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); |
| pw.println("mActive=" + mActive); |
| pw.println("mIsEuicc=" + mIsEuicc); |
| pw.println("isEuiccSupportsMultipleEnabledProfiles=" + isMultipleEnabledProfileSupported()); |
| pw.println("mIsRemovable=" + mIsRemovable); |
| pw.println("mLastRadioState=" + mLastRadioState); |
| pw.println("mIccIds=" + getPrintableIccIds()); |
| pw.println("mPortIdxToPhoneId=" + mPortIdxToPhoneId); |
| pw.println("mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)); |
| pw.println("mCardState=" + mCardState); |
| pw.println("mSupportedMepMode=" + mSupportedMepMode); |
| if (mUiccCard != null) { |
| pw.println("mUiccCard="); |
| mUiccCard.dump(fd, pw, args); |
| } else { |
| pw.println("mUiccCard=null"); |
| } |
| pw.println(); |
| pw.flush(); |
| } |
| |
| @NonNull |
| @Override |
| public String toString() { |
| return "[UiccSlot: mActive=" + mActive + ", mIccId=" + getPrintableIccIds() + ", mIsEuicc=" |
| + mIsEuicc + ", MEP=" + isMultipleEnabledProfileSupported() + ", mPortIdxToPhoneId=" |
| + mPortIdxToPhoneId + ", mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid) |
| + ", mCardState=" + mCardState + " mSupportedMepMode=" + mSupportedMepMode + "]"; |
| } |
| } |