| /* |
| * 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.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.telephony.Rlog; |
| import android.view.WindowManager; |
| |
| import com.android.internal.R; |
| import com.android.internal.telephony.CommandsInterface; |
| import com.android.internal.telephony.CommandsInterface.RadioState; |
| import com.android.internal.telephony.IccCardConstants; |
| import com.android.internal.telephony.uicc.IccCardStatus.CardState; |
| import com.android.internal.telephony.uicc.euicc.EuiccCard; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| |
| /** |
| * 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; |
| |
| private final Object mLock = new Object(); |
| private boolean mActive; |
| private CardState mCardState; |
| private Context mContext; |
| private CommandsInterface mCi; |
| private UiccCard mUiccCard; |
| private RadioState mLastRadioState = RadioState.RADIO_UNAVAILABLE; |
| private boolean mIsEuicc; |
| private String mIccId; |
| private AnswerToReset mAtr; |
| private int mPhoneId = INVALID_PHONE_ID; |
| |
| 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; |
| mCardState = null; |
| } |
| |
| /** |
| * Update slot. The main trigger for this is a change in the ICC Card status. |
| */ |
| public void update(CommandsInterface ci, IccCardStatus ics, int phoneId) { |
| if (DBG) log("cardStatus update: " + ics.toString()); |
| synchronized (mLock) { |
| CardState oldState = mCardState; |
| mCardState = ics.mCardState; |
| mIccId = ics.iccid; |
| mPhoneId = phoneId; |
| parseAtr(ics.atr); |
| mCi = ci; |
| |
| RadioState radioState = mCi.getRadioState(); |
| if (DBG) { |
| log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState); |
| } |
| |
| if (oldState != CardState.CARDSTATE_ABSENT |
| && mCardState == CardState.CARDSTATE_ABSENT) { |
| // No notifications while radio is off or we just powering up |
| if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) { |
| if (DBG) log("update: notify card removed"); |
| sendMessage(obtainMessage(EVENT_CARD_REMOVED, null)); |
| } |
| |
| UiccController.updateInternalIccState( |
| IccCardConstants.INTENT_VALUE_ICC_ABSENT, null, mPhoneId); |
| |
| // no card present in the slot now; dispose card and make mUiccCard null |
| if (mUiccCard != null) { |
| mUiccCard.dispose(); |
| mUiccCard = null; |
| } |
| // 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 != CardState.CARDSTATE_ABSENT) { |
| // No notifications while radio is off or we just powering up |
| if (radioState == RadioState.RADIO_ON && mLastRadioState == RadioState.RADIO_ON) { |
| 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) { |
| loge("update: mUiccCard != null when card was present; disposing it now"); |
| mUiccCard.dispose(); |
| } |
| |
| if (!mIsEuicc) { |
| mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId); |
| } else { |
| mUiccCard = new EuiccCard(mContext, mCi, ics, phoneId); |
| } |
| } else { |
| if (mUiccCard != null) { |
| mUiccCard.update(mContext, mCi, ics); |
| } |
| } |
| mLastRadioState = radioState; |
| } |
| } |
| |
| /** |
| * Update slot based on IccSlotStatus. |
| */ |
| public void update(CommandsInterface ci, IccSlotStatus iss) { |
| if (DBG) log("slotStatus update: " + iss.toString()); |
| synchronized (mLock) { |
| mCi = ci; |
| if (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_INACTIVE) { |
| if (mActive) { |
| mActive = false; |
| mLastRadioState = RadioState.RADIO_UNAVAILABLE; |
| mPhoneId = INVALID_PHONE_ID; |
| if (mUiccCard != null) mUiccCard.dispose(); |
| mUiccCard = null; |
| } |
| parseAtr(iss.atr); |
| mCardState = iss.cardState; |
| mIccId = iss.iccid; |
| } else if (!mActive && iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_ACTIVE) { |
| mActive = true; |
| parseAtr(iss.atr); |
| // todo - ignoring these fields for now; relying on sim state changed to update |
| // these |
| // iss.cardState; |
| // iss.iccid; |
| // iss.logicalSlotIndex; |
| } |
| } |
| } |
| |
| private void checkIsEuiccSupported() { |
| if (mAtr != null && mAtr.isEuiccSupported()) { |
| mIsEuicc = true; |
| } else { |
| mIsEuicc = false; |
| } |
| } |
| |
| private void parseAtr(String atr) { |
| mAtr = AnswerToReset.parseAtr(atr); |
| if (mAtr == null) { |
| return; |
| } |
| checkIsEuiccSupported(); |
| } |
| |
| public boolean isEuicc() { |
| return mIsEuicc; |
| } |
| |
| public boolean isActive() { |
| return mActive; |
| } |
| |
| public int getPhoneId() { |
| return mPhoneId; |
| } |
| |
| public String getIccId() { |
| if (mIccId != null) { |
| return mIccId; |
| } else if (mUiccCard != null) { |
| return mUiccCard.getIccId(); |
| } else { |
| return null; |
| } |
| } |
| |
| 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; |
| } |
| log("onIccSwap: isHotSwapSupported is false, prompt for rebooting"); |
| |
| promptForRestart(isAdded); |
| } |
| |
| private void promptForRestart(boolean isAdded) { |
| synchronized (mLock) { |
| final Resources res = mContext.getResources(); |
| final String dialogComponent = res.getString( |
| R.string.config_iccHotswapPromptForRestartDialogComponent); |
| if (dialogComponent != null) { |
| Intent intent = new Intent().setComponent(ComponentName.unflattenFromString( |
| dialogComponent)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
| .putExtra(EXTRA_ICC_CARD_ADDED, isAdded); |
| try { |
| mContext.startActivity(intent); |
| 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) { |
| if (mCardState == null) { |
| return CardState.CARDSTATE_ABSENT; |
| } else { |
| return mCardState; |
| } |
| } |
| } |
| |
| /** |
| * Returns the UiccCard in the slot. |
| */ |
| public UiccCard getUiccCard() { |
| synchronized (mLock) { |
| return mUiccCard; |
| } |
| } |
| |
| /** |
| * Processes radio state unavailable event |
| */ |
| public void onRadioStateUnavailable() { |
| if (mUiccCard != null) { |
| mUiccCard.dispose(); |
| } |
| mUiccCard = null; |
| |
| if (mPhoneId != INVALID_PHONE_ID) { |
| UiccController.updateInternalIccState( |
| IccCardConstants.INTENT_VALUE_ICC_UNKNOWN, null, mPhoneId); |
| } |
| |
| mCardState = CardState.CARDSTATE_ABSENT; |
| mLastRadioState = RadioState.RADIO_UNAVAILABLE; |
| } |
| |
| private void log(String msg) { |
| Rlog.d(TAG, msg); |
| } |
| |
| private void loge(String msg) { |
| Rlog.e(TAG, msg); |
| } |
| |
| /** |
| * Dump |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| pw.println("UiccSlot:"); |
| pw.println(" mCi=" + mCi); |
| pw.println(" mActive=" + mActive); |
| pw.println(" mLastRadioState=" + mLastRadioState); |
| pw.println(" mCardState=" + mCardState); |
| if (mUiccCard != null) { |
| pw.println(" mUiccCard=" + mUiccCard); |
| mUiccCard.dump(fd, pw, args); |
| } else { |
| pw.println(" mUiccCard=null"); |
| } |
| pw.println(); |
| pw.flush(); |
| pw.flush(); |
| } |
| } |