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