blob: 881ed2dd2d7c380509ae4dd751b1e24e63525f93 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Log;
import com.android.internal.telephony.GsmAlphabet;
import java.io.UnsupportedEncodingException;
/**
* Various methods, useful for dealing with SIM data.
*/
public class IccUtils {
static final String LOG_TAG="IccUtils";
/**
* Many fields in GSM SIM's are stored as nibble-swizzled BCD
*
* Assumes left-justified field that may be padded right with 0xf
* values.
*
* Stops on invalid BCD value, returning string so far
*/
public static String
bcdToString(byte[] data, int offset, int length) {
StringBuilder ret = new StringBuilder(length*2);
for (int i = offset ; i < offset + length ; i++) {
byte b;
int v;
v = data[i] & 0xf;
if (v > 9) break;
ret.append((char)('0' + v));
v = (data[i] >> 4) & 0xf;
if (v > 9) break;
ret.append((char)('0' + v));
}
return ret.toString();
}
/**
* Decodes a GSM-style BCD byte, returning an int ranging from 0-99.
*
* In GSM land, the least significant BCD digit is stored in the most
* significant nibble.
*
* Out-of-range digits are treated as 0 for the sake of the time stamp,
* because of this:
*
* TS 23.040 section 9.2.3.11
* "if the MS receives a non-integer value in the SCTS, it shall
* assume the digit is set to 0 but shall store the entire field
* exactly as received"
*/
public static int
bcdByteToInt(byte b) {
int ret = 0;
// treat out-of-range BCD values as 0
if ((b & 0xf0) <= 0x90) {
ret = (b >> 4) & 0xf;
}
if ((b & 0x0f) <= 0x09) {
ret += (b & 0xf) * 10;
}
return ret;
}
/** Decodes BCD byte like {@link bcdByteToInt}, but the most significant BCD
* digit is expected in the most significant nibble.
*/
public static int
beBcdByteToInt(byte b) {
int ret = 0;
// treat out-of-range BCD values as 0
if ((b & 0xf0) <= 0x90) {
ret = ((b >> 4) & 0xf) * 10;
}
if ((b & 0x0f) <= 0x09) {
ret += (b & 0xf);
}
return ret;
}
/**
* Decodes a string field that's formatted like the EF[ADN] alpha
* identifier
*
* From TS 51.011 10.5.1:
* Coding:
* this alpha tagging shall use either
* - the SMS default 7 bit coded alphabet as defined in
* TS 23.038 [12] with bit 8 set to 0. The alpha identifier
* shall be left justified. Unused bytes shall be set to 'FF'; or
* - one of the UCS2 coded options as defined in annex B.
*
* Annex B from TS 11.11 V8.13.0:
* 1) If the first octet in the alpha string is '80', then the
* remaining octets are 16 bit UCS2 characters ...
* 2) if the first octet in the alpha string is '81', then the
* second octet contains a value indicating the number of
* characters in the string, and the third octet contains an
* 8 bit number which defines bits 15 to 8 of a 16 bit
* base pointer, where bit 16 is set to zero and bits 7 to 1
* are also set to zero. These sixteen bits constitute a
* base pointer to a "half page" in the UCS2 code space, to be
* used with some or all of the remaining octets in the string.
* The fourth and subsequent octets contain codings as follows:
* If bit 8 of the octet is set to zero, the remaining 7 bits
* of the octet contain a GSM Default Alphabet character,
* whereas if bit 8 of the octet is set to one, then the
* remaining seven bits are an offset value added to the
* 16 bit base pointer defined earlier...
* 3) If the first octet of the alpha string is set to '82', then
* the second octet contains a value indicating the number of
* characters in the string, and the third and fourth octets
* contain a 16 bit number which defines the complete 16 bit
* base pointer to a "half page" in the UCS2 code space...
*/
public static String
adnStringFieldToString(byte[] data, int offset, int length) {
if (length >= 1) {
if (data[offset] == (byte) 0x80) {
int ucslen = (length - 1) / 2;
String ret = null;
try {
ret = new String(data, offset + 1, ucslen * 2, "utf-16be");
} catch (UnsupportedEncodingException ex) {
Log.e(LOG_TAG, "implausible UnsupportedEncodingException",
ex);
}
if (ret != null) {
// trim off trailing FFFF characters
ucslen = ret.length();
while (ucslen > 0 && ret.charAt(ucslen - 1) == '\uFFFF')
ucslen--;
return ret.substring(0, ucslen);
}
}
}
boolean isucs2 = false;
char base = '\0';
int len = 0;
if (length >= 3 && data[offset] == (byte) 0x81) {
len = data[offset + 1] & 0xFF;
if (len > length - 3)
len = length - 3;
base = (char) ((data[offset + 2] & 0xFF) << 7);
offset += 3;
isucs2 = true;
} else if (length >= 4 && data[offset] == (byte) 0x82) {
len = data[offset + 1] & 0xFF;
if (len > length - 4)
len = length - 4;
base = (char) (((data[offset + 2] & 0xFF) << 8) |
(data[offset + 3] & 0xFF));
offset += 4;
isucs2 = true;
}
if (isucs2) {
StringBuilder ret = new StringBuilder();
while (len > 0) {
// UCS2 subset case
if (data[offset] < 0) {
ret.append((char) (base + (data[offset] & 0x7F)));
offset++;
len--;
}
// GSM character set case
int count = 0;
while (count < len && data[offset + count] >= 0)
count++;
ret.append(GsmAlphabet.gsm8BitUnpackedToString(data,
offset, count));
offset += count;
len -= count;
}
return ret.toString();
}
return GsmAlphabet.gsm8BitUnpackedToString(data, offset, length);
}
static int
hexCharToInt(char c) {
if (c >= '0' && c <= '9') return (c - '0');
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
throw new RuntimeException ("invalid hex char '" + c + "'");
}
/**
* Converts a hex String to a byte array.
*
* @param s A string of hexadecimal characters, must be an even number of
* chars long
*
* @return byte array representation
*
* @throws RuntimeException on invalid format
*/
public static byte[]
hexStringToBytes(String s) {
byte[] ret;
if (s == null) return null;
int sz = s.length();
ret = new byte[sz/2];
for (int i=0 ; i <sz ; i+=2) {
ret[i/2] = (byte) ((hexCharToInt(s.charAt(i)) << 4)
| hexCharToInt(s.charAt(i+1)));
}
return ret;
}
/**
* Converts a byte array into a String hexidecimal characters
*
* null returns null
*/
public static String
bytesToHexString(byte[] bytes) {
if (bytes == null) return null;
StringBuilder ret = new StringBuilder(2*bytes.length);
for (int i = 0 ; i < bytes.length ; i++) {
int b;
b = 0x0f & (bytes[i] >> 4);
ret.append("0123456789abcdef".charAt(b));
b = 0x0f & bytes[i];
ret.append("0123456789abcdef".charAt(b));
}
return ret.toString();
}
/**
* Convert a TS 24.008 Section 10.5.3.5a Network Name field to a string
* "offset" points to "octet 3", the coding scheme byte
* empty string returned on decode error
*/
public static String
networkNameToString(byte[] data, int offset, int length) {
String ret;
if ((data[offset] & 0x80) != 0x80 || length < 1) {
return "";
}
switch ((data[offset] >>> 4) & 0x7) {
case 0:
// SMS character set
int countSeptets;
int unusedBits = data[offset] & 7;
countSeptets = (((length - 1) * 8) - unusedBits) / 7 ;
ret = GsmAlphabet.gsm7BitPackedToString(data, offset + 1, countSeptets);
break;
case 1:
// UCS2
try {
ret = new String(data,
offset + 1, length - 1, "utf-16");
} catch (UnsupportedEncodingException ex) {
ret = "";
Log.e(LOG_TAG,"implausible UnsupportedEncodingException", ex);
}
break;
// unsupported encoding
default:
ret = "";
break;
}
// "Add CI"
// "The MS should add the letters for the Country's Initials and
// a separator (e.g. a space) to the text string"
if ((data[offset] & 0x40) != 0) {
// FIXME(mkf) add country initials here
}
return ret;
}
/**
* Convert a TS 131.102 image instance of code scheme '11' into Bitmap
* @param data The raw data
* @param length The length of image body
* @return The bitmap
*/
public static Bitmap parseToBnW(byte[] data, int length){
int valueIndex = 0;
int width = data[valueIndex++] & 0xFF;
int height = data[valueIndex++] & 0xFF;
int numOfPixels = width*height;
int[] pixels = new int[numOfPixels];
int pixelIndex = 0;
int bitIndex = 7;
byte currentByte = 0x00;
while (pixelIndex < numOfPixels) {
// reassign data and index for every byte (8 bits).
if (pixelIndex % 8 == 0) {
currentByte = data[valueIndex++];
bitIndex = 7;
}
pixels[pixelIndex++] = bitToRGB((currentByte >> bitIndex-- ) & 0x01);
};
if (pixelIndex != numOfPixels) {
Log.e(LOG_TAG, "parse end and size error");
}
return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888);
}
private static int bitToRGB(int bit){
if(bit == 1){
return Color.WHITE;
} else {
return Color.BLACK;
}
}
/**
* a TS 131.102 image instance of code scheme '11' into color Bitmap
*
* @param data The raw data
* @param length the length of image body
* @param transparency with or without transparency
* @return The color bitmap
*/
public static Bitmap parseToRGB(byte[] data, int length,
boolean transparency) {
int valueIndex = 0;
int width = data[valueIndex++] & 0xFF;
int height = data[valueIndex++] & 0xFF;
int bits = data[valueIndex++] & 0xFF;
int colorNumber = data[valueIndex++] & 0xFF;
int clutOffset = ((data[valueIndex++] & 0xFF) << 8)
| data[valueIndex++];
length = length - 6;
int[] colorIndexArray = getCLUT(data, clutOffset, colorNumber);
if (true == transparency) {
colorIndexArray[colorNumber - 1] = Color.TRANSPARENT;
}
int[] resultArray = null;
if (0 == (8 % bits)) {
resultArray = mapTo2OrderBitColor(data, valueIndex,
(width * height), colorIndexArray, bits);
} else {
resultArray = mapToNon2OrderBitColor(data, valueIndex,
(width * height), colorIndexArray, bits);
}
return Bitmap.createBitmap(resultArray, width, height,
Bitmap.Config.RGB_565);
}
private static int[] mapTo2OrderBitColor(byte[] data, int valueIndex,
int length, int[] colorArray, int bits) {
if (0 != (8 % bits)) {
Log.e(LOG_TAG, "not event number of color");
return mapToNon2OrderBitColor(data, valueIndex, length, colorArray,
bits);
}
int mask = 0x01;
switch (bits) {
case 1:
mask = 0x01;
break;
case 2:
mask = 0x03;
break;
case 4:
mask = 0x0F;
break;
case 8:
mask = 0xFF;
break;
}
int[] resultArray = new int[length];
int resultIndex = 0;
int run = 8 / bits;
while (resultIndex < length) {
byte tempByte = data[valueIndex++];
for (int runIndex = 0; runIndex < run; ++runIndex) {
int offset = run - runIndex - 1;
resultArray[resultIndex++] = colorArray[(tempByte >> (offset * bits))
& mask];
}
}
return resultArray;
}
private static int[] mapToNon2OrderBitColor(byte[] data, int valueIndex,
int length, int[] colorArray, int bits) {
if (0 == (8 % bits)) {
Log.e(LOG_TAG, "not odd number of color");
return mapTo2OrderBitColor(data, valueIndex, length, colorArray,
bits);
}
int[] resultArray = new int[length];
// TODO fix me:
return resultArray;
}
private static int[] getCLUT(byte[] rawData, int offset, int number) {
if (null == rawData) {
return null;
}
int[] result = new int[number];
int endIndex = offset + (number * 3); // 1 color use 3 bytes
int valueIndex = offset;
int colorIndex = 0;
int alpha = 0xff << 24;
do {
result[colorIndex++] = alpha
| ((rawData[valueIndex++] & 0xFF) << 16)
| ((rawData[valueIndex++] & 0xFF) << 8)
| ((rawData[valueIndex++] & 0xFF));
} while (valueIndex < endIndex);
return result;
}
}