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