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