1/*
2 * Copyright (C) 2006 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;
18
19import static android.Manifest.permission.READ_PHONE_STATE;
20import android.app.ActivityManagerNative;
21import android.app.AlertDialog;
22import android.content.Context;
23import android.content.DialogInterface;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.os.AsyncResult;
27import android.os.Handler;
28import android.os.Message;
29import android.os.Power;
30import android.os.PowerManager;
31import android.os.Registrant;
32import android.os.RegistrantList;
33import android.util.Log;
34import android.view.WindowManager;
35
36import com.android.internal.telephony.PhoneBase;
37import com.android.internal.telephony.CommandsInterface.RadioState;
38import com.android.internal.telephony.gsm.SIMRecords;
39
40import android.os.SystemProperties;
41
42import com.android.internal.R;
43
44/**
45 * {@hide}
46 */
47public abstract class IccCard {
48    protected String mLogTag;
49    protected boolean mDbg;
50
51    private IccCardStatus mIccCardStatus = null;
52    protected State mState = null;
53    protected PhoneBase mPhone;
54    private RegistrantList mAbsentRegistrants = new RegistrantList();
55    private RegistrantList mPinLockedRegistrants = new RegistrantList();
56    private RegistrantList mNetworkLockedRegistrants = new RegistrantList();
57
58    private boolean mDesiredPinLocked;
59    private boolean mDesiredFdnEnabled;
60    private boolean mIccPinLocked = true; // Default to locked
61    private boolean mIccFdnEnabled = false; // Default to disabled.
62                                            // Will be updated when SIM_READY.
63
64
65    /* The extra data for broacasting intent INTENT_ICC_STATE_CHANGE */
66    static public final String INTENT_KEY_ICC_STATE = "ss";
67    /* NOT_READY means the ICC interface is not ready (eg, radio is off or powering on) */
68    static public final String INTENT_VALUE_ICC_NOT_READY = "NOT_READY";
69    /* ABSENT means ICC is missing */
70    static public final String INTENT_VALUE_ICC_ABSENT = "ABSENT";
71    /* LOCKED means ICC is locked by pin or by network */
72    static public final String INTENT_VALUE_ICC_LOCKED = "LOCKED";
73    /* READY means ICC is ready to access */
74    static public final String INTENT_VALUE_ICC_READY = "READY";
75    /* IMSI means ICC IMSI is ready in property */
76    static public final String INTENT_VALUE_ICC_IMSI = "IMSI";
77    /* LOADED means all ICC records, including IMSI, are loaded */
78    static public final String INTENT_VALUE_ICC_LOADED = "LOADED";
79    /* The extra data for broacasting intent INTENT_ICC_STATE_CHANGE */
80    static public final String INTENT_KEY_LOCKED_REASON = "reason";
81    /* PIN means ICC is locked on PIN1 */
82    static public final String INTENT_VALUE_LOCKED_ON_PIN = "PIN";
83    /* PUK means ICC is locked on PUK1 */
84    static public final String INTENT_VALUE_LOCKED_ON_PUK = "PUK";
85    /* NETWORK means ICC is locked on NETWORK PERSONALIZATION */
86    static public final String INTENT_VALUE_LOCKED_NETWORK = "NETWORK";
87    /* PERM_DISABLED means ICC is permanently disabled due to puk fails */
88    static public final String INTENT_VALUE_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
89
90
91    protected static final int EVENT_ICC_LOCKED_OR_ABSENT = 1;
92    private static final int EVENT_GET_ICC_STATUS_DONE = 2;
93    protected static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 3;
94    private static final int EVENT_PINPUK_DONE = 4;
95    private static final int EVENT_REPOLL_STATUS_DONE = 5;
96    protected static final int EVENT_ICC_READY = 6;
97    private static final int EVENT_QUERY_FACILITY_LOCK_DONE = 7;
98    private static final int EVENT_CHANGE_FACILITY_LOCK_DONE = 8;
99    private static final int EVENT_CHANGE_ICC_PASSWORD_DONE = 9;
100    private static final int EVENT_QUERY_FACILITY_FDN_DONE = 10;
101    private static final int EVENT_CHANGE_FACILITY_FDN_DONE = 11;
102    private static final int EVENT_ICC_STATUS_CHANGED = 12;
103    private static final int EVENT_CARD_REMOVED = 13;
104    private static final int EVENT_CARD_ADDED = 14;
105
106    /*
107      UNKNOWN is a transient state, for example, after uesr inputs ICC pin under
108      PIN_REQUIRED state, the query for ICC status returns UNKNOWN before it
109      turns to READY
110     */
111    public enum State {
112        UNKNOWN,
113        ABSENT,
114        PIN_REQUIRED,
115        PUK_REQUIRED,
116        NETWORK_LOCKED,
117        READY,
118        NOT_READY,
119        PERM_DISABLED;
120
121        public boolean isPinLocked() {
122            return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
123        }
124
125        public boolean iccCardExist() {
126            return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED)
127                    || (this == NETWORK_LOCKED) || (this == READY)
128                    || (this == PERM_DISABLED));
129        }
130    }
131
132    public State getState() {
133        if (mState == null) {
134            switch(mPhone.mCM.getRadioState()) {
135                /* This switch block must not return anything in
136                 * State.isLocked() or State.ABSENT.
137                 * If it does, handleSimStatus() may break
138                 */
139                case RADIO_OFF:
140                case RADIO_UNAVAILABLE:
141                case SIM_NOT_READY:
142                case RUIM_NOT_READY:
143                    return State.UNKNOWN;
144                case SIM_LOCKED_OR_ABSENT:
145                case RUIM_LOCKED_OR_ABSENT:
146                    //this should be transient-only
147                    return State.UNKNOWN;
148                case SIM_READY:
149                case RUIM_READY:
150                case NV_READY:
151                    return State.READY;
152                case NV_NOT_READY:
153                    return State.ABSENT;
154            }
155        } else {
156            return mState;
157        }
158
159        Log.e(mLogTag, "IccCard.getState(): case should never be reached");
160        return State.UNKNOWN;
161    }
162
163    public IccCard(PhoneBase phone, String logTag, Boolean dbg) {
164        mPhone = phone;
165        mPhone.mCM.registerForIccStatusChanged(mHandler, EVENT_ICC_STATUS_CHANGED, null);
166        mLogTag = logTag;
167        mDbg = dbg;
168    }
169
170    public void dispose() {
171        mPhone.mCM.unregisterForIccStatusChanged(mHandler);
172    }
173
174    protected void finalize() {
175        if(mDbg) Log.d(mLogTag, "IccCard finalized");
176    }
177
178    /**
179     * Notifies handler of any transition into State.ABSENT
180     */
181    public void registerForAbsent(Handler h, int what, Object obj) {
182        Registrant r = new Registrant (h, what, obj);
183
184        mAbsentRegistrants.add(r);
185
186        if (getState() == State.ABSENT) {
187            r.notifyRegistrant();
188        }
189    }
190
191    public void unregisterForAbsent(Handler h) {
192        mAbsentRegistrants.remove(h);
193    }
194
195    /**
196     * Notifies handler of any transition into State.NETWORK_LOCKED
197     */
198    public void registerForNetworkLocked(Handler h, int what, Object obj) {
199        Registrant r = new Registrant (h, what, obj);
200
201        mNetworkLockedRegistrants.add(r);
202
203        if (getState() == State.NETWORK_LOCKED) {
204            r.notifyRegistrant();
205        }
206    }
207
208    public void unregisterForNetworkLocked(Handler h) {
209        mNetworkLockedRegistrants.remove(h);
210    }
211
212    /**
213     * Notifies handler of any transition into State.isPinLocked()
214     */
215    public void registerForLocked(Handler h, int what, Object obj) {
216        Registrant r = new Registrant (h, what, obj);
217
218        mPinLockedRegistrants.add(r);
219
220        if (getState().isPinLocked()) {
221            r.notifyRegistrant();
222        }
223    }
224
225    public void unregisterForLocked(Handler h) {
226        mPinLockedRegistrants.remove(h);
227    }
228
229
230    /**
231     * Supply the ICC PIN to the ICC
232     *
233     * When the operation is complete, onComplete will be sent to its
234     * Handler.
235     *
236     * onComplete.obj will be an AsyncResult
237     *
238     * ((AsyncResult)onComplete.obj).exception == null on success
239     * ((AsyncResult)onComplete.obj).exception != null on fail
240     *
241     * If the supplied PIN is incorrect:
242     * ((AsyncResult)onComplete.obj).exception != null
243     * && ((AsyncResult)onComplete.obj).exception
244     *       instanceof com.android.internal.telephony.gsm.CommandException)
245     * && ((CommandException)(((AsyncResult)onComplete.obj).exception))
246     *          .getCommandError() == CommandException.Error.PASSWORD_INCORRECT
247     *
248     *
249     */
250
251    public void supplyPin (String pin, Message onComplete) {
252        mPhone.mCM.supplyIccPin(pin, mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
253    }
254
255    public void supplyPuk (String puk, String newPin, Message onComplete) {
256        mPhone.mCM.supplyIccPuk(puk, newPin,
257                mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
258    }
259
260    public void supplyPin2 (String pin2, Message onComplete) {
261        mPhone.mCM.supplyIccPin2(pin2,
262                mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
263    }
264
265    public void supplyPuk2 (String puk2, String newPin2, Message onComplete) {
266        mPhone.mCM.supplyIccPuk2(puk2, newPin2,
267                mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
268    }
269
270    public void supplyNetworkDepersonalization (String pin, Message onComplete) {
271        mPhone.mCM.supplyNetworkDepersonalization(pin,
272                mHandler.obtainMessage(EVENT_PINPUK_DONE, onComplete));
273    }
274
275    /**
276     * Check whether ICC pin lock is enabled
277     * This is a sync call which returns the cached pin enabled state
278     *
279     * @return true for ICC locked enabled
280     *         false for ICC locked disabled
281     */
282    public boolean getIccLockEnabled() {
283        return mIccPinLocked;
284     }
285
286    /**
287     * Check whether ICC fdn (fixed dialing number) is enabled
288     * This is a sync call which returns the cached pin enabled state
289     *
290     * @return true for ICC fdn enabled
291     *         false for ICC fdn disabled
292     */
293     public boolean getIccFdnEnabled() {
294        return mIccFdnEnabled;
295     }
296
297     /**
298      * Set the ICC pin lock enabled or disabled
299      * When the operation is complete, onComplete will be sent to its handler
300      *
301      * @param enabled "true" for locked "false" for unlocked.
302      * @param password needed to change the ICC pin state, aka. Pin1
303      * @param onComplete
304      *        onComplete.obj will be an AsyncResult
305      *        ((AsyncResult)onComplete.obj).exception == null on success
306      *        ((AsyncResult)onComplete.obj).exception != null on fail
307      */
308     public void setIccLockEnabled (boolean enabled,
309             String password, Message onComplete) {
310         int serviceClassX;
311         serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
312                 CommandsInterface.SERVICE_CLASS_DATA +
313                 CommandsInterface.SERVICE_CLASS_FAX;
314
315         mDesiredPinLocked = enabled;
316
317         mPhone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_SIM,
318                 enabled, password, serviceClassX,
319                 mHandler.obtainMessage(EVENT_CHANGE_FACILITY_LOCK_DONE, onComplete));
320     }
321
322     /**
323      * Set the ICC fdn enabled or disabled
324      * When the operation is complete, onComplete will be sent to its handler
325      *
326      * @param enabled "true" for locked "false" for unlocked.
327      * @param password needed to change the ICC fdn enable, aka Pin2
328      * @param onComplete
329      *        onComplete.obj will be an AsyncResult
330      *        ((AsyncResult)onComplete.obj).exception == null on success
331      *        ((AsyncResult)onComplete.obj).exception != null on fail
332      */
333     public void setIccFdnEnabled (boolean enabled,
334             String password, Message onComplete) {
335         int serviceClassX;
336         serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
337                 CommandsInterface.SERVICE_CLASS_DATA +
338                 CommandsInterface.SERVICE_CLASS_FAX +
339                 CommandsInterface.SERVICE_CLASS_SMS;
340
341         mDesiredFdnEnabled = enabled;
342
343         mPhone.mCM.setFacilityLock(CommandsInterface.CB_FACILITY_BA_FD,
344                 enabled, password, serviceClassX,
345                 mHandler.obtainMessage(EVENT_CHANGE_FACILITY_FDN_DONE, onComplete));
346     }
347
348     /**
349      * Change the ICC password used in ICC pin lock
350      * When the operation is complete, onComplete will be sent to its handler
351      *
352      * @param oldPassword is the old password
353      * @param newPassword is the new password
354      * @param onComplete
355      *        onComplete.obj will be an AsyncResult
356      *        ((AsyncResult)onComplete.obj).exception == null on success
357      *        ((AsyncResult)onComplete.obj).exception != null on fail
358      */
359     public void changeIccLockPassword(String oldPassword, String newPassword,
360             Message onComplete) {
361         mPhone.mCM.changeIccPin(oldPassword, newPassword,
362                 mHandler.obtainMessage(EVENT_CHANGE_ICC_PASSWORD_DONE, onComplete));
363
364     }
365
366     /**
367      * Change the ICC password used in ICC fdn enable
368      * When the operation is complete, onComplete will be sent to its handler
369      *
370      * @param oldPassword is the old password
371      * @param newPassword is the new password
372      * @param onComplete
373      *        onComplete.obj will be an AsyncResult
374      *        ((AsyncResult)onComplete.obj).exception == null on success
375      *        ((AsyncResult)onComplete.obj).exception != null on fail
376      */
377     public void changeIccFdnPassword(String oldPassword, String newPassword,
378             Message onComplete) {
379         mPhone.mCM.changeIccPin2(oldPassword, newPassword,
380                 mHandler.obtainMessage(EVENT_CHANGE_ICC_PASSWORD_DONE, onComplete));
381
382     }
383
384
385    /**
386     * Returns service provider name stored in ICC card.
387     * If there is no service provider name associated or the record is not
388     * yet available, null will be returned <p>
389     *
390     * Please use this value when display Service Provider Name in idle mode <p>
391     *
392     * Usage of this provider name in the UI is a common carrier requirement.
393     *
394     * Also available via Android property "gsm.sim.operator.alpha"
395     *
396     * @return Service Provider Name stored in ICC card
397     *         null if no service provider name associated or the record is not
398     *         yet available
399     *
400     */
401    public abstract String getServiceProviderName();
402
403    protected void updateStateProperty() {
404        mPhone.setSystemProperty(TelephonyProperties.PROPERTY_SIM_STATE, getState().toString());
405    }
406
407    private void getIccCardStatusDone(AsyncResult ar) {
408        if (ar.exception != null) {
409            Log.e(mLogTag,"Error getting ICC status. "
410                    + "RIL_REQUEST_GET_ICC_STATUS should "
411                    + "never return an error", ar.exception);
412            return;
413        }
414        handleIccCardStatus((IccCardStatus) ar.result);
415    }
416
417    private void handleIccCardStatus(IccCardStatus newCardStatus) {
418        boolean transitionedIntoPinLocked;
419        boolean transitionedIntoAbsent;
420        boolean transitionedIntoNetworkLocked;
421        boolean transitionedIntoPermBlocked;
422        boolean isIccCardRemoved;
423        boolean isIccCardAdded;
424
425        State oldState, newState;
426
427        oldState = mState;
428        mIccCardStatus = newCardStatus;
429        newState = getIccCardState();
430        mState = newState;
431
432        updateStateProperty();
433
434        transitionedIntoPinLocked = (
435                 (oldState != State.PIN_REQUIRED && newState == State.PIN_REQUIRED)
436              || (oldState != State.PUK_REQUIRED && newState == State.PUK_REQUIRED));
437        transitionedIntoAbsent = (oldState != State.ABSENT && newState == State.ABSENT);
438        transitionedIntoNetworkLocked = (oldState != State.NETWORK_LOCKED
439                && newState == State.NETWORK_LOCKED);
440        transitionedIntoPermBlocked = (oldState != State.PERM_DISABLED
441                && newState == State.PERM_DISABLED);
442        isIccCardRemoved = (oldState != null &&
443                        oldState.iccCardExist() && newState == State.ABSENT);
444        isIccCardAdded = (oldState == State.ABSENT &&
445                        newState != null && newState.iccCardExist());
446
447        if (transitionedIntoPinLocked) {
448            if (mDbg) log("Notify SIM pin or puk locked.");
449            mPinLockedRegistrants.notifyRegistrants();
450            broadcastIccStateChangedIntent(INTENT_VALUE_ICC_LOCKED,
451                    (newState == State.PIN_REQUIRED) ?
452                       INTENT_VALUE_LOCKED_ON_PIN : INTENT_VALUE_LOCKED_ON_PUK);
453        } else if (transitionedIntoAbsent) {
454            if (mDbg) log("Notify SIM missing.");
455            mAbsentRegistrants.notifyRegistrants();
456            broadcastIccStateChangedIntent(INTENT_VALUE_ICC_ABSENT, null);
457        } else if (transitionedIntoNetworkLocked) {
458            if (mDbg) log("Notify SIM network locked.");
459            mNetworkLockedRegistrants.notifyRegistrants();
460            broadcastIccStateChangedIntent(INTENT_VALUE_ICC_LOCKED,
461                  INTENT_VALUE_LOCKED_NETWORK);
462        } else if (transitionedIntoPermBlocked) {
463            if (mDbg) log("Notify SIM permanently disabled.");
464            broadcastIccStateChangedIntent(INTENT_VALUE_ICC_ABSENT,
465                    INTENT_VALUE_ABSENT_ON_PERM_DISABLED);
466        }
467
468        if (isIccCardRemoved) {
469            mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_REMOVED, null));
470        } else if (isIccCardAdded) {
471            mHandler.sendMessage(mHandler.obtainMessage(EVENT_CARD_ADDED, null));
472        }
473    }
474
475    private void onIccSwap(boolean isAdded) {
476        // TODO: Here we assume the device can't handle SIM hot-swap
477        //      and has to reboot. We may want to add a property,
478        //      e.g. REBOOT_ON_SIM_SWAP, to indicate if modem support
479        //      hot-swap.
480        DialogInterface.OnClickListener listener = null;
481
482
483        // TODO: SimRecords is not reset while SIM ABSENT (only reset while
484        //       Radio_off_or_not_available). Have to reset in both both
485        //       added or removed situation.
486        listener = new DialogInterface.OnClickListener() {
487            @Override
488            public void onClick(DialogInterface dialog, int which) {
489                if (which == DialogInterface.BUTTON_POSITIVE) {
490                    if (mDbg) log("Reboot due to SIM swap");
491                    PowerManager pm = (PowerManager) mPhone.getContext()
492                    .getSystemService(Context.POWER_SERVICE);
493                    pm.reboot("SIM is added.");
494                }
495            }
496
497        };
498
499        Resources r = Resources.getSystem();
500
501        String title = (isAdded) ? r.getString(R.string.sim_added_title) :
502            r.getString(R.string.sim_removed_title);
503        String message = (isAdded) ? r.getString(R.string.sim_added_message) :
504            r.getString(R.string.sim_removed_message);
505        String buttonTxt = r.getString(R.string.sim_restart_button);
506
507        AlertDialog dialog = new AlertDialog.Builder(mPhone.getContext())
508            .setTitle(title)
509            .setMessage(message)
510            .setPositiveButton(buttonTxt, listener)
511            .create();
512        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
513        dialog.show();
514    }
515
516    /**
517     * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
518     * @param ar is asyncResult of Query_Facility_Locked
519     */
520    private void onQueryFdnEnabled(AsyncResult ar) {
521        if(ar.exception != null) {
522            if(mDbg) log("Error in querying facility lock:" + ar.exception);
523            return;
524        }
525
526        int[] ints = (int[])ar.result;
527        if(ints.length != 0) {
528            mIccFdnEnabled = (0!=ints[0]);
529            if(mDbg) log("Query facility lock : "  + mIccFdnEnabled);
530        } else {
531            Log.e(mLogTag, "[IccCard] Bogus facility lock response");
532        }
533    }
534
535    /**
536     * Interperate EVENT_QUERY_FACILITY_LOCK_DONE
537     * @param ar is asyncResult of Query_Facility_Locked
538     */
539    private void onQueryFacilityLock(AsyncResult ar) {
540        if(ar.exception != null) {
541            if (mDbg) log("Error in querying facility lock:" + ar.exception);
542            return;
543        }
544
545        int[] ints = (int[])ar.result;
546        if(ints.length != 0) {
547            mIccPinLocked = (0!=ints[0]);
548            if(mDbg) log("Query facility lock : "  + mIccPinLocked);
549        } else {
550            Log.e(mLogTag, "[IccCard] Bogus facility lock response");
551        }
552    }
553
554    public void broadcastIccStateChangedIntent(String value, String reason) {
555        Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
556        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
557        intent.putExtra(Phone.PHONE_NAME_KEY, mPhone.getPhoneName());
558        intent.putExtra(INTENT_KEY_ICC_STATE, value);
559        intent.putExtra(INTENT_KEY_LOCKED_REASON, reason);
560        if(mDbg) log("Broadcasting intent ACTION_SIM_STATE_CHANGED " +  value
561                + " reason " + reason);
562        ActivityManagerNative.broadcastStickyIntent(intent, READ_PHONE_STATE);
563    }
564
565    protected Handler mHandler = new Handler() {
566        @Override
567        public void handleMessage(Message msg){
568            AsyncResult ar;
569            int serviceClassX;
570
571            serviceClassX = CommandsInterface.SERVICE_CLASS_VOICE +
572                            CommandsInterface.SERVICE_CLASS_DATA +
573                            CommandsInterface.SERVICE_CLASS_FAX;
574
575            if (!mPhone.mIsTheCurrentActivePhone) {
576                Log.e(mLogTag, "Received message " + msg + "[" + msg.what
577                        + "] while being destroyed. Ignoring.");
578                return;
579            }
580
581            switch (msg.what) {
582                case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
583                    mState = null;
584                    updateStateProperty();
585                    broadcastIccStateChangedIntent(INTENT_VALUE_ICC_NOT_READY, null);
586                    break;
587                case EVENT_ICC_READY:
588                    //TODO: put facility read in SIM_READY now, maybe in REG_NW
589                    mPhone.mCM.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
590                    mPhone.mCM.queryFacilityLock (
591                            CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
592                            obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
593                    mPhone.mCM.queryFacilityLock (
594                            CommandsInterface.CB_FACILITY_BA_FD, "", serviceClassX,
595                            obtainMessage(EVENT_QUERY_FACILITY_FDN_DONE));
596                    break;
597                case EVENT_ICC_LOCKED_OR_ABSENT:
598                    mPhone.mCM.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
599                    mPhone.mCM.queryFacilityLock (
600                            CommandsInterface.CB_FACILITY_BA_SIM, "", serviceClassX,
601                            obtainMessage(EVENT_QUERY_FACILITY_LOCK_DONE));
602                    break;
603                case EVENT_GET_ICC_STATUS_DONE:
604                    ar = (AsyncResult)msg.obj;
605
606                    getIccCardStatusDone(ar);
607                    break;
608                case EVENT_PINPUK_DONE:
609                    // a PIN/PUK/PIN2/PUK2/Network Personalization
610                    // request has completed. ar.userObj is the response Message
611                    // Repoll before returning
612                    ar = (AsyncResult)msg.obj;
613                    // TODO should abstract these exceptions
614                    AsyncResult.forMessage(((Message)ar.userObj)).exception
615                                                        = ar.exception;
616                    mPhone.mCM.getIccCardStatus(
617                        obtainMessage(EVENT_REPOLL_STATUS_DONE, ar.userObj));
618                    break;
619                case EVENT_REPOLL_STATUS_DONE:
620                    // Finished repolling status after PIN operation
621                    // ar.userObj is the response messaeg
622                    // ar.userObj.obj is already an AsyncResult with an
623                    // appropriate exception filled in if applicable
624
625                    ar = (AsyncResult)msg.obj;
626                    getIccCardStatusDone(ar);
627                    ((Message)ar.userObj).sendToTarget();
628                    break;
629                case EVENT_QUERY_FACILITY_LOCK_DONE:
630                    ar = (AsyncResult)msg.obj;
631                    onQueryFacilityLock(ar);
632                    break;
633                case EVENT_QUERY_FACILITY_FDN_DONE:
634                    ar = (AsyncResult)msg.obj;
635                    onQueryFdnEnabled(ar);
636                    break;
637                case EVENT_CHANGE_FACILITY_LOCK_DONE:
638                    ar = (AsyncResult)msg.obj;
639                    if (ar.exception == null) {
640                        mIccPinLocked = mDesiredPinLocked;
641                        if (mDbg) log( "EVENT_CHANGE_FACILITY_LOCK_DONE: " +
642                                "mIccPinLocked= " + mIccPinLocked);
643                    } else {
644                        Log.e(mLogTag, "Error change facility lock with exception "
645                            + ar.exception);
646                    }
647                    AsyncResult.forMessage(((Message)ar.userObj)).exception
648                                                        = ar.exception;
649                    ((Message)ar.userObj).sendToTarget();
650                    break;
651                case EVENT_CHANGE_FACILITY_FDN_DONE:
652                    ar = (AsyncResult)msg.obj;
653
654                    if (ar.exception == null) {
655                        mIccFdnEnabled = mDesiredFdnEnabled;
656                        if (mDbg) log("EVENT_CHANGE_FACILITY_FDN_DONE: " +
657                                "mIccFdnEnabled=" + mIccFdnEnabled);
658                    } else {
659                        Log.e(mLogTag, "Error change facility fdn with exception "
660                                + ar.exception);
661                    }
662                    AsyncResult.forMessage(((Message)ar.userObj)).exception
663                                                        = ar.exception;
664                    ((Message)ar.userObj).sendToTarget();
665                    break;
666                case EVENT_CHANGE_ICC_PASSWORD_DONE:
667                    ar = (AsyncResult)msg.obj;
668                    if(ar.exception != null) {
669                        Log.e(mLogTag, "Error in change sim password with exception"
670                            + ar.exception);
671                    }
672                    AsyncResult.forMessage(((Message)ar.userObj)).exception
673                                                        = ar.exception;
674                    ((Message)ar.userObj).sendToTarget();
675                    break;
676                case EVENT_ICC_STATUS_CHANGED:
677                    Log.d(mLogTag, "Received Event EVENT_ICC_STATUS_CHANGED");
678                    mPhone.mCM.getIccCardStatus(obtainMessage(EVENT_GET_ICC_STATUS_DONE));
679                    break;
680                case EVENT_CARD_REMOVED:
681                    onIccSwap(false);
682                    break;
683                case EVENT_CARD_ADDED:
684                    onIccSwap(true);
685                    break;
686                default:
687                    Log.e(mLogTag, "[IccCard] Unknown Event " + msg.what);
688            }
689        }
690    };
691
692    public State getIccCardState() {
693        if (mIccCardStatus == null) {
694            Log.e(mLogTag, "[IccCard] IccCardStatus is null");
695            return IccCard.State.ABSENT;
696        }
697
698        // this is common for all radio technologies
699        if (!mIccCardStatus.getCardState().isCardPresent()) {
700            return IccCard.State.ABSENT;
701        }
702
703        RadioState currentRadioState = mPhone.mCM.getRadioState();
704        // check radio technology
705        if( currentRadioState == RadioState.RADIO_OFF         ||
706            currentRadioState == RadioState.RADIO_UNAVAILABLE ||
707            currentRadioState == RadioState.SIM_NOT_READY     ||
708            currentRadioState == RadioState.RUIM_NOT_READY    ||
709            currentRadioState == RadioState.NV_NOT_READY      ||
710            currentRadioState == RadioState.NV_READY) {
711            return IccCard.State.NOT_READY;
712        }
713
714        if( currentRadioState == RadioState.SIM_LOCKED_OR_ABSENT  ||
715            currentRadioState == RadioState.SIM_READY             ||
716            currentRadioState == RadioState.RUIM_LOCKED_OR_ABSENT ||
717            currentRadioState == RadioState.RUIM_READY) {
718
719            State csimState =
720                getAppState(mIccCardStatus.getCdmaSubscriptionAppIndex());
721            State usimState =
722                getAppState(mIccCardStatus.getGsmUmtsSubscriptionAppIndex());
723
724            if(mDbg) log("USIM=" + usimState + " CSIM=" + csimState);
725
726            if (mPhone.getLteOnCdmaMode() == Phone.LTE_ON_CDMA_TRUE) {
727                // UICC card contains both USIM and CSIM
728                // Return consolidated status
729                return getConsolidatedState(csimState, usimState, csimState);
730            }
731
732            // check for CDMA radio technology
733            if (currentRadioState == RadioState.RUIM_LOCKED_OR_ABSENT ||
734                currentRadioState == RadioState.RUIM_READY) {
735                return csimState;
736            }
737            return usimState;
738        }
739
740        return IccCard.State.ABSENT;
741    }
742
743    private State getAppState(int appIndex) {
744        IccCardApplication app;
745        if (appIndex >= 0 && appIndex < IccCardStatus.CARD_MAX_APPS) {
746            app = mIccCardStatus.getApplication(appIndex);
747        } else {
748            Log.e(mLogTag, "[IccCard] Invalid Subscription Application index:" + appIndex);
749            return IccCard.State.ABSENT;
750        }
751
752        if (app == null) {
753            Log.e(mLogTag, "[IccCard] Subscription Application in not present");
754            return IccCard.State.ABSENT;
755        }
756
757        // check if PIN required
758        if (app.pin1.isPermBlocked()) {
759            return IccCard.State.PERM_DISABLED;
760        }
761        if (app.app_state.isPinRequired()) {
762            return IccCard.State.PIN_REQUIRED;
763        }
764        if (app.app_state.isPukRequired()) {
765            return IccCard.State.PUK_REQUIRED;
766        }
767        if (app.app_state.isSubscriptionPersoEnabled()) {
768            return IccCard.State.NETWORK_LOCKED;
769        }
770        if (app.app_state.isAppReady()) {
771            return IccCard.State.READY;
772        }
773        if (app.app_state.isAppNotReady()) {
774            return IccCard.State.NOT_READY;
775        }
776        return IccCard.State.NOT_READY;
777    }
778
779    private State getConsolidatedState(State left, State right, State preferredState) {
780        // Check if either is absent.
781        if (right == IccCard.State.ABSENT) return left;
782        if (left == IccCard.State.ABSENT) return right;
783
784        // Only if both are ready, return ready
785        if ((left == IccCard.State.READY) && (right == IccCard.State.READY)) {
786            return State.READY;
787        }
788
789        // Case one is ready, but the other is not.
790        if (((right == IccCard.State.NOT_READY) && (left == IccCard.State.READY)) ||
791            ((left == IccCard.State.NOT_READY) && (right == IccCard.State.READY))) {
792            return IccCard.State.NOT_READY;
793        }
794
795        // At this point, the other state is assumed to be one of locked state
796        if (right == IccCard.State.NOT_READY) return left;
797        if (left == IccCard.State.NOT_READY) return right;
798
799        // At this point, FW currently just assumes the status will be
800        // consistent across the applications...
801        return preferredState;
802    }
803
804    public boolean isApplicationOnIcc(IccCardApplication.AppType type) {
805        if (mIccCardStatus == null) return false;
806
807        for (int i = 0 ; i < mIccCardStatus.getNumApplications(); i++) {
808            IccCardApplication app = mIccCardStatus.getApplication(i);
809            if (app != null && app.app_type == type) {
810                return true;
811            }
812        }
813        return false;
814    }
815
816    /**
817     * @return true if a ICC card is present
818     */
819    public boolean hasIccCard() {
820        if (mIccCardStatus == null) {
821            return false;
822        } else {
823            // Returns ICC card status for both GSM and CDMA mode
824            return mIccCardStatus.getCardState().isCardPresent();
825        }
826    }
827
828    private void log(String msg) {
829        Log.d(mLogTag, "[IccCard] " + msg);
830    }
831}
832