1package com.android.launcher2;
2
3import android.appwidget.AppWidgetProviderInfo;
4import android.content.ComponentName;
5import android.content.ContentValues;
6import android.content.Context;
7import android.content.SharedPreferences;
8import android.content.pm.PackageManager;
9import android.content.pm.ResolveInfo;
10import android.content.res.Configuration;
11import android.content.res.Resources;
12import android.database.Cursor;
13import android.database.sqlite.SQLiteDatabase;
14import android.database.sqlite.SQLiteDiskIOException;
15import android.database.sqlite.SQLiteOpenHelper;
16import android.graphics.Bitmap;
17import android.graphics.Bitmap.Config;
18import android.graphics.BitmapFactory;
19import android.graphics.Canvas;
20import android.graphics.ColorMatrix;
21import android.graphics.ColorMatrixColorFilter;
22import android.graphics.Paint;
23import android.graphics.PorterDuff;
24import android.graphics.Rect;
25import android.graphics.Shader;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28
29import android.os.AsyncTask;
30import android.view.View;
31import com.android.launcher.R;
32
33import java.io.ByteArrayOutputStream;
34import java.io.File;
35import java.lang.ref.SoftReference;
36import java.lang.ref.WeakReference;
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.HashSet;
40
41abstract 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
71class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
72    @Override
73    protected Canvas initialValue() {
74        return new Canvas();
75    }
76}
77
78class PaintCache extends SoftReferenceThreadLocal<Paint> {
79    @Override
80    protected Paint initialValue() {
81        return null;
82    }
83}
84
85class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
86    @Override
87    protected Bitmap initialValue() {
88        return null;
89    }
90}
91
92class RectCache extends SoftReferenceThreadLocal<Rect> {
93    @Override
94    protected Rect initialValue() {
95        return new Rect();
96    }
97}
98
99class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
100    @Override
101    protected BitmapFactory.Options initialValue() {
102        return new BitmapFactory.Options();
103    }
104}
105
106public 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