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 
17 package com.android.internal.telephony.cat;
18 
19 import com.android.internal.telephony.EncodeException;
20 import com.android.internal.telephony.GsmAlphabet;
21 import java.util.Calendar;
22 import java.util.TimeZone;
23 import android.os.SystemProperties;
24 import android.text.TextUtils;
25 
26 import com.android.internal.telephony.cat.AppInterface.CommandType;
27 
28 import java.io.ByteArrayOutputStream;
29 import java.io.UnsupportedEncodingException;
30 
31 abstract class ResponseData {
32     /**
33      * Format the data appropriate for TERMINAL RESPONSE and write it into
34      * the ByteArrayOutputStream object.
35      */
format(ByteArrayOutputStream buf)36     public abstract void format(ByteArrayOutputStream buf);
37 
writeLength(ByteArrayOutputStream buf, int length)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 
49 class SelectItemResponseData extends ResponseData {
50     // members
51     private int mId;
52 
SelectItemResponseData(int id)53     public SelectItemResponseData(int id) {
54         super();
55         mId = id;
56     }
57 
58     @Override
format(ByteArrayOutputStream buf)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 
68 class 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 
GetInkeyInputResponseData(String inData, boolean ucs2, boolean packed)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 
GetInkeyInputResponseData(boolean yesNoResponse)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
format(ByteArrayOutputStream buf)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
173 class LanguageResponseData extends ResponseData {
174     private String mLang;
175 
LanguageResponseData(String lang)176     public LanguageResponseData(String lang) {
177         super();
178         mLang = lang;
179     }
180 
181     @Override
format(ByteArrayOutputStream buf)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
211 class DTTZResponseData extends ResponseData {
212     private Calendar mCalendar;
213 
DTTZResponseData(Calendar cal)214     public DTTZResponseData(Calendar cal) {
215         super();
216         mCalendar = cal;
217     }
218 
219     @Override
format(ByteArrayOutputStream buf)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 
byteToBCD(int value)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 
getTZOffSetByte(long offSetVal)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