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;
SoftReferenceThreadLocal()43     public SoftReferenceThreadLocal() {
44         mThreadLocal = new ThreadLocal<SoftReference<T>>();
45     }
46 
initialValue()47     abstract T initialValue();
48 
set(T t)49     public void set(T t) {
50         mThreadLocal.set(new SoftReference<T>(t));
51     }
52 
get()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
initialValue()73     protected Canvas initialValue() {
74         return new Canvas();
75     }
76 }
77 
78 class PaintCache extends SoftReferenceThreadLocal<Paint> {
79     @Override
initialValue()80     protected Paint initialValue() {
81         return null;
82     }
83 }
84 
85 class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
86     @Override
initialValue()87     protected Bitmap initialValue() {
88         return null;
89     }
90 }
91 
92 class RectCache extends SoftReferenceThreadLocal<Rect> {
93     @Override
initialValue()94     protected Rect initialValue() {
95         return new Rect();
96     }
97 }
98 
99 class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
100     @Override
initialValue()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 
WidgetPreviewLoader(Launcher launcher)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 
recreateDb()177     public void recreateDb() {
178         LauncherApplication app = (LauncherApplication) mLauncher.getApplication();
179         app.recreateWidgetPreviewDb();
180         mDb = app.getWidgetPreviewCacheDb();
181     }
182 
setPreviewSize(int previewWidth, int previewHeight, PagedViewCellLayout widgetSpacingLayout)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 
getPreview(final Object o)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 
recycleBitmap(Object o, Bitmap bitmapToRecycle)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 
CacheDb(Context context)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
onCreate(SQLiteDatabase database)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
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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 
getObjectName(Object o)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 
getObjectPackage(Object o)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 
writeToDb(Object o, Bitmap preview)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 
clearDb()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 
removeFromDb(final CacheDb cacheDb, final String packageName)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 
readFromDb(String name, Bitmap b)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 
generatePreview(Object info, Bitmap preview)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 
generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview)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 
maxWidthForWidgetPreview(int spanX)465     public int maxWidthForWidgetPreview(int spanX) {
466         return Math.min(mPreviewBitmapWidth,
467                 mWidgetSpacingLayout.estimateCellWidth(spanX));
468     }
469 
maxHeightForWidgetPreview(int spanY)470     public int maxHeightForWidgetPreview(int spanY) {
471         return Math.min(mPreviewBitmapHeight,
472                 mWidgetSpacingLayout.estimateCellHeight(spanY));
473     }
474 
generateWidgetPreview(AppWidgetProviderInfo info, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight, Bitmap preview, int[] preScaledWidthOut)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 
generateShortcutPreview( ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview)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 
renderDrawableToBitmap( Drawable d, Bitmap bitmap, int x, int y, int w, int h)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 
renderDrawableToBitmap( Drawable d, Bitmap bitmap, int x, int y, int w, int h, float scale)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