/* * Copyright (C) 2006 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.gsm; import com.android.internal.telephony.*; import android.os.Parcel; import android.os.Parcelable; import android.os.Message; import android.os.Handler; import android.os.Looper; import android.os.AsyncResult; import android.util.Log; import android.telephony.PhoneNumberUtils; import java.util.ArrayList; class AdnRecordLoader extends Handler { static final String LOG_TAG = "GSM"; //***** Instance Variables GSMPhone phone; int ef; int extensionEF; int pendingExtLoads; Message userResponse; String pin2; // For "load one" int recordNumber; // for "load all" ArrayList<AdnRecord> adns; // only valid after EVENT_ADN_LOAD_ALL_DONE // Either an AdnRecord or a reference to adns depending // if this is a load one or load all operation Object result; //***** Event Constants static final int EVENT_ADN_LOAD_DONE = 1; static final int EVENT_EXT_RECORD_LOAD_DONE = 2; static final int EVENT_ADN_LOAD_ALL_DONE = 3; static final int EVENT_EF_LINEAR_RECORD_SIZE_DONE = 4; static final int EVENT_UPDATE_RECORD_DONE = 5; //***** Constructor AdnRecordLoader(GSMPhone phone) { // The telephony unit-test cases may create AdnRecords // in secondary threads super(phone.h.getLooper()); this.phone = phone; } /** * Resulting AdnRecord is placed in response.obj.result * or response.obj.exception is set */ void loadFromEF(int ef, int extensionEF, int recordNumber, Message response) { this.ef = ef; this.extensionEF = extensionEF; this.recordNumber = recordNumber; this.userResponse = response; phone.mSIMFileHandler.loadEFLinearFixed( ef, recordNumber, obtainMessage(EVENT_ADN_LOAD_DONE)); } /** * Resulting ArrayList<adnRecord> is placed in response.obj.result * or response.obj.exception is set */ void loadAllFromEF(int ef, int extensionEF, Message response) { this.ef = ef; this.extensionEF = extensionEF; this.userResponse = response; phone.mSIMFileHandler.loadEFLinearFixedAll( ef, obtainMessage(EVENT_ADN_LOAD_ALL_DONE)); } /** * Write adn to a EF SIM record * It will get the record size of EF record and compose hex adn array * then write the hex array to EF record * * @param adn is set with alphaTag and phoneNubmer * @param ef EF fileid * @param extensionEF extension EF fileid * @param recordNumber 1-based record index * @param pin2 for CHV2 operations, must be null if pin2 is not needed * @param response will be sent to its handler when completed */ void updateEF(AdnRecord adn, int ef, int extensionEF, int recordNumber, String pin2, Message response) { this.ef = ef; this.extensionEF = extensionEF; this.recordNumber = recordNumber; this.userResponse = response; this.pin2 = pin2; phone.mSIMFileHandler.getEFLinearRecordSize( ef, obtainMessage(EVENT_EF_LINEAR_RECORD_SIZE_DONE, adn)); } //***** Overridden from Handler public void handleMessage(Message msg) { AsyncResult ar; byte data[]; AdnRecord adn; try { switch (msg.what) { case EVENT_EF_LINEAR_RECORD_SIZE_DONE: ar = (AsyncResult)(msg.obj); adn = (AdnRecord)(ar.userObj); if (ar.exception != null) { throw new RuntimeException("get EF record size failed", ar.exception); } int[] recordSize = (int[])ar.result; // recordSize is int[3] array // int[0] is the record length // int[1] is the total length of the EF file // int[2] is the number of records in the EF file // So int[0] * int[2] = int[1] if (recordSize.length != 3 || recordNumber > recordSize[2]) { throw new RuntimeException("get wrong EF record size format", ar.exception); } data = adn.buildAdnString(recordSize[0]); if(data == null) { throw new RuntimeException("worong ADN format", ar.exception); } phone.mSIMFileHandler.updateEFLinearFixed(ef, recordNumber, data, pin2, obtainMessage(EVENT_UPDATE_RECORD_DONE)); pendingExtLoads = 1; break; case EVENT_UPDATE_RECORD_DONE: ar = (AsyncResult)(msg.obj); if (ar.exception != null) { throw new RuntimeException("update EF adn record failed", ar.exception); } pendingExtLoads = 0; result = null; break; case EVENT_ADN_LOAD_DONE: ar = (AsyncResult)(msg.obj); data = (byte[])(ar.result); if (ar.exception != null) { throw new RuntimeException("load failed", ar.exception); } if (false) { Log.d(LOG_TAG,"ADN EF: 0x" + Integer.toHexString(ef) + ":" + recordNumber + "\n" + SimUtils.bytesToHexString(data)); } adn = new AdnRecord(ef, recordNumber, data); result = adn; if (adn.hasExtendedRecord()) { // If we have a valid value in the ext record field, // we're not done yet: we need to read the corresponding // ext record and append it pendingExtLoads = 1; phone.mSIMFileHandler.loadEFLinearFixed( extensionEF, adn.extRecord, obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn)); } break; case EVENT_EXT_RECORD_LOAD_DONE: ar = (AsyncResult)(msg.obj); data = (byte[])(ar.result); adn = (AdnRecord)(ar.userObj); if (ar.exception != null) { throw new RuntimeException("load failed", ar.exception); } Log.d(LOG_TAG,"ADN extention EF: 0x" + Integer.toHexString(extensionEF) + ":" + adn.extRecord + "\n" + SimUtils.bytesToHexString(data)); adn.appendExtRecord(data); pendingExtLoads--; // result should have been set in // EVENT_ADN_LOAD_DONE or EVENT_ADN_LOAD_ALL_DONE break; case EVENT_ADN_LOAD_ALL_DONE: ar = (AsyncResult)(msg.obj); ArrayList<byte[]> datas = (ArrayList<byte[]>)(ar.result); if (ar.exception != null) { throw new RuntimeException("load failed", ar.exception); } adns = new ArrayList<AdnRecord>(datas.size()); result = adns; pendingExtLoads = 0; for(int i = 0, s = datas.size() ; i < s ; i++) { adn = new AdnRecord(ef, 1 + i, datas.get(i)); adns.add(adn); if (adn.hasExtendedRecord()) { // If we have a valid value in the ext record field, // we're not done yet: we need to read the corresponding // ext record and append it pendingExtLoads++; phone.mSIMFileHandler.loadEFLinearFixed( extensionEF, adn.extRecord, obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn)); } } break; } } catch (RuntimeException exc) { if (userResponse != null) { AsyncResult.forMessage(userResponse) .exception = exc; userResponse.sendToTarget(); // Loading is all or nothing--either every load succeeds // or we fail the whole thing. userResponse = null; } return; } if (userResponse != null && pendingExtLoads == 0) { AsyncResult.forMessage(userResponse).result = result; userResponse.sendToTarget(); userResponse = null; } } } /** * * Used to load or store ADNs (Abbreviated Dialing Numbers). * * {@hide} * */ public class AdnRecord implements Parcelable { static final String LOG_TAG = "GSM"; //***** Instance Variables String alphaTag = ""; String number = ""; int extRecord = 0xff; int efid; // or 0 if none int recordNumber; // or 0 if none //***** Constants // In an ADN record, everything but the alpha identifier // is in a footer that's 14 bytes static final int FOOTER_SIZE_BYTES = 14; // Maximum size of the un-extended number field static final int MAX_NUMBER_SIZE_BYTES = 11; static final int EXT_RECORD_LENGTH_BYTES = 13; static final int EXT_RECORD_TYPE_ADDITIONAL_DATA = 2; static final int EXT_RECORD_TYPE_MASK = 3; static final int MAX_EXT_CALLED_PARTY_LENGTH = 0xa; // ADN offset static final int ADN_BCD_NUMBER_LENGTH = 0; static final int ADN_TON_AND_NPI = 1; static final int ADN_DAILING_NUMBER_START = 2; static final int ADN_DAILING_NUMBER_END = 11; static final int ADN_CAPABILITY_ID = 12; static final int ADN_EXTENSION_ID = 13; //***** Static Methods public static final Parcelable.Creator<AdnRecord> CREATOR = new Parcelable.Creator<AdnRecord>() { public AdnRecord createFromParcel(Parcel source) { int efid; int recordNumber; String alphaTag; String number; efid = source.readInt(); recordNumber = source.readInt(); alphaTag = source.readString(); number = source.readString(); return new AdnRecord(efid, recordNumber, alphaTag, number); } public AdnRecord[] newArray(int size) { return new AdnRecord[size]; } }; //***** Constructor public AdnRecord (byte[] record) { this(0, 0, record); } public AdnRecord (int efid, int recordNumber, byte[] record) { this.efid = efid; this.recordNumber = recordNumber; parseRecord(record); } public AdnRecord (String alphaTag, String number) { this(0, 0, alphaTag, number); } public AdnRecord (int efid, int recordNumber, String alphaTag, String number) { this.efid = efid; this.recordNumber = recordNumber; this.alphaTag = alphaTag; this.number = number; } //***** Instance Methods public String getAlphaTag() { return alphaTag; } public String getNumber() { return number; } public String toString() { return "ADN Record '" + alphaTag + "' '" + number + "'"; } public boolean isEmpty() { return alphaTag.equals("") && number.equals(""); } public boolean hasExtendedRecord() { return extRecord != 0 && extRecord != 0xff; } public boolean isEqual(AdnRecord adn) { return ( alphaTag.equals(adn.getAlphaTag()) && number.equals(adn.getNumber()) ); } //***** Parcelable Implementation public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { dest.writeInt(efid); dest.writeInt(recordNumber); dest.writeString(alphaTag); dest.writeString(number); } /** * Build adn hex byte array based on record size * The format of byte array is defined in 51.011 10.5.1 * * @param recordSize is the size X of EF record * @return hex byte[recordSize] to be written to EF record * return nulll for wrong format of dialing nubmer or tag */ public byte[] buildAdnString(int recordSize) { byte[] bcdNumber; byte[] byteTag; byte[] adnString = null; int footerOffset = recordSize - FOOTER_SIZE_BYTES; if (number == null || number.equals("") || alphaTag == null || alphaTag.equals("")) { Log.w(LOG_TAG, "[buildAdnString] Empty alpha tag or number"); adnString = new byte[recordSize]; for (int i = 0; i < recordSize; i++) { adnString[i] = (byte) 0xFF; } } else if (number.length() > (ADN_DAILING_NUMBER_END - ADN_DAILING_NUMBER_START + 1) * 2) { Log.w(LOG_TAG, "[buildAdnString] Max length of dailing number is 20"); } else if (alphaTag.length() > footerOffset) { Log.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset); } else { adnString = new byte[recordSize]; for (int i = 0; i < recordSize; i++) { adnString[i] = (byte) 0xFF; } bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(number); System.arraycopy(bcdNumber, 0, adnString, footerOffset + ADN_TON_AND_NPI, bcdNumber.length); adnString[footerOffset + ADN_BCD_NUMBER_LENGTH] = (byte) (bcdNumber.length); adnString[footerOffset + ADN_CAPABILITY_ID] = (byte) 0xFF; // Capacility Id adnString[footerOffset + ADN_EXTENSION_ID] = (byte) 0xFF; // Extension Record Id byteTag = GsmAlphabet.stringToGsm8BitPacked(alphaTag); System.arraycopy(byteTag, 0, adnString, 0, byteTag.length); } return adnString; } /** * See TS 51.011 10.5.10 */ public void appendExtRecord (byte[] extRecord) { try { if (extRecord.length != EXT_RECORD_LENGTH_BYTES) { return; } if ((extRecord[0] & EXT_RECORD_TYPE_MASK) != EXT_RECORD_TYPE_ADDITIONAL_DATA ) { return; } if ((0xff & extRecord[1]) > MAX_EXT_CALLED_PARTY_LENGTH) { // invalid or empty record return; } number += PhoneNumberUtils.calledPartyBCDFragmentToString( extRecord, 2, 0xff & extRecord[1]); // We don't support ext record chaining. } catch (RuntimeException ex) { Log.w(LOG_TAG, "Error parsing AdnRecord ext record", ex); } } //***** Private Methods /** * alphaTag and number are set to null on invalid format */ private void parseRecord(byte[] record) { try { alphaTag = SimUtils.adnStringFieldToString( record, 0, record.length - FOOTER_SIZE_BYTES); int footerOffset = record.length - FOOTER_SIZE_BYTES; int numberLength = 0xff & record[footerOffset]; if (numberLength > MAX_NUMBER_SIZE_BYTES) { // Invalid number length number = ""; return; } // Please note 51.011 10.5.1: // // "If the Dialling Number/SSC String does not contain // a dialling number, e.g. a control string deactivating // a service, the TON/NPI byte shall be set to 'FF' by // the ME (see note 2)." number = PhoneNumberUtils.calledPartyBCDToString( record, footerOffset + 1, numberLength); extRecord = 0xff & record[record.length - 1]; } catch (RuntimeException ex) { Log.w(LOG_TAG, "Error parsing AdnRecord", ex); number = ""; alphaTag = ""; } } }