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