blob: 838dd0f44e48f20f67e96b24f200d338f18182bc [file] [log] [blame]
/*
* Copyright (C) 2006-2007 Google Inc.
*
* 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.cat;
import com.android.internal.telephony.EncodeException;
import com.android.internal.telephony.GsmAlphabet;
import java.util.Calendar;
import java.util.TimeZone;
import android.os.SystemProperties;
import android.text.TextUtils;
import com.android.internal.telephony.cat.AppInterface.CommandType;
import android.annotation.UnsupportedAppUsage;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
abstract class ResponseData {
/**
* Format the data appropriate for TERMINAL RESPONSE and write it into
* the ByteArrayOutputStream object.
*/
@UnsupportedAppUsage
public abstract void format(ByteArrayOutputStream buf);
public static void writeLength(ByteArrayOutputStream buf, int length) {
// As per ETSI 102.220 Sec7.1.2, if the total length is greater
// than 0x7F, it should be coded in two bytes and the first byte
// should be 0x81.
if (length > 0x7F) {
buf.write(0x81);
}
buf.write(length);
}
}
class SelectItemResponseData extends ResponseData {
// members
private int mId;
public SelectItemResponseData(int id) {
super();
mId = id;
}
@Override
public void format(ByteArrayOutputStream buf) {
// Item identifier object
int tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
buf.write(tag); // tag
buf.write(1); // length
buf.write(mId); // identifier of item chosen
}
}
class GetInkeyInputResponseData extends ResponseData {
// members
private boolean mIsUcs2;
private boolean mIsPacked;
private boolean mIsYesNo;
private boolean mYesNoResponse;
public String mInData;
// GetInKey Yes/No response characters constants.
protected static final byte GET_INKEY_YES = 0x01;
protected static final byte GET_INKEY_NO = 0x00;
public GetInkeyInputResponseData(String inData, boolean ucs2, boolean packed) {
super();
mIsUcs2 = ucs2;
mIsPacked = packed;
mInData = inData;
mIsYesNo = false;
}
public GetInkeyInputResponseData(boolean yesNoResponse) {
super();
mIsUcs2 = false;
mIsPacked = false;
mInData = "";
mIsYesNo = true;
mYesNoResponse = yesNoResponse;
}
@Override
public void format(ByteArrayOutputStream buf) {
if (buf == null) {
return;
}
// Text string object
int tag = 0x80 | ComprehensionTlvTag.TEXT_STRING.value();
buf.write(tag); // tag
byte[] data;
if (mIsYesNo) {
data = new byte[1];
data[0] = mYesNoResponse ? GET_INKEY_YES : GET_INKEY_NO;
} else if (mInData != null && mInData.length() > 0) {
try {
// ETSI TS 102 223 8.15, should use the same format as in SMS messages
// on the network.
if (mIsUcs2) {
// ucs2 is by definition big endian.
data = mInData.getBytes("UTF-16BE");
} else if (mIsPacked) {
byte[] tempData = GsmAlphabet
.stringToGsm7BitPacked(mInData, 0, 0);
// The size of the new buffer will be smaller than the original buffer
// since 7-bit GSM packed only requires ((mInData.length * 7) + 7) / 8 bytes.
// And we don't need to copy/store the first byte from the returned array
// because it is used to store the count of septets used.
data = new byte[tempData.length - 1];
System.arraycopy(tempData, 1, data, 0, tempData.length - 1);
} else {
data = GsmAlphabet.stringToGsm8BitPacked(mInData);
}
} catch (UnsupportedEncodingException e) {
data = new byte[0];
} catch (EncodeException e) {
data = new byte[0];
}
} else {
data = new byte[0];
}
// length - one more for data coding scheme.
// ETSI TS 102 223 Annex C (normative): Structure of CAT communications
// Any length within the APDU limits (up to 255 bytes) can thus be encoded on two bytes.
// This coding is chosen to remain compatible with TS 101.220.
// Note that we need to reserve one more byte for coding scheme thus the maximum APDU
// size would be 254 bytes.
if (data.length + 1 <= 255) {
writeLength(buf, data.length + 1);
}
else {
data = new byte[0];
}
// data coding scheme
if (mIsUcs2) {
buf.write(0x08); // UCS2
} else if (mIsPacked) {
buf.write(0x00); // 7 bit packed
} else {
buf.write(0x04); // 8 bit unpacked
}
for (byte b : data) {
buf.write(b);
}
}
}
// For "PROVIDE LOCAL INFORMATION" command.
// See TS 31.111 section 6.4.15/ETSI TS 102 223
// TS 31.124 section 27.22.4.15 for test spec
class LanguageResponseData extends ResponseData {
private String mLang;
public LanguageResponseData(String lang) {
super();
mLang = lang;
}
@Override
public void format(ByteArrayOutputStream buf) {
if (buf == null) {
return;
}
// Text string object
int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
buf.write(tag); // tag
byte[] data;
if (mLang != null && mLang.length() > 0) {
data = GsmAlphabet.stringToGsm8BitPacked(mLang);
}
else {
data = new byte[0];
}
buf.write(data.length);
for (byte b : data) {
buf.write(b);
}
}
}
// For "PROVIDE LOCAL INFORMATION" command.
// See TS 31.111 section 6.4.15/ETSI TS 102 223
// TS 31.124 section 27.22.4.15 for test spec
class DTTZResponseData extends ResponseData {
private Calendar mCalendar;
public DTTZResponseData(Calendar cal) {
super();
mCalendar = cal;
}
@Override
public void format(ByteArrayOutputStream buf) {
if (buf == null) {
return;
}
// DTTZ object
int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
buf.write(tag); // tag
byte[] data = new byte[8];
data[0] = 0x07; // Write length of DTTZ data
if (mCalendar == null) {
mCalendar = Calendar.getInstance();
}
// Fill year byte
data[1] = byteToBCD(mCalendar.get(java.util.Calendar.YEAR) % 100);
// Fill month byte
data[2] = byteToBCD(mCalendar.get(java.util.Calendar.MONTH) + 1);
// Fill day byte
data[3] = byteToBCD(mCalendar.get(java.util.Calendar.DATE));
// Fill hour byte
data[4] = byteToBCD(mCalendar.get(java.util.Calendar.HOUR_OF_DAY));
// Fill minute byte
data[5] = byteToBCD(mCalendar.get(java.util.Calendar.MINUTE));
// Fill second byte
data[6] = byteToBCD(mCalendar.get(java.util.Calendar.SECOND));
String tz = SystemProperties.get("persist.sys.timezone", "");
if (TextUtils.isEmpty(tz)) {
data[7] = (byte) 0xFF; // set FF in terminal response
} else {
TimeZone zone = TimeZone.getTimeZone(tz);
int zoneOffset = zone.getRawOffset() + zone.getDSTSavings();
data[7] = getTZOffSetByte(zoneOffset);
}
for (byte b : data) {
buf.write(b);
}
}
private byte byteToBCD(int value) {
if (value < 0 && value > 99) {
CatLog.d(this, "Err: byteToBCD conversion Value is " + value +
" Value has to be between 0 and 99");
return 0;
}
return (byte) ((value / 10) | ((value % 10) << 4));
}
private byte getTZOffSetByte(long offSetVal) {
boolean isNegative = (offSetVal < 0);
/*
* The 'offSetVal' is in milliseconds. Convert it to hours and compute
* offset While sending T.R to UICC, offset is expressed is 'quarters of
* hours'
*/
long tzOffset = offSetVal / (15 * 60 * 1000);
tzOffset = (isNegative ? -1 : 1) * tzOffset;
byte bcdVal = byteToBCD((int) tzOffset);
// For negative offsets, put '1' in the msb
return isNegative ? (bcdVal |= 0x08) : bcdVal;
}
}