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