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