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