blob: 0a285b904dfedc494c8a2ecc51ade636f9646cca [file] [log] [blame]
/*
* 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);
}
}