1/*
2 * Copyright (C) 2006-2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.internal.telephony.cat;
18
19import com.android.internal.telephony.EncodeException;
20import com.android.internal.telephony.GsmAlphabet;
21import java.util.Calendar;
22import java.util.TimeZone;
23import android.os.SystemProperties;
24import android.text.TextUtils;
25
26import com.android.internal.telephony.cat.AppInterface.CommandType;
27
28import java.io.ByteArrayOutputStream;
29import java.io.UnsupportedEncodingException;
30
31abstract class ResponseData {
32    /**
33     * Format the data appropriate for TERMINAL RESPONSE and write it into
34     * the ByteArrayOutputStream object.
35     */
36    public abstract void format(ByteArrayOutputStream buf);
37
38    public static void writeLength(ByteArrayOutputStream buf, int length) {
39        // As per ETSI 102.220 Sec7.1.2, if the total length is greater
40        // than 0x7F, it should be coded in two bytes and the first byte
41        // should be 0x81.
42        if (length > 0x7F) {
43            buf.write(0x81);
44        }
45        buf.write(length);
46    }
47}
48
49class SelectItemResponseData extends ResponseData {
50    // members
51    private int mId;
52
53    public SelectItemResponseData(int id) {
54        super();
55        mId = id;
56    }
57
58    @Override
59    public void format(ByteArrayOutputStream buf) {
60        // Item identifier object
61        int tag = 0x80 | ComprehensionTlvTag.ITEM_ID.value();
62        buf.write(tag); // tag
63        buf.write(1); // length
64        buf.write(mId); // identifier of item chosen
65    }
66}
67
68class GetInkeyInputResponseData extends ResponseData {
69    // members
70    private boolean mIsUcs2;
71    private boolean mIsPacked;
72    private boolean mIsYesNo;
73    private boolean mYesNoResponse;
74    public String mInData;
75
76    // GetInKey Yes/No response characters constants.
77    protected static final byte GET_INKEY_YES = 0x01;
78    protected static final byte GET_INKEY_NO = 0x00;
79
80    public GetInkeyInputResponseData(String inData, boolean ucs2, boolean packed) {
81        super();
82        mIsUcs2 = ucs2;
83        mIsPacked = packed;
84        mInData = inData;
85        mIsYesNo = false;
86    }
87
88    public GetInkeyInputResponseData(boolean yesNoResponse) {
89        super();
90        mIsUcs2 = false;
91        mIsPacked = false;
92        mInData = "";
93        mIsYesNo = true;
94        mYesNoResponse = yesNoResponse;
95    }
96
97    @Override
98    public void format(ByteArrayOutputStream buf) {
99        if (buf == null) {
100            return;
101        }
102
103        // Text string object
104        int tag = 0x80 | ComprehensionTlvTag.TEXT_STRING.value();
105        buf.write(tag); // tag
106
107        byte[] data;
108
109        if (mIsYesNo) {
110            data = new byte[1];
111            data[0] = mYesNoResponse ? GET_INKEY_YES : GET_INKEY_NO;
112        } else if (mInData != null && mInData.length() > 0) {
113            try {
114                // ETSI TS 102 223 8.15, should use the same format as in SMS messages
115                // on the network.
116                if (mIsUcs2) {
117                    // ucs2 is by definition big endian.
118                    data = mInData.getBytes("UTF-16BE");
119                } else if (mIsPacked) {
120                    byte[] tempData = GsmAlphabet
121                            .stringToGsm7BitPacked(mInData, 0, 0);
122                    // The size of the new buffer will be smaller than the original buffer
123                    // since 7-bit GSM packed only requires ((mInData.length * 7) + 7) / 8 bytes.
124                    // And we don't need to copy/store the first byte from the returned array
125                    // because it is used to store the count of septets used.
126                    data = new byte[tempData.length - 1];
127                    System.arraycopy(tempData, 1, data, 0, tempData.length - 1);
128                } else {
129                    data = GsmAlphabet.stringToGsm8BitPacked(mInData);
130                }
131            } catch (UnsupportedEncodingException e) {
132                data = new byte[0];
133            } catch (EncodeException e) {
134                data = new byte[0];
135            }
136        } else {
137            data = new byte[0];
138        }
139
140        // length - one more for data coding scheme.
141
142        // ETSI TS 102 223 Annex C (normative): Structure of CAT communications
143        // Any length within the APDU limits (up to 255 bytes) can thus be encoded on two bytes.
144        // This coding is chosen to remain compatible with TS 101.220.
145        // Note that we need to reserve one more byte for coding scheme thus the maximum APDU
146        // size would be 254 bytes.
147        if (data.length + 1 <= 255) {
148            writeLength(buf, data.length + 1);
149        }
150        else {
151            data = new byte[0];
152        }
153
154
155        // data coding scheme
156        if (mIsUcs2) {
157            buf.write(0x08); // UCS2
158        } else if (mIsPacked) {
159            buf.write(0x00); // 7 bit packed
160        } else {
161            buf.write(0x04); // 8 bit unpacked
162        }
163
164        for (byte b : data) {
165            buf.write(b);
166        }
167    }
168}
169
170// For "PROVIDE LOCAL INFORMATION" command.
171// See TS 31.111 section 6.4.15/ETSI TS 102 223
172// TS 31.124 section 27.22.4.15 for test spec
173class LanguageResponseData extends ResponseData {
174    private String mLang;
175
176    public LanguageResponseData(String lang) {
177        super();
178        mLang = lang;
179    }
180
181    @Override
182    public void format(ByteArrayOutputStream buf) {
183        if (buf == null) {
184            return;
185        }
186
187        // Text string object
188        int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
189        buf.write(tag); // tag
190
191        byte[] data;
192
193        if (mLang != null && mLang.length() > 0) {
194            data = GsmAlphabet.stringToGsm8BitPacked(mLang);
195        }
196        else {
197            data = new byte[0];
198        }
199
200        buf.write(data.length);
201
202        for (byte b : data) {
203            buf.write(b);
204        }
205    }
206}
207
208// For "PROVIDE LOCAL INFORMATION" command.
209// See TS 31.111 section 6.4.15/ETSI TS 102 223
210// TS 31.124 section 27.22.4.15 for test spec
211class DTTZResponseData extends ResponseData {
212    private Calendar mCalendar;
213
214    public DTTZResponseData(Calendar cal) {
215        super();
216        mCalendar = cal;
217    }
218
219    @Override
220    public void format(ByteArrayOutputStream buf) {
221        if (buf == null) {
222            return;
223        }
224
225        // DTTZ object
226        int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
227        buf.write(tag); // tag
228
229        byte[] data = new byte[8];
230
231        data[0] = 0x07; // Write length of DTTZ data
232
233        if (mCalendar == null) {
234            mCalendar = Calendar.getInstance();
235        }
236        // Fill year byte
237        data[1] = byteToBCD(mCalendar.get(java.util.Calendar.YEAR) % 100);
238
239        // Fill month byte
240        data[2] = byteToBCD(mCalendar.get(java.util.Calendar.MONTH) + 1);
241
242        // Fill day byte
243        data[3] = byteToBCD(mCalendar.get(java.util.Calendar.DATE));
244
245        // Fill hour byte
246        data[4] = byteToBCD(mCalendar.get(java.util.Calendar.HOUR_OF_DAY));
247
248        // Fill minute byte
249        data[5] = byteToBCD(mCalendar.get(java.util.Calendar.MINUTE));
250
251        // Fill second byte
252        data[6] = byteToBCD(mCalendar.get(java.util.Calendar.SECOND));
253
254        String tz = SystemProperties.get("persist.sys.timezone", "");
255        if (TextUtils.isEmpty(tz)) {
256            data[7] = (byte) 0xFF;    // set FF in terminal response
257        } else {
258            TimeZone zone = TimeZone.getTimeZone(tz);
259            int zoneOffset = zone.getRawOffset() + zone.getDSTSavings();
260            data[7] = getTZOffSetByte(zoneOffset);
261        }
262
263        for (byte b : data) {
264            buf.write(b);
265        }
266    }
267
268    private byte byteToBCD(int value) {
269        if (value < 0 && value > 99) {
270            CatLog.d(this, "Err: byteToBCD conversion Value is " + value +
271                           " Value has to be between 0 and 99");
272            return 0;
273        }
274
275        return (byte) ((value / 10) | ((value % 10) << 4));
276    }
277
278    private byte getTZOffSetByte(long offSetVal) {
279        boolean isNegative = (offSetVal < 0);
280
281        /*
282         * The 'offSetVal' is in milliseconds. Convert it to hours and compute
283         * offset While sending T.R to UICC, offset is expressed is 'quarters of
284         * hours'
285         */
286
287         long tzOffset = offSetVal / (15 * 60 * 1000);
288         tzOffset = (isNegative ? -1 : 1) * tzOffset;
289         byte bcdVal = byteToBCD((int) tzOffset);
290         // For negative offsets, put '1' in the msb
291         return isNegative ?  (bcdVal |= 0x08) : bcdVal;
292    }
293
294}
295
296