Home | History | Annotate | Download | only in launcher3
      1 package com.android.launcher3;
      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 java.io.ByteArrayOutputStream;
     29 import java.io.File;
     30 import java.lang.ref.SoftReference;
     31 import java.lang.ref.WeakReference;
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 
     36 abstract class SoftReferenceThreadLocal<T> {
     37     private ThreadLocal<SoftReference<T>> mThreadLocal;
     38     public SoftReferenceThreadLocal() {
     39         mThreadLocal = new ThreadLocal<SoftReference<T>>();
     40     }
     41 
     42     abstract T initialValue();
     43 
     44     public void set(T t) {
     45         mThreadLocal.set(new SoftReference<T>(t));
     46     }
     47 
     48     public T get() {
     49         SoftReference<T> reference = mThreadLocal.get();
     50         T obj;
     51         if (reference == null) {
     52             obj = initialValue();
     53             mThreadLocal.set(new SoftReference<T>(obj));
     54             return obj;
     55         } else {
     56             obj = reference.get();
     57             if (obj == null) {
     58                 obj = initialValue();
     59                 mThreadLocal.set(new SoftReference<T>(obj));
     60             }
     61             return obj;
     62         }
     63     }
     64 }
     65 
     66 class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
     67     @Override
     68     protected Canvas initialValue() {
     69         return new Canvas();
     70     }
     71 }
     72 
     73 class PaintCache extends SoftReferenceThreadLocal<Paint> {
     74     @Override
     75     protected Paint initialValue() {
     76         return null;
     77     }
     78 }
     79 
     80 class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
     81     @Override
     82     protected Bitmap initialValue() {
     83         return null;
     84     }
     85 }
     86 
     87 class RectCache extends SoftReferenceThreadLocal<Rect> {
     88     @Override
     89     protected Rect initialValue() {
     90         return new Rect();
     91     }
     92 }
     93 
     94 class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
     95     @Override
     96     protected BitmapFactory.Options initialValue() {
     97         return new BitmapFactory.Options();
     98     }
     99 }
    100 
    101 public class WidgetPreviewLoader {
    102     static final String TAG = "WidgetPreviewLoader";
    103 
    104     private int mPreviewBitmapWidth;
    105     private int mPreviewBitmapHeight;
    106     private String mSize;
    107     private Context mContext;
    108     private PackageManager mPackageManager;
    109     private PagedViewCellLayout mWidgetSpacingLayout;
    110 
    111     // Used for drawing shortcut previews
    112     private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
    113     private PaintCache mCachedShortcutPreviewPaint = new PaintCache();
    114     private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
    115 
    116     // Used for drawing widget previews
    117     private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
    118     private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
    119     private RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
    120     private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
    121     private String mCachedSelectQuery;
    122     private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache();
    123 
    124     private int mAppIconSize;
    125     private IconCache mIconCache;
    126 
    127     private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
    128 
    129     private CacheDb mDb;
    130 
    131     private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews;
    132     private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
    133     private static HashSet<String> sInvalidPackages;
    134 
    135     static {
    136         sInvalidPackages = new HashSet<String>();
    137     }
    138 
    139     public WidgetPreviewLoader(Context context) {
    140         LauncherAppState app = LauncherAppState.getInstance();
    141         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
    142 
    143         mContext = context;
    144         mPackageManager = mContext.getPackageManager();
    145         mAppIconSize = grid.iconSizePx;
    146         mIconCache = app.getIconCache();
    147         mDb = app.getWidgetPreviewCacheDb();
    148         mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
    149         mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
    150     }
    151 
    152     public void setPreviewSize(int previewWidth, int previewHeight,
    153             PagedViewCellLayout widgetSpacingLayout) {
    154         mPreviewBitmapWidth = previewWidth;
    155         mPreviewBitmapHeight = previewHeight;
    156         mSize = previewWidth + "x" + previewHeight;
    157         mWidgetSpacingLayout = widgetSpacingLayout;
    158     }
    159 
    160     public Bitmap getPreview(final Object o) {
    161         final String name = getObjectName(o);
    162         final String packageName = getObjectPackage(o);
    163         // check if the package is valid
    164         boolean packageValid = true;
    165         synchronized(sInvalidPackages) {
    166             packageValid = !sInvalidPackages.contains(packageName);
    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 removePackageFromDb(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     public static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
    360         new AsyncTask<Void, Void, Void>() {
    361             public Void doInBackground(Void ... args) {
    362                 SQLiteDatabase db = cacheDb.getWritableDatabase();
    363                 db.delete(CacheDb.TABLE_NAME,
    364                         CacheDb.COLUMN_NAME + " = ? ", // SELECT query
    365                         new String[] { objectName }); // args to SELECT query
    366                 return null;
    367             }
    368         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
    369     }
    370 
    371     private Bitmap readFromDb(String name, Bitmap b) {
    372         if (mCachedSelectQuery == null) {
    373             mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
    374                     CacheDb.COLUMN_SIZE + " = ?";
    375         }
    376         SQLiteDatabase db = mDb.getReadableDatabase();
    377         Cursor result = db.query(CacheDb.TABLE_NAME,
    378                 new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
    379                 mCachedSelectQuery, // select query
    380                 new String[] { name, mSize }, // args to select query
    381                 null,
    382                 null,
    383                 null,
    384                 null);
    385         if (result.getCount() > 0) {
    386             result.moveToFirst();
    387             byte[] blob = result.getBlob(0);
    388             result.close();
    389             final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get();
    390             opts.inBitmap = b;
    391             opts.inSampleSize = 1;
    392             try {
    393                 return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
    394             } catch (IllegalArgumentException e) {
    395                 removeItemFromDb(mDb, name);
    396                 return null;
    397             }
    398         } else {
    399             result.close();
    400             return null;
    401         }
    402     }
    403 
    404     public Bitmap generatePreview(Object info, Bitmap preview) {
    405         if (preview != null &&
    406                 (preview.getWidth() != mPreviewBitmapWidth ||
    407                 preview.getHeight() != mPreviewBitmapHeight)) {
    408             throw new RuntimeException("Improperly sized bitmap passed as argument");
    409         }
    410         if (info instanceof AppWidgetProviderInfo) {
    411             return generateWidgetPreview((AppWidgetProviderInfo) info, preview);
    412         } else {
    413             return generateShortcutPreview(
    414                     (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
    415         }
    416     }
    417 
    418     public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) {
    419         int[] cellSpans = Launcher.getSpanForWidget(mContext, info);
    420         int maxWidth = maxWidthForWidgetPreview(cellSpans[0]);
    421         int maxHeight = maxHeightForWidgetPreview(cellSpans[1]);
    422         return generateWidgetPreview(info.provider, info.previewImage, info.icon,
    423                 cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null);
    424     }
    425 
    426     public int maxWidthForWidgetPreview(int spanX) {
    427         return Math.min(mPreviewBitmapWidth,
    428                 mWidgetSpacingLayout.estimateCellWidth(spanX));
    429     }
    430 
    431     public int maxHeightForWidgetPreview(int spanY) {
    432         return Math.min(mPreviewBitmapHeight,
    433                 mWidgetSpacingLayout.estimateCellHeight(spanY));
    434     }
    435 
    436     public Bitmap generateWidgetPreview(ComponentName provider, int previewImage,
    437             int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight,
    438             Bitmap preview, int[] preScaledWidthOut) {
    439         // Load the preview image if possible
    440         String packageName = provider.getPackageName();
    441         if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
    442         if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
    443 
    444         Drawable drawable = null;
    445         if (previewImage != 0) {
    446             drawable = mPackageManager.getDrawable(packageName, previewImage, null);
    447             if (drawable == null) {
    448                 Log.w(TAG, "Can't load widget preview drawable 0x" +
    449                         Integer.toHexString(previewImage) + " for provider: " + provider);
    450             }
    451         }
    452 
    453         int previewWidth;
    454         int previewHeight;
    455         Bitmap defaultPreview = null;
    456         boolean widgetPreviewExists = (drawable != null);
    457         if (widgetPreviewExists) {
    458             previewWidth = drawable.getIntrinsicWidth();
    459             previewHeight = drawable.getIntrinsicHeight();
    460         } else {
    461             // Generate a preview image if we couldn't load one
    462             if (cellHSpan < 1) cellHSpan = 1;
    463             if (cellVSpan < 1) cellVSpan = 1;
    464 
    465             BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
    466                     .getDrawable(R.drawable.widget_tile);
    467             final int previewDrawableWidth = previewDrawable
    468                     .getIntrinsicWidth();
    469             final int previewDrawableHeight = previewDrawable
    470                     .getIntrinsicHeight();
    471             previewWidth = previewDrawableWidth * cellHSpan;
    472             previewHeight = previewDrawableHeight * cellVSpan;
    473 
    474             defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight,
    475                     Config.ARGB_8888);
    476             final Canvas c = mCachedAppWidgetPreviewCanvas.get();
    477             c.setBitmap(defaultPreview);
    478             previewDrawable.setBounds(0, 0, previewWidth, previewHeight);
    479             previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
    480                     Shader.TileMode.REPEAT);
    481             previewDrawable.draw(c);
    482             c.setBitmap(null);
    483 
    484             // Draw the icon in the top left corner
    485             int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
    486             int smallestSide = Math.min(previewWidth, previewHeight);
    487             float iconScale = Math.min((float) smallestSide
    488                     / (mAppIconSize + 2 * minOffset), 1f);
    489 
    490             try {
    491                 Drawable icon = null;
    492                 int hoffset =
    493                         (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
    494                 int yoffset =
    495                         (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
    496                 if (iconId > 0)
    497                     icon = mIconCache.getFullResIcon(packageName, iconId);
    498                 if (icon != null) {
    499                     renderDrawableToBitmap(icon, defaultPreview, hoffset,
    500                             yoffset, (int) (mAppIconSize * iconScale),
    501                             (int) (mAppIconSize * iconScale));
    502                 }
    503             } catch (Resources.NotFoundException e) {
    504             }
    505         }
    506 
    507         // Scale to fit width only - let the widget preview be clipped in the
    508         // vertical dimension
    509         float scale = 1f;
    510         if (preScaledWidthOut != null) {
    511             preScaledWidthOut[0] = previewWidth;
    512         }
    513         if (previewWidth > maxPreviewWidth) {
    514             scale = maxPreviewWidth / (float) previewWidth;
    515         }
    516         if (scale != 1f) {
    517             previewWidth = (int) (scale * previewWidth);
    518             previewHeight = (int) (scale * previewHeight);
    519         }
    520 
    521         // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
    522         if (preview == null) {
    523             preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
    524         }
    525 
    526         // Draw the scaled preview into the final bitmap
    527         int x = (preview.getWidth() - previewWidth) / 2;
    528         if (widgetPreviewExists) {
    529             renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,
    530                     previewHeight);
    531         } else {
    532             final Canvas c = mCachedAppWidgetPreviewCanvas.get();
    533             final Rect src = mCachedAppWidgetPreviewSrcRect.get();
    534             final Rect dest = mCachedAppWidgetPreviewDestRect.get();
    535             c.setBitmap(preview);
    536             src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
    537             dest.set(x, 0, x + previewWidth, previewHeight);
    538 
    539             Paint p = mCachedAppWidgetPreviewPaint.get();
    540             if (p == null) {
    541                 p = new Paint();
    542                 p.setFilterBitmap(true);
    543                 mCachedAppWidgetPreviewPaint.set(p);
    544             }
    545             c.drawBitmap(defaultPreview, src, dest, p);
    546             c.setBitmap(null);
    547         }
    548         return preview;
    549     }
    550 
    551     private Bitmap generateShortcutPreview(
    552             ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) {
    553         Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
    554         final Canvas c = mCachedShortcutPreviewCanvas.get();
    555         if (tempBitmap == null ||
    556                 tempBitmap.getWidth() != maxWidth ||
    557                 tempBitmap.getHeight() != maxHeight) {
    558             tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
    559             mCachedShortcutPreviewBitmap.set(tempBitmap);
    560         } else {
    561             c.setBitmap(tempBitmap);
    562             c.drawColor(0, PorterDuff.Mode.CLEAR);
    563             c.setBitmap(null);
    564         }
    565         // Render the icon
    566         Drawable icon = mIconCache.getFullResIcon(info);
    567 
    568         int paddingTop = mContext.
    569                 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
    570         int paddingLeft = mContext.
    571                 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
    572         int paddingRight = mContext.
    573                 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
    574 
    575         int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
    576 
    577         renderDrawableToBitmap(
    578                 icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
    579 
    580         if (preview != null &&
    581                 (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) {
    582             throw new RuntimeException("Improperly sized bitmap passed as argument");
    583         } else if (preview == null) {
    584             preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
    585         }
    586 
    587         c.setBitmap(preview);
    588         // Draw a desaturated/scaled version of the icon in the background as a watermark
    589         Paint p = mCachedShortcutPreviewPaint.get();
    590         if (p == null) {
    591             p = new Paint();
    592             ColorMatrix colorMatrix = new ColorMatrix();
    593             colorMatrix.setSaturation(0);
    594             p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
    595             p.setAlpha((int) (255 * 0.06f));
    596             mCachedShortcutPreviewPaint.set(p);
    597         }
    598         c.drawBitmap(tempBitmap, 0, 0, p);
    599         c.setBitmap(null);
    600 
    601         renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
    602 
    603         return preview;
    604     }
    605 
    606 
    607     public static void renderDrawableToBitmap(
    608             Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
    609         renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
    610     }
    611 
    612     private static void renderDrawableToBitmap(
    613             Drawable d, Bitmap bitmap, int x, int y, int w, int h,
    614             float scale) {
    615         if (bitmap != null) {
    616             Canvas c = new Canvas(bitmap);
    617             c.scale(scale, scale);
    618             Rect oldBounds = d.copyBounds();
    619             d.setBounds(x, y, x + w, y + h);
    620             d.draw(c);
    621             d.setBounds(oldBounds); // Restore the bounds
    622             c.setBitmap(null);
    623         }
    624     }
    625 
    626 }
    627