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