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