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