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