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