RuimRecords.java revision 2351b17aba5350004fc76707f3b3d2859ce773c8
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony.uicc;
18
19
20import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
21import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
22
23import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
24import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
25
26import java.io.FileDescriptor;
27import java.io.PrintWriter;
28import java.util.ArrayList;
29import java.util.Arrays;
30import java.util.Locale;
31import android.content.Context;
32import android.os.AsyncResult;
33import android.os.Message;
34import android.os.SystemProperties;
35import android.telephony.TelephonyManager;
36import android.telephony.Rlog;
37import android.text.TextUtils;
38import android.util.Log;
39
40import com.android.internal.telephony.CommandsInterface;
41import com.android.internal.telephony.GsmAlphabet;
42import com.android.internal.telephony.MccTable;
43
44import com.android.internal.telephony.cdma.sms.UserData;
45import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
46import com.android.internal.util.BitwiseInputStream;
47
48/**
49 * {@hide}
50 */
51public final class RuimRecords extends IccRecords {
52    static final String LOG_TAG = "RuimRecords";
53
54    private boolean  mOtaCommited=false;
55
56    // ***** Instance Variables
57
58    private String mMyMobileNumber;
59    private String mMin2Min1;
60
61    private String mPrlVersion;
62    // From CSIM application
63    private byte[] mEFpl = null;
64    private byte[] mEFli = null;
65    boolean mCsimSpnDisplayCondition = false;
66    private String mMdn;
67    private String mMin;
68    private String mHomeSystemId;
69    private String mHomeNetworkId;
70    private String mNai;
71
72    @Override
73    public String toString() {
74        return "RuimRecords: " + super.toString()
75                + " m_ota_commited" + mOtaCommited
76                + " mMyMobileNumber=" + "xxxx"
77                + " mMin2Min1=" + mMin2Min1
78                + " mPrlVersion=" + mPrlVersion
79                + " mEFpl=" + mEFpl
80                + " mEFli=" + mEFli
81                + " mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition
82                + " mMdn=" + mMdn
83                + " mMin=" + mMin
84                + " mHomeSystemId=" + mHomeSystemId
85                + " mHomeNetworkId=" + mHomeNetworkId;
86    }
87
88    // ***** Event Constants
89    private static final int EVENT_GET_IMSI_DONE = 3;
90    private static final int EVENT_GET_DEVICE_IDENTITY_DONE = 4;
91    private static final int EVENT_GET_ICCID_DONE = 5;
92    private static final int EVENT_GET_CDMA_SUBSCRIPTION_DONE = 10;
93    private static final int EVENT_UPDATE_DONE = 14;
94    private static final int EVENT_GET_SST_DONE = 17;
95    private static final int EVENT_GET_ALL_SMS_DONE = 18;
96    private static final int EVENT_MARK_SMS_READ_DONE = 19;
97
98    private static final int EVENT_SMS_ON_RUIM = 21;
99    private static final int EVENT_GET_SMS_DONE = 22;
100
101    private static final int EVENT_RUIM_REFRESH = 31;
102
103    public RuimRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
104        super(app, c, ci);
105
106        mAdnCache = new AdnRecordCache(mFh);
107
108        mRecordsRequested = false;  // No load request is made till SIM ready
109
110        // recordsToLoad is set to 0 because no requests are made yet
111        mRecordsToLoad = 0;
112
113        // NOTE the EVENT_SMS_ON_RUIM is not registered
114        mCi.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
115
116        // Start off by setting empty state
117        resetRecords();
118
119        mParentApp.registerForReady(this, EVENT_APP_READY, null);
120        if (DBG) log("RuimRecords X ctor this=" + this);
121    }
122
123    @Override
124    public void dispose() {
125        if (DBG) log("Disposing RuimRecords " + this);
126        //Unregister for all events
127        mCi.unregisterForIccRefresh(this);
128        mParentApp.unregisterForReady(this);
129        resetRecords();
130        super.dispose();
131    }
132
133    @Override
134    protected void finalize() {
135        if(DBG) log("RuimRecords finalized");
136    }
137
138    protected void resetRecords() {
139        mCountVoiceMessages = 0;
140        mMncLength = UNINITIALIZED;
141        log("setting0 mMncLength" + mMncLength);
142        mIccId = null;
143
144        mAdnCache.reset();
145
146        // Don't clean up PROPERTY_ICC_OPERATOR_ISO_COUNTRY and
147        // PROPERTY_ICC_OPERATOR_NUMERIC here. Since not all CDMA
148        // devices have RUIM, these properties should keep the original
149        // values, e.g. build time settings, when there is no RUIM but
150        // set new values when RUIM is available and loaded.
151
152        // recordsRequested is set to false indicating that the SIM
153        // read requests made so far are not valid. This is set to
154        // true only when fresh set of read requests are made.
155        mRecordsRequested = false;
156    }
157
158    @Override
159    public String getIMSI() {
160        return mImsi;
161    }
162
163    public String getMdnNumber() {
164        return mMyMobileNumber;
165    }
166
167    public String getCdmaMin() {
168         return mMin2Min1;
169    }
170
171    /** Returns null if RUIM is not yet ready */
172    public String getPrlVersion() {
173        return mPrlVersion;
174    }
175
176    @Override
177    /** Returns null if RUIM is not yet ready */
178    public String getNAI() {
179        return mNai;
180    }
181
182    @Override
183    public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){
184        // In CDMA this is Operator/OEM dependent
185        AsyncResult.forMessage((onComplete)).exception =
186                new IccException("setVoiceMailNumber not implemented");
187        onComplete.sendToTarget();
188        loge("method setVoiceMailNumber is not implemented");
189    }
190
191    /**
192     * Called by CCAT Service when REFRESH is received.
193     * @param fileChanged indicates whether any files changed
194     * @param fileList if non-null, a list of EF files that changed
195     */
196    @Override
197    public void onRefresh(boolean fileChanged, int[] fileList) {
198        if (fileChanged) {
199            // A future optimization would be to inspect fileList and
200            // only reload those files that we care about.  For now,
201            // just re-fetch all RUIM records that we cache.
202            fetchRuimRecords();
203        }
204    }
205
206    private int adjstMinDigits (int digits) {
207        // Per C.S0005 section 2.3.1.
208        digits += 111;
209        digits = (digits % 10 == 0)?(digits - 10):digits;
210        digits = ((digits / 10) % 10 == 0)?(digits - 100):digits;
211        digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits;
212        return digits;
213    }
214
215    /**
216     * Returns the 5 or 6 digit MCC/MNC of the operator that
217     *  provided the RUIM card. Returns null of RUIM is not yet ready
218     */
219    public String getRUIMOperatorNumeric() {
220        if (mImsi == null) {
221            return null;
222        }
223
224        if (mMncLength != UNINITIALIZED && mMncLength != UNKNOWN) {
225            // Length = length of MCC + length of MNC
226            // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3)
227            return mImsi.substring(0, 3 + mMncLength);
228        }
229
230        // Guess the MNC length based on the MCC if we don't
231        // have a valid value in ef[ad]
232
233        int mcc = Integer.parseInt(mImsi.substring(0,3));
234        return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
235    }
236
237    // Refer to ETSI TS 102.221
238    private class EfPlLoaded implements IccRecordLoaded {
239        @Override
240        public String getEfName() {
241            return "EF_PL";
242        }
243
244        @Override
245        public void onRecordLoaded(AsyncResult ar) {
246            mEFpl = (byte[]) ar.result;
247            if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
248        }
249    }
250
251    // Refer to C.S0065 5.2.26
252    private class EfCsimLiLoaded implements IccRecordLoaded {
253        @Override
254        public String getEfName() {
255            return "EF_CSIM_LI";
256        }
257
258        @Override
259        public void onRecordLoaded(AsyncResult ar) {
260            mEFli = (byte[]) ar.result;
261            // convert csim efli data to iso 639 format
262            for (int i = 0; i < mEFli.length; i+=2) {
263                switch(mEFli[i+1]) {
264                case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
265                case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
266                case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
267                case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
268                case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
269                case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
270                case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
271                default: mEFli[i] = ' '; mEFli[i+1] = ' ';
272                }
273            }
274
275            if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
276        }
277    }
278
279    // Refer to C.S0065 5.2.32
280    private class EfCsimSpnLoaded implements IccRecordLoaded {
281        @Override
282        public String getEfName() {
283            return "EF_CSIM_SPN";
284        }
285
286        @Override
287        public void onRecordLoaded(AsyncResult ar) {
288            byte[] data = (byte[]) ar.result;
289            if (DBG) log("CSIM_SPN=" +
290                         IccUtils.bytesToHexString(data));
291
292            // C.S0065 for EF_SPN decoding
293            mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
294
295            int encoding = data[1];
296            int language = data[2];
297            byte[] spnData = new byte[32];
298            int len = ((data.length - 3) < 32) ? (data.length - 3) : 32;
299            System.arraycopy(data, 3, spnData, 0, len);
300
301            int numBytes;
302            for (numBytes = 0; numBytes < spnData.length; numBytes++) {
303                if ((spnData[numBytes] & 0xFF) == 0xFF) break;
304            }
305
306            if (numBytes == 0) {
307                setServiceProviderName("");
308                return;
309            }
310            try {
311                switch (encoding) {
312                case UserData.ENCODING_OCTET:
313                case UserData.ENCODING_LATIN:
314                    setServiceProviderName(new String(spnData, 0, numBytes, "ISO-8859-1"));
315                    break;
316                case UserData.ENCODING_IA5:
317                case UserData.ENCODING_GSM_7BIT_ALPHABET:
318                    setServiceProviderName(
319                            GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7));
320                    break;
321                case UserData.ENCODING_7BIT_ASCII:
322                    String spn = new String(spnData, 0, numBytes, "US-ASCII");
323                    // To address issues with incorrect encoding scheme
324                    // programmed in some commercial CSIM cards, the decoded
325                    // SPN is checked to have characters in printable ASCII
326                    // range. If not, they are decoded with
327                    // ENCODING_GSM_7BIT_ALPHABET scheme.
328                    if (TextUtils.isPrintableAsciiOnly(spn)) {
329                        setServiceProviderName(spn);
330                    } else {
331                        if (DBG) log("Some corruption in SPN decoding = " + spn);
332                        if (DBG) log("Using ENCODING_GSM_7BIT_ALPHABET scheme...");
333                        setServiceProviderName(
334                                GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes * 8) / 7));
335                    }
336                break;
337                case UserData.ENCODING_UNICODE_16:
338                    setServiceProviderName(new String(spnData, 0, numBytes, "utf-16"));
339                    break;
340                default:
341                    log("SPN encoding not supported");
342                }
343            } catch(Exception e) {
344                log("spn decode error: " + e);
345            }
346            if (DBG) log("spn=" + getServiceProviderName());
347            if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
348            SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
349        }
350    }
351
352    private class EfCsimMdnLoaded implements IccRecordLoaded {
353        @Override
354        public String getEfName() {
355            return "EF_CSIM_MDN";
356        }
357
358        @Override
359        public void onRecordLoaded(AsyncResult ar) {
360            byte[] data = (byte[]) ar.result;
361            if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
362            // Refer to C.S0065 5.2.35
363            int mdnDigitsNum = 0x0F & data[0];
364            mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
365            if (DBG) log("CSIM MDN=" + mMdn);
366        }
367    }
368
369    private class EfCsimImsimLoaded implements IccRecordLoaded {
370        @Override
371        public String getEfName() {
372            return "EF_CSIM_IMSIM";
373        }
374
375        @Override
376        public void onRecordLoaded(AsyncResult ar) {
377            byte[] data = (byte[]) ar.result;
378            if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
379            // C.S0065 section 5.2.2 for IMSI_M encoding
380            // C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
381            boolean provisioned = ((data[7] & 0x80) == 0x80);
382
383            if (provisioned) {
384                int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
385                int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
386                int digit7 = 0x0F & (data[4] >> 2);
387                if (digit7 > 0x09) digit7 = 0;
388                int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
389                first3digits = adjstMinDigits(first3digits);
390                second3digits = adjstMinDigits(second3digits);
391                last3digits = adjstMinDigits(last3digits);
392
393                StringBuilder builder = new StringBuilder();
394                builder.append(String.format(Locale.US, "%03d", first3digits));
395                builder.append(String.format(Locale.US, "%03d", second3digits));
396                builder.append(String.format(Locale.US, "%d", digit7));
397                builder.append(String.format(Locale.US, "%03d", last3digits));
398                mMin = builder.toString();
399                if (DBG) log("min present=" + mMin);
400            } else {
401                if (DBG) log("min not present");
402            }
403        }
404    }
405
406    private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
407        @Override
408        public String getEfName() {
409            return "EF_CSIM_CDMAHOME";
410        }
411
412        @Override
413        public void onRecordLoaded(AsyncResult ar) {
414            // Per C.S0065 section 5.2.8
415            ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
416            if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
417            if (dataList.isEmpty()) {
418                return;
419            }
420            StringBuilder sidBuf = new StringBuilder();
421            StringBuilder nidBuf = new StringBuilder();
422
423            for (byte[] data : dataList) {
424                if (data.length == 5) {
425                    int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
426                    int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
427                    sidBuf.append(sid).append(',');
428                    nidBuf.append(nid).append(',');
429                }
430            }
431            // remove trailing ","
432            sidBuf.setLength(sidBuf.length()-1);
433            nidBuf.setLength(nidBuf.length()-1);
434
435            mHomeSystemId = sidBuf.toString();
436            mHomeNetworkId = nidBuf.toString();
437        }
438    }
439
440    private class EfCsimEprlLoaded implements IccRecordLoaded {
441        @Override
442        public String getEfName() {
443            return "EF_CSIM_EPRL";
444        }
445        @Override
446        public void onRecordLoaded(AsyncResult ar) {
447            onGetCSimEprlDone(ar);
448        }
449    }
450
451    private void onGetCSimEprlDone(AsyncResult ar) {
452        // C.S0065 section 5.2.57 for EFeprl encoding
453        // C.S0016 section 3.5.5 for PRL format.
454        byte[] data = (byte[]) ar.result;
455        if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data));
456
457        // Only need the first 4 bytes of record
458        if (data.length > 3) {
459            int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
460            mPrlVersion = Integer.toString(prlId);
461        }
462        if (DBG) log("CSIM PRL version=" + mPrlVersion);
463    }
464
465    private class EfCsimMipUppLoaded implements IccRecordLoaded {
466        @Override
467        public String getEfName() {
468            return "EF_CSIM_MIPUPP";
469        }
470
471        boolean checkLengthLegal(int length, int expectLength) {
472            if(length < expectLength) {
473                Log.e(LOG_TAG, "CSIM MIPUPP format error, length = " + length  +
474                        "expected length at least =" + expectLength);
475                return false;
476            } else {
477                return true;
478            }
479        }
480
481        @Override
482        public void onRecordLoaded(AsyncResult ar) {
483            // 3GPP2 C.S0065 section 5.2.24
484            byte[] data = (byte[]) ar.result;
485
486            if(data.length < 1) {
487                Log.e(LOG_TAG,"MIPUPP read error");
488                return;
489            }
490
491            BitwiseInputStream bitStream = new BitwiseInputStream(data);
492            try {
493                int  mipUppLength = bitStream.read(8);
494                //transfer length from byte to bit
495                mipUppLength = (mipUppLength << 3);
496
497                if (!checkLengthLegal(mipUppLength, 1)) {
498                    return;
499                }
500                //parse the MIPUPP body 3GPP2 C.S0016-C 3.5.8.6
501                int retryInfoInclude = bitStream.read(1);
502                mipUppLength--;
503
504                if(retryInfoInclude == 1) {
505                    if (!checkLengthLegal(mipUppLength, 11)) {
506                        return;
507                    }
508                    bitStream.skip(11); //not used now
509                    //transfer length from byte to bit
510                    mipUppLength -= 11;
511                }
512
513                if (!checkLengthLegal(mipUppLength, 4)) {
514                    return;
515                }
516                int numNai = bitStream.read(4);
517                mipUppLength -= 4;
518
519                //start parse NAI body
520                for(int index = 0; index < numNai; index++) {
521                    if (!checkLengthLegal(mipUppLength, 4)) {
522                        return;
523                    }
524                    int naiEntryIndex = bitStream.read(4);
525                    mipUppLength -= 4;
526
527                    if (!checkLengthLegal(mipUppLength, 8)) {
528                        return;
529                    }
530                    int naiLength = bitStream.read(8);
531                    mipUppLength -= 8;
532
533                    if(naiEntryIndex == 0) {
534                        //we find the one!
535                        if (!checkLengthLegal(mipUppLength, naiLength << 3)) {
536                            return;
537                        }
538                        char naiCharArray[] = new char[naiLength];
539                        for(int index1 = 0; index1 < naiLength; index1++) {
540                            naiCharArray[index1] = (char)(bitStream.read(8) & 0xFF);
541                        }
542                        mNai =  new String(naiCharArray);
543                        return; //need not parsing further
544                    } else {
545                        //ignore this NAI body
546                        if (!checkLengthLegal(mipUppLength, (naiLength << 3) + 102)) {
547                            return;
548                        }
549                        bitStream.skip((naiLength << 3) + 101);//not used
550                        int mnAaaSpiIndicator = bitStream.read(1);
551                        mipUppLength -= ((naiLength << 3) + 102);
552
553                        if(mnAaaSpiIndicator == 1) {
554                            if (!checkLengthLegal(mipUppLength, 32)) {
555                                return;
556                            }
557                            bitStream.skip(32); //not used
558                            mipUppLength -= 32;
559                        }
560
561                        //MN-HA_AUTH_ALGORITHM
562                        if (!checkLengthLegal(mipUppLength, 5)) {
563                            return;
564                        }
565                        bitStream.skip(4);
566                        mipUppLength -= 4;
567                        int mnHaSpiIndicator = bitStream.read(1);
568                        mipUppLength--;
569
570                        if(mnHaSpiIndicator == 1) {
571                            if (!checkLengthLegal(mipUppLength, 32)) {
572                                return;
573                            }
574                            bitStream.skip(32);
575                            mipUppLength -= 32;
576                        }
577                    }
578                }
579            } catch(Exception e) {
580              Log.e(LOG_TAG,"MIPUPP read Exception error!");
581                return;
582            }
583        }
584    }
585
586    @Override
587    public void handleMessage(Message msg) {
588        AsyncResult ar;
589
590        byte data[];
591
592        boolean isRecordLoadResponse = false;
593
594        if (mDestroyed.get()) {
595            loge("Received message " + msg +
596                    "[" + msg.what + "] while being destroyed. Ignoring.");
597            return;
598        }
599
600        try { switch (msg.what) {
601            case EVENT_APP_READY:
602                onReady();
603                break;
604
605            case EVENT_GET_DEVICE_IDENTITY_DONE:
606                log("Event EVENT_GET_DEVICE_IDENTITY_DONE Received");
607            break;
608
609            /* IO events */
610            case EVENT_GET_IMSI_DONE:
611                isRecordLoadResponse = true;
612
613                ar = (AsyncResult)msg.obj;
614                if (ar.exception != null) {
615                    loge("Exception querying IMSI, Exception:" + ar.exception);
616                    break;
617                }
618
619                mImsi = (String) ar.result;
620
621                // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
622                // than 15 (and usually 15).
623                if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
624                    loge("invalid IMSI " + mImsi);
625                    mImsi = null;
626                }
627
628                // FIXME: CSIM IMSI may not contain the MNC.
629                if (false) {
630                    log("IMSI: " + mImsi.substring(0, 6) + "xxxxxxxxx");
631
632                    String operatorNumeric = getRUIMOperatorNumeric();
633                    if (operatorNumeric != null) {
634                        if (operatorNumeric.length() <= 6) {
635                            log("update mccmnc=" + operatorNumeric);
636                            MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
637                        }
638                    }
639                } else {
640                    String operatorNumeric = getRUIMOperatorNumeric();
641                    log("NO update mccmnc=" + operatorNumeric);
642                }
643
644            break;
645
646            case EVENT_GET_CDMA_SUBSCRIPTION_DONE:
647                ar = (AsyncResult)msg.obj;
648                String localTemp[] = (String[])ar.result;
649                if (ar.exception != null) {
650                    break;
651                }
652
653                mMyMobileNumber = localTemp[0];
654                mMin2Min1 = localTemp[3];
655                mPrlVersion = localTemp[4];
656
657                log("MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1);
658
659            break;
660
661            case EVENT_GET_ICCID_DONE:
662                isRecordLoadResponse = true;
663
664                ar = (AsyncResult)msg.obj;
665                data = (byte[])ar.result;
666
667                if (ar.exception != null) {
668                    break;
669                }
670
671                mIccId = IccUtils.bcdToString(data, 0, data.length);
672
673                log("iccid: " + mIccId);
674
675            break;
676
677            case EVENT_UPDATE_DONE:
678                ar = (AsyncResult)msg.obj;
679                if (ar.exception != null) {
680                    Rlog.i(LOG_TAG, "RuimRecords update failed", ar.exception);
681                }
682            break;
683
684            case EVENT_GET_ALL_SMS_DONE:
685            case EVENT_MARK_SMS_READ_DONE:
686            case EVENT_SMS_ON_RUIM:
687            case EVENT_GET_SMS_DONE:
688                Rlog.w(LOG_TAG, "Event not supported: " + msg.what);
689                break;
690
691            // TODO: probably EF_CST should be read instead
692            case EVENT_GET_SST_DONE:
693                log("Event EVENT_GET_SST_DONE Received");
694            break;
695
696            case EVENT_RUIM_REFRESH:
697                isRecordLoadResponse = false;
698                ar = (AsyncResult)msg.obj;
699                if (ar.exception == null) {
700                    handleRuimRefresh((IccRefreshResponse)ar.result);
701                }
702                break;
703
704            default:
705                super.handleMessage(msg);   // IccRecords handles generic record load responses
706
707        }}catch (RuntimeException exc) {
708            // I don't want these exceptions to be fatal
709            Rlog.w(LOG_TAG, "Exception parsing RUIM record", exc);
710        } finally {
711            // Count up record load responses even if they are fails
712            if (isRecordLoadResponse) {
713                onRecordLoaded();
714            }
715        }
716    }
717
718    /**
719     * Returns an array of languages we have assets for.
720     *
721     * NOTE: This array will have duplicates. If this method will be caused
722     * frequently or in a tight loop, it can be rewritten for efficiency.
723     */
724    private static String[] getAssetLanguages(Context ctx) {
725        final String[] locales = ctx.getAssets().getLocales();
726        final String[] localeLangs = new String[locales.length];
727        for (int i = 0; i < locales.length; ++i) {
728            final String localeStr = locales[i];
729            final int separator = localeStr.indexOf('-');
730            if (separator < 0) {
731                localeLangs[i] = localeStr;
732            } else {
733                localeLangs[i] = localeStr.substring(0, separator);
734            }
735        }
736
737        return localeLangs;
738    }
739
740    private String findBestLanguage(byte[] languages) {
741        final String[] assetLanguages = getAssetLanguages(mContext);
742
743        if ((languages == null) || (assetLanguages == null)) return null;
744
745        // Each 2-bytes consists of one language
746        for (int i = 0; (i + 1) < languages.length; i += 2) {
747            final String lang;
748            try {
749                lang = new String(languages, i, 2, "ISO-8859-1");
750            } catch(java.io.UnsupportedEncodingException e) {
751                log("Failed to parse SIM language records");
752                continue;
753            }
754
755            for (int j = 0; j < assetLanguages.length; j++) {
756                if (assetLanguages[j].equals(lang)) {
757                    return lang;
758                }
759            }
760        }
761
762        // no match found. return null
763        return null;
764    }
765
766    private void setLocaleFromCsim() {
767        String prefLang = null;
768        // check EFli then EFpl
769        prefLang = findBestLanguage(mEFli);
770
771        if (prefLang == null) {
772            prefLang = findBestLanguage(mEFpl);
773        }
774
775        if (prefLang != null) {
776            // check country code from SIM
777            String imsi = getIMSI();
778            String country = null;
779            if (imsi != null) {
780                country = MccTable.countryCodeForMcc(
781                                    Integer.parseInt(imsi.substring(0,3)));
782            }
783            log("Setting locale to " + prefLang + "_" + country);
784            MccTable.setSystemLocale(mContext, prefLang, country);
785        } else {
786            log ("No suitable CSIM selected locale");
787        }
788    }
789
790    @Override
791    protected void onRecordLoaded() {
792        // One record loaded successfully or failed, In either case
793        // we need to update the recordsToLoad count
794        mRecordsToLoad -= 1;
795        if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
796
797        if (mRecordsToLoad == 0 && mRecordsRequested == true) {
798            onAllRecordsLoaded();
799        } else if (mRecordsToLoad < 0) {
800            loge("recordsToLoad <0, programmer error suspected");
801            mRecordsToLoad = 0;
802        }
803    }
804
805    @Override
806    protected void onAllRecordsLoaded() {
807        if (DBG) log("record load complete");
808
809        // Further records that can be inserted are Operator/OEM dependent
810
811        // FIXME: CSIM IMSI may not contain the MNC.
812        if (false) {
813            String operator = getRUIMOperatorNumeric();
814            if (!TextUtils.isEmpty(operator)) {
815                log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
816                        operator + "'");
817                log("update icc_operator_numeric=" + operator);
818                SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
819            } else {
820                log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
821            }
822
823            if (!TextUtils.isEmpty(mImsi)) {
824                log("onAllRecordsLoaded set mcc imsi=" + mImsi);
825                SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
826                        MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
827            } else {
828                log("onAllRecordsLoaded empty imsi skipping setting mcc");
829            }
830        }
831
832        setLocaleFromCsim();
833        mRecordsLoadedRegistrants.notifyRegistrants(
834            new AsyncResult(null, null, null));
835    }
836
837    @Override
838    public void onReady() {
839        fetchRuimRecords();
840
841        mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
842    }
843
844
845    private void fetchRuimRecords() {
846        mRecordsRequested = true;
847
848        if (DBG) log("fetchRuimRecords " + mRecordsToLoad);
849
850        mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
851        mRecordsToLoad++;
852
853        mFh.loadEFTransparent(EF_ICCID,
854                obtainMessage(EVENT_GET_ICCID_DONE));
855        mRecordsToLoad++;
856
857        mFh.loadEFTransparent(EF_PL,
858                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
859        mRecordsToLoad++;
860
861        mFh.loadEFTransparent(EF_CSIM_LI,
862                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
863        mRecordsToLoad++;
864
865        mFh.loadEFTransparent(EF_CSIM_SPN,
866                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
867        mRecordsToLoad++;
868
869        mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
870                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
871        mRecordsToLoad++;
872
873        mFh.loadEFTransparent(EF_CSIM_IMSIM,
874                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
875        mRecordsToLoad++;
876
877        mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
878                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
879        mRecordsToLoad++;
880
881        // Entire PRL could be huge. We are only interested in
882        // the first 4 bytes of the record.
883        mFh.loadEFTransparent(EF_CSIM_EPRL, 4,
884                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
885        mRecordsToLoad++;
886
887        mFh.loadEFTransparent(EF_CSIM_MIPUPP,
888                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMipUppLoaded()));
889        mRecordsToLoad++;
890
891        if (DBG) log("fetchRuimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
892        // Further records that can be inserted are Operator/OEM dependent
893    }
894
895    /**
896     * {@inheritDoc}
897     *
898     * No Display rule for RUIMs yet.
899     */
900    @Override
901    public int getDisplayRule(String plmn) {
902        // TODO together with spn
903        return 0;
904    }
905
906    @Override
907    public boolean isProvisioned() {
908        // If UICC card has CSIM app, look for MDN and MIN field
909        // to determine if the SIM is provisioned.  Otherwise,
910        // consider the SIM is provisioned. (for case of ordinal
911        // USIM only UICC.)
912        // If PROPERTY_TEST_CSIM is defined, bypess provision check
913        // and consider the SIM is provisioned.
914        if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
915            return true;
916        }
917
918        if (mParentApp == null) {
919            return false;
920        }
921
922        if (mParentApp.getType() == AppType.APPTYPE_CSIM &&
923            ((mMdn == null) || (mMin == null))) {
924            return false;
925        }
926        return true;
927    }
928
929    @Override
930    public void setVoiceMessageWaiting(int line, int countWaiting) {
931        if (line != 1) {
932            // only profile 1 is supported
933            return;
934        }
935
936        // range check
937        if (countWaiting < 0) {
938            countWaiting = -1;
939        } else if (countWaiting > 0xff) {
940            // C.S0015-B v2, 4.5.12
941            // range: 0-99
942            countWaiting = 0xff;
943        }
944        mCountVoiceMessages = countWaiting;
945
946        mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
947    }
948
949    private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
950        if (refreshResponse == null) {
951            if (DBG) log("handleRuimRefresh received without input");
952            return;
953        }
954
955        if (refreshResponse.aid != null &&
956                !refreshResponse.aid.equals(mParentApp.getAid())) {
957            // This is for different app. Ignore.
958            return;
959        }
960
961        switch (refreshResponse.refreshResult) {
962            case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
963                if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED");
964                mAdnCache.reset();
965                fetchRuimRecords();
966                break;
967            case IccRefreshResponse.REFRESH_RESULT_INIT:
968                if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT");
969                // need to reload all files (that we care about)
970                onIccRefreshInit();
971                break;
972            case IccRefreshResponse.REFRESH_RESULT_RESET:
973                if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
974                if (requirePowerOffOnSimRefreshReset()) {
975                    mCi.setRadioPower(false, null);
976                    /* Note: no need to call setRadioPower(true).  Assuming the desired
977                    * radio power state is still ON (as tracked by ServiceStateTracker),
978                    * ServiceStateTracker will call setRadioPower when it receives the
979                    * RADIO_STATE_CHANGED notification for the power off.  And if the
980                    * desired power state has changed in the interim, we don't want to
981                    * override it with an unconditional power on.
982                    */
983                }
984                break;
985            default:
986                // unknown refresh operation
987                if (DBG) log("handleRuimRefresh with unknown operation");
988                break;
989        }
990    }
991
992    public String getMdn() {
993        return mMdn;
994    }
995
996    public String getMin() {
997        return mMin;
998    }
999
1000    public String getSid() {
1001        return mHomeSystemId;
1002    }
1003
1004    public String getNid() {
1005        return mHomeNetworkId;
1006    }
1007
1008    public boolean getCsimSpnDisplayCondition() {
1009        return mCsimSpnDisplayCondition;
1010    }
1011    @Override
1012    protected void log(String s) {
1013        Rlog.d(LOG_TAG, "[RuimRecords] " + s);
1014    }
1015
1016    @Override
1017    protected void loge(String s) {
1018        Rlog.e(LOG_TAG, "[RuimRecords] " + s);
1019    }
1020
1021    @Override
1022    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1023        pw.println("RuimRecords: " + this);
1024        pw.println(" extends:");
1025        super.dump(fd, pw, args);
1026        pw.println(" mOtaCommited=" + mOtaCommited);
1027        pw.println(" mMyMobileNumber=" + mMyMobileNumber);
1028        pw.println(" mMin2Min1=" + mMin2Min1);
1029        pw.println(" mPrlVersion=" + mPrlVersion);
1030        pw.println(" mEFpl[]=" + Arrays.toString(mEFpl));
1031        pw.println(" mEFli[]=" + Arrays.toString(mEFli));
1032        pw.println(" mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition);
1033        pw.println(" mMdn=" + mMdn);
1034        pw.println(" mMin=" + mMin);
1035        pw.println(" mHomeSystemId=" + mHomeSystemId);
1036        pw.println(" mHomeNetworkId=" + mHomeNetworkId);
1037        pw.flush();
1038    }
1039}
1040