WidgetPreviewLoader.java revision 374753cabf05cde1ad669d07bde47e34fdcbe499
1package com.android.launcher2; 2 3import android.appwidget.AppWidgetProviderInfo; 4import android.content.ComponentName; 5import android.content.ContentValues; 6import android.content.Context; 7import android.content.SharedPreferences; 8import android.content.pm.PackageManager; 9import android.content.pm.ResolveInfo; 10import android.content.res.Resources; 11import android.database.Cursor; 12import android.database.sqlite.SQLiteDatabase; 13import android.database.sqlite.SQLiteDiskIOException; 14import android.database.sqlite.SQLiteOpenHelper; 15import android.graphics.Bitmap; 16import android.graphics.Bitmap.Config; 17import android.graphics.BitmapFactory; 18import android.graphics.Canvas; 19import android.graphics.ColorMatrix; 20import android.graphics.ColorMatrixColorFilter; 21import android.graphics.Paint; 22import android.graphics.PorterDuff; 23import android.graphics.Rect; 24import android.graphics.Shader; 25import android.graphics.drawable.BitmapDrawable; 26import android.graphics.drawable.Drawable; 27import android.os.AsyncTask; 28import android.util.Log; 29 30import com.android.launcher.R; 31 32import java.io.ByteArrayOutputStream; 33import java.io.File; 34import java.lang.ref.SoftReference; 35import java.lang.ref.WeakReference; 36import java.util.ArrayList; 37import java.util.HashMap; 38import java.util.HashSet; 39 40abstract class SoftReferenceThreadLocal<T> { 41 private ThreadLocal<SoftReference<T>> mThreadLocal; 42 public SoftReferenceThreadLocal() { 43 mThreadLocal = new ThreadLocal<SoftReference<T>>(); 44 } 45 46 abstract T initialValue(); 47 48 public void set(T t) { 49 mThreadLocal.set(new SoftReference<T>(t)); 50 } 51 52 public T get() { 53 SoftReference<T> reference = mThreadLocal.get(); 54 T obj; 55 if (reference == null) { 56 obj = initialValue(); 57 mThreadLocal.set(new SoftReference<T>(obj)); 58 return obj; 59 } else { 60 obj = reference.get(); 61 if (obj == null) { 62 obj = initialValue(); 63 mThreadLocal.set(new SoftReference<T>(obj)); 64 } 65 return obj; 66 } 67 } 68} 69 70class CanvasCache extends SoftReferenceThreadLocal<Canvas> { 71 @Override 72 protected Canvas initialValue() { 73 return new Canvas(); 74 } 75} 76 77class PaintCache extends SoftReferenceThreadLocal<Paint> { 78 @Override 79 protected Paint initialValue() { 80 return null; 81 } 82} 83 84class BitmapCache extends SoftReferenceThreadLocal<Bitmap> { 85 @Override 86 protected Bitmap initialValue() { 87 return null; 88 } 89} 90 91class RectCache extends SoftReferenceThreadLocal<Rect> { 92 @Override 93 protected Rect initialValue() { 94 return new Rect(); 95 } 96} 97 98class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> { 99 @Override 100 protected BitmapFactory.Options initialValue() { 101 return new BitmapFactory.Options(); 102 } 103} 104 105public class WidgetPreviewLoader { 106 static final String TAG = "WidgetPreviewLoader"; 107 static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version"; 108 109 private int mPreviewBitmapWidth; 110 private int mPreviewBitmapHeight; 111 private String mSize; 112 private Context mContext; 113 private Launcher mLauncher; 114 private PackageManager mPackageManager; 115 private PagedViewCellLayout mWidgetSpacingLayout; 116 117 // Used for drawing shortcut previews 118 private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); 119 private PaintCache mCachedShortcutPreviewPaint = new PaintCache(); 120 private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); 121 122 // Used for drawing widget previews 123 private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); 124 private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); 125 private RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); 126 private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); 127 private String mCachedSelectQuery; 128 private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache(); 129 130 private int mAppIconSize; 131 private IconCache mIconCache; 132 133 private final float sWidgetPreviewIconPaddingPercentage = 0.25f; 134 135 private CacheDb mDb; 136 137 private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews; 138 private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps; 139 private static HashSet<String> sInvalidPackages; 140 141 static { 142 sInvalidPackages = new HashSet<String>(); 143 } 144 145 public WidgetPreviewLoader(Launcher launcher) { 146 mContext = mLauncher = launcher; 147 mPackageManager = mContext.getPackageManager(); 148 mAppIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.app_icon_size); 149 LauncherApplication app = (LauncherApplication) launcher.getApplicationContext(); 150 mIconCache = app.getIconCache(); 151 mDb = app.getWidgetPreviewCacheDb(); 152 mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>(); 153 mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>(); 154 155 SharedPreferences sp = launcher.getSharedPreferences( 156 LauncherApplication.getSharedPreferencesKey(), Context.MODE_PRIVATE); 157 final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null); 158 final String versionName = android.os.Build.VERSION.INCREMENTAL; 159 if (!versionName.equals(lastVersionName)) { 160 // clear all the previews whenever the system version changes, to ensure that previews 161 // are up-to-date for any apps that might have been updated with the system 162 clearDb(); 163 SharedPreferences.Editor editor = sp.edit(); 164 editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName); 165 editor.commit(); 166 } 167 } 168 169 public void recreateDb() { 170 LauncherApplication app = (LauncherApplication) mLauncher.getApplication(); 171 app.recreateWidgetPreviewDb(); 172 mDb = app.getWidgetPreviewCacheDb(); 173 } 174 175 public void setPreviewSize(int previewWidth, int previewHeight, 176 PagedViewCellLayout widgetSpacingLayout) { 177 mPreviewBitmapWidth = previewWidth; 178 mPreviewBitmapHeight = previewHeight; 179 mSize = previewWidth + "x" + previewHeight; 180 mWidgetSpacingLayout = widgetSpacingLayout; 181 } 182 183 public Bitmap getPreview(final Object o) { 184 String name = getObjectName(o); 185 // check if the package is valid 186 boolean packageValid = true; 187 synchronized(sInvalidPackages) { 188 packageValid = !sInvalidPackages.contains(getObjectPackage(o)); 189 } 190 if (!packageValid) { 191 return null; 192 } 193 if (packageValid) { 194 synchronized(mLoadedPreviews) { 195 // check if it exists in our existing cache 196 if (mLoadedPreviews.containsKey(name) && mLoadedPreviews.get(name).get() != null) { 197 return mLoadedPreviews.get(name).get(); 198 } 199 } 200 } 201 202 Bitmap unusedBitmap = null; 203 synchronized(mUnusedBitmaps) { 204 // not in cache; we need to load it from the db 205 while ((unusedBitmap == null || !unusedBitmap.isMutable() || 206 unusedBitmap.getWidth() != mPreviewBitmapWidth || 207 unusedBitmap.getHeight() != mPreviewBitmapHeight) 208 && mUnusedBitmaps.size() > 0) { 209 unusedBitmap = mUnusedBitmaps.remove(0).get(); 210 } 211 if (unusedBitmap != null) { 212 final Canvas c = mCachedAppWidgetPreviewCanvas.get(); 213 c.setBitmap(unusedBitmap); 214 c.drawColor(0, PorterDuff.Mode.CLEAR); 215 c.setBitmap(null); 216 } 217 } 218 219 if (unusedBitmap == null) { 220 unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight, 221 Bitmap.Config.ARGB_8888); 222 } 223 224 Bitmap preview = null; 225 226 if (packageValid) { 227 preview = readFromDb(name, unusedBitmap); 228 } 229 230 if (preview != null) { 231 synchronized(mLoadedPreviews) { 232 mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview)); 233 } 234 return preview; 235 } else { 236 // it's not in the db... we need to generate it 237 final Bitmap generatedPreview = generatePreview(o, unusedBitmap); 238 preview = generatedPreview; 239 if (preview != unusedBitmap) { 240 throw new RuntimeException("generatePreview is not recycling the bitmap " + o); 241 } 242 243 synchronized(mLoadedPreviews) { 244 mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview)); 245 } 246 247 // write to db on a thread pool... this can be done lazily and improves the performance 248 // of the first time widget previews are loaded 249 new AsyncTask<Void, Void, Void>() { 250 public Void doInBackground(Void ... args) { 251 writeToDb(o, generatedPreview); 252 return null; 253 } 254 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); 255 256 return preview; 257 } 258 } 259 260 public void recycleBitmap(Object o, Bitmap bitmapToRecycle) { 261 String name = getObjectName(o); 262 synchronized (mLoadedPreviews) { 263 if (mLoadedPreviews.containsKey(name)) { 264 Bitmap b = mLoadedPreviews.get(name).get(); 265 if (b == bitmapToRecycle) { 266 mLoadedPreviews.remove(name); 267 if (bitmapToRecycle.isMutable()) { 268 synchronized (mUnusedBitmaps) { 269 mUnusedBitmaps.add(new SoftReference<Bitmap>(b)); 270 } 271 } 272 } else { 273 throw new RuntimeException("Bitmap passed in doesn't match up"); 274 } 275 } 276 } 277 } 278 279 static class CacheDb extends SQLiteOpenHelper { 280 final static int DB_VERSION = 2; 281 final static String DB_NAME = "widgetpreviews.db"; 282 final static String TABLE_NAME = "shortcut_and_widget_previews"; 283 final static String COLUMN_NAME = "name"; 284 final static String COLUMN_SIZE = "size"; 285 final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; 286 Context mContext; 287 288 public CacheDb(Context context) { 289 super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION); 290 // Store the context for later use 291 mContext = context; 292 } 293 294 @Override 295 public void onCreate(SQLiteDatabase database) { 296 database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + 297 COLUMN_NAME + " TEXT NOT NULL, " + 298 COLUMN_SIZE + " TEXT NOT NULL, " + 299 COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " + 300 "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " + 301 ");"); 302 } 303 304 @Override 305 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 306 if (oldVersion != newVersion) { 307 // Delete all the records; they'll be repopulated as this is a cache 308 db.execSQL("DELETE FROM " + TABLE_NAME); 309 } 310 } 311 } 312 313 private static final String WIDGET_PREFIX = "Widget:"; 314 private static final String SHORTCUT_PREFIX = "Shortcut:"; 315 316 private static String getObjectName(Object o) { 317 // should cache the string builder 318 StringBuilder sb = new StringBuilder(); 319 String output; 320 if (o instanceof AppWidgetProviderInfo) { 321 sb.append(WIDGET_PREFIX); 322 sb.append(((AppWidgetProviderInfo) o).provider.flattenToString()); 323 output = sb.toString(); 324 sb.setLength(0); 325 } else { 326 sb.append(SHORTCUT_PREFIX); 327 328 ResolveInfo info = (ResolveInfo) o; 329 sb.append(new ComponentName(info.activityInfo.packageName, 330 info.activityInfo.name).flattenToString()); 331 output = sb.toString(); 332 sb.setLength(0); 333 } 334 return output; 335 } 336 337 private String getObjectPackage(Object o) { 338 if (o instanceof AppWidgetProviderInfo) { 339 return ((AppWidgetProviderInfo) o).provider.getPackageName(); 340 } else { 341 ResolveInfo info = (ResolveInfo) o; 342 return info.activityInfo.packageName; 343 } 344 } 345 346 private void writeToDb(Object o, Bitmap preview) { 347 String name = getObjectName(o); 348 SQLiteDatabase db = mDb.getWritableDatabase(); 349 ContentValues values = new ContentValues(); 350 351 values.put(CacheDb.COLUMN_NAME, name); 352 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 353 preview.compress(Bitmap.CompressFormat.PNG, 100, stream); 354 values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray()); 355 values.put(CacheDb.COLUMN_SIZE, mSize); 356 try { 357 db.insert(CacheDb.TABLE_NAME, null, values); 358 } catch (SQLiteDiskIOException e) { 359 recreateDb(); 360 } 361 } 362 363 private void clearDb() { 364 SQLiteDatabase db = mDb.getWritableDatabase(); 365 // Delete everything 366 try { 367 db.delete(CacheDb.TABLE_NAME, null, null); 368 } catch (SQLiteDiskIOException e) { 369 } 370 } 371 372 public static void removeFromDb(final CacheDb cacheDb, final String packageName) { 373 synchronized(sInvalidPackages) { 374 sInvalidPackages.add(packageName); 375 } 376 new AsyncTask<Void, Void, Void>() { 377 public Void doInBackground(Void ... args) { 378 SQLiteDatabase db = cacheDb.getWritableDatabase(); 379 try { 380 db.delete(CacheDb.TABLE_NAME, 381 CacheDb.COLUMN_NAME + " LIKE ? OR " + 382 CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query 383 new String[] { 384 WIDGET_PREFIX + packageName + "/%", 385 SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query 386 ); 387 synchronized(sInvalidPackages) { 388 sInvalidPackages.remove(packageName); 389 } 390 } catch (SQLiteDiskIOException e) { 391 } 392 return null; 393 } 394 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); 395 } 396 397 private Bitmap readFromDb(String name, Bitmap b) { 398 if (mCachedSelectQuery == null) { 399 mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " + 400 CacheDb.COLUMN_SIZE + " = ?"; 401 } 402 SQLiteDatabase db = mDb.getReadableDatabase(); 403 Cursor result; 404 try { 405 result = db.query(CacheDb.TABLE_NAME, 406 new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return 407 mCachedSelectQuery, // select query 408 new String[] { name, mSize }, // args to select query 409 null, 410 null, 411 null, 412 null); 413 } catch (SQLiteDiskIOException e) { 414 recreateDb(); 415 return null; 416 } 417 if (result.getCount() > 0) { 418 result.moveToFirst(); 419 byte[] blob = result.getBlob(0); 420 result.close(); 421 final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get(); 422 opts.inBitmap = b; 423 opts.inSampleSize = 1; 424 Bitmap out = BitmapFactory.decodeByteArray(blob, 0, blob.length, opts); 425 return out; 426 } else { 427 result.close(); 428 return null; 429 } 430 } 431 432 public Bitmap generatePreview(Object info, Bitmap preview) { 433 if (preview != null && 434 (preview.getWidth() != mPreviewBitmapWidth || 435 preview.getHeight() != mPreviewBitmapHeight)) { 436 throw new RuntimeException("Improperly sized bitmap passed as argument"); 437 } 438 if (info instanceof AppWidgetProviderInfo) { 439 return generateWidgetPreview((AppWidgetProviderInfo) info, preview); 440 } else { 441 return generateShortcutPreview( 442 (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview); 443 } 444 } 445 446 public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) { 447 int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); 448 int maxWidth = maxWidthForWidgetPreview(cellSpans[0]); 449 int maxHeight = maxHeightForWidgetPreview(cellSpans[1]); 450 return generateWidgetPreview(info.provider, info.previewImage, info.icon, 451 cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null); 452 } 453 454 public int maxWidthForWidgetPreview(int spanX) { 455 return Math.min(mPreviewBitmapWidth, 456 mWidgetSpacingLayout.estimateCellWidth(spanX)); 457 } 458 459 public int maxHeightForWidgetPreview(int spanY) { 460 return Math.min(mPreviewBitmapHeight, 461 mWidgetSpacingLayout.estimateCellHeight(spanY)); 462 } 463 464 public Bitmap generateWidgetPreview(ComponentName provider, int previewImage, 465 int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight, 466 Bitmap preview, int[] preScaledWidthOut) { 467 // Load the preview image if possible 468 String packageName = provider.getPackageName(); 469 if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE; 470 if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE; 471 472 Drawable drawable = null; 473 if (previewImage != 0) { 474 drawable = mPackageManager.getDrawable(packageName, previewImage, null); 475 if (drawable == null) { 476 Log.w(TAG, "Can't load widget preview drawable 0x" + 477 Integer.toHexString(previewImage) + " for provider: " + provider); 478 } 479 } 480 481 int previewWidth; 482 int previewHeight; 483 Bitmap defaultPreview = null; 484 boolean widgetPreviewExists = (drawable != null); 485 if (widgetPreviewExists) { 486 previewWidth = drawable.getIntrinsicWidth(); 487 previewHeight = drawable.getIntrinsicHeight(); 488 } else { 489 // Generate a preview image if we couldn't load one 490 if (cellHSpan < 1) cellHSpan = 1; 491 if (cellVSpan < 1) cellVSpan = 1; 492 493 BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources() 494 .getDrawable(R.drawable.widget_preview_tile); 495 final int previewDrawableWidth = previewDrawable 496 .getIntrinsicWidth(); 497 final int previewDrawableHeight = previewDrawable 498 .getIntrinsicHeight(); 499 previewWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips 500 previewHeight = previewDrawableHeight * cellVSpan; 501 502 defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight, 503 Config.ARGB_8888); 504 final Canvas c = mCachedAppWidgetPreviewCanvas.get(); 505 c.setBitmap(defaultPreview); 506 previewDrawable.setBounds(0, 0, previewWidth, previewHeight); 507 previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, 508 Shader.TileMode.REPEAT); 509 previewDrawable.draw(c); 510 c.setBitmap(null); 511 512 // Draw the icon in the top left corner 513 int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); 514 int smallestSide = Math.min(previewWidth, previewHeight); 515 float iconScale = Math.min((float) smallestSide 516 / (mAppIconSize + 2 * minOffset), 1f); 517 518 try { 519 Drawable icon = null; 520 int hoffset = 521 (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); 522 int yoffset = 523 (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); 524 if (iconId > 0) 525 icon = mIconCache.getFullResIcon(packageName, iconId, 526 android.os.Process.myUserHandle()); 527 if (icon != null) { 528 renderDrawableToBitmap(icon, defaultPreview, hoffset, 529 yoffset, (int) (mAppIconSize * iconScale), 530 (int) (mAppIconSize * iconScale)); 531 } 532 } catch (Resources.NotFoundException e) { 533 } 534 } 535 536 // Scale to fit width only - let the widget preview be clipped in the 537 // vertical dimension 538 float scale = 1f; 539 if (preScaledWidthOut != null) { 540 preScaledWidthOut[0] = previewWidth; 541 } 542 if (previewWidth > maxPreviewWidth) { 543 scale = maxPreviewWidth / (float) previewWidth; 544 } 545 if (scale != 1f) { 546 previewWidth = (int) (scale * previewWidth); 547 previewHeight = (int) (scale * previewHeight); 548 } 549 550 // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size 551 if (preview == null) { 552 preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); 553 } 554 555 // Draw the scaled preview into the final bitmap 556 int x = (preview.getWidth() - previewWidth) / 2; 557 if (widgetPreviewExists) { 558 renderDrawableToBitmap(drawable, preview, x, 0, previewWidth, 559 previewHeight); 560 } else { 561 final Canvas c = mCachedAppWidgetPreviewCanvas.get(); 562 final Rect src = mCachedAppWidgetPreviewSrcRect.get(); 563 final Rect dest = mCachedAppWidgetPreviewDestRect.get(); 564 c.setBitmap(preview); 565 src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); 566 dest.set(x, 0, x + previewWidth, previewHeight); 567 568 Paint p = mCachedAppWidgetPreviewPaint.get(); 569 if (p == null) { 570 p = new Paint(); 571 p.setFilterBitmap(true); 572 mCachedAppWidgetPreviewPaint.set(p); 573 } 574 c.drawBitmap(defaultPreview, src, dest, p); 575 c.setBitmap(null); 576 } 577 return preview; 578 } 579 580 private Bitmap generateShortcutPreview( 581 ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) { 582 Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get(); 583 final Canvas c = mCachedShortcutPreviewCanvas.get(); 584 if (tempBitmap == null || 585 tempBitmap.getWidth() != maxWidth || 586 tempBitmap.getHeight() != maxHeight) { 587 tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); 588 mCachedShortcutPreviewBitmap.set(tempBitmap); 589 } else { 590 c.setBitmap(tempBitmap); 591 c.drawColor(0, PorterDuff.Mode.CLEAR); 592 c.setBitmap(null); 593 } 594 // Render the icon 595 Drawable icon = mIconCache.getFullResIcon(info, android.os.Process.myUserHandle()); 596 597 int paddingTop = mContext. 598 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); 599 int paddingLeft = mContext. 600 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left); 601 int paddingRight = mContext. 602 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right); 603 604 int scaledIconWidth = (maxWidth - paddingLeft - paddingRight); 605 606 renderDrawableToBitmap( 607 icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth); 608 609 if (preview != null && 610 (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) { 611 throw new RuntimeException("Improperly sized bitmap passed as argument"); 612 } else if (preview == null) { 613 preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); 614 } 615 616 c.setBitmap(preview); 617 // Draw a desaturated/scaled version of the icon in the background as a watermark 618 Paint p = mCachedShortcutPreviewPaint.get(); 619 if (p == null) { 620 p = new Paint(); 621 ColorMatrix colorMatrix = new ColorMatrix(); 622 colorMatrix.setSaturation(0); 623 p.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); 624 p.setAlpha((int) (255 * 0.06f)); 625 mCachedShortcutPreviewPaint.set(p); 626 } 627 c.drawBitmap(tempBitmap, 0, 0, p); 628 c.setBitmap(null); 629 630 renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize); 631 632 return preview; 633 } 634 635 636 public static void renderDrawableToBitmap( 637 Drawable d, Bitmap bitmap, int x, int y, int w, int h) { 638 renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); 639 } 640 641 private static void renderDrawableToBitmap( 642 Drawable d, Bitmap bitmap, int x, int y, int w, int h, 643 float scale) { 644 if (bitmap != null) { 645 Canvas c = new Canvas(bitmap); 646 c.scale(scale, scale); 647 Rect oldBounds = d.copyBounds(); 648 d.setBounds(x, y, x + w, y + h); 649 d.draw(c); 650 d.setBounds(oldBounds); // Restore the bounds 651 c.setBitmap(null); 652 } 653 } 654 655} 656