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.cat; 18 19 import com.android.internal.telephony.uicc.IccFileHandler; 20 21 import android.graphics.Bitmap; 22 import android.graphics.Color; 23 import android.os.AsyncResult; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Looper; 27 import android.os.Message; 28 import 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 */ 35 class 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 IconLoader(Looper looper , IccFileHandler fh)70 private IconLoader(Looper looper , IccFileHandler fh) { 71 super(looper); 72 mSimFH = fh; 73 74 mIconsCache = new HashMap<Integer, Bitmap>(50); 75 } 76 getInstance(Handler caller, IccFileHandler fh)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 loadIcons(int[] recordNumbers, Message msg)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 loadIcon(int recordNumber, Message msg)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 startLoadingIcon(int recordNumber)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 handleMessage(Message msg)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 CatLog.d(this, "load icon done"); 145 ar = (AsyncResult) msg.obj; 146 byte[] rawData = ((byte[]) ar.result); 147 if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_BASIC) { 148 mCurrentIcon = parseToBnW(rawData, rawData.length); 149 mIconsCache.put(mRecordNumber, mCurrentIcon); 150 postIcon(); 151 } else if (mId.mCodingScheme == ImageDescriptor.CODING_SCHEME_COLOUR) { 152 mIconData = rawData; 153 readClut(); 154 } else { 155 CatLog.d(this, "else /postIcon "); 156 postIcon(); 157 } 158 break; 159 case EVENT_READ_CLUT_DONE: 160 ar = (AsyncResult) msg.obj; 161 byte [] clut = ((byte[]) ar.result); 162 mCurrentIcon = parseToRGB(mIconData, mIconData.length, 163 false, clut); 164 mIconsCache.put(mRecordNumber, mCurrentIcon); 165 postIcon(); 166 break; 167 } 168 } catch (Exception e) { 169 CatLog.d(this, "Icon load failed"); 170 // post null icon back to the caller. 171 postIcon(); 172 } 173 } 174 175 /** 176 * Handles Image descriptor parsing and required processing. This is the 177 * first step required to handle retrieving icons from the SIM. 178 * 179 * @param rawData byte [] containing Image Instance descriptor as defined in 180 * TS 51.011. 181 */ handleImageDescriptor(byte[] rawData)182 private boolean handleImageDescriptor(byte[] rawData) { 183 mId = ImageDescriptor.parse(rawData, 1); 184 if (mId == null) { 185 return false; 186 } 187 return true; 188 } 189 190 // Start reading color lookup table from SIM card. readClut()191 private void readClut() { 192 int length = mIconData[3] * CLUT_ENTRY_SIZE; 193 Message msg = obtainMessage(EVENT_READ_CLUT_DONE); 194 mSimFH.loadEFImgTransparent(mId.mImageId, 195 mIconData[CLUT_LOCATION_OFFSET], 196 mIconData[CLUT_LOCATION_OFFSET + 1], length, msg); 197 } 198 199 // Start reading Image Descriptor from SIM card. readId()200 private void readId() { 201 if (mRecordNumber < 0) { 202 mCurrentIcon = null; 203 postIcon(); 204 return; 205 } 206 Message msg = obtainMessage(EVENT_READ_EF_IMG_RECOED_DONE); 207 mSimFH.loadEFImgLinearFixed(mRecordNumber, msg); 208 } 209 210 // Start reading icon bytes array from SIM card. readIconData()211 private void readIconData() { 212 Message msg = obtainMessage(EVENT_READ_ICON_DONE); 213 mSimFH.loadEFImgTransparent(mId.mImageId, 0, 0, mId.mLength ,msg); 214 } 215 216 // When all is done pass icon back to caller. postIcon()217 private void postIcon() { 218 if (mState == STATE_SINGLE_ICON) { 219 mEndMsg.obj = mCurrentIcon; 220 mEndMsg.sendToTarget(); 221 } else if (mState == STATE_MULTI_ICONS) { 222 mIcons[mCurrentRecordIndex++] = mCurrentIcon; 223 // If not all icons were loaded, start loading the next one. 224 if (mCurrentRecordIndex < mRecordNumbers.length) { 225 startLoadingIcon(mRecordNumbers[mCurrentRecordIndex]); 226 } else { 227 mEndMsg.obj = mIcons; 228 mEndMsg.sendToTarget(); 229 } 230 } 231 } 232 233 /** 234 * Convert a TS 131.102 image instance of code scheme '11' into Bitmap 235 * @param data The raw data 236 * @param length The length of image body 237 * @return The bitmap 238 */ parseToBnW(byte[] data, int length)239 public static Bitmap parseToBnW(byte[] data, int length){ 240 int valueIndex = 0; 241 int width = data[valueIndex++] & 0xFF; 242 int height = data[valueIndex++] & 0xFF; 243 int numOfPixels = width*height; 244 245 int[] pixels = new int[numOfPixels]; 246 247 int pixelIndex = 0; 248 int bitIndex = 7; 249 byte currentByte = 0x00; 250 while (pixelIndex < numOfPixels) { 251 // reassign data and index for every byte (8 bits). 252 if (pixelIndex % 8 == 0) { 253 currentByte = data[valueIndex++]; 254 bitIndex = 7; 255 } 256 pixels[pixelIndex++] = bitToBnW((currentByte >> bitIndex-- ) & 0x01); 257 } 258 259 if (pixelIndex != numOfPixels) { 260 CatLog.d("IconLoader", "parseToBnW; size error"); 261 } 262 return Bitmap.createBitmap(pixels, width, height, Bitmap.Config.ARGB_8888); 263 } 264 265 /** 266 * Decode one bit to a black and white color: 267 * 0 is black 268 * 1 is white 269 * @param bit to decode 270 * @return RGB color 271 */ bitToBnW(int bit)272 private static int bitToBnW(int bit){ 273 if(bit == 1){ 274 return Color.WHITE; 275 } else { 276 return Color.BLACK; 277 } 278 } 279 280 /** 281 * a TS 131.102 image instance of code scheme '11' into color Bitmap 282 * 283 * @param data The raw data 284 * @param length the length of image body 285 * @param transparency with or without transparency 286 * @param clut coulor lookup table 287 * @return The color bitmap 288 */ parseToRGB(byte[] data, int length, boolean transparency, byte[] clut)289 public static Bitmap parseToRGB(byte[] data, int length, 290 boolean transparency, byte[] clut) { 291 int valueIndex = 0; 292 int width = data[valueIndex++] & 0xFF; 293 int height = data[valueIndex++] & 0xFF; 294 int bitsPerImg = data[valueIndex++] & 0xFF; 295 int numOfClutEntries = data[valueIndex++] & 0xFF; 296 297 if (true == transparency) { 298 clut[numOfClutEntries - 1] = Color.TRANSPARENT; 299 } 300 301 int numOfPixels = width * height; 302 int[] pixels = new int[numOfPixels]; 303 304 valueIndex = 6; 305 int pixelIndex = 0; 306 int bitsStartOffset = 8 - bitsPerImg; 307 int bitIndex = bitsStartOffset; 308 byte currentByte = data[valueIndex++]; 309 int mask = getMask(bitsPerImg); 310 boolean bitsOverlaps = (8 % bitsPerImg == 0); 311 while (pixelIndex < numOfPixels) { 312 // reassign data and index for every byte (8 bits). 313 if (bitIndex < 0) { 314 currentByte = data[valueIndex++]; 315 bitIndex = bitsOverlaps ? (bitsStartOffset) : (bitIndex * -1); 316 } 317 int clutEntry = ((currentByte >> bitIndex) & mask); 318 int clutIndex = clutEntry * CLUT_ENTRY_SIZE; 319 pixels[pixelIndex++] = Color.rgb(clut[clutIndex], 320 clut[clutIndex + 1], clut[clutIndex + 2]); 321 bitIndex -= bitsPerImg; 322 } 323 324 return Bitmap.createBitmap(pixels, width, height, 325 Bitmap.Config.ARGB_8888); 326 } 327 328 /** 329 * Calculate bit mask for a given number of bits. The mask should enable to 330 * make a bitwise and to the given number of bits. 331 * @param numOfBits number of bits to calculate mask for. 332 * @return bit mask 333 */ getMask(int numOfBits)334 private static int getMask(int numOfBits) { 335 int mask = 0x00; 336 337 switch (numOfBits) { 338 case 1: 339 mask = 0x01; 340 break; 341 case 2: 342 mask = 0x03; 343 break; 344 case 3: 345 mask = 0x07; 346 break; 347 case 4: 348 mask = 0x0F; 349 break; 350 case 5: 351 mask = 0x1F; 352 break; 353 case 6: 354 mask = 0x3F; 355 break; 356 case 7: 357 mask = 0x7F; 358 break; 359 case 8: 360 mask = 0xFF; 361 break; 362 } 363 return mask; 364 } dispose()365 public void dispose() { 366 mSimFH = null; 367 if (sThread != null) { 368 sThread.quit(); 369 sThread = null; 370 } 371 mIconsCache = null; 372 sLoader = null; 373 } 374 } 375