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