1 /* 2 * Copyright 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.uicc; 18 19 import android.app.AlertDialog; 20 import android.content.ActivityNotFoundException; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.DialogInterface; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.os.Handler; 27 import android.os.Message; 28 import android.os.PowerManager; 29 import android.telephony.Rlog; 30 import android.telephony.TelephonyManager; 31 import android.text.TextUtils; 32 import android.view.WindowManager; 33 34 import com.android.internal.R; 35 import com.android.internal.telephony.CommandsInterface; 36 import com.android.internal.telephony.IccCardConstants; 37 import com.android.internal.telephony.uicc.IccCardStatus.CardState; 38 import com.android.internal.telephony.uicc.euicc.EuiccCard; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 43 /** 44 * This class represents a physical slot on the device. 45 */ 46 public class UiccSlot extends Handler { 47 private static final String TAG = "UiccSlot"; 48 private static final boolean DBG = true; 49 50 public static final String EXTRA_ICC_CARD_ADDED = 51 "com.android.internal.telephony.uicc.ICC_CARD_ADDED"; 52 public static final int INVALID_PHONE_ID = -1; 53 54 private final Object mLock = new Object(); 55 private boolean mActive; 56 private boolean mStateIsUnknown = true; 57 private CardState mCardState; 58 private Context mContext; 59 private CommandsInterface mCi; 60 private UiccCard mUiccCard; 61 private int mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE; 62 private boolean mIsEuicc; 63 private String mIccId; 64 private AnswerToReset mAtr; 65 private int mPhoneId = INVALID_PHONE_ID; 66 private boolean mIsRemovable; 67 68 private static final int EVENT_CARD_REMOVED = 13; 69 private static final int EVENT_CARD_ADDED = 14; 70 UiccSlot(Context c, boolean isActive)71 public UiccSlot(Context c, boolean isActive) { 72 if (DBG) log("Creating"); 73 mContext = c; 74 mActive = isActive; 75 mCardState = null; 76 } 77 78 /** 79 * Update slot. The main trigger for this is a change in the ICC Card status. 80 */ update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex)81 public void update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex) { 82 if (DBG) log("cardStatus update: " + ics.toString()); 83 synchronized (mLock) { 84 CardState oldState = mCardState; 85 mCardState = ics.mCardState; 86 mIccId = ics.iccid; 87 mPhoneId = phoneId; 88 parseAtr(ics.atr); 89 mCi = ci; 90 mIsRemovable = isSlotRemovable(slotIndex); 91 92 int radioState = mCi.getRadioState(); 93 if (DBG) { 94 log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState); 95 } 96 97 if (absentStateUpdateNeeded(oldState)) { 98 updateCardStateAbsent(); 99 // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to 100 // create a new UiccCard instance in two scenarios: 101 // 1. mCardState is changing from ABSENT to non ABSENT. 102 // 2. The latest mCardState is not ABSENT, but there is no UiccCard instance. 103 } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT 104 || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) { 105 // No notifications while radio is off or we just powering up 106 if (radioState == TelephonyManager.RADIO_POWER_ON 107 && mLastRadioState == TelephonyManager.RADIO_POWER_ON) { 108 if (DBG) log("update: notify card added"); 109 sendMessage(obtainMessage(EVENT_CARD_ADDED, null)); 110 } 111 112 // card is present in the slot now; create new mUiccCard 113 if (mUiccCard != null) { 114 loge("update: mUiccCard != null when card was present; disposing it now"); 115 mUiccCard.dispose(); 116 } 117 118 if (!mIsEuicc) { 119 mUiccCard = new UiccCard(mContext, mCi, ics, mPhoneId, mLock); 120 } else { 121 // The EID should be reported with the card status, but in case it's not we want 122 // to catch that here 123 if (TextUtils.isEmpty(ics.eid)) { 124 loge("update: eid is missing. ics.eid=" + ics.eid); 125 } 126 mUiccCard = new EuiccCard(mContext, mCi, ics, phoneId, mLock); 127 } 128 } else { 129 if (mUiccCard != null) { 130 mUiccCard.update(mContext, mCi, ics); 131 } 132 } 133 mLastRadioState = radioState; 134 } 135 } 136 137 /** 138 * Update slot based on IccSlotStatus. 139 */ update(CommandsInterface ci, IccSlotStatus iss, int slotIndex)140 public void update(CommandsInterface ci, IccSlotStatus iss, int slotIndex) { 141 if (DBG) log("slotStatus update: " + iss.toString()); 142 synchronized (mLock) { 143 CardState oldState = mCardState; 144 mCi = ci; 145 parseAtr(iss.atr); 146 mCardState = iss.cardState; 147 mIccId = iss.iccid; 148 mIsRemovable = isSlotRemovable(slotIndex); 149 if (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_INACTIVE) { 150 // TODO: (b/79432584) evaluate whether should broadcast card state change 151 // even if it's inactive. 152 if (mActive) { 153 mActive = false; 154 mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE; 155 UiccController.updateInternalIccState( 156 mContext, IccCardConstants.State.ABSENT, null, mPhoneId, 157 true /* special notification for absent card in an inactive slot */); 158 mPhoneId = INVALID_PHONE_ID; 159 nullifyUiccCard(true /* sim state is unknown */); 160 } 161 } else { 162 mActive = true; 163 mPhoneId = iss.logicalSlotIndex; 164 if (absentStateUpdateNeeded(oldState)) { 165 updateCardStateAbsent(); 166 } 167 // TODO: (b/79432584) Create UiccCard or EuiccCard object here. 168 // Right now It's OK not creating it because Card status update will do it. 169 // But we should really make them symmetric. 170 } 171 } 172 } 173 absentStateUpdateNeeded(CardState oldState)174 private boolean absentStateUpdateNeeded(CardState oldState) { 175 return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null) 176 && mCardState == CardState.CARDSTATE_ABSENT; 177 } 178 updateCardStateAbsent()179 private void updateCardStateAbsent() { 180 int radioState = 181 (mCi == null) ? TelephonyManager.RADIO_POWER_UNAVAILABLE : mCi.getRadioState(); 182 // No notifications while radio is off or we just powering up 183 if (radioState == TelephonyManager.RADIO_POWER_ON 184 && mLastRadioState == TelephonyManager.RADIO_POWER_ON) { 185 if (DBG) log("update: notify card removed"); 186 sendMessage(obtainMessage(EVENT_CARD_REMOVED, null)); 187 } 188 189 UiccController.updateInternalIccState( 190 mContext, IccCardConstants.State.ABSENT, null, mPhoneId); 191 192 // no card present in the slot now; dispose card and make mUiccCard null 193 nullifyUiccCard(false /* sim state is not unknown */); 194 mLastRadioState = radioState; 195 } 196 197 // whenever we set mUiccCard to null, we lose the ability to differentiate between absent and 198 // unknown states. To mitigate this, we will us mStateIsUnknown to keep track. The sim is only 199 // unknown if we haven't heard from the radio or if the radio has become unavailable. nullifyUiccCard(boolean stateUnknown)200 private void nullifyUiccCard(boolean stateUnknown) { 201 if (mUiccCard != null) { 202 mUiccCard.dispose(); 203 } 204 mStateIsUnknown = stateUnknown; 205 mUiccCard = null; 206 } 207 isStateUnknown()208 public boolean isStateUnknown() { 209 if (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) { 210 // mStateIsUnknown is valid only in this scenario. 211 return mStateIsUnknown; 212 } 213 // if mUiccCard is null, assume the state to be UNKNOWN for now. 214 // The state may be known but since the actual card object is not available, 215 // it is safer to return UNKNOWN. 216 return mUiccCard == null; 217 } 218 219 // Return true if a slot index is for removable UICCs or eUICCs isSlotRemovable(int slotIndex)220 private boolean isSlotRemovable(int slotIndex) { 221 int[] euiccSlots = mContext.getResources() 222 .getIntArray(com.android.internal.R.array.non_removable_euicc_slots); 223 if (euiccSlots == null) { 224 return true; 225 } 226 for (int euiccSlot : euiccSlots) { 227 if (euiccSlot == slotIndex) { 228 return false; 229 } 230 } 231 232 return true; 233 } 234 checkIsEuiccSupported()235 private void checkIsEuiccSupported() { 236 if (mAtr != null && mAtr.isEuiccSupported()) { 237 mIsEuicc = true; 238 } else { 239 mIsEuicc = false; 240 } 241 } 242 parseAtr(String atr)243 private void parseAtr(String atr) { 244 mAtr = AnswerToReset.parseAtr(atr); 245 checkIsEuiccSupported(); 246 } 247 isEuicc()248 public boolean isEuicc() { 249 return mIsEuicc; 250 } 251 isActive()252 public boolean isActive() { 253 return mActive; 254 } 255 getPhoneId()256 public int getPhoneId() { 257 return mPhoneId; 258 } 259 isRemovable()260 public boolean isRemovable() { 261 return mIsRemovable; 262 } 263 getIccId()264 public String getIccId() { 265 if (mIccId != null) { 266 return mIccId; 267 } else if (mUiccCard != null) { 268 return mUiccCard.getIccId(); 269 } else { 270 return null; 271 } 272 } 273 isExtendedApduSupported()274 public boolean isExtendedApduSupported() { 275 return (mAtr != null && mAtr.isExtendedApduSupported()); 276 } 277 278 @Override finalize()279 protected void finalize() { 280 if (DBG) log("UiccSlot finalized"); 281 } 282 onIccSwap(boolean isAdded)283 private void onIccSwap(boolean isAdded) { 284 285 boolean isHotSwapSupported = mContext.getResources().getBoolean( 286 R.bool.config_hotswapCapable); 287 288 if (isHotSwapSupported) { 289 log("onIccSwap: isHotSwapSupported is true, don't prompt for rebooting"); 290 return; 291 } 292 log("onIccSwap: isHotSwapSupported is false, prompt for rebooting"); 293 294 promptForRestart(isAdded); 295 } 296 promptForRestart(boolean isAdded)297 private void promptForRestart(boolean isAdded) { 298 synchronized (mLock) { 299 final Resources res = mContext.getResources(); 300 final String dialogComponent = res.getString( 301 R.string.config_iccHotswapPromptForRestartDialogComponent); 302 if (dialogComponent != null) { 303 Intent intent = new Intent().setComponent(ComponentName.unflattenFromString( 304 dialogComponent)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 305 .putExtra(EXTRA_ICC_CARD_ADDED, isAdded); 306 try { 307 mContext.startActivity(intent); 308 return; 309 } catch (ActivityNotFoundException e) { 310 loge("Unable to find ICC hotswap prompt for restart activity: " + e); 311 } 312 } 313 314 // TODO: Here we assume the device can't handle SIM hot-swap 315 // and has to reboot. We may want to add a property, 316 // e.g. REBOOT_ON_SIM_SWAP, to indicate if modem support 317 // hot-swap. 318 DialogInterface.OnClickListener listener = null; 319 320 321 // TODO: SimRecords is not reset while SIM ABSENT (only reset while 322 // Radio_off_or_not_available). Have to reset in both both 323 // added or removed situation. 324 listener = new DialogInterface.OnClickListener() { 325 @Override 326 public void onClick(DialogInterface dialog, int which) { 327 synchronized (mLock) { 328 if (which == DialogInterface.BUTTON_POSITIVE) { 329 if (DBG) log("Reboot due to SIM swap"); 330 PowerManager pm = (PowerManager) mContext 331 .getSystemService(Context.POWER_SERVICE); 332 pm.reboot("SIM is added."); 333 } 334 } 335 } 336 337 }; 338 339 Resources r = Resources.getSystem(); 340 341 String title = (isAdded) ? r.getString(R.string.sim_added_title) : 342 r.getString(R.string.sim_removed_title); 343 String message = (isAdded) ? r.getString(R.string.sim_added_message) : 344 r.getString(R.string.sim_removed_message); 345 String buttonTxt = r.getString(R.string.sim_restart_button); 346 347 AlertDialog dialog = new AlertDialog.Builder(mContext) 348 .setTitle(title) 349 .setMessage(message) 350 .setPositiveButton(buttonTxt, listener) 351 .create(); 352 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 353 dialog.show(); 354 } 355 } 356 357 @Override handleMessage(Message msg)358 public void handleMessage(Message msg) { 359 switch (msg.what) { 360 case EVENT_CARD_REMOVED: 361 onIccSwap(false); 362 break; 363 case EVENT_CARD_ADDED: 364 onIccSwap(true); 365 break; 366 default: 367 loge("Unknown Event " + msg.what); 368 } 369 } 370 371 /** 372 * Returns the state of the UiccCard in the slot. 373 * @return 374 */ getCardState()375 public CardState getCardState() { 376 synchronized (mLock) { 377 if (mCardState == null) { 378 return CardState.CARDSTATE_ABSENT; 379 } else { 380 return mCardState; 381 } 382 } 383 } 384 385 /** 386 * Returns the UiccCard in the slot. 387 */ getUiccCard()388 public UiccCard getUiccCard() { 389 synchronized (mLock) { 390 return mUiccCard; 391 } 392 } 393 394 /** 395 * Processes radio state unavailable event 396 */ onRadioStateUnavailable()397 public void onRadioStateUnavailable() { 398 nullifyUiccCard(true /* sim state is unknown */); 399 400 if (mPhoneId != INVALID_PHONE_ID) { 401 UiccController.updateInternalIccState( 402 mContext, IccCardConstants.State.UNKNOWN, null, mPhoneId); 403 } 404 405 mCardState = null; 406 mLastRadioState = TelephonyManager.RADIO_POWER_UNAVAILABLE; 407 } 408 log(String msg)409 private void log(String msg) { 410 Rlog.d(TAG, msg); 411 } 412 loge(String msg)413 private void loge(String msg) { 414 Rlog.e(TAG, msg); 415 } 416 417 /** 418 * Dump 419 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)420 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 421 pw.println("UiccSlot:"); 422 pw.println(" mCi=" + mCi); 423 pw.println(" mActive=" + mActive); 424 pw.println(" mIsEuicc=" + mIsEuicc); 425 pw.println(" mLastRadioState=" + mLastRadioState); 426 pw.println(" mIccId=" + mIccId); 427 pw.println(" mCardState=" + mCardState); 428 if (mUiccCard != null) { 429 pw.println(" mUiccCard=" + mUiccCard); 430 mUiccCard.dump(fd, pw, args); 431 } else { 432 pw.println(" mUiccCard=null"); 433 } 434 pw.println(); 435 pw.flush(); 436 } 437 } 438