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