IconLoader.java revision eca4a2ab310bf8d96cf98b5e880e2f8eef7ffeb6
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.cat; 18 19import com.android.internal.telephony.uicc.IccFileHandler; 20 21import android.graphics.Bitmap; 22import android.graphics.Color; 23import android.os.AsyncResult; 24import android.os.Handler; 25import android.os.HandlerThread; 26import android.os.Looper; 27import android.os.Message; 28import java.util.HashMap; 29 30/** 31 * Class for loading icons from the SIM card. Has two states: single, for loading 32 * one icon. Multi, for loading icons list. 33 * 34 */ 35class IconLoader extends Handler { 36 // members 37 private int mState = STATE_SINGLE_ICON; 38 private ImageDescriptor mId = null; 39 private Bitmap mCurrentIcon = null; 40 private int mRecordNumber; 41 private IccFileHandler mSimFH = null; 42 private Message mEndMsg = null; 43 private byte[] mIconData = null; 44 // multi icons state members 45 private int[] mRecordNumbers = null; 46 private int mCurrentRecordIndex = 0; 47 private Bitmap[] mIcons = null; 48 private HashMap<Integer, Bitmap> mIconsCache = null; 49 50 private static IconLoader sLoader = null; 51 private static HandlerThread sThread = null; 52 53 // Loader state values. 54 private static final int STATE_SINGLE_ICON = 1; 55 private static final int STATE_MULTI_ICONS = 2; 56 57 // Finished loading single record from a linear-fixed EF-IMG. 58 private static final int EVENT_READ_EF_IMG_RECOED_DONE = 1; 59 // Finished loading single icon from a Transparent DF-Graphics. 60 private static final int EVENT_READ_ICON_DONE = 2; 61 // Finished loading single colour icon lookup table. 62 private static final int EVENT_READ_CLUT_DONE = 3; 63 64 // Color lookup table offset inside the EF. 65 private static final int CLUT_LOCATION_OFFSET = 4; 66 // CLUT entry size, {Red, Green, Black} 67 private static final int CLUT_ENTRY_SIZE = 3; 68 69 70 private IconLoader(Looper looper , IccFileHandler fh) { 71 super(looper); 72 mSimFH = fh; 73 74 mIconsCache = new HashMap<Integer, Bitmap>(50); 75 } 76 77 static IconLoader getInstance(Handler caller, IccFileHandler fh) { 78 if (sLoader != null) { 79 return sLoader; 80 } 81 if (fh != null) { 82 sThread = new HandlerThread("Cat Icon Loader"); 83 sThread.start(); 84 return new IconLoader(sThread.getLooper(), fh); 85 } 86 return null; 87 } 88 89 void loadIcons(int[] recordNumbers, Message msg) { 90 if (recordNumbers == null || recordNumbers.length == 0 || msg == null) { 91 return; 92 } 93 mEndMsg = msg; 94 // initialize multi icons load variables. 95 mIcons = new Bitmap[recordNumbers.length]; 96 mRecordNumbers = recordNumbers; 97 mCurrentRecordIndex = 0; 98 mState = STATE_MULTI_ICONS; 99 startLoadingIcon(recordNumbers[0]); 100 } 101 102 void loadIcon(int recordNumber, Message msg) { 103 if (msg == null) { 104 return; 105 } 106 mEndMsg = msg; 107 mState = STATE_SINGLE_ICON; 108 startLoadingIcon(recordNumber); 109 } 110 111 private void startLoadingIcon(int recordNumber) { 112 // Reset the load variables. 113 mId = null; 114 mIconData = null; 115 mCurrentIcon = null; 116 mRecordNumber = recordNumber; 117 118 // make sure the icon was not already loaded and saved in the local cache. 119 if (mIconsCache.containsKey(recordNumber)) { 120 mCurrentIcon = mIconsCache.get(recordNumber); 121 postIcon(); 122 return; 123 } 124 125 // start the first phase ==> loading Image Descriptor. 126 readId(); 127 } 128 129 @Override 130 public void handleMessage(Message msg) { 131 AsyncResult ar; 132 133 try { 134 switch (msg.what) { 135 case EVENT_READ_EF_IMG_RECOED_DONE: 136 ar = (AsyncResult) msg.obj; 137 if (handleImageDescriptor((byte[]) ar.result)) { 138 readIconData(); 139 } else { 140 throw new Exception("Unable to parse image descriptor"); 141 } 142 break; 143 case EVENT_READ_ICON_DONE: 144 ar = (AsyncResult) msg.obj; 145 byte[] rawData = ((byte[]) ar.result); 146 if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_BASIC) { 147 mCurrentIcon = parseToBnW(rawData, rawData.length); 148 mIconsCache.put(mRecordNumber, mCurrentIcon); 149 postIcon(); 150 } else if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_COLOUR) { 151 mIconData = rawData; 152 readClut(); 153 } 154 break; 155 case EVENT_READ_CLUT_DONE: 156 ar = (AsyncResult) msg.obj; 157 byte [] clut = ((byte[]) ar.result); 158 mCurrentIcon = parseToRGB(mIconData, mIconData.length, 159 false, clut); 160 mIconsCache.put(mRecordNumber, mCurrentIcon); 161 postIcon(); 162 break; 163 } 164 } catch (Exception e) { 165 CatLog.d(this, "Icon load failed"); 166 // post null icon back to the caller. 167 postIcon(); 168 } 169 } 170 171 /** 172 * Handles Image descriptor parsing and required processing. This is the 173 * first step required to handle retrieving icons from the SIM. 174 * 175 * @param rawData byte [] containing Image Instance descriptor as defined in 176 * TS 51.011. 177 */ 178 private boolean handleImageDescriptor(byte[] rawData) { 179 mId = ImageDescriptor.parse(rawData, 1); 180 if (mId == null) { 181 return false; 182 } 183 return true; 184 } 185 186 // Start reading color lookup table from SIM card. 187 private void readClut() { 188 int length = mIconData[3] * CLUT_ENTRY_SIZE; 189 Message msg = obtainMessage(EVENT_READ_CLUT_DONE); 190 mSimFH.loadEFImgTransparent(mId.mImageId, 191 mIconData[CLUT_LOCATION_OFFSET], 192 mIconData[CLUT_LOCATION_OFFSET + 1], length, msg); 193 } 194 195 // Start reading Image Descriptor from SIM card. 196 private void readId() { 197 if (mRecordNumber < 0) { 198 mCurrentIcon = null; 199 postIcon(); 200 return; 201 } 202 Message msg = obtainMessage(EVENT_READ_EF_IMG_RECOED_DONE); 203 mSimFH.loadEFImgLinearFixed(mRecordNumber, msg); 204 } 205 206 // Start reading icon bytes array from SIM card. 207 private void readIconData() { 208 Message msg = obtainMessage(EVENT_READ_ICON_DONE); 209 mSimFH.loadEFImgTransparent(mId.mImageId, 0, 0, mId.mLength ,msg); 210 } 211 212 // When all is done pass icon back to caller. 213 private void postIcon() { 214 if (mState == STATE_SINGLE_ICON) { 215 mEndMsg.obj = mCurrentIcon; 216 mEndMsg.sendToTarget(); 217 } else if (mState == STATE_MULTI_ICONS) { 218 mIcons[mCurrentRecordIndex++] = mCurrentIcon; 219 // If not all icons were loaded, start loading the next one. 220 if (mCurrentRecordIndex < mRecordNumbers.length) { 221 startLoadingIcon(mRecordNumbers[mCurrentRecordIndex]); 222 } else { 223 mEndMsg.obj = mIcons; 224 mEndMsg.sendToTarget(); 225 } 226 } 227 } 228 229 /** 230 * Convert a TS 131.102 image instance of code scheme '11' into Bitmap 231 * @param data The raw data 232 * @param length The length of image body 233 * @return The bitmap 234 */ 235 public static Bitmap parseToBnW(byte[] data, int length){ 236 int valueIndex = 0; 237 int width = data[valueIndex++] & 0xFF; 238 int height = data[valueIndex++] & 0xFF; 239 int numOfPixels = width*height; 240 241 int[] pixels = new int[numOfPixels]; 242 243 int pixelIndex = 0; 244 int bitIndex = 7; 245 byte currentByte = 0x00; 246 while (pixelIndex < numOfPixels) { 247 // reassign data and index for every byte (8 bits). 248 if (pixelIndex % 8 == 0) { 249 currentByte = data[valueIndex++]; 250 bitIndex = 7; 251 } 252 pixels[pixelIndex++] = bitToBnW((currentByte >> bitIndex-- ) & 0x01); 253 } 254 255 if (pixelIndex != numOfPixels) { 256 CatLog.d("IconLoader", "parseToBnW; size error"); 257 } 258 return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); 259 } 260 261 /** 262 * Decode one bit to a black and white color: 263 * 0 is black 264 * 1 is white 265 * @param bit to decode 266 * @return RGB color 267 */ 268 private static int bitToBnW(int bit){ 269 if(bit == 1){ 270 return Color.WHITE; 271 } else { 272 return Color.BLACK; 273 } 274 } 275 276 /** 277 * a TS 131.102 image instance of code scheme '11' into color Bitmap 278 * 279 * @param data The raw data 280 * @param length the length of image body 281 * @param transparency with or without transparency 282 * @param clut coulor lookup table 283 * @return The color bitmap 284 */ 285 public static Bitmap parseToRGB(byte[] data, int length, 286 boolean transparency, byte[] clut) { 287 int valueIndex = 0; 288 int width = data[valueIndex++] & 0xFF; 289 int height = data[valueIndex++] & 0xFF; 290 int bitsPerImg = data[valueIndex++] & 0xFF; 291 int numOfClutEntries = data[valueIndex++] & 0xFF; 292 293 if (true == transparency) { 294 clut[numOfClutEntries - 1] = Color.TRANSPARENT; 295 } 296 297 int numOfPixels = width * height; 298 int[] pixels = new int[numOfPixels]; 299 300 valueIndex = 6; 301 int pixelIndex = 0; 302 int bitsStartOffset = 8 - bitsPerImg; 303 int bitIndex = bitsStartOffset; 304 byte currentByte = data[valueIndex++]; 305 int mask = getMask(bitsPerImg); 306 boolean bitsOverlaps = (8 % bitsPerImg == 0); 307 while (pixelIndex < numOfPixels) { 308 // reassign data and index for every byte (8 bits). 309 if (bitIndex < 0) { 310 currentByte = data[valueIndex++]; 311 bitIndex = bitsOverlaps ? (bitsStartOffset) : (bitIndex * -1); 312 } 313 int clutEntry = ((currentByte >> bitIndex) & mask); 314 int clutIndex = clutEntry * CLUT_ENTRY_SIZE; 315 pixels[pixelIndex++] = Color.rgb(clut[clutIndex], 316 clut[clutIndex + 1], clut[clutIndex + 2]); 317 bitIndex -= bitsPerImg; 318 } 319 320 return Bitmap.createBitmap(pixels, width, height, 321 Bitmap.Config.ARGB_8888); 322 } 323 324 /** 325 * Calculate bit mask for a given number of bits. The mask should enable to 326 * make a bitwise and to the given number of bits. 327 * @param numOfBits number of bits to calculate mask for. 328 * @return bit mask 329 */ 330 private static int getMask(int numOfBits) { 331 int mask = 0x00; 332 333 switch (numOfBits) { 334 case 1: 335 mask = 0x01; 336 break; 337 case 2: 338 mask = 0x03; 339 break; 340 case 3: 341 mask = 0x07; 342 break; 343 case 4: 344 mask = 0x0F; 345 break; 346 case 5: 347 mask = 0x1F; 348 break; 349 case 6: 350 mask = 0x3F; 351 break; 352 case 7: 353 mask = 0x7F; 354 break; 355 case 8: 356 mask = 0xFF; 357 break; 358 } 359 return mask; 360 } 361 public void dispose() { 362 mSimFH = null; 363 if (sThread != null) { 364 sThread.quit(); 365 sThread = null; 366 } 367 mIconsCache = null; 368 sLoader = null; 369 } 370} 371