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.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.AlertDialog; 22 import android.content.ActivityNotFoundException; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.Intent; 27 import android.content.res.Resources; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.PowerManager; 31 import android.os.UserHandle; 32 import android.telephony.SubscriptionInfo; 33 import android.telephony.SubscriptionManager; 34 import android.telephony.TelephonyManager; 35 import android.text.TextUtils; 36 import android.view.WindowManager; 37 38 import com.android.internal.R; 39 import com.android.internal.telephony.CommandsInterface; 40 import com.android.internal.telephony.IccCardConstants; 41 import com.android.internal.telephony.Phone; 42 import com.android.internal.telephony.PhoneFactory; 43 import com.android.internal.telephony.uicc.IccCardStatus.CardState; 44 import com.android.internal.telephony.uicc.euicc.EuiccCard; 45 import com.android.internal.telephony.util.TelephonyUtils; 46 import com.android.telephony.Rlog; 47 48 import java.io.FileDescriptor; 49 import java.io.PrintWriter; 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.util.HashMap; 53 import java.util.List; 54 import java.util.Map; 55 import java.util.stream.Collectors; 56 57 /** 58 * This class represents a physical slot on the device. 59 */ 60 public class UiccSlot extends Handler { 61 private static final String TAG = "UiccSlot"; 62 private static final boolean DBG = true; 63 64 public static final String EXTRA_ICC_CARD_ADDED = 65 "com.android.internal.telephony.uicc.ICC_CARD_ADDED"; 66 public static final int INVALID_PHONE_ID = -1; 67 68 @Retention(RetentionPolicy.SOURCE) 69 @IntDef( 70 prefix = {"VOLTAGE_CLASS_"}, 71 value = {VOLTAGE_CLASS_UNKNOWN, VOLTAGE_CLASS_A, VOLTAGE_CLASS_B, VOLTAGE_CLASS_C}) 72 public @interface VoltageClass {} 73 74 public static final int VOLTAGE_CLASS_UNKNOWN = 0; 75 public static final int VOLTAGE_CLASS_A = 1; 76 public static final int VOLTAGE_CLASS_B = 2; 77 public static final int VOLTAGE_CLASS_C = 3; 78 79 private final Object mLock = new Object(); 80 private boolean mActive; 81 private boolean mStateIsUnknown = true; 82 private CardState mCardState; 83 private Context mContext; 84 private UiccCard mUiccCard; 85 private boolean mIsEuicc; 86 private @VoltageClass int mMinimumVoltageClass; 87 private String mEid; 88 private AnswerToReset mAtr; 89 private boolean mIsRemovable; 90 // Map each available portIdx to phoneId 91 private HashMap<Integer, Integer> mPortIdxToPhoneId = new HashMap<>(); 92 //Map each available portIdx with old radio state for state checking 93 private HashMap<Integer, Integer> mLastRadioState = new HashMap<>(); 94 // Store iccId of each port. 95 private HashMap<Integer, String> mIccIds = new HashMap<>(); 96 97 private static final int EVENT_CARD_REMOVED = 13; 98 private static final int EVENT_CARD_ADDED = 14; 99 UiccSlot(Context c, boolean isActive)100 public UiccSlot(Context c, boolean isActive) { 101 if (DBG) log("Creating"); 102 mContext = c; 103 mActive = isActive; 104 mCardState = null; 105 } 106 107 /** 108 * Update slot. The main trigger for this is a change in the ICC Card status. 109 */ update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex)110 public void update(CommandsInterface ci, IccCardStatus ics, int phoneId, int slotIndex) { 111 if (DBG) log("cardStatus update: " + ics.toString()); 112 synchronized (mLock) { 113 mPortIdxToPhoneId.put(ics.mSlotPortMapping.mPortIndex, phoneId); 114 CardState oldState = mCardState; 115 mCardState = ics.mCardState; 116 mIccIds.put(ics.mSlotPortMapping.mPortIndex, ics.iccid); 117 parseAtr(ics.atr); 118 mIsRemovable = isSlotRemovable(slotIndex); 119 120 int radioState = ci.getRadioState(); 121 if (DBG) { 122 log("update: radioState=" + radioState + " mLastRadioState=" + mLastRadioState); 123 } 124 125 if (absentStateUpdateNeeded(oldState)) { 126 updateCardStateAbsent(ci.getRadioState(), phoneId, 127 ics.mSlotPortMapping.mPortIndex); 128 // Because mUiccCard may be updated in both IccCardStatus and IccSlotStatus, we need to 129 // create a new UiccCard instance in two scenarios: 130 // 1. mCardState is changing from ABSENT to non ABSENT. 131 // 2. The latest mCardState is not ABSENT, but there is no UiccCard instance. 132 } else if ((oldState == null || oldState == CardState.CARDSTATE_ABSENT 133 || mUiccCard == null) && mCardState != CardState.CARDSTATE_ABSENT) { 134 // No notification while we are just powering up 135 if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE 136 && mLastRadioState.getOrDefault(ics.mSlotPortMapping.mPortIndex, 137 TelephonyManager.RADIO_POWER_UNAVAILABLE) 138 != TelephonyManager.RADIO_POWER_UNAVAILABLE) { 139 if (DBG) log("update: notify card added"); 140 sendMessage(obtainMessage(EVENT_CARD_ADDED, null)); 141 } 142 143 // card is present in the slot now; create new mUiccCard 144 if (mUiccCard != null) { 145 loge("update: mUiccCard != null when card was present; disposing it now"); 146 mUiccCard.dispose(); 147 } 148 149 if (!mIsEuicc) { 150 // Uicc does not support MEP, passing false by default. 151 mUiccCard = new UiccCard(mContext, ci, ics, phoneId, mLock, false); 152 } else { 153 // The EID should be reported with the card status, but in case it's not we want 154 // to catch that here 155 if (TextUtils.isEmpty(ics.eid)) { 156 loge("update: eid is missing. ics.eid=" 157 + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, ics.eid)); 158 } 159 mUiccCard = new EuiccCard(mContext, ci, ics, phoneId, mLock, 160 isMultipleEnabledProfileSupported()); 161 } 162 } else { 163 if (mUiccCard != null) { 164 mUiccCard.update(mContext, ci, ics, phoneId); 165 } 166 } 167 mLastRadioState.put(ics.mSlotPortMapping.mPortIndex, radioState); 168 } 169 } 170 171 /** 172 * Update slot based on IccSlotStatus. 173 */ update(CommandsInterface[] ci, IccSlotStatus iss, int slotIndex)174 public void update(CommandsInterface[] ci, IccSlotStatus iss, int slotIndex) { 175 if (DBG) log("slotStatus update: " + iss.toString()); 176 synchronized (mLock) { 177 IccSimPortInfo[] simPortInfos = iss.mSimPortInfos; 178 CardState oldState = mCardState; 179 parseAtr(iss.atr); 180 mCardState = iss.cardState; 181 mEid = iss.eid; 182 mIsRemovable = isSlotRemovable(slotIndex); 183 184 for (int i = 0; i < simPortInfos.length; i++) { 185 int phoneId = iss.mSimPortInfos[i].mLogicalSlotIndex; 186 mIccIds.put(i, simPortInfos[i].mIccId); 187 if (!iss.mSimPortInfos[i].mPortActive) { 188 // TODO: (b/79432584) evaluate whether should broadcast card state change 189 // even if it's inactive. 190 UiccController.updateInternalIccStateForInactivePort(mContext, 191 mPortIdxToPhoneId.getOrDefault(i, INVALID_PHONE_ID), 192 iss.mSimPortInfos[i].mIccId); 193 mLastRadioState.put(i, TelephonyManager.RADIO_POWER_UNAVAILABLE); 194 if (mUiccCard != null) { 195 // Dispose the port 196 mUiccCard.disposePort(i); 197 } 198 } else { 199 if (absentStateUpdateNeeded(oldState)) { 200 int radioState = SubscriptionManager.isValidPhoneId(phoneId) ? 201 ci[phoneId].getRadioState() : 202 TelephonyManager.RADIO_POWER_UNAVAILABLE; 203 updateCardStateAbsent(radioState, phoneId, i); 204 } 205 // TODO: (b/79432584) Create UiccCard or EuiccCard object here. 206 // Right now It's OK not creating it because Card status update will do it. 207 // But we should really make them symmetric. 208 } 209 } 210 // From MEP, Card can have multiple ports. So dispose UiccCard only when all the 211 // ports are inactive. 212 if (!hasActivePort(simPortInfos)) { 213 if (mActive) { 214 mActive = false; 215 nullifyUiccCard(true /* sim state is unknown */); 216 } 217 } else { 218 mActive = true; 219 } 220 mPortIdxToPhoneId.clear(); 221 for (int i = 0; i < simPortInfos.length; i++) { 222 // If port is not active, update with invalid phone id(i.e. -1) 223 mPortIdxToPhoneId.put(i, simPortInfos[i].mPortActive ? 224 simPortInfos[i].mLogicalSlotIndex : INVALID_PHONE_ID); 225 } 226 // Since the MEP capability is related with number ports reported, thus need to 227 // update the flag after UiccCard creation. 228 if (mUiccCard != null) { 229 mUiccCard.updateSupportMultipleEnabledProfile(isMultipleEnabledProfileSupported()); 230 } 231 } 232 } 233 hasActivePort(IccSimPortInfo[] simPortInfos)234 private boolean hasActivePort(IccSimPortInfo[] simPortInfos) { 235 for (IccSimPortInfo simPortInfo : simPortInfos) { 236 if (simPortInfo.mPortActive) { 237 return true; 238 } 239 } 240 return false; 241 } 242 243 /* Return valid phoneId if possible from the portIdx mapping*/ getAnyValidPhoneId()244 private int getAnyValidPhoneId() { 245 for (int phoneId : mPortIdxToPhoneId.values()) { 246 if (SubscriptionManager.isValidPhoneId(phoneId)) { 247 return phoneId; 248 } 249 } 250 return INVALID_PHONE_ID; 251 } 252 253 @NonNull getPortList()254 public int[] getPortList() { 255 synchronized (mLock) { 256 return mPortIdxToPhoneId.keySet().stream().mapToInt(Integer::valueOf).toArray(); 257 } 258 } 259 260 /** Return whether the passing portIndex belong to this physical slot */ isValidPortIndex(int portIndex)261 public boolean isValidPortIndex(int portIndex) { 262 return mPortIdxToPhoneId.containsKey(portIndex); 263 } 264 getPortIndexFromPhoneId(int phoneId)265 public int getPortIndexFromPhoneId(int phoneId) { 266 synchronized (mLock) { 267 for (Map.Entry<Integer, Integer> entry : mPortIdxToPhoneId.entrySet()) { 268 if (entry.getValue() == phoneId) { 269 return entry.getKey(); 270 } 271 } 272 return TelephonyManager.DEFAULT_PORT_INDEX; 273 } 274 } 275 getPortIndexFromIccId(String iccId)276 public int getPortIndexFromIccId(String iccId) { 277 synchronized (mLock) { 278 for (Map.Entry<Integer, String> entry : mIccIds.entrySet()) { 279 if (IccUtils.compareIgnoreTrailingFs(entry.getValue(), iccId)) { 280 return entry.getKey(); 281 } 282 } 283 // If iccId is not found, return invalid port index. 284 return TelephonyManager.INVALID_PORT_INDEX; 285 } 286 } 287 getPhoneIdFromPortIndex(int portIndex)288 public int getPhoneIdFromPortIndex(int portIndex) { 289 synchronized (mLock) { 290 return mPortIdxToPhoneId.getOrDefault(portIndex, INVALID_PHONE_ID); 291 } 292 } 293 isPortActive(int portIdx)294 public boolean isPortActive(int portIdx) { 295 synchronized (mLock) { 296 return SubscriptionManager.isValidPhoneId( 297 mPortIdxToPhoneId.getOrDefault(portIdx, INVALID_PHONE_ID)); 298 } 299 } 300 301 /* Returns true if multiple enabled profiles are supported */ isMultipleEnabledProfileSupported()302 public boolean isMultipleEnabledProfileSupported() { 303 // even ATR suggest UICC supports multiple enabled profiles, MEP can be disabled per 304 // carrier restrictions, so checking the real number of ports reported from modem is 305 // necessary. 306 return mPortIdxToPhoneId.size() > 1 && mAtr != null && 307 mAtr.isMultipleEnabledProfilesSupported(); 308 } 309 absentStateUpdateNeeded(CardState oldState)310 private boolean absentStateUpdateNeeded(CardState oldState) { 311 return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null) 312 && mCardState == CardState.CARDSTATE_ABSENT; 313 } 314 updateCardStateAbsent(int radioState, int phoneId, int portIndex)315 private void updateCardStateAbsent(int radioState, int phoneId, int portIndex) { 316 // No notification while we are just powering up 317 if (radioState != TelephonyManager.RADIO_POWER_UNAVAILABLE 318 && mLastRadioState.getOrDefault( 319 portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE) 320 != TelephonyManager.RADIO_POWER_UNAVAILABLE) { 321 if (DBG) log("update: notify card removed"); 322 sendMessage(obtainMessage(EVENT_CARD_REMOVED, null)); 323 } 324 UiccController.updateInternalIccState(mContext, IccCardConstants.State.ABSENT, 325 null, phoneId); 326 // no card present in the slot now; dispose card and make mUiccCard null 327 nullifyUiccCard(false /* sim state is not unknown */); 328 mLastRadioState.put(portIndex, TelephonyManager.RADIO_POWER_UNAVAILABLE); 329 } 330 331 // whenever we set mUiccCard to null, we lose the ability to differentiate between absent and 332 // unknown states. To mitigate this, we will us mStateIsUnknown to keep track. The sim is only 333 // unknown if we haven't heard from the radio or if the radio has become unavailable. nullifyUiccCard(boolean stateUnknown)334 private void nullifyUiccCard(boolean stateUnknown) { 335 if (mUiccCard != null) { 336 mUiccCard.dispose(); 337 } 338 mStateIsUnknown = stateUnknown; 339 mUiccCard = null; 340 } 341 isStateUnknown()342 public boolean isStateUnknown() { 343 if (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) { 344 // mStateIsUnknown is valid only in this scenario. 345 return mStateIsUnknown; 346 } 347 // if mUiccCard is null, assume the state to be UNKNOWN for now. 348 // The state may be known but since the actual card object is not available, 349 // it is safer to return UNKNOWN. 350 return mUiccCard == null; 351 } 352 353 // Return true if a slot index is for removable UICCs or eUICCs isSlotRemovable(int slotIndex)354 private boolean isSlotRemovable(int slotIndex) { 355 int[] euiccSlots = mContext.getResources() 356 .getIntArray(com.android.internal.R.array.non_removable_euicc_slots); 357 if (euiccSlots == null) { 358 return true; 359 } 360 for (int euiccSlot : euiccSlots) { 361 if (euiccSlot == slotIndex) { 362 return false; 363 } 364 } 365 366 return true; 367 } 368 checkIsEuiccSupported()369 private void checkIsEuiccSupported() { 370 if (mAtr == null) { 371 mIsEuicc = false; 372 return; 373 } 374 mIsEuicc = mAtr.isEuiccSupported(); 375 log(" checkIsEuiccSupported : " + mIsEuicc); 376 } 377 checkMinimumVoltageClass()378 private void checkMinimumVoltageClass() { 379 mMinimumVoltageClass = VOLTAGE_CLASS_UNKNOWN; 380 if (mAtr == null) { 381 return; 382 } 383 // Supported voltage classes are stored in the 5 least significant bits of the TA byte for 384 // global interface. 385 List<AnswerToReset.InterfaceByte> interfaceBytes = mAtr.getInterfaceBytes(); 386 for (int i = 0; i < interfaceBytes.size() - 1; i++) { 387 if (interfaceBytes.get(i).getTD() != null 388 && (interfaceBytes.get(i).getTD() & AnswerToReset.T_MASK) 389 == AnswerToReset.T_VALUE_FOR_GLOBAL_INTERFACE 390 && interfaceBytes.get(i + 1).getTA() != null) { 391 byte ta = interfaceBytes.get(i + 1).getTA(); 392 if ((ta & 0x01) != 0) { 393 mMinimumVoltageClass = VOLTAGE_CLASS_A; 394 } 395 if ((ta & 0x02) != 0) { 396 mMinimumVoltageClass = VOLTAGE_CLASS_B; 397 } 398 if ((ta & 0x04) != 0) { 399 mMinimumVoltageClass = VOLTAGE_CLASS_C; 400 } 401 return; 402 } 403 } 404 // Use default value - only class A 405 mMinimumVoltageClass = VOLTAGE_CLASS_A; 406 } 407 parseAtr(String atr)408 private void parseAtr(String atr) { 409 mAtr = AnswerToReset.parseAtr(atr); 410 checkIsEuiccSupported(); 411 checkMinimumVoltageClass(); 412 } 413 isEuicc()414 public boolean isEuicc() { 415 return mIsEuicc; 416 } 417 418 @VoltageClass getMinimumVoltageClass()419 public int getMinimumVoltageClass() { 420 return mMinimumVoltageClass; 421 } 422 isActive()423 public boolean isActive() { 424 return mActive; 425 } 426 isRemovable()427 public boolean isRemovable() { 428 return mIsRemovable; 429 } 430 431 /** 432 * Returns the iccId specific to the port index. 433 * Always use {@link com.android.internal.telephony.uicc.UiccPort#getIccId} to get the iccId. 434 * Use this API to get the iccId of the inactive port only. 435 */ getIccId(int portIdx)436 public String getIccId(int portIdx) { 437 return mIccIds.get(portIdx); 438 } 439 getEid()440 public String getEid() { 441 return mEid; 442 } 443 isExtendedApduSupported()444 public boolean isExtendedApduSupported() { 445 return (mAtr != null && mAtr.isExtendedApduSupported()); 446 } 447 448 @Override finalize()449 protected void finalize() { 450 if (DBG) log("UiccSlot finalized"); 451 } 452 onIccSwap(boolean isAdded)453 private void onIccSwap(boolean isAdded) { 454 455 boolean isHotSwapSupported = mContext.getResources().getBoolean( 456 R.bool.config_hotswapCapable); 457 458 if (isHotSwapSupported) { 459 log("onIccSwap: isHotSwapSupported is true, don't prompt for rebooting"); 460 return; 461 } 462 // As this check is for shutdown status check, use any phoneId 463 Phone phone = PhoneFactory.getPhone(getAnyValidPhoneId()); 464 if (phone != null && phone.isShuttingDown()) { 465 log("onIccSwap: already doing shutdown, no need to prompt"); 466 return; 467 } 468 469 log("onIccSwap: isHotSwapSupported is false, prompt for rebooting"); 470 471 promptForRestart(isAdded); 472 } 473 promptForRestart(boolean isAdded)474 private void promptForRestart(boolean isAdded) { 475 synchronized (mLock) { 476 final Resources res = mContext.getResources(); 477 final ComponentName dialogComponent = ComponentName.unflattenFromString( 478 res.getString(R.string.config_iccHotswapPromptForRestartDialogComponent)); 479 if (dialogComponent != null) { 480 Intent intent = new Intent().setComponent(dialogComponent) 481 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 482 .putExtra(EXTRA_ICC_CARD_ADDED, isAdded); 483 try { 484 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 485 return; 486 } catch (ActivityNotFoundException e) { 487 loge("Unable to find ICC hotswap prompt for restart activity: " + e); 488 } 489 } 490 491 // TODO: Here we assume the device can't handle SIM hot-swap 492 // and has to reboot. We may want to add a property, 493 // e.g. REBOOT_ON_SIM_SWAP, to indicate if modem support 494 // hot-swap. 495 DialogInterface.OnClickListener listener = null; 496 497 498 // TODO: SimRecords is not reset while SIM ABSENT (only reset while 499 // Radio_off_or_not_available). Have to reset in both both 500 // added or removed situation. 501 listener = new DialogInterface.OnClickListener() { 502 @Override 503 public void onClick(DialogInterface dialog, int which) { 504 synchronized (mLock) { 505 if (which == DialogInterface.BUTTON_POSITIVE) { 506 if (DBG) log("Reboot due to SIM swap"); 507 PowerManager pm = (PowerManager) mContext 508 .getSystemService(Context.POWER_SERVICE); 509 pm.reboot("SIM is added."); 510 } 511 } 512 } 513 514 }; 515 516 Resources r = Resources.getSystem(); 517 518 String title = (isAdded) ? r.getString(R.string.sim_added_title) : 519 r.getString(R.string.sim_removed_title); 520 String message = (isAdded) ? r.getString(R.string.sim_added_message) : 521 r.getString(R.string.sim_removed_message); 522 String buttonTxt = r.getString(R.string.sim_restart_button); 523 524 AlertDialog dialog = new AlertDialog.Builder(mContext) 525 .setTitle(title) 526 .setMessage(message) 527 .setPositiveButton(buttonTxt, listener) 528 .create(); 529 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 530 dialog.show(); 531 } 532 } 533 534 @Override handleMessage(Message msg)535 public void handleMessage(Message msg) { 536 switch (msg.what) { 537 case EVENT_CARD_REMOVED: 538 onIccSwap(false); 539 break; 540 case EVENT_CARD_ADDED: 541 onIccSwap(true); 542 break; 543 default: 544 loge("Unknown Event " + msg.what); 545 } 546 } 547 548 /** 549 * Returns the state of the UiccCard in the slot. 550 * @return 551 */ getCardState()552 public CardState getCardState() { 553 synchronized (mLock) { 554 if (mCardState == null) { 555 return CardState.CARDSTATE_ABSENT; 556 } else { 557 return mCardState; 558 } 559 } 560 } 561 562 /** 563 * Returns the UiccCard in the slot. 564 */ getUiccCard()565 public UiccCard getUiccCard() { 566 synchronized (mLock) { 567 return mUiccCard; 568 } 569 } 570 571 /** 572 * Processes radio state unavailable event 573 */ onRadioStateUnavailable(int phoneId)574 public void onRadioStateUnavailable(int phoneId) { 575 nullifyUiccCard(true /* sim state is unknown */); 576 577 if (phoneId != INVALID_PHONE_ID) { 578 UiccController.updateInternalIccState( 579 mContext, IccCardConstants.State.UNKNOWN, null, phoneId); 580 mLastRadioState.put(getPortIndexFromPhoneId(phoneId), 581 TelephonyManager.RADIO_POWER_UNAVAILABLE); 582 } 583 584 mCardState = null; 585 } 586 log(String msg)587 private void log(String msg) { 588 Rlog.d(TAG, msg); 589 } 590 loge(String msg)591 private void loge(String msg) { 592 Rlog.e(TAG, msg); 593 } 594 getPrintableIccIds()595 private Map<Integer, String> getPrintableIccIds() { 596 Map<Integer, String> printableIccIds = mIccIds.entrySet().stream() 597 .collect(Collectors.toMap(Map.Entry::getKey, 598 e -> SubscriptionInfo.givePrintableIccid(e.getValue()))); 599 return printableIccIds; 600 } 601 602 /** 603 * Dump 604 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)605 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 606 pw.println("UiccSlot:"); 607 pw.println(" mActive=" + mActive); 608 pw.println(" mIsEuicc=" + mIsEuicc); 609 pw.println(" isEuiccSupportsMultipleEnabledProfiles=" 610 + isMultipleEnabledProfileSupported()); 611 pw.println(" mIsRemovable=" + mIsRemovable); 612 pw.println(" mLastRadioState=" + mLastRadioState); 613 pw.println(" mIccIds=" + getPrintableIccIds()); 614 pw.println(" mPortIdxToPhoneId=" + mPortIdxToPhoneId); 615 pw.println(" mEid=" + Rlog.pii(TelephonyUtils.IS_DEBUGGABLE, mEid)); 616 pw.println(" mCardState=" + mCardState); 617 if (mUiccCard != null) { 618 pw.println(" mUiccCard=" + mUiccCard); 619 mUiccCard.dump(fd, pw, args); 620 } else { 621 pw.println(" mUiccCard=null"); 622 } 623 pw.println(); 624 pw.flush(); 625 } 626 } 627