/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.telephony.cdma; import android.os.AsyncResult; import android.os.SystemProperties; import android.util.Log; import com.android.internal.telephony.AdnRecordLoader; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.IccCardApplication.AppType; import com.android.internal.telephony.IccFileHandler; import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.MccTable; import com.android.internal.telephony.PhoneBase; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.telephony.gsm.SIMRecords; import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.ims.IsimUiccRecords; import java.util.ArrayList; import java.util.Locale; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OPERATOR_ALPHA; import static com.android.internal.telephony.TelephonyProperties.PROPERTY_TEST_CSIM; /** * {@hide} */ public final class CdmaLteUiccRecords extends SIMRecords { // From CSIM application private byte[] mEFpl = null; private byte[] mEFli = null; boolean mCsimSpnDisplayCondition = false; private String mMdn; private String mMin; private String mPrlVersion; private String mHomeSystemId; private String mHomeNetworkId; private final IsimUiccRecords mIsimUiccRecords = new IsimUiccRecords(); public CdmaLteUiccRecords(PhoneBase p) { super(p); } // Refer to ETSI TS 102.221 private class EfPlLoaded implements IccRecordLoaded { public String getEfName() { return "EF_PL"; } public void onRecordLoaded(AsyncResult ar) { mEFpl = (byte[]) ar.result; if (DBG) log("EF_PL=" + IccUtils.bytesToHexString(mEFpl)); } } // Refer to C.S0065 5.2.26 private class EfCsimLiLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_LI"; } public void onRecordLoaded(AsyncResult ar) { mEFli = (byte[]) ar.result; // convert csim efli data to iso 639 format for (int i = 0; i < mEFli.length; i+=2) { switch(mEFli[i+1]) { case 0x01: mEFli[i] = 'e'; mEFli[i+1] = 'n';break; case 0x02: mEFli[i] = 'f'; mEFli[i+1] = 'r';break; case 0x03: mEFli[i] = 'e'; mEFli[i+1] = 's';break; case 0x04: mEFli[i] = 'j'; mEFli[i+1] = 'a';break; case 0x05: mEFli[i] = 'k'; mEFli[i+1] = 'o';break; case 0x06: mEFli[i] = 'z'; mEFli[i+1] = 'h';break; case 0x07: mEFli[i] = 'h'; mEFli[i+1] = 'e';break; default: mEFli[i] = ' '; mEFli[i+1] = ' '; } } if (DBG) log("EF_LI=" + IccUtils.bytesToHexString(mEFli)); } } // Refer to C.S0065 5.2.32 private class EfCsimSpnLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_SPN"; } public void onRecordLoaded(AsyncResult ar) { byte[] data = (byte[]) ar.result; if (DBG) log("CSIM_SPN=" + IccUtils.bytesToHexString(data)); // C.S0065 for EF_SPN decoding mCsimSpnDisplayCondition = ((0x01 & data[0]) != 0); int encoding = data[1]; int language = data[2]; byte[] spnData = new byte[32]; System.arraycopy(data, 3, spnData, 0, (data.length < 32) ? data.length : 32); int numBytes; for (numBytes = 0; numBytes < spnData.length; numBytes++) { if ((spnData[numBytes] & 0xFF) == 0xFF) break; } if (numBytes == 0) { spn = ""; return; } try { switch (encoding) { case UserData.ENCODING_OCTET: case UserData.ENCODING_LATIN: spn = new String(spnData, 0, numBytes, "ISO-8859-1"); break; case UserData.ENCODING_IA5: case UserData.ENCODING_GSM_7BIT_ALPHABET: case UserData.ENCODING_7BIT_ASCII: spn = GsmAlphabet.gsm7BitPackedToString(spnData, 0, (numBytes*8)/7); break; case UserData.ENCODING_UNICODE_16: spn = new String(spnData, 0, numBytes, "utf-16"); break; default: log("SPN encoding not supported"); } } catch(Exception e) { log("spn decode error: " + e); } if (DBG) log("spn=" + spn); if (DBG) log("spnCondition=" + mCsimSpnDisplayCondition); phone.setSystemProperty(PROPERTY_ICC_OPERATOR_ALPHA, spn); } } private class EfCsimMdnLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_MDN"; } public void onRecordLoaded(AsyncResult ar) { byte[] data = (byte[]) ar.result; if (DBG) log("CSIM_MDN=" + IccUtils.bytesToHexString(data)); int mdnDigitsNum = 0x0F & data[0]; mMdn = IccUtils.cdmaBcdToString(data, 1, mdnDigitsNum); if (DBG) log("CSIM MDN=" + mMdn); } } private class EfCsimImsimLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_IMSIM"; } public void onRecordLoaded(AsyncResult ar) { byte[] data = (byte[]) ar.result; if (DBG) log("CSIM_IMSIM=" + IccUtils.bytesToHexString(data)); // C.S0065 section 5.2.2 for IMSI_M encoding // C.S0005 section 2.3.1 for MIN encoding in IMSI_M. boolean provisioned = ((data[7] & 0x80) == 0x80); if (provisioned) { int first3digits = ((0x03 & data[2]) << 8) + (0xFF & data[1]); int second3digits = (((0xFF & data[5]) << 8) | (0xFF & data[4])) >> 6; int digit7 = 0x0F & (data[4] >> 2); if (digit7 > 0x09) digit7 = 0; int last3digits = ((0x03 & data[4]) << 8) | (0xFF & data[3]); first3digits = adjstMinDigits(first3digits); second3digits = adjstMinDigits(second3digits); last3digits = adjstMinDigits(last3digits); StringBuilder builder = new StringBuilder(); builder.append(String.format(Locale.US, "%03d", first3digits)); builder.append(String.format(Locale.US, "%03d", second3digits)); builder.append(String.format(Locale.US, "%d", digit7)); builder.append(String.format(Locale.US, "%03d", last3digits)); mMin = builder.toString(); if (DBG) log("min present=" + mMin); } else { if (DBG) log("min not present"); } } } private class EfCsimCdmaHomeLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_CDMAHOME"; } public void onRecordLoaded(AsyncResult ar) { // Per C.S0065 section 5.2.8 ArrayList<byte[]> dataList = (ArrayList<byte[]>) ar.result; if (DBG) log("CSIM_CDMAHOME data size=" + dataList.size()); if (dataList.isEmpty()) { return; } StringBuilder sidBuf = new StringBuilder(); StringBuilder nidBuf = new StringBuilder(); for (byte[] data : dataList) { if (data.length == 5) { int sid = ((data[1] & 0xFF) << 8) | (data[0] & 0xFF); int nid = ((data[3] & 0xFF) << 8) | (data[2] & 0xFF); sidBuf.append(sid).append(','); nidBuf.append(nid).append(','); } } // remove trailing "," sidBuf.setLength(sidBuf.length()-1); nidBuf.setLength(nidBuf.length()-1); mHomeSystemId = sidBuf.toString(); mHomeNetworkId = nidBuf.toString(); } } private class EfCsimEprlLoaded implements IccRecordLoaded { public String getEfName() { return "EF_CSIM_EPRL"; } public void onRecordLoaded(AsyncResult ar) { onGetCSimEprlDone(ar); } } @Override protected void onRecordLoaded() { // One record loaded successfully or failed, In either case // we need to update the recordsToLoad count recordsToLoad -= 1; if (recordsToLoad == 0 && recordsRequested == true) { onAllRecordsLoaded(); } else if (recordsToLoad < 0) { Log.e(LOG_TAG, "SIMRecords: recordsToLoad <0, programmer error suspected"); recordsToLoad = 0; } } @Override protected void onAllRecordsLoaded() { setLocaleFromCsim(); super.onAllRecordsLoaded(); // broadcasts ICC state change to "LOADED" } @Override protected void fetchSimRecords() { IccFileHandler iccFh = phone.getIccFileHandler(); recordsRequested = true; phone.mCM.getIMSI(obtainMessage(EVENT_GET_IMSI_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_PL, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfPlLoaded())); recordsToLoad++; new AdnRecordLoader(phone).loadFromEF(EF_MSISDN, EF_EXT1, 1, obtainMessage(EVENT_GET_MSISDN_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE)); recordsToLoad++; iccFh.loadEFTransparent(EF_CSIM_LI, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimLiLoaded())); recordsToLoad++; iccFh.loadEFTransparent(EF_CSIM_SPN, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimSpnLoaded())); recordsToLoad++; iccFh.loadEFLinearFixed(EF_CSIM_MDN, 1, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimMdnLoaded())); recordsToLoad++; iccFh.loadEFTransparent(EF_CSIM_IMSIM, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimImsimLoaded())); recordsToLoad++; iccFh.loadEFLinearFixedAll(EF_CSIM_CDMAHOME, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimCdmaHomeLoaded())); recordsToLoad++; iccFh.loadEFTransparent(EF_CSIM_EPRL, obtainMessage(EVENT_GET_ICC_RECORD_DONE, new EfCsimEprlLoaded())); recordsToLoad++; // load ISIM records recordsToLoad += mIsimUiccRecords.fetchIsimRecords(iccFh, this); } private int adjstMinDigits (int digits) { // Per C.S0005 section 2.3.1. digits += 111; digits = (digits % 10 == 0)?(digits - 10):digits; digits = ((digits / 10) % 10 == 0)?(digits - 100):digits; digits = ((digits / 100) % 10 == 0)?(digits - 1000):digits; return digits; } private void onGetCSimEprlDone(AsyncResult ar) { // C.S0065 section 5.2.57 for EFeprl encoding // C.S0016 section 3.5.5 for PRL format. byte[] data = (byte[]) ar.result; if (DBG) log("CSIM_EPRL=" + IccUtils.bytesToHexString(data)); // Only need the first 4 bytes of record if (data.length > 3) { int prlId = ((data[2] & 0xFF) << 8) | (data[3] & 0xFF); mPrlVersion = Integer.toString(prlId); } if (DBG) log("CSIM PRL version=" + mPrlVersion); } private void setLocaleFromCsim() { String prefLang = null; // check EFli then EFpl prefLang = findBestLanguage(mEFli); if (prefLang == null) { prefLang = findBestLanguage(mEFpl); } if (prefLang != null) { // check country code from SIM String imsi = getIMSI(); String country = null; if (imsi != null) { country = MccTable.countryCodeForMcc( Integer.parseInt(imsi.substring(0,3))); } log("Setting locale to " + prefLang + "_" + country); phone.setSystemLocale(prefLang, country, false); } else { log ("No suitable CSIM selected locale"); } } private String findBestLanguage(byte[] languages) { String bestMatch = null; String[] locales = phone.getContext().getAssets().getLocales(); if ((languages == null) || (locales == null)) return null; // Each 2-bytes consists of one language for (int i = 0; (i + 1) < languages.length; i += 2) { try { String lang = new String(languages, i, 2, "ISO-8859-1"); for (int j = 0; j < locales.length; j++) { if (locales[j] != null && locales[j].length() >= 2 && locales[j].substring(0, 2).equals(lang)) { return lang; } } if (bestMatch != null) break; } catch(java.io.UnsupportedEncodingException e) { log ("Failed to parse SIM language records"); } } // no match found. return null return null; } @Override protected void log(String s) { Log.d(LOG_TAG, "[CSIM] " + s); } @Override protected void loge(String s) { Log.e(LOG_TAG, "[CSIM] " + s); } public String getMdn() { return mMdn; } public String getMin() { return mMin; } public String getSid() { return mHomeSystemId; } public String getNid() { return mHomeNetworkId; } public String getPrlVersion() { return mPrlVersion; } public boolean getCsimSpnDisplayCondition() { return mCsimSpnDisplayCondition; } @Override public IsimRecords getIsimRecords() { return mIsimUiccRecords; } @Override public boolean isProvisioned() { // If UICC card has CSIM app, look for MDN and MIN field // to determine if the SIM is provisioned. Otherwise, // consider the SIM is provisioned. (for case of ordinal // USIM only UICC.) // If PROPERTY_TEST_CSIM is defined, bypess provision check // and consider the SIM is provisioned. if (SystemProperties.getBoolean(PROPERTY_TEST_CSIM, false)) { return true; } if (phone.mIccCard.isApplicationOnIcc(AppType.APPTYPE_CSIM) && ((mMdn == null) || (mMin == null))) { return false; } return true; } /** * Dispatch 3GPP format message. For CDMA/LTE phones, * send the message to the secondary 3GPP format SMS dispatcher. */ @Override protected int dispatchGsmMessage(SmsMessageBase message) { return ((CDMALTEPhone) phone).m3gppSMS.dispatchMessage(message); } }