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 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
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     */
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     */
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
162    private boolean absentStateUpdateNeeded(CardState oldState) {
163        return (oldState != CardState.CARDSTATE_ABSENT || mUiccCard != null)
164                && mCardState == CardState.CARDSTATE_ABSENT;
165    }
166
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.
190    private void nullifyUiccCard(boolean stateUnknown) {
191        mStateIsUnknown = stateUnknown;
192        mUiccCard = null;
193    }
194
195    public boolean isStateUnknown() {
196        return (mCardState == null || mCardState == CardState.CARDSTATE_ABSENT) && mStateIsUnknown;
197    }
198
199    private void checkIsEuiccSupported() {
200        if (mAtr != null && mAtr.isEuiccSupported()) {
201            mIsEuicc = true;
202        } else {
203            mIsEuicc = false;
204        }
205    }
206
207    private void parseAtr(String atr) {
208        mAtr = AnswerToReset.parseAtr(atr);
209        if (mAtr == null) {
210            return;
211        }
212        checkIsEuiccSupported();
213    }
214
215    public boolean isEuicc() {
216        return mIsEuicc;
217    }
218
219    public boolean isActive() {
220        return mActive;
221    }
222
223    public int getPhoneId() {
224        return mPhoneId;
225    }
226
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
237    public boolean isExtendedApduSupported() {
238        return  (mAtr != null && mAtr.isExtendedApduSupported());
239    }
240
241    @Override
242    protected void finalize() {
243        if (DBG) log("UiccSlot finalized");
244    }
245
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
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
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     */
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     */
351    public UiccCard getUiccCard() {
352        synchronized (mLock) {
353            return mUiccCard;
354        }
355    }
356
357    /**
358     * Processes radio state unavailable event
359     */
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
375    private void log(String msg) {
376        Rlog.d(TAG, msg);
377    }
378
379    private void loge(String msg) {
380        Rlog.e(TAG, msg);
381    }
382
383    /**
384     * Dump
385     */
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