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