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