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