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