IccUtils.java revision 8cd6dad0052a57b16412087581a40b0df80dd15d
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
17package com.android.internal.telephony.uicc;
18
19import android.content.res.Resources;
20import android.content.res.Resources.NotFoundException;
21import android.graphics.Bitmap;
22import android.graphics.Color;
23import android.telephony.Rlog;
24
25import com.android.internal.telephony.GsmAlphabet;
26
27import java.io.UnsupportedEncodingException;
28
29/**
30 * Various methods, useful for dealing with SIM data.
31 */
32public 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
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     * all invalid character 'f'
68     */
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 valid plmn we trim all character 'f'
80        if (ret.contains("f")) {
81            ret = ret.replaceAll("f", "");
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
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
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
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
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
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
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[]
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
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
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     */
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
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     */
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
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
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
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