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