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