1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony; 18 19 import android.content.res.Resources; 20 import android.content.res.Resources.NotFoundException; 21 import android.graphics.Bitmap; 22 import android.graphics.Color; 23 import android.util.Log; 24 25 import com.android.internal.telephony.GsmAlphabet; 26 import java.io.UnsupportedEncodingException; 27 import java.nio.ByteBuffer; 28 import java.nio.charset.Charset; 29 30 /** 31 * Various methods, useful for dealing with SIM data. 32 */ 33 public class IccUtils { 34 static final String LOG_TAG="IccUtils"; 35 36 /** 37 * Many fields in GSM SIM's are stored as nibble-swizzled BCD 38 * 39 * Assumes left-justified field that may be padded right with 0xf 40 * values. 41 * 42 * Stops on invalid BCD value, returning string so far 43 */ 44 public static String 45 bcdToString(byte[] data, int offset, int length) { 46 StringBuilder ret = new StringBuilder(length*2); 47 48 for (int i = offset ; i < offset + length ; i++) { 49 byte b; 50 int v; 51 52 v = data[i] & 0xf; 53 if (v > 9) break; 54 ret.append((char)('0' + v)); 55 56 v = (data[i] >> 4) & 0xf; 57 // Some PLMNs have 'f' as high nibble, ignore it 58 if (v == 0xf) continue; 59 if (v > 9) break; 60 ret.append((char)('0' + v)); 61 } 62 63 return ret.toString(); 64 } 65 66 /** 67 * Decode cdma byte into String. 68 */ 69 public static String 70 cdmaBcdToString(byte[] data, int offset, int length) { 71 StringBuilder ret = new StringBuilder(length); 72 73 int count = 0; 74 for (int i = offset; count < length; i++) { 75 int v; 76 v = data[i] & 0xf; 77 if (v > 9) v = 0; 78 ret.append((char)('0' + v)); 79 80 if (++count == length) break; 81 82 v = (data[i] >> 4) & 0xf; 83 if (v > 9) v = 0; 84 ret.append((char)('0' + v)); 85 ++count; 86 } 87 return ret.toString(); 88 } 89 90 /** 91 * Decodes a GSM-style BCD byte, returning an int ranging from 0-99. 92 * 93 * In GSM land, the least significant BCD digit is stored in the most 94 * significant nibble. 95 * 96 * Out-of-range digits are treated as 0 for the sake of the time stamp, 97 * because of this: 98 * 99 * TS 23.040 section 9.2.3.11 100 * "if the MS receives a non-integer value in the SCTS, it shall 101 * assume the digit is set to 0 but shall store the entire field 102 * exactly as received" 103 */ 104 public static int 105 gsmBcdByteToInt(byte b) { 106 int ret = 0; 107 108 // treat out-of-range BCD values as 0 109 if ((b & 0xf0) <= 0x90) { 110 ret = (b >> 4) & 0xf; 111 } 112 113 if ((b & 0x0f) <= 0x09) { 114 ret += (b & 0xf) * 10; 115 } 116 117 return ret; 118 } 119 120 /** 121 * Decodes a CDMA style BCD byte like {@link gsmBcdByteToInt}, but 122 * opposite nibble format. The least significant BCD digit 123 * is in the least significant nibble and the most significant 124 * is in the most significant nibble. 125 */ 126 public static int 127 cdmaBcdByteToInt(byte b) { 128 int ret = 0; 129 130 // treat out-of-range BCD values as 0 131 if ((b & 0xf0) <= 0x90) { 132 ret = ((b >> 4) & 0xf) * 10; 133 } 134 135 if ((b & 0x0f) <= 0x09) { 136 ret += (b & 0xf); 137 } 138 139 return ret; 140 } 141 142 /** 143 * Decodes a string field that's formatted like the EF[ADN] alpha 144 * identifier 145 * 146 * From TS 51.011 10.5.1: 147 * Coding: 148 * this alpha tagging shall use either 149 * - the SMS default 7 bit coded alphabet as defined in 150 * TS 23.038 [12] with bit 8 set to 0. The alpha identifier 151 * shall be left justified. Unused bytes shall be set to 'FF'; or 152 * - one of the UCS2 coded options as defined in annex B. 153 * 154 * Annex B from TS 11.11 V8.13.0: 155 * 1) If the first octet in the alpha string is '80', then the 156 * remaining octets are 16 bit UCS2 characters ... 157 * 2) if the first octet in the alpha string is '81', then the 158 * second octet contains a value indicating the number of 159 * characters in the string, and the third octet contains an 160 * 8 bit number which defines bits 15 to 8 of a 16 bit 161 * base pointer, where bit 16 is set to zero and bits 7 to 1 162 * are also set to zero. These sixteen bits constitute a 163 * base pointer to a "half page" in the UCS2 code space, to be 164 * used with some or all of the remaining octets in the string. 165 * The fourth and subsequent octets contain codings as follows: 166 * If bit 8 of the octet is set to zero, the remaining 7 bits 167 * of the octet contain a GSM Default Alphabet character, 168 * whereas if bit 8 of the octet is set to one, then the 169 * remaining seven bits are an offset value added to the 170 * 16 bit base pointer defined earlier... 171 * 3) If the first octet of the alpha string is set to '82', then 172 * the second octet contains a value indicating the number of 173 * characters in the string, and the third and fourth octets 174 * contain a 16 bit number which defines the complete 16 bit 175 * base pointer to a "half page" in the UCS2 code space... 176 */ 177 public static String 178 adnStringFieldToString(byte[] data, int offset, int length) { 179 if (length == 0) { 180 return ""; 181 } 182 if (length >= 1) { 183 if (data[offset] == (byte) 0x80) { 184 int ucslen = (length - 1) / 2; 185 String ret = null; 186 187 try { 188 ret = new String(data, offset + 1, ucslen * 2, "utf-16be"); 189 } catch (UnsupportedEncodingException ex) { 190 Log.e(LOG_TAG, "implausible UnsupportedEncodingException", 191 ex); 192 } 193 194 if (ret != null) { 195 // trim off trailing FFFF characters 196 197 ucslen = ret.length(); 198 while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF') 199 ucslen--; 200 201 return ret.substring(0, ucslen); 202 } 203 } 204 } 205 206 boolean isucs2 = false; 207 char base = '\0'; 208 int len = 0; 209 210 if (length >= 3 && data[offset] == (byte) 0x81) { 211 len = data[offset + 1] & 0xFF; 212 if (len > length - 3) 213 len = length - 3; 214 215 base = (char) ((data[offset + 2] & 0xFF) << 7); 216 offset += 3; 217 isucs2 = true; 218 } else if (length >= 4 && data[offset] == (byte) 0x82) { 219 len = data[offset + 1] & 0xFF; 220 if (len > length - 4) 221 len = length - 4; 222 223 base = (char) (((data[offset + 2] & 0xFF) << 8) | 224 (data[offset + 3] & 0xFF)); 225 offset += 4; 226 isucs2 = true; 227 } 228 229 if (isucs2) { 230 StringBuilder ret = new StringBuilder(); 231 232 while (len > 0) { 233 // UCS2 subset case 234 235 if (data[offset] < 0) { 236 ret.append((char) (base + (data[offset] & 0x7F))); 237 offset++; 238 len--; 239 } 240 241 // GSM character set case 242 243 int count = 0; 244 while (count < len && data[offset + count] >= 0) 245 count++; 246 247 ret.append(GsmAlphabet.gsm8BitUnpackedToString(data, 248 offset, count)); 249 250 offset += count; 251 len -= count; 252 } 253 254 return ret.toString(); 255 } 256 257 Resources resource = Resources.getSystem(); 258 String defaultCharset = ""; 259 try { 260 defaultCharset = 261 resource.getString(com.android.internal.R.string.gsm_alphabet_default_charset); 262 } catch (NotFoundException e) { 263 // Ignore Exception and defaultCharset is set to a empty string. 264 } 265 return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length, defaultCharset.trim()); 266 } 267 268 static int 269 hexCharToInt(char c) { 270 if (c >= '0' && c <= '9') return (c - '0'); 271 if (c >= 'A' && c <= 'F') return (c - 'A' + 10); 272 if (c >= 'a' && c <= 'f') return (c - 'a' + 10); 273 274 throw new RuntimeException ("invalid hex char '" + c + "'"); 275 } 276 277 /** 278 * Converts a hex String to a byte array. 279 * 280 * @param s A string of hexadecimal characters, must be an even number of 281 * chars long 282 * 283 * @return byte array representation 284 * 285 * @throws RuntimeException on invalid format 286 */ 287 public static byte[] 288 hexStringToBytes(String s) { 289 byte[] ret; 290 291 if (s == null) return null; 292 293 int sz = s.length(); 294 295 ret = new byte[sz/2]; 296 297 for (int i=0 ; i <sz ; i+=2) { 298 ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4) 299 | hexCharToInt(s.charAt(i+1))); 300 } 301 302 return ret; 303 } 304 305 306 /** 307 * Converts a byte array into a String of hexadecimal characters. 308 * 309 * @param bytes an array of bytes 310 * 311 * @return hex string representation of bytes array 312 */ 313 public static String 314 bytesToHexString(byte[] bytes) { 315 if (bytes == null) return null; 316 317 StringBuilder ret = new StringBuilder(2*bytes.length); 318 319 for (int i = 0 ; i < bytes.length ; i++) { 320 int b; 321 322 b = 0x0f & (bytes[i] >> 4); 323 324 ret.append("0123456789abcdef".charAt(b)); 325 326 b = 0x0f & bytes[i]; 327 328 ret.append("0123456789abcdef".charAt(b)); 329 } 330 331 return ret.toString(); 332 } 333 334 335 /** 336 * Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string 337 * "offset" points to "octet 3", the coding scheme byte 338 * empty string returned on decode error 339 */ 340 public static String 341 networkNameToString(byte[] data, int offset, int length) { 342 String ret; 343 344 if ((data[offset] & 0x80) != 0x80 || length < 1) { 345 return ""; 346 } 347 348 switch ((data[offset] >>> 4) & 0x7) { 349 case 0: 350 // SMS character set 351 int countSeptets; 352 int unusedBits = data[offset] & 7; 353 countSeptets = (((length - 1) * 8) - unusedBits) / 7 ; 354 ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets); 355 break; 356 case 1: 357 // UCS2 358 try { 359 ret = new String(data, 360 offset + 1, length - 1, "utf-16"); 361 } catch (UnsupportedEncodingException ex) { 362 ret = ""; 363 Log.e(LOG_TAG,"implausible UnsupportedEncodingException", ex); 364 } 365 break; 366 367 // unsupported encoding 368 default: 369 ret = ""; 370 break; 371 } 372 373 // "Add CI" 374 // "The MS should add the letters for the Country's Initials and 375 // a separator (e.g. a space) to the text string" 376 377 if ((data[offset] & 0x40) != 0) { 378 // FIXME(mkf) add country initials here 379 380 } 381 382 return ret; 383 } 384 385 /** 386 * Convert a TS 131.102 image instance of code scheme '11' into Bitmap 387 * @param data The raw data 388 * @param length The length of image body 389 * @return The bitmap 390 */ 391 public static Bitmap parseToBnW(byte[] data, int length){ 392 int valueIndex = 0; 393 int width = data[valueIndex++] & 0xFF; 394 int height = data[valueIndex++] & 0xFF; 395 int numOfPixels = width*height; 396 397 int[] pixels = new int[numOfPixels]; 398 399 int pixelIndex = 0; 400 int bitIndex = 7; 401 byte currentByte = 0x00; 402 while (pixelIndex < numOfPixels) { 403 // reassign data and index for every byte (8 bits). 404 if (pixelIndex % 8 == 0) { 405 currentByte = data[valueIndex++]; 406 bitIndex = 7; 407 } 408 pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01); 409 }; 410 411 if (pixelIndex != numOfPixels) { 412 Log.e(LOG_TAG, "parse end and size error"); 413 } 414 return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); 415 } 416 417 private static int bitToRGB(int bit){ 418 if(bit == 1){ 419 return Color.WHITE; 420 } else { 421 return Color.BLACK; 422 } 423 } 424 425 /** 426 * a TS 131.102 image instance of code scheme '11' into color Bitmap 427 * 428 * @param data The raw data 429 * @param length the length of image body 430 * @param transparency with or without transparency 431 * @return The color bitmap 432 */ 433 public static Bitmap parseToRGB(byte[] data, int length, 434 boolean transparency) { 435 int valueIndex = 0; 436 int width = data[valueIndex++] & 0xFF; 437 int height = data[valueIndex++] & 0xFF; 438 int bits = data[valueIndex++] & 0xFF; 439 int colorNumber = data[valueIndex++] & 0xFF; 440 int clutOffset = ((data[valueIndex++] & 0xFF) << 8) 441 | (data[valueIndex++] & 0xFF); 442 443 int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber); 444 if (true == transparency) { 445 colorIndexArray[colorNumber - 1] = Color.TRANSPARENT; 446 } 447 448 int[] resultArray = null; 449 if (0 == (8 % bits)) { 450 resultArray = mapTo2OrderBitColor(data, valueIndex, 451 (width * height), colorIndexArray, bits); 452 } else { 453 resultArray = mapToNon2OrderBitColor(data, valueIndex, 454 (width * height), colorIndexArray, bits); 455 } 456 457 return Bitmap.createBitmap(resultArray, width, height, 458 Bitmap.Config.RGB_565); 459 } 460 461 private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex, 462 int length, int[] colorArray, int bits) { 463 if (0 != (8 % bits)) { 464 Log.e(LOG_TAG, "not event number of color"); 465 return mapToNon2OrderBitColor(data, valueIndex, length, colorArray, 466 bits); 467 } 468 469 int mask = 0x01; 470 switch (bits) { 471 case 1: 472 mask = 0x01; 473 break; 474 case 2: 475 mask = 0x03; 476 break; 477 case 4: 478 mask = 0x0F; 479 break; 480 case 8: 481 mask = 0xFF; 482 break; 483 } 484 485 int[] resultArray = new int[length]; 486 int resultIndex = 0; 487 int run = 8 / bits; 488 while (resultIndex < length) { 489 byte tempByte = data[valueIndex++]; 490 for (int runIndex = 0; runIndex < run; ++runIndex) { 491 int offset = run - runIndex - 1; 492 resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits)) 493 & mask]; 494 } 495 } 496 return resultArray; 497 } 498 499 private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex, 500 int length, int[] colorArray, int bits) { 501 if (0 == (8 % bits)) { 502 Log.e(LOG_TAG, "not odd number of color"); 503 return mapTo2OrderBitColor(data, valueIndex, length, colorArray, 504 bits); 505 } 506 507 int[] resultArray = new int[length]; 508 // TODO fix me: 509 return resultArray; 510 } 511 512 private static int[] getCLUT(byte[] rawData, int offset, int number) { 513 if (null == rawData) { 514 return null; 515 } 516 517 int[] result = new int[number]; 518 int endIndex = offset + (number * 3); // 1 color use 3 bytes 519 int valueIndex = offset; 520 int colorIndex = 0; 521 int alpha = 0xff << 24; 522 do { 523 result[colorIndex++] = alpha 524 | ((rawData[valueIndex++] & 0xFF) << 16) 525 | ((rawData[valueIndex++] & 0xFF) << 8) 526 | ((rawData[valueIndex++] & 0xFF)); 527 } while (valueIndex < endIndex); 528 return result; 529 } 530 } 531