1 package com.android.launcher2; 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.Configuration; 11 import android.content.res.Resources; 12 import android.database.Cursor; 13 import android.database.sqlite.SQLiteDatabase; 14 import android.database.sqlite.SQLiteDiskIOException; 15 import android.database.sqlite.SQLiteOpenHelper; 16 import android.graphics.Bitmap; 17 import android.graphics.Bitmap.Config; 18 import android.graphics.BitmapFactory; 19 import android.graphics.Canvas; 20 import android.graphics.ColorMatrix; 21 import android.graphics.ColorMatrixColorFilter; 22 import android.graphics.Paint; 23 import android.graphics.PorterDuff; 24 import android.graphics.Rect; 25 import android.graphics.Shader; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.Drawable; 28 29 import android.os.AsyncTask; 30 import android.view.View; 31 import com.android.launcher.R; 32 33 import java.io.ByteArrayOutputStream; 34 import java.io.File; 35 import java.lang.ref.SoftReference; 36 import java.lang.ref.WeakReference; 37 import java.util.ArrayList; 38 import java.util.HashMap; 39 import java.util.HashSet; 40 41 abstract class SoftReferenceThreadLocal<T> { 42 private ThreadLocal<SoftReference<T>> mThreadLocal; SoftReferenceThreadLocal()43 public SoftReferenceThreadLocal() { 44 mThreadLocal = new ThreadLocal<SoftReference<T>>(); 45 } 46 initialValue()47 abstract T initialValue(); 48 set(T t)49 public void set(T t) { 50 mThreadLocal.set(new SoftReference<T>(t)); 51 } 52 get()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 71 class CanvasCache extends SoftReferenceThreadLocal<Canvas> { 72 @Override initialValue()73 protected Canvas initialValue() { 74 return new Canvas(); 75 } 76 } 77 78 class PaintCache extends SoftReferenceThreadLocal<Paint> { 79 @Override initialValue()80 protected Paint initialValue() { 81 return null; 82 } 83 } 84 85 class BitmapCache extends SoftReferenceThreadLocal<Bitmap> { 86 @Override initialValue()87 protected Bitmap initialValue() { 88 return null; 89 } 90 } 91 92 class RectCache extends SoftReferenceThreadLocal<Rect> { 93 @Override initialValue()94 protected Rect initialValue() { 95 return new Rect(); 96 } 97 } 98 99 class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> { 100 @Override initialValue()101 protected BitmapFactory.Options initialValue() { 102 return new BitmapFactory.Options(); 103 } 104 } 105 106 public 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 WidgetPreviewLoader(Launcher launcher)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 recreateDb()177 public void recreateDb() { 178 LauncherApplication app = (LauncherApplication) mLauncher.getApplication(); 179 app.recreateWidgetPreviewDb(); 180 mDb = app.getWidgetPreviewCacheDb(); 181 } 182 setPreviewSize(int previewWidth, int previewHeight, PagedViewCellLayout widgetSpacingLayout)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 getPreview(final Object o)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 recycleBitmap(Object o, Bitmap bitmapToRecycle)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 CacheDb(Context context)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 onCreate(SQLiteDatabase database)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 onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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 getObjectName(Object o)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 getObjectPackage(Object o)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 writeToDb(Object o, Bitmap preview)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 clearDb()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 removeFromDb(final CacheDb cacheDb, final String packageName)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 readFromDb(String name, Bitmap b)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 generatePreview(Object info, Bitmap preview)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 generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview)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 maxWidthForWidgetPreview(int spanX)465 public int maxWidthForWidgetPreview(int spanX) { 466 return Math.min(mPreviewBitmapWidth, 467 mWidgetSpacingLayout.estimateCellWidth(spanX)); 468 } 469 maxHeightForWidgetPreview(int spanY)470 public int maxHeightForWidgetPreview(int spanY) { 471 return Math.min(mPreviewBitmapHeight, 472 mWidgetSpacingLayout.estimateCellHeight(spanY)); 473 } 474 generateWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight, Bitmap preview, int[] preScaledWidthOut)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 generateShortcutPreview( ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview)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 renderDrawableToBitmap( Drawable d, Bitmap bitmap, int x, int y, int w, int h)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 renderDrawableToBitmap( Drawable d, Bitmap bitmap, int x, int y, int w, int h, float scale)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