Home | History | Annotate | Download | only in telephony
      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.content.res.Resources;
     20 import android.content.res.Resources.NotFoundException;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Color;
     23 import android.util.Log;
     24 
     25 import com.android.internal.telephony.GsmAlphabet;
     26 import java.io.UnsupportedEncodingException;
     27 import java.nio.ByteBuffer;
     28 import java.nio.charset.Charset;
     29 
     30 /**
     31  * Various methods, useful for dealing with SIM data.
     32  */
     33 public 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