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