RuimRecords.java revision 062a2a3838c8d8adf16f4d9fbde8d52450da0336
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.SubscriptionManager;
36import android.telephony.Rlog;
37import android.text.TextUtils;
38import android.util.Log;
39import android.content.res.Resources;
40
41import com.android.internal.telephony.CommandsInterface;
42import com.android.internal.telephony.GsmAlphabet;
43import com.android.internal.telephony.MccTable;
44import com.android.internal.telephony.SubscriptionController;
45
46import com.android.internal.telephony.cdma.sms.UserData;
47import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
48import com.android.internal.util.BitwiseInputStream;
49
50/**
51 * {@hide}
52 */
53public final class RuimRecords extends IccRecords {
54    static final String LOG_TAG = "RuimRecords";
55
56    private boolean  mOtaCommited=false;
57
58    // ***** Instance Variables
59
60    private String mMyMobileNumber;
61    private String mMin2Min1;
62
63    private String mPrlVersion;
64    // From CSIM application
65    private byte[] mEFpl = null;
66    private byte[] mEFli = null;
67    boolean mCsimSpnDisplayCondition = false;
68    private String mMdn;
69    private String mMin;
70    private String mHomeSystemId;
71    private String mHomeNetworkId;
72    private String mNai;
73
74    @Override
75    public String toString() {
76        return "RuimRecords: " + super.toString()
77                + " m_ota_commited" + mOtaCommited
78                + " mMyMobileNumber=" + "xxxx"
79                + " mMin2Min1=" + mMin2Min1
80                + " mPrlVersion=" + mPrlVersion
81                + " mEFpl=" + mEFpl
82                + " mEFli=" + mEFli
83                + " mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition
84                + " mMdn=" + mMdn
85                + " mMin=" + mMin
86                + " mHomeSystemId=" + mHomeSystemId
87                + " mHomeNetworkId=" + mHomeNetworkId;
88    }
89
90    // ***** Event Constants
91    private static final int EVENT_GET_IMSI_DONE = 3;
92    private static final int EVENT_GET_DEVICE_IDENTITY_DONE = 4;
93    private static final int EVENT_GET_ICCID_DONE = 5;
94    private static final int EVENT_GET_CDMA_SUBSCRIPTION_DONE = 10;
95    private static final int EVENT_UPDATE_DONE = 14;
96    private static final int EVENT_GET_SST_DONE = 17;
97    private static final int EVENT_GET_ALL_SMS_DONE = 18;
98    private static final int EVENT_MARK_SMS_READ_DONE = 19;
99
100    private static final int EVENT_SMS_ON_RUIM = 21;
101    private static final int EVENT_GET_SMS_DONE = 22;
102
103    private static final int EVENT_RUIM_REFRESH = 31;
104
105    public RuimRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
106        super(app, c, ci);
107
108        mAdnCache = new AdnRecordCache(mFh);
109
110        mRecordsRequested = false;  // No load request is made till SIM ready
111
112        // recordsToLoad is set to 0 because no requests are made yet
113        mRecordsToLoad = 0;
114
115        // NOTE the EVENT_SMS_ON_RUIM is not registered
116        mCi.registerForIccRefresh(this, EVENT_RUIM_REFRESH, null);
117
118        // Start off by setting empty state
119        resetRecords();
120
121        mParentApp.registerForReady(this, EVENT_APP_READY, null);
122        if (DBG) log("RuimRecords X ctor this=" + this);
123    }
124
125    @Override
126    public void dispose() {
127        if (DBG) log("Disposing RuimRecords " + this);
128        //Unregister for all events
129        mCi.unregisterForIccRefresh(this);
130        mParentApp.unregisterForReady(this);
131        resetRecords();
132        super.dispose();
133    }
134
135    @Override
136    protected void finalize() {
137        if(DBG) log("RuimRecords finalized");
138    }
139
140    protected void resetRecords() {
141        mMncLength = UNINITIALIZED;
142        log("setting0 mMncLength" + mMncLength);
143        mIccId = null;
144
145        mAdnCache.reset();
146
147        // Don't clean up PROPERTY_ICC_OPERATOR_ISO_COUNTRY and
148        // PROPERTY_ICC_OPERATOR_NUMERIC here. Since not all CDMA
149        // devices have RUIM, these properties should keep the original
150        // values, e.g. build time settings, when there is no RUIM but
151        // set new values when RUIM is available and loaded.
152
153        // recordsRequested is set to false indicating that the SIM
154        // read requests made so far are not valid. This is set to
155        // true only when fresh set of read requests are made.
156        mRecordsRequested = false;
157    }
158
159    @Override
160    public String getIMSI() {
161        return mImsi;
162    }
163
164    public String getMdnNumber() {
165        return mMyMobileNumber;
166    }
167
168    public String getCdmaMin() {
169         return mMin2Min1;
170    }
171
172    /** Returns null if RUIM is not yet ready */
173    public String getPrlVersion() {
174        return mPrlVersion;
175    }
176
177    @Override
178    /** Returns null if RUIM is not yet ready */
179    public String getNAI() {
180        return mNai;
181    }
182
183    @Override
184    public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){
185        // In CDMA this is Operator/OEM dependent
186        AsyncResult.forMessage((onComplete)).exception =
187                new IccException("setVoiceMailNumber not implemented");
188        onComplete.sendToTarget();
189        loge("method setVoiceMailNumber is not implemented");
190    }
191
192    /**
193     * Called by CCAT Service when REFRESH is received.
194     * @param fileChanged indicates whether any files changed
195     * @param fileList if non-null, a list of EF files that changed
196     */
197    @Override
198    public void onRefresh(boolean fileChanged, int[] fileList) {
199        if (fileChanged) {
200            // A future optimization would be to inspect fileList and
201            // only reload those files that we care about.  For now,
202            // just re-fetch all RUIM records that we cache.
203            fetchRuimRecords();
204        }
205    }
206
207    private int adjstMinDigits (int digits) {
208        // Per C.S0005 section 2.3.1.
209        digits += 111;
210        digits = (digits % 10 == 0)?(digits - 10):digits;
211        digits = ((digits / 10) % 10 == 0)?(digits - 100):digits;
212        digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits;
213        return digits;
214    }
215
216    /**
217     * Returns the 5 or 6 digit MCC/MNC of the operator that
218     *  provided the RUIM card. Returns null of RUIM is not yet ready
219     */
220    public String getRUIMOperatorNumeric() {
221        if (mImsi == null) {
222            return null;
223        }
224
225        if (mMncLength != UNINITIALIZED && mMncLength != UNKNOWN) {
226            // Length = length of MCC + length of MNC
227            // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3)
228            return mImsi.substring(0, 3 + mMncLength);
229        }
230
231        // Guess the MNC length based on the MCC if we don't
232        // have a valid value in ef[ad]
233
234        int mcc = Integer.parseInt(mImsi.substring(0,3));
235        return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
236    }
237
238    // Refer to ETSI TS 102.221
239    private class EfPlLoaded implements IccRecordLoaded {
240        @Override
241        public String getEfName() {
242            return "EF_PL";
243        }
244
245        @Override
246        public void onRecordLoaded(AsyncResult ar) {
247            mEFpl = (byte[]) ar.result;
248            if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
249        }
250    }
251
252    // Refer to C.S0065 5.2.26
253    private class EfCsimLiLoaded implements IccRecordLoaded {
254        @Override
255        public String getEfName() {
256            return "EF_CSIM_LI";
257        }
258
259        @Override
260        public void onRecordLoaded(AsyncResult ar) {
261            mEFli = (byte[]) ar.result;
262            // convert csim efli data to iso 639 format
263            for (int i = 0; i < mEFli.length; i+=2) {
264                switch(mEFli[i+1]) {
265                case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
266                case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
267                case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
268                case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
269                case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
270                case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
271                case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
272                default: mEFli[i] = ' '; mEFli[i+1] = ' ';
273                }
274            }
275
276            if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
277        }
278    }
279
280    // Refer to C.S0065 5.2.32
281    private class EfCsimSpnLoaded implements IccRecordLoaded {
282        @Override
283        public String getEfName() {
284            return "EF_CSIM_SPN";
285        }
286
287        @Override
288        public void onRecordLoaded(AsyncResult ar) {
289            byte[] data = (byte[]) ar.result;
290            if (DBG) log("CSIM_SPN=" +
291                         IccUtils.bytesToHexString(data));
292
293            // C.S0065 for EF_SPN decoding
294            mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
295
296            int encoding = data[1];
297            int language = data[2];
298            byte[] spnData = new byte[32];
299            int len = ((data.length - 3) < 32) ? (data.length - 3) : 32;
300            System.arraycopy(data, 3, spnData, 0, len);
301
302            int numBytes;
303            for (numBytes = 0; numBytes < spnData.length; numBytes++) {
304                if ((spnData[numBytes] & 0xFF) == 0xFF) break;
305            }
306
307            if (numBytes == 0) {
308                setServiceProviderName("");
309                return;
310            }
311            try {
312                switch (encoding) {
313                case UserData.ENCODING_OCTET:
314                case UserData.ENCODING_LATIN:
315                    setServiceProviderName(new String(spnData, 0, numBytes, "ISO-8859-1"));
316                    break;
317                case UserData.ENCODING_IA5:
318                case UserData.ENCODING_GSM_7BIT_ALPHABET:
319                    setServiceProviderName(
320                            GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7));
321                    break;
322                case UserData.ENCODING_7BIT_ASCII:
323                    String spn = new String(spnData, 0, numBytes, "US-ASCII");
324                    // To address issues with incorrect encoding scheme
325                    // programmed in some commercial CSIM cards, the decoded
326                    // SPN is checked to have characters in printable ASCII
327                    // range. If not, they are decoded with
328                    // ENCODING_GSM_7BIT_ALPHABET scheme.
329                    if (TextUtils.isPrintableAsciiOnly(spn)) {
330                        setServiceProviderName(spn);
331                    } else {
332                        if (DBG) log("Some corruption in SPN decoding = " + spn);
333                        if (DBG) log("Using ENCODING_GSM_7BIT_ALPHABET scheme...");
334                        setServiceProviderName(
335                                GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes * 8) / 7));
336                    }
337                break;
338                case UserData.ENCODING_UNICODE_16:
339                    setServiceProviderName(new String(spnData, 0, numBytes, "utf-16"));
340                    break;
341                default:
342                    log("SPN encoding not supported");
343                }
344            } catch(Exception e) {
345                log("spn decode error: " + e);
346            }
347            if (DBG) log("spn=" + getServiceProviderName());
348            if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
349            SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
350        }
351    }
352
353    private class EfCsimMdnLoaded implements IccRecordLoaded {
354        @Override
355        public String getEfName() {
356            return "EF_CSIM_MDN";
357        }
358
359        @Override
360        public void onRecordLoaded(AsyncResult ar) {
361            byte[] data = (byte[]) ar.result;
362            if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
363            // Refer to C.S0065 5.2.35
364            int mdnDigitsNum = 0x0F & data[0];
365            mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
366            if (DBG) log("CSIM MDN=" + mMdn);
367        }
368    }
369
370    private class EfCsimImsimLoaded implements IccRecordLoaded {
371        @Override
372        public String getEfName() {
373            return "EF_CSIM_IMSIM";
374        }
375
376        @Override
377        public void onRecordLoaded(AsyncResult ar) {
378            byte[] data = (byte[]) ar.result;
379            if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
380            // C.S0065 section 5.2.2 for IMSI_M encoding
381            // C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
382            boolean provisioned = ((data[7] & 0x80) == 0x80);
383
384            if (provisioned) {
385                int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
386                int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
387                int digit7 = 0x0F & (data[4] >> 2);
388                if (digit7 > 0x09) digit7 = 0;
389                int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
390                first3digits = adjstMinDigits(first3digits);
391                second3digits = adjstMinDigits(second3digits);
392                last3digits = adjstMinDigits(last3digits);
393
394                StringBuilder builder = new StringBuilder();
395                builder.append(String.format(Locale.US, "%03d", first3digits));
396                builder.append(String.format(Locale.US, "%03d", second3digits));
397                builder.append(String.format(Locale.US, "%d", digit7));
398                builder.append(String.format(Locale.US, "%03d", last3digits));
399                mMin = builder.toString();
400                if (DBG) log("min present=" + mMin);
401            } else {
402                if (DBG) log("min not present");
403            }
404        }
405    }
406
407    private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
408        @Override
409        public String getEfName() {
410            return "EF_CSIM_CDMAHOME";
411        }
412
413        @Override
414        public void onRecordLoaded(AsyncResult ar) {
415            // Per C.S0065 section 5.2.8
416            ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
417            if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
418            if (dataList.isEmpty()) {
419                return;
420            }
421            StringBuilder sidBuf = new StringBuilder();
422            StringBuilder nidBuf = new StringBuilder();
423
424            for (byte[] data : dataList) {
425                if (data.length == 5) {
426                    int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
427                    int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
428                    sidBuf.append(sid).append(',');
429                    nidBuf.append(nid).append(',');
430                }
431            }
432            // remove trailing ","
433            sidBuf.setLength(sidBuf.length()-1);
434            nidBuf.setLength(nidBuf.length()-1);
435
436            mHomeSystemId = sidBuf.toString();
437            mHomeNetworkId = nidBuf.toString();
438        }
439    }
440
441    private class EfCsimEprlLoaded implements IccRecordLoaded {
442        @Override
443        public String getEfName() {
444            return "EF_CSIM_EPRL";
445        }
446        @Override
447        public void onRecordLoaded(AsyncResult ar) {
448            onGetCSimEprlDone(ar);
449        }
450    }
451
452    private void onGetCSimEprlDone(AsyncResult ar) {
453        // C.S0065 section 5.2.57 for EFeprl encoding
454        // C.S0016 section 3.5.5 for PRL format.
455        byte[] data = (byte[]) ar.result;
456        if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data));
457
458        // Only need the first 4 bytes of record
459        if (data.length > 3) {
460            int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
461            mPrlVersion = Integer.toString(prlId);
462        }
463        if (DBG) log("CSIM PRL version=" + mPrlVersion);
464    }
465
466    private class EfCsimMipUppLoaded implements IccRecordLoaded {
467        @Override
468        public String getEfName() {
469            return "EF_CSIM_MIPUPP";
470        }
471
472        boolean checkLengthLegal(int length, int expectLength) {
473            if(length < expectLength) {
474                Log.e(LOG_TAG, "CSIM MIPUPP format error, length = " + length  +
475                        "expected length at least =" + expectLength);
476                return false;
477            } else {
478                return true;
479            }
480        }
481
482        @Override
483        public void onRecordLoaded(AsyncResult ar) {
484            // 3GPP2 C.S0065 section 5.2.24
485            byte[] data = (byte[]) ar.result;
486
487            if(data.length < 1) {
488                Log.e(LOG_TAG,"MIPUPP read error");
489                return;
490            }
491
492            BitwiseInputStream bitStream = new BitwiseInputStream(data);
493            try {
494                int  mipUppLength = bitStream.read(8);
495                //transfer length from byte to bit
496                mipUppLength = (mipUppLength << 3);
497
498                if (!checkLengthLegal(mipUppLength, 1)) {
499                    return;
500                }
501                //parse the MIPUPP body 3GPP2 C.S0016-C 3.5.8.6
502                int retryInfoInclude = bitStream.read(1);
503                mipUppLength--;
504
505                if(retryInfoInclude == 1) {
506                    if (!checkLengthLegal(mipUppLength, 11)) {
507                        return;
508                    }
509                    bitStream.skip(11); //not used now
510                    //transfer length from byte to bit
511                    mipUppLength -= 11;
512                }
513
514                if (!checkLengthLegal(mipUppLength, 4)) {
515                    return;
516                }
517                int numNai = bitStream.read(4);
518                mipUppLength -= 4;
519
520                //start parse NAI body
521                for(int index = 0; index < numNai; index++) {
522                    if (!checkLengthLegal(mipUppLength, 4)) {
523                        return;
524                    }
525                    int naiEntryIndex = bitStream.read(4);
526                    mipUppLength -= 4;
527
528                    if (!checkLengthLegal(mipUppLength, 8)) {
529                        return;
530                    }
531                    int naiLength = bitStream.read(8);
532                    mipUppLength -= 8;
533
534                    if(naiEntryIndex == 0) {
535                        //we find the one!
536                        if (!checkLengthLegal(mipUppLength, naiLength << 3)) {
537                            return;
538                        }
539                        char naiCharArray[] = new char[naiLength];
540                        for(int index1 = 0; index1 < naiLength; index1++) {
541                            naiCharArray[index1] = (char)(bitStream.read(8) & 0xFF);
542                        }
543                        mNai =  new String(naiCharArray);
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: " + 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    private String findBestLanguage(byte[] languages) {
742        final String[] assetLanguages = getAssetLanguages(mContext);
743
744        if ((languages == null) || (assetLanguages == null)) return null;
745
746        // Each 2-bytes consists of one language
747        for (int i = 0; (i + 1) < languages.length; i += 2) {
748            final String lang;
749            try {
750                lang = new String(languages, i, 2, "ISO-8859-1");
751            } catch(java.io.UnsupportedEncodingException e) {
752                log("Failed to parse SIM language records");
753                continue;
754            }
755
756            for (int j = 0; j < assetLanguages.length; j++) {
757                if (assetLanguages[j].equals(lang)) {
758                    return lang;
759                }
760            }
761        }
762
763        // no match found. return null
764        return null;
765    }
766
767    private void setLocaleFromCsim() {
768        String prefLang = null;
769        // check EFli then EFpl
770        prefLang = findBestLanguage(mEFli);
771
772        if (prefLang == null) {
773            prefLang = findBestLanguage(mEFpl);
774        }
775
776        if (prefLang != null) {
777            // check country code from SIM
778            String imsi = getIMSI();
779            String country = null;
780            if (imsi != null) {
781                country = MccTable.countryCodeForMcc(
782                                    Integer.parseInt(imsi.substring(0,3)));
783            }
784            log("Setting locale to " + prefLang + "_" + country);
785            MccTable.setSystemLocale(mContext, prefLang, country);
786        } else {
787            log ("No suitable CSIM selected locale");
788        }
789    }
790
791    @Override
792    protected void onRecordLoaded() {
793        // One record loaded successfully or failed, In either case
794        // we need to update the recordsToLoad count
795        mRecordsToLoad -= 1;
796        if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
797
798        if (mRecordsToLoad == 0 && mRecordsRequested == true) {
799            onAllRecordsLoaded();
800        } else if (mRecordsToLoad < 0) {
801            loge("recordsToLoad <0, programmer error suspected");
802            mRecordsToLoad = 0;
803        }
804    }
805
806    @Override
807    protected void onAllRecordsLoaded() {
808        if (DBG) log("record load complete");
809
810        // Further records that can be inserted are Operator/OEM dependent
811
812        // FIXME: CSIM IMSI may not contain the MNC.
813        if (false) {
814            String operator = getRUIMOperatorNumeric();
815            if (!TextUtils.isEmpty(operator)) {
816                log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
817                        operator + "'");
818                log("update icc_operator_numeric=" + operator);
819                SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
820            } else {
821                log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
822            }
823
824            if (!TextUtils.isEmpty(mImsi)) {
825                log("onAllRecordsLoaded set mcc imsi=" + mImsi);
826                SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
827                        MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
828            } else {
829                log("onAllRecordsLoaded empty imsi skipping setting mcc");
830            }
831        }
832
833        setLocaleFromCsim();
834        mRecordsLoadedRegistrants.notifyRegistrants(
835            new AsyncResult(null, null, null));
836
837        // TODO: The below is hacky since the SubscriptionController may not be ready at this time.
838        if (!TextUtils.isEmpty(mMdn)) {
839            int phoneId = mParentApp.getUiccCard().getPhoneId();
840            int[] subIds = SubscriptionController.getInstance().getSubId(phoneId);
841            if (subIds != null) {
842                log("Calling setDisplayNumber for subId and number " + subIds[0] + " and " + mMdn);
843                SubscriptionManager.from(mContext).setDisplayNumber(mMdn, subIds[0]);
844            } else {
845                log("Cannot call setDisplayNumber: invalid subId");
846            }
847        }
848    }
849
850    @Override
851    public void onReady() {
852        fetchRuimRecords();
853
854        mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
855    }
856
857
858    private void fetchRuimRecords() {
859        mRecordsRequested = true;
860
861        if (DBG) log("fetchRuimRecords " + mRecordsToLoad);
862
863        mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
864        mRecordsToLoad++;
865
866        mFh.loadEFTransparent(EF_ICCID,
867                obtainMessage(EVENT_GET_ICCID_DONE));
868        mRecordsToLoad++;
869
870        Resources resource = Resources.getSystem();
871        if (resource.getBoolean(com.android.internal.R.bool.config_use_sim_language_file)) {
872            mFh.loadEFTransparent(EF_PL,
873                    obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
874            mRecordsToLoad++;
875
876            mFh.loadEFTransparent(EF_CSIM_LI,
877                    obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
878            mRecordsToLoad++;
879        }
880
881        mFh.loadEFTransparent(EF_CSIM_SPN,
882                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
883        mRecordsToLoad++;
884
885        mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
886                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
887        mRecordsToLoad++;
888
889        mFh.loadEFTransparent(EF_CSIM_IMSIM,
890                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
891        mRecordsToLoad++;
892
893        mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
894                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
895        mRecordsToLoad++;
896
897        // Entire PRL could be huge. We are only interested in
898        // the first 4 bytes of the record.
899        mFh.loadEFTransparent(EF_CSIM_EPRL, 4,
900                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
901        mRecordsToLoad++;
902
903        mFh.loadEFTransparent(EF_CSIM_MIPUPP,
904                obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMipUppLoaded()));
905        mRecordsToLoad++;
906
907        if (DBG) log("fetchRuimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
908        // Further records that can be inserted are Operator/OEM dependent
909    }
910
911    /**
912     * {@inheritDoc}
913     *
914     * No Display rule for RUIMs yet.
915     */
916    @Override
917    public int getDisplayRule(String plmn) {
918        // TODO together with spn
919        return 0;
920    }
921
922    @Override
923    public boolean isProvisioned() {
924        // If UICC card has CSIM app, look for MDN and MIN field
925        // to determine if the SIM is provisioned.  Otherwise,
926        // consider the SIM is provisioned. (for case of ordinal
927        // USIM only UICC.)
928        // If PROPERTY_TEST_CSIM is defined, bypess provision check
929        // and consider the SIM is provisioned.
930        if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
931            return true;
932        }
933
934        if (mParentApp == null) {
935            return false;
936        }
937
938        if (mParentApp.getType() == AppType.APPTYPE_CSIM &&
939            ((mMdn == null) || (mMin == null))) {
940            return false;
941        }
942        return true;
943    }
944
945    @Override
946    public void setVoiceMessageWaiting(int line, int countWaiting) {
947        // Will be used in future to store voice mail count in UIM
948        // C.S0023-D_v1.0 does not have a file id in UIM for MWI
949        log("RuimRecords:setVoiceMessageWaiting - NOP for CDMA");
950    }
951
952    @Override
953    public int getVoiceMessageCount() {
954        // Will be used in future to retrieve voice mail count for UIM
955        // C.S0023-D_v1.0 does not have a file id in UIM for MWI
956        log("RuimRecords:getVoiceMessageCount - NOP for CDMA");
957        return 0;
958    }
959
960    private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
961        if (refreshResponse == null) {
962            if (DBG) log("handleRuimRefresh received without input");
963            return;
964        }
965
966        if (refreshResponse.aid != null &&
967                !refreshResponse.aid.equals(mParentApp.getAid())) {
968            // This is for different app. Ignore.
969            return;
970        }
971
972        switch (refreshResponse.refreshResult) {
973            case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
974                if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED");
975                mAdnCache.reset();
976                fetchRuimRecords();
977                break;
978            case IccRefreshResponse.REFRESH_RESULT_INIT:
979                if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT");
980                // need to reload all files (that we care about)
981                onIccRefreshInit();
982                break;
983            case IccRefreshResponse.REFRESH_RESULT_RESET:
984                // Refresh reset is handled by the UiccCard object.
985                if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
986                break;
987            default:
988                // unknown refresh operation
989                if (DBG) log("handleRuimRefresh with unknown operation");
990                break;
991        }
992    }
993
994    public String getMdn() {
995        return mMdn;
996    }
997
998    public String getMin() {
999        return mMin;
1000    }
1001
1002    public String getSid() {
1003        return mHomeSystemId;
1004    }
1005
1006    public String getNid() {
1007        return mHomeNetworkId;
1008    }
1009
1010    public boolean getCsimSpnDisplayCondition() {
1011        return mCsimSpnDisplayCondition;
1012    }
1013    @Override
1014    protected void log(String s) {
1015        Rlog.d(LOG_TAG, "[RuimRecords] " + s);
1016    }
1017
1018    @Override
1019    protected void loge(String s) {
1020        Rlog.e(LOG_TAG, "[RuimRecords] " + s);
1021    }
1022
1023    @Override
1024    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1025        pw.println("RuimRecords: " + this);
1026        pw.println(" extends:");
1027        super.dump(fd, pw, args);
1028        pw.println(" mOtaCommited=" + mOtaCommited);
1029        pw.println(" mMyMobileNumber=" + mMyMobileNumber);
1030        pw.println(" mMin2Min1=" + mMin2Min1);
1031        pw.println(" mPrlVersion=" + mPrlVersion);
1032        pw.println(" mEFpl[]=" + Arrays.toString(mEFpl));
1033        pw.println(" mEFli[]=" + Arrays.toString(mEFli));
1034        pw.println(" mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition);
1035        pw.println(" mMdn=" + mMdn);
1036        pw.println(" mMin=" + mMin);
1037        pw.println(" mHomeSystemId=" + mHomeSystemId);
1038        pw.println(" mHomeNetworkId=" + mHomeNetworkId);
1039        pw.flush();
1040    }
1041}
1042