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