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