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