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