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                    int size = mInData.length();
121
122                    byte[] tempData = GsmAlphabet
123                            .stringToGsm7BitPacked(mInData, 0, 0);
124                    data = new byte[size];
125                    // Since stringToGsm7BitPacked() set byte 0 in the
126                    // returned byte array to the count of septets used...
127                    // copy to a new array without byte 0.
128                    System.arraycopy(tempData, 1, data, 0, size);
129                } else {
130                    data = GsmAlphabet.stringToGsm8BitPacked(mInData);
131                }
132            } catch (UnsupportedEncodingException e) {
133                data = new byte[0];
134            } catch (EncodeException e) {
135                data = new byte[0];
136            }
137        } else {
138            data = new byte[0];
139        }
140
141        // length - one more for data coding scheme.
142        writeLength(buf, data.length + 1);
143
144        // data coding scheme
145        if (mIsUcs2) {
146            buf.write(0x08); // UCS2
147        } else if (mIsPacked) {
148            buf.write(0x00); // 7 bit packed
149        } else {
150            buf.write(0x04); // 8 bit unpacked
151        }
152
153        for (byte b : data) {
154            buf.write(b);
155        }
156    }
157}
158
159// For "PROVIDE LOCAL INFORMATION" command.
160// See TS 31.111 section 6.4.15/ETSI TS 102 223
161// TS 31.124 section 27.22.4.15 for test spec
162class LanguageResponseData extends ResponseData {
163    private String mLang;
164
165    public LanguageResponseData(String lang) {
166        super();
167        mLang = lang;
168    }
169
170    @Override
171    public void format(ByteArrayOutputStream buf) {
172        if (buf == null) {
173            return;
174        }
175
176        // Text string object
177        int tag = 0x80 | ComprehensionTlvTag.LANGUAGE.value();
178        buf.write(tag); // tag
179
180        byte[] data;
181
182        if (mLang != null && mLang.length() > 0) {
183            data = GsmAlphabet.stringToGsm8BitPacked(mLang);
184        }
185        else {
186            data = new byte[0];
187        }
188
189        buf.write(data.length);
190
191        for (byte b : data) {
192            buf.write(b);
193        }
194    }
195}
196
197// For "PROVIDE LOCAL INFORMATION" command.
198// See TS 31.111 section 6.4.15/ETSI TS 102 223
199// TS 31.124 section 27.22.4.15 for test spec
200class DTTZResponseData extends ResponseData {
201    private Calendar mCalendar;
202
203    public DTTZResponseData(Calendar cal) {
204        super();
205        mCalendar = cal;
206    }
207
208    @Override
209    public void format(ByteArrayOutputStream buf) {
210        if (buf == null) {
211            return;
212        }
213
214        // DTTZ object
215        int tag = 0x80 | CommandType.PROVIDE_LOCAL_INFORMATION.value();
216        buf.write(tag); // tag
217
218        byte[] data = new byte[8];
219
220        data[0] = 0x07; // Write length of DTTZ data
221
222        if (mCalendar == null) {
223            mCalendar = Calendar.getInstance();
224        }
225        // Fill year byte
226        data[1] = byteToBCD(mCalendar.get(java.util.Calendar.YEAR) % 100);
227
228        // Fill month byte
229        data[2] = byteToBCD(mCalendar.get(java.util.Calendar.MONTH) + 1);
230
231        // Fill day byte
232        data[3] = byteToBCD(mCalendar.get(java.util.Calendar.DATE));
233
234        // Fill hour byte
235        data[4] = byteToBCD(mCalendar.get(java.util.Calendar.HOUR_OF_DAY));
236
237        // Fill minute byte
238        data[5] = byteToBCD(mCalendar.get(java.util.Calendar.MINUTE));
239
240        // Fill second byte
241        data[6] = byteToBCD(mCalendar.get(java.util.Calendar.SECOND));
242
243        String tz = SystemProperties.get("persist.sys.timezone", "");
244        if (TextUtils.isEmpty(tz)) {
245            data[7] = (byte) 0xFF;    // set FF in terminal response
246        } else {
247            TimeZone zone = TimeZone.getTimeZone(tz);
248            int zoneOffset = zone.getRawOffset() + zone.getDSTSavings();
249            data[7] = getTZOffSetByte(zoneOffset);
250        }
251
252        for (byte b : data) {
253            buf.write(b);
254        }
255    }
256
257    private byte byteToBCD(int value) {
258        if (value < 0 && value > 99) {
259            CatLog.d(this, "Err: byteToBCD conversion Value is " + value +
260                           " Value has to be between 0 and 99");
261            return 0;
262        }
263
264        return (byte) ((value / 10) | ((value % 10) << 4));
265    }
266
267    private byte getTZOffSetByte(long offSetVal) {
268        boolean isNegative = (offSetVal < 0);
269
270        /*
271         * The 'offSetVal' is in milliseconds. Convert it to hours and compute
272         * offset While sending T.R to UICC, offset is expressed is 'quarters of
273         * hours'
274         */
275
276         long tzOffset = offSetVal / (15 * 60 * 1000);
277         tzOffset = (isNegative ? -1 : 1) * tzOffset;
278         byte bcdVal = byteToBCD((int) tzOffset);
279         // For negative offsets, put '1' in the msb
280         return isNegative ?  (bcdVal |= 0x08) : bcdVal;
281    }
282
283}
284
285