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