Home | History | Annotate | Download | only in uicc
      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 
     17 package com.android.internal.telephony.uicc;
     18 
     19 
     20 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ISO_COUNTRY;
     21 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC;
     22 
     23 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA;
     24 import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM;
     25 
     26 import java.io.FileDescriptor;
     27 import java.io.PrintWriter;
     28 import java.util.ArrayList;
     29 import java.util.Arrays;
     30 import java.util.Locale;
     31 import android.content.Context;
     32 import android.os.AsyncResult;
     33 import android.os.Message;
     34 import android.os.SystemProperties;
     35 import android.telephony.TelephonyManager;
     36 import android.telephony.Rlog;
     37 import android.text.TextUtils;
     38 
     39 import com.android.internal.telephony.CommandsInterface;
     40 import com.android.internal.telephony.GsmAlphabet;
     41 import com.android.internal.telephony.MccTable;
     42 
     43 import com.android.internal.telephony.cdma.sms.UserData;
     44 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppType;
     45 
     46 
     47 /**
     48  * {@hide}
     49  */
     50 public final class RuimRecords extends IccRecords {
     51     static final String LOG_TAG = "RuimRecords";
     52 
     53     private boolean  mOtaCommited=false;
     54 
     55     // ***** Instance Variables
     56 
     57     private String mMyMobileNumber;
     58     private String mMin2Min1;
     59 
     60     private String mPrlVersion;
     61     // From CSIM application
     62     private byte[] mEFpl = null;
     63     private byte[] mEFli = null;
     64     boolean mCsimSpnDisplayCondition = false;
     65     private String mMdn;
     66     private String mMin;
     67     private String mHomeSystemId;
     68     private String mHomeNetworkId;
     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         mCountVoiceMessages = 0;
    138         mMncLength = UNINITIALIZED;
    139         log("setting0 mMncLength" + mMncLength);
    140         mIccId = 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     public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete){
    176         // In CDMA this is Operator/OEM dependent
    177         AsyncResult.forMessage((onComplete)).exception =
    178                 new IccException("setVoiceMailNumber not implemented");
    179         onComplete.sendToTarget();
    180         loge("method setVoiceMailNumber is not implemented");
    181     }
    182 
    183     /**
    184      * Called by CCAT Service when REFRESH is received.
    185      * @param fileChanged indicates whether any files changed
    186      * @param fileList if non-null, a list of EF files that changed
    187      */
    188     @Override
    189     public void onRefresh(boolean fileChanged, int[] fileList) {
    190         if (fileChanged) {
    191             // A future optimization would be to inspect fileList and
    192             // only reload those files that we care about.  For now,
    193             // just re-fetch all RUIM records that we cache.
    194             fetchRuimRecords();
    195         }
    196     }
    197 
    198     private int adjstMinDigits (int digits) {
    199         // Per C.S0005 section 2.3.1.
    200         digits += 111;
    201         digits = (digits % 10 == 0)?(digits - 10):digits;
    202         digits = ((digits / 10) % 10 == 0)?(digits - 100):digits;
    203         digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits;
    204         return digits;
    205     }
    206 
    207     /**
    208      * Returns the 5 or 6 digit MCC/MNC of the operator that
    209      *  provided the RUIM card. Returns null of RUIM is not yet ready
    210      */
    211     public String getRUIMOperatorNumeric() {
    212         if (mImsi == null) {
    213             return null;
    214         }
    215 
    216         if (mMncLength != UNINITIALIZED && mMncLength != UNKNOWN) {
    217             // Length = length of MCC + length of MNC
    218             // length of mcc = 3 (3GPP2 C.S0005 - Section 2.3)
    219             return mImsi.substring(0, 3 + mMncLength);
    220         }
    221 
    222         // Guess the MNC length based on the MCC if we don't
    223         // have a valid value in ef[ad]
    224 
    225         int mcc = Integer.parseInt(mImsi.substring(0,3));
    226         return mImsi.substring(0, 3 + MccTable.smallestDigitsMccForMnc(mcc));
    227     }
    228 
    229     // Refer to ETSI TS 102.221
    230     private class EfPlLoaded implements IccRecordLoaded {
    231         @Override
    232         public String getEfName() {
    233             return "EF_PL";
    234         }
    235 
    236         @Override
    237         public void onRecordLoaded(AsyncResult ar) {
    238             mEFpl = (byte[]) ar.result;
    239             if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl));
    240         }
    241     }
    242 
    243     // Refer to C.S0065 5.2.26
    244     private class EfCsimLiLoaded implements IccRecordLoaded {
    245         @Override
    246         public String getEfName() {
    247             return "EF_CSIM_LI";
    248         }
    249 
    250         @Override
    251         public void onRecordLoaded(AsyncResult ar) {
    252             mEFli = (byte[]) ar.result;
    253             // convert csim efli data to iso 639 format
    254             for (int i = 0; i < mEFli.length; i+=2) {
    255                 switch(mEFli[i+1]) {
    256                 case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break;
    257                 case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break;
    258                 case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break;
    259                 case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break;
    260                 case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break;
    261                 case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break;
    262                 case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break;
    263                 default: mEFli[i] = ' '; mEFli[i+1] = ' ';
    264                 }
    265             }
    266 
    267             if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli));
    268         }
    269     }
    270 
    271     // Refer to C.S0065 5.2.32
    272     private class EfCsimSpnLoaded implements IccRecordLoaded {
    273         @Override
    274         public String getEfName() {
    275             return "EF_CSIM_SPN";
    276         }
    277 
    278         @Override
    279         public void onRecordLoaded(AsyncResult ar) {
    280             byte[] data = (byte[]) ar.result;
    281             if (DBG) log("CSIM_SPN=" +
    282                          IccUtils.bytesToHexString(data));
    283 
    284             // C.S0065 for EF_SPN decoding
    285             mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0);
    286 
    287             int encoding = data[1];
    288             int language = data[2];
    289             byte[] spnData = new byte[32];
    290             int len = ((data.length - 3) < 32) ? (data.length - 3) : 32;
    291             System.arraycopy(data, 3, spnData, 0, len);
    292 
    293             int numBytes;
    294             for (numBytes = 0; numBytes < spnData.length; numBytes++) {
    295                 if ((spnData[numBytes] & 0xFF) == 0xFF) break;
    296             }
    297 
    298             if (numBytes == 0) {
    299                 setServiceProviderName("");
    300                 return;
    301             }
    302             try {
    303                 switch (encoding) {
    304                 case UserData.ENCODING_OCTET:
    305                 case UserData.ENCODING_LATIN:
    306                     setServiceProviderName(new String(spnData, 0, numBytes, "ISO-8859-1"));
    307                     break;
    308                 case UserData.ENCODING_IA5:
    309                 case UserData.ENCODING_GSM_7BIT_ALPHABET:
    310                     setServiceProviderName(
    311                             GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7));
    312                     break;
    313                 case UserData.ENCODING_7BIT_ASCII:
    314                     String spn = new String(spnData, 0, numBytes, "US-ASCII");
    315                     // To address issues with incorrect encoding scheme
    316                     // programmed in some commercial CSIM cards, the decoded
    317                     // SPN is checked to have characters in printable ASCII
    318                     // range. If not, they are decoded with
    319                     // ENCODING_GSM_7BIT_ALPHABET scheme.
    320                     if (TextUtils.isPrintableAsciiOnly(spn)) {
    321                         setServiceProviderName(spn);
    322                     } else {
    323                         if (DBG) log("Some corruption in SPN decoding = " + spn);
    324                         if (DBG) log("Using ENCODING_GSM_7BIT_ALPHABET scheme...");
    325                         setServiceProviderName(
    326                                 GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes * 8) / 7));
    327                     }
    328                 break;
    329                 case UserData.ENCODING_UNICODE_16:
    330                     setServiceProviderName(new String(spnData, 0, numBytes, "utf-16"));
    331                     break;
    332                 default:
    333                     log("SPN encoding not supported");
    334                 }
    335             } catch(Exception e) {
    336                 log("spn decode error: " + e);
    337             }
    338             if (DBG) log("spn=" + getServiceProviderName());
    339             if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition);
    340             SystemProperties.set(PROPERTY_ICC_OPERATOR_ALPHA, getServiceProviderName());
    341         }
    342     }
    343 
    344     private class EfCsimMdnLoaded implements IccRecordLoaded {
    345         @Override
    346         public String getEfName() {
    347             return "EF_CSIM_MDN";
    348         }
    349 
    350         @Override
    351         public void onRecordLoaded(AsyncResult ar) {
    352             byte[] data = (byte[]) ar.result;
    353             if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data));
    354             // Refer to C.S0065 5.2.35
    355             int mdnDigitsNum = 0x0F & data[0];
    356             mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum);
    357             if (DBG) log("CSIM MDN=" + mMdn);
    358         }
    359     }
    360 
    361     private class EfCsimImsimLoaded implements IccRecordLoaded {
    362         @Override
    363         public String getEfName() {
    364             return "EF_CSIM_IMSIM";
    365         }
    366 
    367         @Override
    368         public void onRecordLoaded(AsyncResult ar) {
    369             byte[] data = (byte[]) ar.result;
    370             if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data));
    371             // C.S0065 section 5.2.2 for IMSI_M encoding
    372             // C.S0005 section 2.3.1 for MIN encoding in IMSI_M.
    373             boolean provisioned = ((data[7] & 0x80) == 0x80);
    374 
    375             if (provisioned) {
    376                 int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]);
    377                 int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6;
    378                 int digit7 = 0x0F & (data[4] >> 2);
    379                 if (digit7 > 0x09) digit7 = 0;
    380                 int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]);
    381                 first3digits = adjstMinDigits(first3digits);
    382                 second3digits = adjstMinDigits(second3digits);
    383                 last3digits = adjstMinDigits(last3digits);
    384 
    385                 StringBuilder builder = new StringBuilder();
    386                 builder.append(String.format(Locale.US, "%03d", first3digits));
    387                 builder.append(String.format(Locale.US, "%03d", second3digits));
    388                 builder.append(String.format(Locale.US, "%d", digit7));
    389                 builder.append(String.format(Locale.US, "%03d", last3digits));
    390                 mMin = builder.toString();
    391                 if (DBG) log("min present=" + mMin);
    392             } else {
    393                 if (DBG) log("min not present");
    394             }
    395         }
    396     }
    397 
    398     private class EfCsimCdmaHomeLoaded implements IccRecordLoaded {
    399         @Override
    400         public String getEfName() {
    401             return "EF_CSIM_CDMAHOME";
    402         }
    403 
    404         @Override
    405         public void onRecordLoaded(AsyncResult ar) {
    406             // Per C.S0065 section 5.2.8
    407             ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result;
    408             if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size());
    409             if (dataList.isEmpty()) {
    410                 return;
    411             }
    412             StringBuilder sidBuf = new StringBuilder();
    413             StringBuilder nidBuf = new StringBuilder();
    414 
    415             for (byte[] data : dataList) {
    416                 if (data.length == 5) {
    417                     int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF);
    418                     int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF);
    419                     sidBuf.append(sid).append(',');
    420                     nidBuf.append(nid).append(',');
    421                 }
    422             }
    423             // remove trailing ","
    424             sidBuf.setLength(sidBuf.length()-1);
    425             nidBuf.setLength(nidBuf.length()-1);
    426 
    427             mHomeSystemId = sidBuf.toString();
    428             mHomeNetworkId = nidBuf.toString();
    429         }
    430     }
    431 
    432     private class EfCsimEprlLoaded implements IccRecordLoaded {
    433         @Override
    434         public String getEfName() {
    435             return "EF_CSIM_EPRL";
    436         }
    437         @Override
    438         public void onRecordLoaded(AsyncResult ar) {
    439             onGetCSimEprlDone(ar);
    440         }
    441     }
    442 
    443     private void onGetCSimEprlDone(AsyncResult ar) {
    444         // C.S0065 section 5.2.57 for EFeprl encoding
    445         // C.S0016 section 3.5.5 for PRL format.
    446         byte[] data = (byte[]) ar.result;
    447         if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data));
    448 
    449         // Only need the first 4 bytes of record
    450         if (data.length > 3) {
    451             int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF);
    452             mPrlVersion = Integer.toString(prlId);
    453         }
    454         if (DBG) log("CSIM PRL version=" + mPrlVersion);
    455     }
    456 
    457     @Override
    458     public void handleMessage(Message msg) {
    459         AsyncResult ar;
    460 
    461         byte data[];
    462 
    463         boolean isRecordLoadResponse = false;
    464 
    465         if (mDestroyed.get()) {
    466             loge("Received message " + msg +
    467                     "[" + msg.what + "] while being destroyed. Ignoring.");
    468             return;
    469         }
    470 
    471         try { switch (msg.what) {
    472             case EVENT_APP_READY:
    473                 onReady();
    474                 break;
    475 
    476             case EVENT_GET_DEVICE_IDENTITY_DONE:
    477                 log("Event EVENT_GET_DEVICE_IDENTITY_DONE Received");
    478             break;
    479 
    480             /* IO events */
    481             case EVENT_GET_IMSI_DONE:
    482                 isRecordLoadResponse = true;
    483 
    484                 ar = (AsyncResult)msg.obj;
    485                 if (ar.exception != null) {
    486                     loge("Exception querying IMSI, Exception:" + ar.exception);
    487                     break;
    488                 }
    489 
    490                 mImsi = (String) ar.result;
    491 
    492                 // IMSI (MCC+MNC+MSIN) is at least 6 digits, but not more
    493                 // than 15 (and usually 15).
    494                 if (mImsi != null && (mImsi.length() < 6 || mImsi.length() > 15)) {
    495                     loge("invalid IMSI " + mImsi);
    496                     mImsi = null;
    497                 }
    498 
    499                 // FIXME: CSIM IMSI may not contain the MNC.
    500                 if (false) {
    501                     log("IMSI: " + mImsi.substring(0, 6) + "xxxxxxxxx");
    502 
    503                     String operatorNumeric = getRUIMOperatorNumeric();
    504                     if (operatorNumeric != null) {
    505                         if (operatorNumeric.length() <= 6) {
    506                             log("update mccmnc=" + operatorNumeric);
    507                             MccTable.updateMccMncConfiguration(mContext, operatorNumeric, false);
    508                         }
    509                     }
    510                 } else {
    511                     String operatorNumeric = getRUIMOperatorNumeric();
    512                     log("NO update mccmnc=" + operatorNumeric);
    513                 }
    514 
    515             break;
    516 
    517             case EVENT_GET_CDMA_SUBSCRIPTION_DONE:
    518                 ar = (AsyncResult)msg.obj;
    519                 String localTemp[] = (String[])ar.result;
    520                 if (ar.exception != null) {
    521                     break;
    522                 }
    523 
    524                 mMyMobileNumber = localTemp[0];
    525                 mMin2Min1 = localTemp[3];
    526                 mPrlVersion = localTemp[4];
    527 
    528                 log("MDN: " + mMyMobileNumber + " MIN: " + mMin2Min1);
    529 
    530             break;
    531 
    532             case EVENT_GET_ICCID_DONE:
    533                 isRecordLoadResponse = true;
    534 
    535                 ar = (AsyncResult)msg.obj;
    536                 data = (byte[])ar.result;
    537 
    538                 if (ar.exception != null) {
    539                     break;
    540                 }
    541 
    542                 mIccId = IccUtils.bcdToString(data, 0, data.length);
    543 
    544                 log("iccid: " + mIccId);
    545 
    546             break;
    547 
    548             case EVENT_UPDATE_DONE:
    549                 ar = (AsyncResult)msg.obj;
    550                 if (ar.exception != null) {
    551                     Rlog.i(LOG_TAG, "RuimRecords update failed", ar.exception);
    552                 }
    553             break;
    554 
    555             case EVENT_GET_ALL_SMS_DONE:
    556             case EVENT_MARK_SMS_READ_DONE:
    557             case EVENT_SMS_ON_RUIM:
    558             case EVENT_GET_SMS_DONE:
    559                 Rlog.w(LOG_TAG, "Event not supported: " + msg.what);
    560                 break;
    561 
    562             // TODO: probably EF_CST should be read instead
    563             case EVENT_GET_SST_DONE:
    564                 log("Event EVENT_GET_SST_DONE Received");
    565             break;
    566 
    567             case EVENT_RUIM_REFRESH:
    568                 isRecordLoadResponse = false;
    569                 ar = (AsyncResult)msg.obj;
    570                 if (ar.exception == null) {
    571                     handleRuimRefresh((IccRefreshResponse)ar.result);
    572                 }
    573                 break;
    574 
    575             default:
    576                 super.handleMessage(msg);   // IccRecords handles generic record load responses
    577 
    578         }}catch (RuntimeException exc) {
    579             // I don't want these exceptions to be fatal
    580             Rlog.w(LOG_TAG, "Exception parsing RUIM record", exc);
    581         } finally {
    582             // Count up record load responses even if they are fails
    583             if (isRecordLoadResponse) {
    584                 onRecordLoaded();
    585             }
    586         }
    587     }
    588 
    589     /**
    590      * Returns an array of languages we have assets for.
    591      *
    592      * NOTE: This array will have duplicates. If this method will be caused
    593      * frequently or in a tight loop, it can be rewritten for efficiency.
    594      */
    595     private static String[] getAssetLanguages(Context ctx) {
    596         final String[] locales = ctx.getAssets().getLocales();
    597         final String[] localeLangs = new String[locales.length];
    598         for (int i = 0; i < locales.length; ++i) {
    599             final String localeStr = locales[i];
    600             final int separator = localeStr.indexOf('-');
    601             if (separator < 0) {
    602                 localeLangs[i] = localeStr;
    603             } else {
    604                 localeLangs[i] = localeStr.substring(0, separator);
    605             }
    606         }
    607 
    608         return localeLangs;
    609     }
    610 
    611     private String findBestLanguage(byte[] languages) {
    612         final String[] assetLanguages = getAssetLanguages(mContext);
    613 
    614         if ((languages == null) || (assetLanguages == null)) return null;
    615 
    616         // Each 2-bytes consists of one language
    617         for (int i = 0; (i + 1) < languages.length; i += 2) {
    618             final String lang;
    619             try {
    620                 lang = new String(languages, i, 2, "ISO-8859-1");
    621             } catch(java.io.UnsupportedEncodingException e) {
    622                 log("Failed to parse SIM language records");
    623                 continue;
    624             }
    625 
    626             for (int j = 0; j < assetLanguages.length; j++) {
    627                 if (assetLanguages[j].equals(lang)) {
    628                     return lang;
    629                 }
    630             }
    631         }
    632 
    633         // no match found. return null
    634         return null;
    635     }
    636 
    637     private void setLocaleFromCsim() {
    638         String prefLang = null;
    639         // check EFli then EFpl
    640         prefLang = findBestLanguage(mEFli);
    641 
    642         if (prefLang == null) {
    643             prefLang = findBestLanguage(mEFpl);
    644         }
    645 
    646         if (prefLang != null) {
    647             // check country code from SIM
    648             String imsi = getIMSI();
    649             String country = null;
    650             if (imsi != null) {
    651                 country = MccTable.countryCodeForMcc(
    652                                     Integer.parseInt(imsi.substring(0,3)));
    653             }
    654             log("Setting locale to " + prefLang + "_" + country);
    655             MccTable.setSystemLocale(mContext, prefLang, country);
    656         } else {
    657             log ("No suitable CSIM selected locale");
    658         }
    659     }
    660 
    661     @Override
    662     protected void onRecordLoaded() {
    663         // One record loaded successfully or failed, In either case
    664         // we need to update the recordsToLoad count
    665         mRecordsToLoad -= 1;
    666         if (DBG) log("onRecordLoaded " + mRecordsToLoad + " requested: " + mRecordsRequested);
    667 
    668         if (mRecordsToLoad == 0 && mRecordsRequested == true) {
    669             onAllRecordsLoaded();
    670         } else if (mRecordsToLoad < 0) {
    671             loge("recordsToLoad <0, programmer error suspected");
    672             mRecordsToLoad = 0;
    673         }
    674     }
    675 
    676     @Override
    677     protected void onAllRecordsLoaded() {
    678         if (DBG) log("record load complete");
    679 
    680         // Further records that can be inserted are Operator/OEM dependent
    681 
    682         // FIXME: CSIM IMSI may not contain the MNC.
    683         if (false) {
    684             String operator = getRUIMOperatorNumeric();
    685             if (!TextUtils.isEmpty(operator)) {
    686                 log("onAllRecordsLoaded set 'gsm.sim.operator.numeric' to operator='" +
    687                         operator + "'");
    688                 log("update icc_operator_numeric=" + operator);
    689                 SystemProperties.set(PROPERTY_ICC_OPERATOR_NUMERIC, operator);
    690             } else {
    691                 log("onAllRecordsLoaded empty 'gsm.sim.operator.numeric' skipping");
    692             }
    693 
    694             if (!TextUtils.isEmpty(mImsi)) {
    695                 log("onAllRecordsLoaded set mcc imsi=" + mImsi);
    696                 SystemProperties.set(PROPERTY_ICC_OPERATOR_ISO_COUNTRY,
    697                         MccTable.countryCodeForMcc(Integer.parseInt(mImsi.substring(0,3))));
    698             } else {
    699                 log("onAllRecordsLoaded empty imsi skipping setting mcc");
    700             }
    701         }
    702 
    703         setLocaleFromCsim();
    704         mRecordsLoadedRegistrants.notifyRegistrants(
    705             new AsyncResult(null, null, null));
    706     }
    707 
    708     @Override
    709     public void onReady() {
    710         fetchRuimRecords();
    711 
    712         mCi.getCDMASubscription(obtainMessage(EVENT_GET_CDMA_SUBSCRIPTION_DONE));
    713     }
    714 
    715 
    716     private void fetchRuimRecords() {
    717         mRecordsRequested = true;
    718 
    719         if (DBG) log("fetchRuimRecords " + mRecordsToLoad);
    720 
    721         mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
    722         mRecordsToLoad++;
    723 
    724         mFh.loadEFTransparent(EF_ICCID,
    725                 obtainMessage(EVENT_GET_ICCID_DONE));
    726         mRecordsToLoad++;
    727 
    728         mFh.loadEFTransparent(EF_PL,
    729                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded()));
    730         mRecordsToLoad++;
    731 
    732         mFh.loadEFTransparent(EF_CSIM_LI,
    733                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded()));
    734         mRecordsToLoad++;
    735 
    736         mFh.loadEFTransparent(EF_CSIM_SPN,
    737                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded()));
    738         mRecordsToLoad++;
    739 
    740         mFh.loadEFLinearFixed(EF_CSIM_MDN, 1,
    741                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded()));
    742         mRecordsToLoad++;
    743 
    744         mFh.loadEFTransparent(EF_CSIM_IMSIM,
    745                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded()));
    746         mRecordsToLoad++;
    747 
    748         mFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME,
    749                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded()));
    750         mRecordsToLoad++;
    751 
    752         // Entire PRL could be huge. We are only interested in
    753         // the first 4 bytes of the record.
    754         mFh.loadEFTransparent(EF_CSIM_EPRL, 4,
    755                 obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded()));
    756         mRecordsToLoad++;
    757 
    758         if (DBG) log("fetchRuimRecords " + mRecordsToLoad + " requested: " + mRecordsRequested);
    759         // Further records that can be inserted are Operator/OEM dependent
    760     }
    761 
    762     /**
    763      * {@inheritDoc}
    764      *
    765      * No Display rule for RUIMs yet.
    766      */
    767     @Override
    768     public int getDisplayRule(String plmn) {
    769         // TODO together with spn
    770         return 0;
    771     }
    772 
    773     @Override
    774     public boolean isProvisioned() {
    775         // If UICC card has CSIM app, look for MDN and MIN field
    776         // to determine if the SIM is provisioned.  Otherwise,
    777         // consider the SIM is provisioned. (for case of ordinal
    778         // USIM only UICC.)
    779         // If PROPERTY_TEST_CSIM is defined, bypess provision check
    780         // and consider the SIM is provisioned.
    781         if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) {
    782             return true;
    783         }
    784 
    785         if (mParentApp == null) {
    786             return false;
    787         }
    788 
    789         if (mParentApp.getType() == AppType.APPTYPE_CSIM &&
    790             ((mMdn == null) || (mMin == null))) {
    791             return false;
    792         }
    793         return true;
    794     }
    795 
    796     @Override
    797     public void setVoiceMessageWaiting(int line, int countWaiting) {
    798         if (line != 1) {
    799             // only profile 1 is supported
    800             return;
    801         }
    802 
    803         // range check
    804         if (countWaiting < 0) {
    805             countWaiting = -1;
    806         } else if (countWaiting > 0xff) {
    807             // C.S0015-B v2, 4.5.12
    808             // range: 0-99
    809             countWaiting = 0xff;
    810         }
    811         mCountVoiceMessages = countWaiting;
    812 
    813         mRecordsEventsRegistrants.notifyResult(EVENT_MWI);
    814     }
    815 
    816     private void handleRuimRefresh(IccRefreshResponse refreshResponse) {
    817         if (refreshResponse == null) {
    818             if (DBG) log("handleRuimRefresh received without input");
    819             return;
    820         }
    821 
    822         if (refreshResponse.aid != null &&
    823                 !refreshResponse.aid.equals(mParentApp.getAid())) {
    824             // This is for different app. Ignore.
    825             return;
    826         }
    827 
    828         switch (refreshResponse.refreshResult) {
    829             case IccRefreshResponse.REFRESH_RESULT_FILE_UPDATE:
    830                 if (DBG) log("handleRuimRefresh with SIM_REFRESH_FILE_UPDATED");
    831                 mAdnCache.reset();
    832                 fetchRuimRecords();
    833                 break;
    834             case IccRefreshResponse.REFRESH_RESULT_INIT:
    835                 if (DBG) log("handleRuimRefresh with SIM_REFRESH_INIT");
    836                 // need to reload all files (that we care about)
    837                 onIccRefreshInit();
    838                 break;
    839             case IccRefreshResponse.REFRESH_RESULT_RESET:
    840                 if (DBG) log("handleRuimRefresh with SIM_REFRESH_RESET");
    841                 if (requirePowerOffOnSimRefreshReset()) {
    842                     mCi.setRadioPower(false, null);
    843                     /* Note: no need to call setRadioPower(true).  Assuming the desired
    844                     * radio power state is still ON (as tracked by ServiceStateTracker),
    845                     * ServiceStateTracker will call setRadioPower when it receives the
    846                     * RADIO_STATE_CHANGED notification for the power off.  And if the
    847                     * desired power state has changed in the interim, we don't want to
    848                     * override it with an unconditional power on.
    849                     */
    850                 }
    851                 break;
    852             default:
    853                 // unknown refresh operation
    854                 if (DBG) log("handleRuimRefresh with unknown operation");
    855                 break;
    856         }
    857     }
    858 
    859     public String getMdn() {
    860         return mMdn;
    861     }
    862 
    863     public String getMin() {
    864         return mMin;
    865     }
    866 
    867     public String getSid() {
    868         return mHomeSystemId;
    869     }
    870 
    871     public String getNid() {
    872         return mHomeNetworkId;
    873     }
    874 
    875     public boolean getCsimSpnDisplayCondition() {
    876         return mCsimSpnDisplayCondition;
    877     }
    878     @Override
    879     protected void log(String s) {
    880         Rlog.d(LOG_TAG, "[RuimRecords] " + s);
    881     }
    882 
    883     @Override
    884     protected void loge(String s) {
    885         Rlog.e(LOG_TAG, "[RuimRecords] " + s);
    886     }
    887 
    888     @Override
    889     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    890         pw.println("RuimRecords: " + this);
    891         pw.println(" extends:");
    892         super.dump(fd, pw, args);
    893         pw.println(" mOtaCommited=" + mOtaCommited);
    894         pw.println(" mMyMobileNumber=" + mMyMobileNumber);
    895         pw.println(" mMin2Min1=" + mMin2Min1);
    896         pw.println(" mPrlVersion=" + mPrlVersion);
    897         pw.println(" mEFpl[]=" + Arrays.toString(mEFpl));
    898         pw.println(" mEFli[]=" + Arrays.toString(mEFli));
    899         pw.println(" mCsimSpnDisplayCondition=" + mCsimSpnDisplayCondition);
    900         pw.println(" mMdn=" + mMdn);
    901         pw.println(" mMin=" + mMin);
    902         pw.println(" mHomeSystemId=" + mHomeSystemId);
    903         pw.println(" mHomeNetworkId=" + mHomeNetworkId);
    904         pw.flush();
    905     }
    906 
    907     private void setSystemProperty(String key, String val) {
    908         // Update the system properties only in case NON-DSDS.
    909         // TODO: Shall have a better approach!
    910         if (!TelephonyManager.getDefault().isMultiSimEnabled()) {
    911             SystemProperties.set(key, val);
    912         }
    913     }
    914 }
    915