/* * 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.cat; import com.android.internal.telephony.uicc.IccFileHandler; import android.graphics.Bitmap; import android.graphics.Color; import android.os.AsyncResult; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import java.util.HashMap; /** * Class for loading icons from the SIM card. Has two states: single, for loading * one icon. Multi, for loading icons list. * */ class IconLoader extends Handler { // members private int mState = STATE_SINGLE_ICON; private ImageDescriptor mId = null; private Bitmap mCurrentIcon = null; private int mRecordNumber; private IccFileHandler mSimFH = null; private Message mEndMsg = null; private byte[] mIconData = null; // multi icons state members private int[] mRecordNumbers = null; private int mCurrentRecordIndex = 0; private Bitmap[] mIcons = null; private HashMap mIconsCache = null; private static IconLoader sLoader = null; private static HandlerThread sThread = null; // Loader state values. private static final int STATE_SINGLE_ICON = 1; private static final int STATE_MULTI_ICONS = 2; // Finished loading single record from a linear-fixed EF-IMG. private static final int EVENT_READ_EF_IMG_RECOED_DONE = 1; // Finished loading single icon from a Transparent DF-Graphics. private static final int EVENT_READ_ICON_DONE = 2; // Finished loading single colour icon lookup table. private static final int EVENT_READ_CLUT_DONE = 3; // Color lookup table offset inside the EF. private static final int CLUT_LOCATION_OFFSET = 4; // CLUT entry size, {Red, Green, Black} private static final int CLUT_ENTRY_SIZE = 3; private IconLoader(Looper looper , IccFileHandler fh) { super(looper); mSimFH = fh; mIconsCache = new HashMap(50); } static IconLoader getInstance(Handler caller, IccFileHandler fh) { if (sLoader != null) { return sLoader; } if (fh != null) { sThread = new HandlerThread("Cat Icon Loader"); sThread.start(); return new IconLoader(sThread.getLooper(), fh); } return null; } void loadIcons(int[] recordNumbers, Message msg) { if (recordNumbers == null || recordNumbers.length == 0 || msg == null) { return; } mEndMsg = msg; // initialize multi icons load variables. mIcons = new Bitmap[recordNumbers.length]; mRecordNumbers = recordNumbers; mCurrentRecordIndex = 0; mState = STATE_MULTI_ICONS; startLoadingIcon(recordNumbers[0]); } void loadIcon(int recordNumber, Message msg) { if (msg == null) { return; } mEndMsg = msg; mState = STATE_SINGLE_ICON; startLoadingIcon(recordNumber); } private void startLoadingIcon(int recordNumber) { // Reset the load variables. mId = null; mIconData = null; mCurrentIcon = null; mRecordNumber = recordNumber; // make sure the icon was not already loaded and saved in the local cache. if (mIconsCache.containsKey(recordNumber)) { mCurrentIcon = mIconsCache.get(recordNumber); postIcon(); return; } // start the first phase ==> loading Image Descriptor. readId(); } @Override public void handleMessage(Message msg) { AsyncResult ar; try { switch (msg.what) { case EVENT_READ_EF_IMG_RECOED_DONE: ar = (AsyncResult) msg.obj; if (handleImageDescriptor((byte[]) ar.result)) { readIconData(); } else { throw new Exception("Unable to parse image descriptor"); } break; case EVENT_READ_ICON_DONE: CatLog.d(this, "load icon done"); ar = (AsyncResult) msg.obj; byte[] rawData = ((byte[]) ar.result); if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_BASIC) { mCurrentIcon = parseToBnW(rawData, rawData.length); mIconsCache.put(mRecordNumber, mCurrentIcon); postIcon(); } else if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_COLOUR) { mIconData = rawData; readClut(); } else { CatLog.d(this, "else /postIcon "); postIcon(); } break; case EVENT_READ_CLUT_DONE: ar = (AsyncResult) msg.obj; byte [] clut = ((byte[]) ar.result); mCurrentIcon = parseToRGB(mIconData, mIconData.length, false, clut); mIconsCache.put(mRecordNumber, mCurrentIcon); postIcon(); break; } } catch (Exception e) { CatLog.d(this, "Icon load failed"); // post null icon back to the caller. postIcon(); } } /** * Handles Image descriptor parsing and required processing. This is the * first step required to handle retrieving icons from the SIM. * * @param rawData byte [] containing Image Instance descriptor as defined in * TS 51.011. */ private boolean handleImageDescriptor(byte[] rawData) { mId = ImageDescriptor.parse(rawData, 1); if (mId == null) { return false; } return true; } // Start reading color lookup table from SIM card. private void readClut() { int length = mIconData[3] * CLUT_ENTRY_SIZE; Message msg = obtainMessage(EVENT_READ_CLUT_DONE); mSimFH.loadEFImgTransparent(mId.mImageId, mIconData[CLUT_LOCATION_OFFSET], mIconData[CLUT_LOCATION_OFFSET + 1], length, msg); } // Start reading Image Descriptor from SIM card. private void readId() { if (mRecordNumber < 0) { mCurrentIcon = null; postIcon(); return; } Message msg = obtainMessage(EVENT_READ_EF_IMG_RECOED_DONE); mSimFH.loadEFImgLinearFixed(mRecordNumber, msg); } // Start reading icon bytes array from SIM card. private void readIconData() { Message msg = obtainMessage(EVENT_READ_ICON_DONE); mSimFH.loadEFImgTransparent(mId.mImageId, 0, 0, mId.mLength ,msg); } // When all is done pass icon back to caller. private void postIcon() { if (mState == STATE_SINGLE_ICON) { mEndMsg.obj = mCurrentIcon; mEndMsg.sendToTarget(); } else if (mState == STATE_MULTI_ICONS) { mIcons[mCurrentRecordIndex++] = mCurrentIcon; // If not all icons were loaded, start loading the next one. if (mCurrentRecordIndex < mRecordNumbers.length) { startLoadingIcon(mRecordNumbers[mCurrentRecordIndex]); } else { mEndMsg.obj = mIcons; mEndMsg.sendToTarget(); } } } /** * 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++] = bitToBnW((currentByte >> bitIndex-- ) & 0x01); } if (pixelIndex != numOfPixels) { CatLog.d("IconLoader", "parseToBnW; size error"); } return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); } /** * Decode one bit to a black and white color: * 0 is black * 1 is white * @param bit to decode * @return RGB color */ private static int bitToBnW(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 * @param clut coulor lookup table * @return The color bitmap */ public static Bitmap parseToRGB(byte[] data, int length, boolean transparency, byte[] clut) { int valueIndex = 0; int width = data[valueIndex++] & 0xFF; int height = data[valueIndex++] & 0xFF; int bitsPerImg = data[valueIndex++] & 0xFF; int numOfClutEntries = data[valueIndex++] & 0xFF; if (true == transparency) { clut[numOfClutEntries - 1] = Color.TRANSPARENT; } int numOfPixels = width * height; int[] pixels = new int[numOfPixels]; valueIndex = 6; int pixelIndex = 0; int bitsStartOffset = 8 - bitsPerImg; int bitIndex = bitsStartOffset; byte currentByte = data[valueIndex++]; int mask = getMask(bitsPerImg); boolean bitsOverlaps = (8 % bitsPerImg == 0); while (pixelIndex < numOfPixels) { // reassign data and index for every byte (8 bits). if (bitIndex < 0) { currentByte = data[valueIndex++]; bitIndex = bitsOverlaps ? (bitsStartOffset) : (bitIndex * -1); } int clutEntry = ((currentByte >> bitIndex) & mask); int clutIndex = clutEntry * CLUT_ENTRY_SIZE; pixels[pixelIndex++] = Color.rgb(clut[clutIndex], clut[clutIndex + 1], clut[clutIndex + 2]); bitIndex -= bitsPerImg; } return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); } /** * Calculate bit mask for a given number of bits. The mask should enable to * make a bitwise and to the given number of bits. * @param numOfBits number of bits to calculate mask for. * @return bit mask */ private static int getMask(int numOfBits) { int mask = 0x00; switch (numOfBits) { case 1: mask = 0x01; break; case 2: mask = 0x03; break; case 3: mask = 0x07; break; case 4: mask = 0x0F; break; case 5: mask = 0x1F; break; case 6: mask = 0x3F; break; case 7: mask = 0x7F; break; case 8: mask = 0xFF; break; } return mask; } public void dispose() { mSimFH = null; if (sThread != null) { sThread.quit(); sThread = null; } mIconsCache = null; sLoader = null; } }