SIMRecords.java revision 88b861c8a6c7c6a669ad2fe1bcef580159c2ace4
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.gsm;
18
19import android.app.AlarmManager;
20import android.content.Context;
21import android.os.AsyncResult;
22import android.os.RegistrantList;
23import android.os.Registrant;
24import android.os.Handler;
25import android.os.Message;
26import android.os.SystemProperties;
27import android.telephony.gsm.SmsMessage;
28import android.util.Log;
29import java.util.ArrayList;
30
31import static com.android.internal.telephony.TelephonyProperties.*;
32import com.android.internal.telephony.SimCard;
33
34/**
35 * {@hide}
36 */
37public final class SIMRecords extends Handler implements SimConstants
38{
39    static final String LOG_TAG = "GSM";
40
41    private static final boolean CRASH_RIL = false;
42
43    private static final boolean DBG = true;
44
45    //***** Instance Variables
46
47    GSMPhone phone;
48    RegistrantList recordsLoadedRegistrants = new RegistrantList();
49
50    int recordsToLoad;  // number of pending load requests
51
52    AdnRecordCache adnCache;
53
54    VoiceMailConstants mVmConfig;
55    SpnOverride mSpnOverride;
56
57    //***** Cached SIM State; cleared on channel close
58
59    boolean recordsRequested = false; // true if we've made requests for the sim records
60
61    String imsi;
62    String iccid;
63    String msisdn = null;  // My mobile number
64    String msisdnTag = null;
65    String voiceMailNum = null;
66    String voiceMailTag = null;
67    String newVoiceMailNum = null;
68    String newVoiceMailTag = null;
69    boolean isVoiceMailFixed = false;
70    int countVoiceMessages = 0;
71    boolean callForwardingEnabled;
72    int mncLength = 0;   // 0 is used to indicate that the value
73                         // is not initialized
74    int mailboxIndex = 0; // 0 is no mailbox dailing number associated
75
76    /**
77     * Sates only used by getSpnFsm FSM
78     */
79    private Get_Spn_Fsm_State spnState;
80
81    /** CPHS service information (See CPHS 4.2 B.3.1.1)
82     *  It will be set in onSimReady if reading GET_CPHS_INFO successfully
83     *  mCphsInfo[0] is CPHS Phase
84     *  mCphsInfo[1] and mCphsInfo[2] is CPHS Service Table
85     */
86    private byte[] mCphsInfo = null;
87
88    byte[] efMWIS = null;
89    byte[] efCPHS_MWI =null;
90    byte[] mEfCff = null;
91    byte[] mEfCfis = null;
92
93
94    String spn;
95    int spnDisplayCondition;
96    // Numeric network codes listed in TS 51.011 EF[SPDI]
97    ArrayList<String> spdiNetworks = null;
98
99    String pnnHomeName = null;
100
101    //***** Constants
102
103    // Bitmasks for SPN display rules.
104    static final int SPN_RULE_SHOW_SPN  = 0x01;
105    static final int SPN_RULE_SHOW_PLMN = 0x02;
106
107    // From TS 51.011 EF[SPDI] section
108    static final int TAG_SPDI_PLMN_LIST = 0x80;
109
110    // Full Name IEI from TS 24.008
111    static final int TAG_FULL_NETWORK_NAME = 0x43;
112
113    // Short Name IEI from TS 24.008
114    static final int TAG_SHORT_NETWORK_NAME = 0x45;
115
116    // active CFF from CPHS 4.2 B.4.5
117    static final int CFF_UNCONDITIONAL_ACTIVE = 0x0a;
118    static final int CFF_UNCONDITIONAL_DEACTIVE = 0x05;
119    static final int CFF_LINE1_MASK = 0x0f;
120    static final int CFF_LINE1_RESET = 0xf0;
121
122    // CPHS Service Table (See CPHS 4.2 B.3.1)
123    private static final int CPHS_SST_MBN_MASK = 0x30;
124    private static final int CPHS_SST_MBN_ENABLED = 0x30;
125
126    //***** Event Constants
127
128    private static final int EVENT_SIM_READY = 1;
129    private static final int EVENT_RADIO_OFF_OR_NOT_AVAILABLE = 2;
130    private static final int EVENT_GET_IMSI_DONE = 3;
131    private static final int EVENT_GET_ICCID_DONE = 4;
132    private static final int EVENT_GET_MBI_DONE = 5;
133    private static final int EVENT_GET_MBDN_DONE = 6;
134    private static final int EVENT_GET_MWIS_DONE = 7;
135    private static final int EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE = 8;
136    private static final int EVENT_GET_AD_DONE = 9; // Admin data on SIM
137    private static final int EVENT_GET_MSISDN_DONE = 10;
138    private static final int EVENT_GET_CPHS_MAILBOX_DONE = 11;
139    private static final int EVENT_GET_SPN_DONE = 12;
140    private static final int EVENT_GET_SPDI_DONE = 13;
141    private static final int EVENT_UPDATE_DONE = 14;
142    private static final int EVENT_GET_PNN_DONE = 15;
143    private static final int EVENT_GET_SST_DONE = 17;
144    private static final int EVENT_GET_ALL_SMS_DONE = 18;
145    private static final int EVENT_MARK_SMS_READ_DONE = 19;
146    private static final int EVENT_SET_MBDN_DONE = 20;
147    private static final int EVENT_SMS_ON_SIM = 21;
148    private static final int EVENT_GET_SMS_DONE = 22;
149    private static final int EVENT_GET_CFF_DONE = 24;
150    private static final int EVENT_SET_CPHS_MAILBOX_DONE = 25;
151    private static final int EVENT_GET_INFO_CPHS_DONE = 26;
152    private static final int EVENT_SET_MSISDN_DONE = 30;
153    private static final int EVENT_SIM_REFRESH = 31;
154    private static final int EVENT_GET_CFIS_DONE = 32;
155
156    private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
157
158    //***** Constructor
159
160    SIMRecords(GSMPhone phone)
161    {
162        this.phone = phone;
163
164        adnCache = new AdnRecordCache(phone);
165
166        mVmConfig = new VoiceMailConstants();
167        mSpnOverride = new SpnOverride();
168
169        recordsRequested = false;  // No load request is made till SIM ready
170
171        // recordsToLoad is set to 0 because no requests are made yet
172        recordsToLoad = 0;
173
174
175        phone.mCM.registerForSIMReady(this, EVENT_SIM_READY, null);
176        phone.mCM.registerForOffOrNotAvailable(
177                        this, EVENT_RADIO_OFF_OR_NOT_AVAILABLE, null);
178        phone.mCM.setOnSmsOnSim(this, EVENT_SMS_ON_SIM, null);
179        phone.mCM.setOnSimRefresh(this, EVENT_SIM_REFRESH, null);
180
181        // Start off by setting empty state
182        onRadioOffOrNotAvailable();
183
184    }
185
186    AdnRecordCache getAdnCache() {
187        return adnCache;
188    }
189
190    private void onRadioOffOrNotAvailable()
191    {
192        imsi = null;
193        msisdn = null;
194        voiceMailNum = null;
195        countVoiceMessages = 0;
196        mncLength = 0;
197        iccid = null;
198        spn = null;
199        // -1 means no EF_SPN found; treat accordingly.
200        spnDisplayCondition = -1;
201        efMWIS = null;
202        efCPHS_MWI = null;
203        spdiNetworks = null;
204        pnnHomeName = null;
205
206        adnCache.reset();
207
208        phone.setSystemProperty(PROPERTY_SIM_OPERATOR_NUMERIC, null);
209        phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, null);
210        phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ISO_COUNTRY, null);
211
212        // recordsRequested is set to false indicating that the SIM
213        // read requests made so far are not valid. This is set to
214        // true only when fresh set of read requests are made.
215        recordsRequested = false;
216    }
217
218
219    //***** Public Methods
220    public void registerForRecordsLoaded(Handler h, int what, Object obj)
221    {
222        Registrant r = new Registrant(h, what, obj);
223        recordsLoadedRegistrants.add(r);
224
225        if (recordsToLoad == 0 && recordsRequested == true) {
226            r.notifyRegistrant(new AsyncResult(null, null, null));
227        }
228    }
229
230    /** Returns null if SIM is not yet ready */
231    public String getIMSI()
232    {
233        return imsi;
234    }
235
236    public String getMsisdnNumber()
237    {
238        return msisdn;
239    }
240
241    /**
242     * Set subscriber number to SIM record
243     *
244     * The subscriber number is stored in EF_MSISDN (TS 51.011)
245     *
246     * When the operation is complete, onComplete will be sent to its handler
247     *
248     * @param alphaTag alpha-tagging of the dailing nubmer (up to 10 characters)
249     * @param number dailing nubmer (up to 20 digits)
250     *        if the number starts with '+', then set to international TOA
251     * @param onComplete
252     *        onComplete.obj will be an AsyncResult
253     *        ((AsyncResult)onComplete.obj).exception == null on success
254     *        ((AsyncResult)onComplete.obj).exception != null on fail
255     */
256    public void setMsisdnNumber(String alphaTag, String number,
257            Message onComplete) {
258
259        msisdn = number;
260        msisdnTag = alphaTag;
261
262        if(DBG) log("Set MSISDN: " + msisdnTag +" " + msisdn);
263
264
265        AdnRecord adn = new AdnRecord(msisdnTag, msisdn);
266
267        new AdnRecordLoader(phone).updateEF(adn, EF_MSISDN, EF_EXT1, 1, null,
268                obtainMessage(EVENT_SET_MSISDN_DONE, onComplete));
269    }
270
271    public String getMsisdnAlphaTag() {
272        return msisdnTag;
273    }
274
275    public String getVoiceMailNumber()
276    {
277        return voiceMailNum;
278    }
279
280    /**
281     * Return Service Provider Name stored in SIM
282     * @return null if SIM is not yet ready
283     */
284    String getServiceProviderName()
285    {
286        return spn;
287    }
288
289    /**
290     * Set voice mail number to SIM record
291     *
292     * The voice mail number can be stored either in EF_MBDN (TS 51.011) or
293     * EF_MAILBOX_CPHS (CPHS 4.2)
294     *
295     * If EF_MBDN is available, store the voice mail number to EF_MBDN
296     *
297     * If EF_MAILBOX_CPHS is enabled, store the voice mail number to EF_CHPS
298     *
299     * So the voice mail number will be stored in both EFs if both are available
300     *
301     * Return error only if both EF_MBDN and EF_MAILBOX_CPHS fail.
302     *
303     * When the operation is complete, onComplete will be sent to its handler
304     *
305     * @param alphaTag alpha-tagging of the dailing nubmer (upto 10 characters)
306     * @param voiceNumber dailing nubmer (upto 20 digits)
307     *        if the number is start with '+', then set to international TOA
308     * @param onComplete
309     *        onComplete.obj will be an AsyncResult
310     *        ((AsyncResult)onComplete.obj).exception == null on success
311     *        ((AsyncResult)onComplete.obj).exception != null on fail
312     */
313    public void setVoiceMailNumber(String alphaTag, String voiceNumber,
314            Message onComplete) {
315        if (isVoiceMailFixed) {
316            AsyncResult.forMessage((onComplete)).exception =
317                    new SimVmFixedException("Voicemail number is fixed by operator");
318            onComplete.sendToTarget();
319            return;
320        }
321
322        newVoiceMailNum = voiceNumber;
323        newVoiceMailTag = alphaTag;
324
325        AdnRecord adn = new AdnRecord(newVoiceMailTag, newVoiceMailNum);
326
327        if (mailboxIndex != 0 && mailboxIndex != 0xff) {
328
329            new AdnRecordLoader(phone).updateEF(adn, EF_MBDN, EF_EXT6,
330                    mailboxIndex, null,
331                    obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
332
333        } else if (isCphsMailboxEnabled()) {
334
335            new AdnRecordLoader(phone).updateEF(adn, EF_MAILBOX_CPHS,
336                    EF_EXT1, 1, null,
337                    obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
338
339        }else {
340            AsyncResult.forMessage((onComplete)).exception =
341                    new SimVmNotSupportedException("Update SIM voice mailbox error");
342            onComplete.sendToTarget();
343        }
344    }
345
346    public String getVoiceMailAlphaTag()
347    {
348        return voiceMailTag;
349    }
350
351    /**
352     * Sets the SIM voice message waiting indicator records
353     * @param line GSM Subscriber Profile Number, one-based. Only '1' is supported
354     * @param countWaiting The number of messages waiting, if known. Use
355     *                     -1 to indicate that an unknown number of
356     *                      messages are waiting
357     */
358    public void
359    setVoiceMessageWaiting(int line, int countWaiting)
360    {
361        if (line != 1) {
362            // only profile 1 is supported
363            return;
364        }
365
366        // range check
367        if (countWaiting < 0) {
368            countWaiting = -1;
369        } else if (countWaiting > 0xff) {
370            // TS 23.040 9.2.3.24.2
371            // "The value 255 shall be taken to mean 255 or greater"
372            countWaiting = 0xff;
373        }
374
375        countVoiceMessages = countWaiting;
376
377        phone.notifyMessageWaitingIndicator();
378
379        try {
380            if (efMWIS != null) {
381                // TS 51.011 10.3.45
382
383                // lsb of byte 0 is 'voicemail' status
384                efMWIS[0] = (byte)((efMWIS[0] & 0xfe)
385                                    | (countVoiceMessages == 0 ? 0 : 1));
386
387                // byte 1 is the number of voice messages waiting
388                if (countWaiting < 0) {
389                    // The spec does not define what this should be
390                    // if we don't know the count
391                    efMWIS[1] = 0;
392                } else {
393                    efMWIS[1] = (byte) countWaiting;
394                }
395
396                phone.mSIMFileHandler.updateEFLinearFixed(
397                    EF_MWIS, 1, efMWIS, null,
398                    obtainMessage (EVENT_UPDATE_DONE, EF_MWIS));
399            }
400
401            if (efCPHS_MWI != null) {
402                    // Refer CPHS4_2.WW6 B4.2.3
403                efCPHS_MWI[0] = (byte)((efCPHS_MWI[0] & 0xf0)
404                            | (countVoiceMessages == 0 ? 0x5 : 0xa));
405
406                phone.mSIMFileHandler.updateEFTransparent(
407                    EF_VOICE_MAIL_INDICATOR_CPHS, efCPHS_MWI,
408                    obtainMessage (EVENT_UPDATE_DONE, EF_VOICE_MAIL_INDICATOR_CPHS));
409            }
410        } catch (ArrayIndexOutOfBoundsException ex) {
411            Log.w(LOG_TAG,
412                "Error saving voice mail state to SIM. Probably malformed SIM record", ex);
413        }
414    }
415
416    /** @return  true if there are messages waiting, false otherwise. */
417    public boolean getVoiceMessageWaiting()
418    {
419        return countVoiceMessages != 0;
420    }
421
422    /**
423     * Returns number of voice messages waiting, if available
424     * If not available (eg, on an older CPHS SIM) -1 is returned if
425     * getVoiceMessageWaiting() is true
426     */
427    public int getCountVoiceMessages()
428    {
429        return countVoiceMessages;
430    }
431
432    public boolean getVoiceCallForwardingFlag() {
433        return callForwardingEnabled;
434    }
435
436    public void setVoiceCallForwardingFlag(int line, boolean enable) {
437
438        if (line != 1) return; // only line 1 is supported
439
440        callForwardingEnabled = enable;
441
442        phone.notifyCallForwardingIndicator();
443
444        try {
445            if (mEfCfis != null) {
446                // lsb is of byte 1 is voice status
447                if (enable) {
448                    mEfCfis[1] |= 1;
449                } else {
450                    mEfCfis[1] &= 0xfe;
451                }
452
453                // TODO: Should really update other fields in EF_CFIS, eg,
454                // dialing number.  We don't read or use it right now.
455
456                phone.mSIMFileHandler.updateEFLinearFixed(
457                        EF_CFIS, 1, mEfCfis, null,
458                        obtainMessage (EVENT_UPDATE_DONE, EF_CFIS));
459            }
460
461            if (mEfCff != null) {
462                if (enable) {
463                    mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
464                            | CFF_UNCONDITIONAL_ACTIVE);
465                } else {
466                    mEfCff[0] = (byte) ((mEfCff[0] & CFF_LINE1_RESET)
467                            | CFF_UNCONDITIONAL_DEACTIVE);
468                }
469
470                phone.mSIMFileHandler.updateEFTransparent(
471                        EF_CFF_CPHS, mEfCff,
472                        obtainMessage (EVENT_UPDATE_DONE, EF_CFF_CPHS));
473            }
474        } catch (ArrayIndexOutOfBoundsException ex) {
475            Log.w(LOG_TAG,
476                    "Error saving call fowarding flag to SIM. "
477                            + "Probably malformed SIM record", ex);
478
479        }
480    }
481
482    /**
483     * Called by STK Service when REFRESH is received.
484     * @param fileChanged indicates whether any files changed
485     * @param fileList if non-null, a list of EF files that changed
486     */
487    public void onRefresh(boolean fileChanged, int[] fileList) {
488        if (fileChanged) {
489            // A future optimization would be to inspect fileList and
490            // only reload those files that we care about.  For now,
491            // just re-fetch all SIM records that we cache.
492            fetchSimRecords();
493        }
494    }
495
496    /** Returns the 5 or 6 digit MCC/MNC of the operator that
497     *  provided the SIM card. Returns null of SIM is not yet ready
498     */
499    String getSIMOperatorNumeric()
500    {
501        if (imsi == null) {
502            return null;
503        }
504
505        if (mncLength != 0) {
506            // Length = length of MCC + length of MNC
507            // length of mcc = 3 (TS 23.003 Section 2.2)
508            return imsi.substring(0, 3 + mncLength);
509        }
510
511        // Guess the MNC length based on the MCC if we don't
512        // have a valid value in ef[ad]
513
514        int mcc;
515
516        mcc = Integer.parseInt(imsi.substring(0,3));
517
518        return imsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
519    }
520
521    boolean getRecordsLoaded()
522    {
523        if (recordsToLoad == 0 && recordsRequested == true) {
524            return true;
525        } else {
526            return false;
527        }
528    }
529
530    /**
531     * If the timezone is not already set, set it based on the MCC of the SIM.
532     * @param mcc Mobile Country Code of the SIM
533     */
534    private void setTimezoneFromMccIfNeeded(int mcc) {
535        String timezone = SystemProperties.get(TIMEZONE_PROPERTY);
536        if (timezone == null || timezone.length() == 0) {
537            String zoneId = MccTable.defaultTimeZoneForMcc(mcc);
538
539            if (zoneId != null && zoneId.length() > 0) {
540                // Set time zone based on MCC
541                AlarmManager alarm =
542                    (AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
543                alarm.setTimeZone(zoneId);
544            }
545        }
546    }
547
548    /**
549     * If the locale is not already set, set it based on the MCC of the SIM.
550     * @param mcc Mobile Country Code of the SIM
551     */
552    private void setLocaleFromMccIfNeeded(int mcc) {
553        String language = MccTable.defaultLanguageForMcc(mcc);
554        String country = MccTable.countryCodeForMcc(mcc);
555
556        phone.setSystemLocale(language, country);
557    }
558
559    //***** Overridden from Handler
560    public void handleMessage(Message msg)
561    {
562        AsyncResult ar;
563        AdnRecord adn;
564
565        byte data[];
566
567        boolean isRecordLoadResponse = false;
568
569        try { switch (msg.what) {
570            case EVENT_SIM_READY:
571                onSimReady();
572            break;
573
574            case EVENT_RADIO_OFF_OR_NOT_AVAILABLE:
575                onRadioOffOrNotAvailable();
576            break;
577
578            /* IO events */
579            case EVENT_GET_IMSI_DONE:
580                isRecordLoadResponse = true;
581
582                ar = (AsyncResult)msg.obj;
583
584                if (ar.exception != null) {
585                    Log.e(LOG_TAG, "Exception querying IMSI, Exception:" + ar.exception);
586                    break;
587                }
588
589                imsi = (String) ar.result;
590
591                // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
592                // than 15 (and usually 15).
593                if (imsi != null && (imsi.length() < 6 || imsi.length() > 15)) {
594                    Log.e(LOG_TAG, "invalid IMSI " + imsi);
595                    imsi = null;
596                }
597
598                Log.d(LOG_TAG, "IMSI: " + imsi.substring(0, 6) + "xxxxxxxxx");
599                phone.mSimCard.updateImsiConfiguration(imsi);
600                phone.mSimCard.broadcastSimStateChangedIntent(
601                        SimCard.INTENT_VALUE_SIM_IMSI, null);
602
603                int mcc = Integer.parseInt(imsi.substring(0, 3));
604                setTimezoneFromMccIfNeeded(mcc);
605                setLocaleFromMccIfNeeded(mcc);
606            break;
607
608            case EVENT_GET_MBI_DONE:
609                boolean isValidMbdn;
610                isRecordLoadResponse = true;
611
612                ar = (AsyncResult)msg.obj;
613                data = (byte[]) ar.result;
614
615                isValidMbdn = false;
616                if (ar.exception == null) {
617                    // Refer TS 51.011 Section 10.3.44 for content details
618                    Log.d(LOG_TAG, "EF_MBI: " +
619                            SimUtils.bytesToHexString(data));
620
621                    // Voice mail record number stored first
622                    mailboxIndex = (int)data[0] & 0xff;
623
624                    // check if dailing numbe id valid
625                    if (mailboxIndex != 0 && mailboxIndex != 0xff) {
626                        Log.d(LOG_TAG, "Got valid mailbox number for MBDN");
627                        isValidMbdn = true;
628                    }
629                }
630
631                // one more record to load
632                recordsToLoad += 1;
633
634                if (isValidMbdn) {
635                    // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED
636                    new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
637                            mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
638                } else {
639                    // If this EF not present, try mailbox as in CPHS standard
640                    // CPHS (CPHS4_2.WW6) is a european standard.
641                    new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS,
642                            EF_EXT1, 1,
643                            obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
644                }
645
646                break;
647            case EVENT_GET_CPHS_MAILBOX_DONE:
648            case EVENT_GET_MBDN_DONE:
649                isRecordLoadResponse = true;
650
651                ar = (AsyncResult)msg.obj;
652
653                if (ar.exception != null) {
654
655                    Log.d(LOG_TAG, "Invalid or missing EF"
656                        + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) ? "[MAILBOX]" : "[MBDN]"));
657
658                    // Bug #645770 fall back to CPHS
659                    // FIXME should use SST to decide
660
661                    if (msg.what == EVENT_GET_MBDN_DONE) {
662                        //load CPHS on fail...
663                        // FIXME right now, only load line1's CPHS voice mail entry
664
665                        recordsToLoad += 1;
666                        new AdnRecordLoader(phone).loadFromEF(
667                                EF_MAILBOX_CPHS, EF_EXT1, 1,
668                                obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
669                    }
670                    break;
671                }
672
673                adn = (AdnRecord)ar.result;
674
675                Log.d(LOG_TAG, "VM: " + adn + ((msg.what == EVENT_GET_CPHS_MAILBOX_DONE) ? " EF[MAILBOX]" : " EF[MBDN]"));
676
677                if (adn.isEmpty() && msg.what == EVENT_GET_MBDN_DONE) {
678                    // Bug #645770 fall back to CPHS
679                    // FIXME should use SST to decide
680                    // FIXME right now, only load line1's CPHS voice mail entry
681                    recordsToLoad += 1;
682                    new AdnRecordLoader(phone).loadFromEF(
683                            EF_MAILBOX_CPHS, EF_EXT1, 1,
684                            obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
685
686                    break;
687                }
688
689                voiceMailNum = adn.getNumber();
690                voiceMailTag = adn.getAlphaTag();
691            break;
692
693            case EVENT_GET_MSISDN_DONE:
694                isRecordLoadResponse = true;
695
696                ar = (AsyncResult)msg.obj;
697
698                if (ar.exception != null) {
699                    Log.d(LOG_TAG, "Invalid or missing EF[MSISDN]");
700                    break;
701                }
702
703                adn = (AdnRecord)ar.result;
704
705                msisdn = adn.getNumber();
706                msisdnTag = adn.getAlphaTag();
707
708                Log.d(LOG_TAG, "MSISDN: " + msisdn);
709            break;
710
711            case EVENT_SET_MSISDN_DONE:
712                isRecordLoadResponse = false;
713                ar = (AsyncResult)msg.obj;
714
715                if (ar.userObj != null) {
716                    AsyncResult.forMessage(((Message) ar.userObj)).exception
717                            = ar.exception;
718                    ((Message) ar.userObj).sendToTarget();
719                }
720                break;
721
722            case EVENT_GET_MWIS_DONE:
723                isRecordLoadResponse = true;
724
725                ar = (AsyncResult)msg.obj;
726                data = (byte[])ar.result;
727
728                if (ar.exception != null) {
729                    break;
730                }
731
732                Log.d(LOG_TAG, "EF_MWIS: " +
733                   SimUtils.bytesToHexString(data));
734
735                efMWIS = data;
736
737                if ((data[0] & 0xff) == 0xff) {
738                    Log.d(LOG_TAG, "SIMRecords: Uninitialized record MWIS");
739                    break;
740                }
741
742                // Refer TS 51.011 Section 10.3.45 for the content description
743                boolean voiceMailWaiting = ((data[0] & 0x01) != 0);
744                countVoiceMessages = data[1] & 0xff;
745
746                if (voiceMailWaiting && countVoiceMessages == 0) {
747                    // Unknown count = -1
748                    countVoiceMessages = -1;
749                }
750
751                phone.notifyMessageWaitingIndicator();
752            break;
753
754            case EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE:
755                isRecordLoadResponse = true;
756
757                ar = (AsyncResult)msg.obj;
758                data = (byte[])ar.result;
759
760                if (ar.exception != null) {
761                    break;
762                }
763
764                efCPHS_MWI = data;
765
766                // Use this data if the EF[MWIS] exists and
767                // has been loaded
768
769                if (efMWIS == null) {
770                    int indicator = (int)(data[0] & 0xf);
771
772                    // Refer CPHS4_2.WW6 B4.2.3
773                    if (indicator == 0xA) {
774                        // Unknown count = -1
775                        countVoiceMessages = -1;
776                    } else if (indicator == 0x5) {
777                        countVoiceMessages = 0;
778                    }
779
780                    phone.notifyMessageWaitingIndicator();
781                }
782            break;
783
784            case EVENT_GET_ICCID_DONE:
785                isRecordLoadResponse = true;
786
787                ar = (AsyncResult)msg.obj;
788                data = (byte[])ar.result;
789
790                if (ar.exception != null) {
791                    break;
792                }
793
794                iccid = SimUtils.bcdToString(data, 0, data.length);
795
796                Log.d(LOG_TAG, "iccid: " + iccid);
797
798            break;
799
800
801            case EVENT_GET_AD_DONE:
802                isRecordLoadResponse = true;
803
804                ar = (AsyncResult)msg.obj;
805                data = (byte[])ar.result;
806
807                if (ar.exception != null) {
808                    break;
809                }
810
811                Log.d(LOG_TAG, "EF_AD: " +
812                    SimUtils.bytesToHexString(data));
813
814                if (data.length < 3) {
815                    Log.d(LOG_TAG, "SIMRecords: Corrupt AD data on SIM");
816                    break;
817                }
818
819                if (data.length == 3) {
820                    Log.d(LOG_TAG, "SIMRecords: MNC length not present in EF_AD");
821                    break;
822                }
823
824                mncLength = (int)data[3] & 0xf;
825
826                if (mncLength == 0xf) {
827                    // Resetting mncLength to 0 to indicate that it is not
828                    // initialised
829                    mncLength = 0;
830
831                    Log.d(LOG_TAG, "SIMRecords: MNC length not present in EF_AD");
832                    break;
833                }
834
835            break;
836
837            case EVENT_GET_SPN_DONE:
838                isRecordLoadResponse = true;
839                ar = (AsyncResult) msg.obj;
840                getSpnFsm(false, ar);
841            break;
842
843            case EVENT_GET_CFF_DONE:
844                isRecordLoadResponse = true;
845
846                ar = (AsyncResult) msg.obj;
847                data = (byte[]) ar.result;
848
849                if (ar.exception != null) {
850                    break;
851                }
852
853                Log.d(LOG_TAG, "EF_CFF_CPHS: " +
854                        SimUtils.bytesToHexString(data));
855                mEfCff = data;
856
857                if (mEfCfis == null) {
858                    callForwardingEnabled =
859                        ((data[0] & CFF_LINE1_MASK) == CFF_UNCONDITIONAL_ACTIVE);
860
861                    phone.notifyCallForwardingIndicator();
862                }
863                break;
864
865            case EVENT_GET_SPDI_DONE:
866                isRecordLoadResponse = true;
867
868                ar = (AsyncResult)msg.obj;
869                data = (byte[])ar.result;
870
871                if (ar.exception != null) {
872                    break;
873                }
874
875                parseEfSpdi(data);
876            break;
877
878            case EVENT_UPDATE_DONE:
879                ar = (AsyncResult)msg.obj;
880                if (ar.exception != null) {
881                    Log.i(LOG_TAG, "SIMRecords update failed", ar.exception);
882                }
883            break;
884
885            case EVENT_GET_PNN_DONE:
886                isRecordLoadResponse = true;
887
888                ar = (AsyncResult)msg.obj;
889                data = (byte[])ar.result;
890
891                if (ar.exception != null) {
892                    break;
893                }
894
895                SimTlv tlv = new SimTlv(data, 0, data.length);
896
897                for ( ; tlv.isValidObject() ; tlv.nextObject()) {
898                    if (tlv.getTag() == TAG_FULL_NETWORK_NAME) {
899                        pnnHomeName
900                            = SimUtils.networkNameToString(
901                                tlv.getData(), 0, tlv.getData().length);
902                        break;
903                    }
904                }
905            break;
906
907            case EVENT_GET_ALL_SMS_DONE:
908                isRecordLoadResponse = true;
909
910                ar = (AsyncResult)msg.obj;
911                if (ar.exception != null)
912                    break;
913
914                handleSmses((ArrayList) ar.result);
915                break;
916
917            case EVENT_MARK_SMS_READ_DONE:
918                Log.i("ENF", "marked read: sms " + msg.arg1);
919                break;
920
921
922            case EVENT_SMS_ON_SIM:
923                isRecordLoadResponse = false;
924
925                ar = (AsyncResult)msg.obj;
926
927                int[] index = (int[])ar.result;
928
929                if (ar.exception != null || index.length != 1) {
930                    Log.e(LOG_TAG, "[SIMRecords] Error on SMS_ON_SIM with exp "
931                            + ar.exception + " length " + index.length);
932                } else {
933                    Log.d(LOG_TAG, "READ EF_SMS RECORD index=" + index[0]);
934                    phone.mSIMFileHandler.loadEFLinearFixed(EF_SMS,index[0],obtainMessage(EVENT_GET_SMS_DONE));
935                }
936                break;
937
938            case EVENT_GET_SMS_DONE:
939                isRecordLoadResponse = false;
940                ar = (AsyncResult)msg.obj;
941                if (ar.exception == null) {
942                    handleSms((byte[])ar.result);
943                } else {
944                    Log.e(LOG_TAG, "[SIMRecords] Error on GET_SMS with exp "
945                            + ar.exception);
946                }
947                break;
948            case EVENT_GET_SST_DONE:
949                isRecordLoadResponse = true;
950
951                ar = (AsyncResult)msg.obj;
952                data = (byte[])ar.result;
953
954                if (ar.exception != null) {
955                    break;
956                }
957
958                //Log.d(LOG_TAG, "SST: " + SimUtils.bytesToHexString(data));
959            break;
960
961            case EVENT_GET_INFO_CPHS_DONE:
962                isRecordLoadResponse = true;
963
964                ar = (AsyncResult)msg.obj;
965
966                if (ar.exception != null) {
967                    break;
968                }
969
970                mCphsInfo = (byte[])ar.result;
971
972                if (DBG) log("iCPHS: " + SimUtils.bytesToHexString(mCphsInfo));
973            break;
974
975            case EVENT_SET_MBDN_DONE:
976                isRecordLoadResponse = false;
977                ar = (AsyncResult)msg.obj;
978
979                if (ar.exception == null) {
980                    voiceMailNum = newVoiceMailNum;
981                    voiceMailTag = newVoiceMailTag;
982                }
983
984                if (isCphsMailboxEnabled()) {
985                    adn = new AdnRecord(voiceMailTag, voiceMailNum);
986                    Message onCphsCompleted = (Message) ar.userObj;
987
988                    /* write to cphs mailbox whenever it is available but
989                    * we only need notify caller once if both updating are
990                    * successful.
991                    *
992                    * so if set_mbdn successful, notify caller here and set
993                    * onCphsCompleted to null
994                    */
995                    if (ar.exception == null && ar.userObj != null) {
996                        AsyncResult.forMessage(((Message) ar.userObj)).exception
997                                = null;
998                        ((Message) ar.userObj).sendToTarget();
999
1000                        if (DBG) log("Callback with MBDN successful.");
1001
1002                        onCphsCompleted = null;
1003                    }
1004
1005                    new AdnRecordLoader(phone).
1006                            updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
1007                            obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
1008                                    onCphsCompleted));
1009                } else {
1010                    if (ar.userObj != null) {
1011                        AsyncResult.forMessage(((Message) ar.userObj)).exception
1012                                = ar.exception;
1013                        ((Message) ar.userObj).sendToTarget();
1014                    }
1015                }
1016                break;
1017            case EVENT_SET_CPHS_MAILBOX_DONE:
1018                isRecordLoadResponse = false;
1019                ar = (AsyncResult)msg.obj;
1020                if(ar.exception == null) {
1021                    voiceMailNum = newVoiceMailNum;
1022                    voiceMailTag = newVoiceMailTag;
1023                } else {
1024                    if (DBG) log("Set CPHS MailBox with exception: "
1025                            + ar.exception);
1026                }
1027                if (ar.userObj != null) {
1028                    if (DBG) log("Callback with CPHS MB successful.");
1029                    AsyncResult.forMessage(((Message) ar.userObj)).exception
1030                            = ar.exception;
1031                    ((Message) ar.userObj).sendToTarget();
1032                }
1033                break;
1034            case EVENT_SIM_REFRESH:
1035                isRecordLoadResponse = false;
1036                ar = (AsyncResult)msg.obj;
1037		if (DBG) log("Sim REFRESH with exception: " + ar.exception);
1038                if (ar.exception == null) {
1039                    handleSimRefresh((int[])(ar.result));
1040                }
1041                break;
1042            case EVENT_GET_CFIS_DONE:
1043                isRecordLoadResponse = true;
1044
1045                ar = (AsyncResult)msg.obj;
1046                data = (byte[])ar.result;
1047
1048                if (ar.exception != null) {
1049                    break;
1050                }
1051
1052                Log.d(LOG_TAG, "EF_CFIS: " +
1053                   SimUtils.bytesToHexString(data));
1054
1055                mEfCfis = data;
1056
1057                // Refer TS 51.011 Section 10.3.46 for the content description
1058                callForwardingEnabled = ((data[1] & 0x01) != 0);
1059
1060                phone.notifyCallForwardingIndicator();
1061                break;
1062
1063        }}catch (RuntimeException exc) {
1064            // I don't want these exceptions to be fatal
1065            Log.w(LOG_TAG, "Exception parsing SIM record", exc);
1066        } finally {
1067            // Count up record load responses even if they are fails
1068            if (isRecordLoadResponse) {
1069                onRecordLoaded();
1070            }
1071        }
1072    }
1073
1074    private void handleFileUpdate(int efid) {
1075        switch(efid) {
1076            case EF_MBDN:
1077                recordsToLoad++;
1078                new AdnRecordLoader(phone).loadFromEF(EF_MBDN, EF_EXT6,
1079                        mailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
1080                break;
1081            case EF_MAILBOX_CPHS:
1082                recordsToLoad++;
1083                new AdnRecordLoader(phone).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1,
1084                        1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
1085                break;
1086            default:
1087                // For now, fetch all records if this is not a
1088                // voicemail number.
1089                // TODO: Handle other cases, instead of fetching all.
1090                adnCache.reset();
1091                fetchSimRecords();
1092                break;
1093        }
1094    }
1095
1096    private void handleSimRefresh(int[] result) {
1097        if (result == null || result.length == 0) {
1098	    if (DBG) log("handleSimRefresh without input");
1099            return;
1100        }
1101
1102        switch ((result[0])) {
1103            case CommandsInterface.SIM_REFRESH_FILE_UPDATED:
1104 		if (DBG) log("handleSimRefresh with SIM_REFRESH_FILE_UPDATED");
1105		// result[1] contains the EFID of the updated file.
1106                int efid = result[1];
1107                handleFileUpdate(efid);
1108                break;
1109            case CommandsInterface.SIM_REFRESH_INIT:
1110		if (DBG) log("handleSimRefresh with SIM_REFRESH_INIT");
1111                // need to reload all files (that we care about)
1112                adnCache.reset();
1113                fetchSimRecords();
1114                break;
1115            case CommandsInterface.SIM_REFRESH_RESET:
1116		if (DBG) log("handleSimRefresh with SIM_REFRESH_RESET");
1117                phone.mCM.setRadioPower(false, null);
1118                /* Note: no need to call setRadioPower(true).  Assuming the desired
1119                * radio power state is still ON (as tracked by ServiceStateTracker),
1120                * ServiceStateTracker will call setRadioPower when it receives the
1121                * RADIO_STATE_CHANGED notification for the power off.  And if the
1122                * desired power state has changed in the interim, we don't want to
1123                * override it with an unconditional power on.
1124                */
1125                break;
1126            default:
1127                // unknown refresh operation
1128		if (DBG) log("handleSimRefresh with unknown operation");
1129                break;
1130        }
1131    }
1132
1133    private void handleSms(byte[] ba)
1134    {
1135        if (ba[0] != 0)
1136            Log.d("ENF", "status : " + ba[0]);
1137
1138        // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
1139        // 3 == "received by MS from network; message to be read"
1140        if (ba[0] == 3) {
1141            int n = ba.length;
1142
1143            // Note: Data may include trailing FF's.  That's OK; message
1144            // should still parse correctly.
1145            byte[] nba = new byte[n - 1];
1146            System.arraycopy(ba, 1, nba, 0, n - 1);
1147
1148            String pdu = SimUtils.bytesToHexString(nba);
1149            // XXX first line is bogus
1150            SmsMessage message = SmsMessage.newFromCMT(
1151                                new String[] { "", pdu });
1152
1153            phone.mSMS.dispatchMessage(message);
1154        }
1155    }
1156
1157
1158    private void handleSmses(ArrayList messages) {
1159        int count = messages.size();
1160
1161        for (int i = 0; i < count; i++) {
1162            byte[] ba = (byte[]) messages.get(i);
1163
1164            if (ba[0] != 0)
1165                Log.i("ENF", "status " + i + ": " + ba[0]);
1166
1167            // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
1168            // 3 == "received by MS from network; message to be read"
1169
1170            if (ba[0] == 3) {
1171                int n = ba.length;
1172
1173                // Note: Data may include trailing FF's.  That's OK; message
1174                // should still parse correctly.
1175                byte[] nba = new byte[n - 1];
1176                System.arraycopy(ba, 1, nba, 0, n - 1);
1177
1178                String pdu = SimUtils.bytesToHexString(nba);
1179                // XXX first line is bogus
1180                SmsMessage message = SmsMessage.newFromCMT(
1181                        new String[] { "", pdu });
1182
1183                phone.mSMS.dispatchMessage(message);
1184
1185                // 3GPP TS 51.011 v5.0.0 (20011-12)  10.5.3
1186                // 1 == "received by MS from network; message read"
1187
1188                ba[0] = 1;
1189
1190                if (false) { // XXX writing seems to crash RdoServD
1191                    phone.mSIMFileHandler.updateEFLinearFixed(EF_SMS, i, ba, null,
1192                                    obtainMessage(EVENT_MARK_SMS_READ_DONE, i));
1193                }
1194            }
1195        }
1196    }
1197
1198
1199    //***** Private Methods
1200
1201    private void onRecordLoaded()
1202    {
1203        // One record loaded successfully or failed, In either case
1204        // we need to update the recordsToLoad count
1205        recordsToLoad -= 1;
1206
1207        if (recordsToLoad == 0 && recordsRequested == true) {
1208            onAllRecordsLoaded();
1209        } else if (recordsToLoad < 0) {
1210            Log.e(LOG_TAG, "SIMRecords: recordsToLoad <0, programmer error suspected");
1211            recordsToLoad = 0;
1212        }
1213    }
1214
1215    private void onAllRecordsLoaded()
1216    {
1217        Log.d(LOG_TAG, "SIMRecords: record load complete");
1218
1219        String operator = getSIMOperatorNumeric();
1220
1221        // Some fields require more than one SIM record to set
1222
1223        phone.setSystemProperty(PROPERTY_SIM_OPERATOR_NUMERIC, operator);
1224
1225        if (imsi != null) {
1226            phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ISO_COUNTRY,
1227                                        MccTable.countryCodeForMcc(
1228                                            Integer.parseInt(imsi.substring(0,3))));
1229        }
1230        else {
1231            Log.e("SIM", "[SIMRecords] onAllRecordsLoaded: imsi is NULL!");
1232        }
1233
1234        setVoiceMailByCountry(operator);
1235        setSpnFromConfig(operator);
1236
1237        recordsLoadedRegistrants.notifyRegistrants(
1238            new AsyncResult(null, null, null));
1239        phone.mSimCard.broadcastSimStateChangedIntent(
1240                SimCard.INTENT_VALUE_SIM_LOADED, null);
1241    }
1242
1243    private void setSpnFromConfig(String carrier) {
1244        if (mSpnOverride.containsCarrier(carrier)) {
1245            spn = mSpnOverride.getSpn(carrier);
1246        }
1247    }
1248
1249    private void setVoiceMailByCountry (String spn) {
1250        if (mVmConfig.containsCarrier(spn)) {
1251            isVoiceMailFixed = true;
1252            voiceMailNum = mVmConfig.getVoiceMailNumber(spn);
1253            voiceMailTag = mVmConfig.getVoiceMailTag(spn);
1254        }
1255    }
1256
1257    private void onSimReady() {
1258        /* broadcast intent SIM_READY here so that we can make sure
1259          READY is sent before IMSI ready
1260        */
1261        phone.mSimCard.broadcastSimStateChangedIntent(
1262                SimCard.INTENT_VALUE_SIM_READY, null);
1263
1264        fetchSimRecords();
1265    }
1266
1267    private void fetchSimRecords() {
1268        recordsRequested = true;
1269
1270	Log.v(LOG_TAG, "SIMRecords:fetchSimRecords " + recordsToLoad);
1271
1272        phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE));
1273        recordsToLoad++;
1274
1275        phone.mSIMFileHandler.loadEFTransparent(EF_ICCID,
1276                            obtainMessage(EVENT_GET_ICCID_DONE));
1277        recordsToLoad++;
1278
1279        // FIXME should examine EF[MSISDN]'s capability configuration
1280        // to determine which is the voice/data/fax line
1281        new AdnRecordLoader(phone).loadFromEF(EF_MSISDN, EF_EXT1, 1,
1282                    obtainMessage(EVENT_GET_MSISDN_DONE));
1283        recordsToLoad++;
1284
1285        // Record number is subscriber profile
1286        phone.mSIMFileHandler.loadEFLinearFixed(EF_MBI, 1,
1287                        obtainMessage(EVENT_GET_MBI_DONE));
1288        recordsToLoad++;
1289
1290        phone.mSIMFileHandler.loadEFTransparent(EF_AD,
1291                        obtainMessage(EVENT_GET_AD_DONE));
1292        recordsToLoad++;
1293
1294        // Record number is subscriber profile
1295        phone.mSIMFileHandler.loadEFLinearFixed(EF_MWIS, 1,
1296                        obtainMessage(EVENT_GET_MWIS_DONE));
1297        recordsToLoad++;
1298
1299
1300        // Also load CPHS-style voice mail indicator, which stores
1301        // the same info as EF[MWIS]. If both exist, both are updated
1302        // but the EF[MWIS] data is preferred
1303        // Please note this must be loaded after EF[MWIS]
1304        phone.mSIMFileHandler.loadEFTransparent(
1305                EF_VOICE_MAIL_INDICATOR_CPHS,
1306                obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));
1307        recordsToLoad++;
1308
1309        // Same goes for Call Forward Status indicator: fetch both
1310        // EF[CFIS] and CPHS-EF, with EF[CFIS] preferred.
1311        phone.mSIMFileHandler.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
1312        recordsToLoad++;
1313        phone.mSIMFileHandler.loadEFTransparent(EF_CFF_CPHS,
1314                obtainMessage(EVENT_GET_CFF_DONE));
1315        recordsToLoad++;
1316
1317
1318        getSpnFsm(true, null);
1319
1320        phone.mSIMFileHandler.loadEFTransparent(EF_SPDI,
1321            obtainMessage(EVENT_GET_SPDI_DONE));
1322        recordsToLoad++;
1323
1324        phone.mSIMFileHandler.loadEFLinearFixed(EF_PNN, 1,
1325            obtainMessage(EVENT_GET_PNN_DONE));
1326        recordsToLoad++;
1327
1328        phone.mSIMFileHandler.loadEFTransparent(EF_SST,
1329            obtainMessage(EVENT_GET_SST_DONE));
1330        recordsToLoad++;
1331
1332        phone.mSIMFileHandler.loadEFTransparent(EF_INFO_CPHS,
1333                obtainMessage(EVENT_GET_INFO_CPHS_DONE));
1334        recordsToLoad++;
1335
1336        // XXX should seek instead of examining them all
1337        if (false) { // XXX
1338            phone.mSIMFileHandler.loadEFLinearFixedAll(EF_SMS,
1339                obtainMessage(EVENT_GET_ALL_SMS_DONE));
1340            recordsToLoad++;
1341        }
1342
1343        if (CRASH_RIL) {
1344            String sms = "0107912160130310f20404d0110041007030208054832b0120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
1345            byte[] ba = SimUtils.hexStringToBytes(sms);
1346
1347            phone.mSIMFileHandler.updateEFLinearFixed(EF_SMS, 1, ba, null,
1348                            obtainMessage(EVENT_MARK_SMS_READ_DONE, 1));
1349        }
1350    }
1351
1352    /**
1353     * Returns the SpnDisplayRule based on settings on the SIM and the
1354     * specified plmn (currently-registered PLMN).  See TS 22.101 Annex A
1355     * and TS 51.011 10.3.11 for details.
1356     *
1357     * If the SPN is not found on the SIM, the rule is always PLMN_ONLY.
1358     */
1359    int getDisplayRule(String plmn) {
1360        int rule;
1361        if (spn == null || spnDisplayCondition == -1) {
1362            // EF_SPN was not found on the SIM, or not yet loaded.  Just show ONS.
1363            rule = SPN_RULE_SHOW_PLMN;
1364        } else if (isOnMatchingPlmn(plmn)) {
1365            rule = SPN_RULE_SHOW_SPN;
1366            if ((spnDisplayCondition & 0x01) == 0x01) {
1367                // ONS required when registered to HPLMN or PLMN in EF_SPDI
1368                rule |= SPN_RULE_SHOW_PLMN;
1369            }
1370        } else {
1371            rule = SPN_RULE_SHOW_PLMN;
1372            if ((spnDisplayCondition & 0x02) == 0x00) {
1373                // SPN required if not registered to HPLMN or PLMN in EF_SPDI
1374                rule |= SPN_RULE_SHOW_SPN;
1375            }
1376        }
1377        return rule;
1378    }
1379
1380    /**
1381     * Checks if plmn is HPLMN or on the spdiNetworks list.
1382     */
1383    private boolean isOnMatchingPlmn(String plmn) {
1384        if (plmn == null) return false;
1385
1386        if (plmn.equals(getSIMOperatorNumeric())) {
1387            return true;
1388        }
1389
1390        if (spdiNetworks != null) {
1391            for (String spdiNet : spdiNetworks) {
1392                if (plmn.equals(spdiNet)) {
1393                    return true;
1394                }
1395            }
1396        }
1397        return false;
1398    }
1399
1400    /**
1401     * States of Get SPN Finite State Machine which only used by getSpnFsm()
1402     */
1403    private enum Get_Spn_Fsm_State {
1404        IDLE,               // No initialized
1405        INIT,               // Start FSM
1406        READ_SPN_3GPP,      // Load EF_SPN firstly
1407        READ_SPN_CPHS,      // Load EF_SPN_CPHS secondly
1408        READ_SPN_SHORT_CPHS // Load EF_SPN_SHORT_CPHS last
1409    }
1410
1411    /**
1412     * Finite State Machine to load Service Provider Name , which can be stored
1413     * in either EF_SPN (3GPP), EF_SPN_CPHS, or EF_SPN_SHORT_CPHS (CPHS4.2)
1414     *
1415     * After starting, FSM will search SPN EFs in order and stop after finding
1416     * the first valid SPN
1417     *
1418     * @param start set true only for initialize loading
1419     * @param ar the AsyncResult from loadEFTransparent
1420     *        ar.exception holds exception in error
1421     *        ar.result is byte[] for data in success
1422     */
1423    private void getSpnFsm(boolean start, AsyncResult ar) {
1424        byte[] data;
1425
1426        if (start) {
1427            spnState = Get_Spn_Fsm_State.INIT;
1428        }
1429
1430        switch(spnState){
1431            case INIT:
1432                spn = null;
1433
1434                phone.mSIMFileHandler.loadEFTransparent( EF_SPN,
1435                        obtainMessage(EVENT_GET_SPN_DONE));
1436                recordsToLoad++;
1437
1438                spnState = Get_Spn_Fsm_State.READ_SPN_3GPP;
1439                break;
1440            case READ_SPN_3GPP:
1441                if (ar != null && ar.exception == null) {
1442                    data = (byte[]) ar.result;
1443                    spnDisplayCondition = 0xff & data[0];
1444                    spn = SimUtils.adnStringFieldToString(data, 1, data.length - 1);
1445
1446                    if (DBG) log("Load EF_SPN: " + spn
1447                            + " spnDisplayCondition: " + spnDisplayCondition);
1448                    phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
1449
1450                    spnState = Get_Spn_Fsm_State.IDLE;
1451                } else {
1452                    phone.mSIMFileHandler.loadEFTransparent( EF_SPN_CPHS,
1453                            obtainMessage(EVENT_GET_SPN_DONE));
1454                    recordsToLoad++;
1455
1456                    spnState = Get_Spn_Fsm_State.READ_SPN_CPHS;
1457
1458                    // See TS 51.011 10.3.11.  Basically, default to
1459                    // show PLMN always, and SPN also if roaming.
1460                    spnDisplayCondition = -1;
1461                }
1462                break;
1463            case READ_SPN_CPHS:
1464                if (ar != null && ar.exception == null) {
1465                    data = (byte[]) ar.result;
1466                    spn = SimUtils.adnStringFieldToString(
1467                            data, 0, data.length - 1 );
1468
1469                    if (DBG) log("Load EF_SPN_CPHS: " + spn);
1470                    phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
1471
1472                    spnState = Get_Spn_Fsm_State.IDLE;
1473                } else {
1474                    phone.mSIMFileHandler.loadEFTransparent( EF_SPN_SHORT_CPHS,
1475                            obtainMessage(EVENT_GET_SPN_DONE));
1476                    recordsToLoad++;
1477
1478                    spnState = Get_Spn_Fsm_State.READ_SPN_SHORT_CPHS;
1479                }
1480                break;
1481            case READ_SPN_SHORT_CPHS:
1482                if (ar != null && ar.exception == null) {
1483                    data = (byte[]) ar.result;
1484                    spn = SimUtils.adnStringFieldToString(
1485                            data, 0, data.length - 1);
1486
1487                    if (DBG) log("Load EF_SPN_SHORT_CPHS: " + spn);
1488                    phone.setSystemProperty(PROPERTY_SIM_OPERATOR_ALPHA, spn);
1489                }else {
1490                    if (DBG) log("No SPN loaded in either CHPS or 3GPP");
1491                }
1492
1493                spnState = Get_Spn_Fsm_State.IDLE;
1494                break;
1495            default:
1496                spnState = Get_Spn_Fsm_State.IDLE;
1497        }
1498    }
1499
1500    /**
1501     * Parse TS 51.011 EF[SPDI] record
1502     * This record contains the list of numeric network IDs that
1503     * are treated specially when determining SPN display
1504     */
1505    private void
1506    parseEfSpdi(byte[] data)
1507    {
1508        SimTlv tlv = new SimTlv(data, 0, data.length);
1509
1510        byte[] plmnEntries = null;
1511
1512        // There should only be one TAG_SPDI_PLMN_LIST
1513        for ( ; tlv.isValidObject() ; tlv.nextObject()) {
1514            if (tlv.getTag() == TAG_SPDI_PLMN_LIST) {
1515                plmnEntries = tlv.getData();
1516                break;
1517            }
1518        }
1519
1520        if (plmnEntries == null) {
1521            return;
1522        }
1523
1524        spdiNetworks = new ArrayList<String>(plmnEntries.length / 3);
1525
1526        for (int i = 0 ; i + 2 < plmnEntries.length ; i += 3) {
1527            String plmnCode;
1528            plmnCode = SimUtils.bcdToString(plmnEntries, i, 3);
1529
1530            // Valid operator codes are 5 or 6 digits
1531            if (plmnCode.length() >= 5) {
1532                log("EF_SPDI network: " + plmnCode);
1533                spdiNetworks.add(plmnCode);
1534            }
1535        }
1536    }
1537
1538    /**
1539     * check to see if Mailbox Number is allocated and activated in CPHS SST
1540     */
1541    private boolean isCphsMailboxEnabled() {
1542        if (mCphsInfo == null)  return false;
1543        return ((mCphsInfo[1] & CPHS_SST_MBN_MASK) == CPHS_SST_MBN_ENABLED );
1544    }
1545
1546    private void log(String s) {
1547        Log.d(LOG_TAG, "[SIMRecords] " + s);
1548    }
1549}
1550