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