• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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