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.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