blob: 7e1ad6d7614009a1ce3ccd0aa7d7a9d0cb23256a [file] [log] [blame]
Daniel Sandler325dc232013-06-05 22:57:57 -04001package com.android.launcher3;
Michael Jurka05713af2013-01-23 12:39:24 +01002
3import android.appwidget.AppWidgetProviderInfo;
4import android.content.ComponentName;
5import android.content.ContentValues;
6import android.content.Context;
Michael Jurka8ff02ca2013-11-01 14:19:27 +01007import android.content.SharedPreferences;
Michael Jurka05713af2013-01-23 12:39:24 +01008import android.content.pm.PackageManager;
9import android.content.pm.ResolveInfo;
10import android.content.res.Resources;
11import android.database.Cursor;
12import android.database.sqlite.SQLiteDatabase;
13import android.database.sqlite.SQLiteOpenHelper;
14import android.graphics.Bitmap;
15import android.graphics.Bitmap.Config;
16import android.graphics.BitmapFactory;
17import android.graphics.Canvas;
18import android.graphics.ColorMatrix;
19import android.graphics.ColorMatrixColorFilter;
20import android.graphics.Paint;
21import android.graphics.PorterDuff;
22import android.graphics.Rect;
23import android.graphics.Shader;
24import android.graphics.drawable.BitmapDrawable;
25import android.graphics.drawable.Drawable;
26import android.os.AsyncTask;
27import android.util.Log;
28
Michael Jurka05713af2013-01-23 12:39:24 +010029import java.io.ByteArrayOutputStream;
30import java.io.File;
31import java.lang.ref.SoftReference;
32import java.lang.ref.WeakReference;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.HashSet;
36
37abstract class SoftReferenceThreadLocal<T> {
38 private ThreadLocal<SoftReference<T>> mThreadLocal;
39 public SoftReferenceThreadLocal() {
40 mThreadLocal = new ThreadLocal<SoftReference<T>>();
41 }
42
43 abstract T initialValue();
44
45 public void set(T t) {
46 mThreadLocal.set(new SoftReference<T>(t));
47 }
48
49 public T get() {
50 SoftReference<T> reference = mThreadLocal.get();
51 T obj;
52 if (reference == null) {
53 obj = initialValue();
54 mThreadLocal.set(new SoftReference<T>(obj));
55 return obj;
56 } else {
57 obj = reference.get();
58 if (obj == null) {
59 obj = initialValue();
60 mThreadLocal.set(new SoftReference<T>(obj));
61 }
62 return obj;
63 }
64 }
65}
66
67class CanvasCache extends SoftReferenceThreadLocal<Canvas> {
68 @Override
69 protected Canvas initialValue() {
70 return new Canvas();
71 }
72}
73
74class PaintCache extends SoftReferenceThreadLocal<Paint> {
75 @Override
76 protected Paint initialValue() {
77 return null;
78 }
79}
80
81class BitmapCache extends SoftReferenceThreadLocal<Bitmap> {
82 @Override
83 protected Bitmap initialValue() {
84 return null;
85 }
86}
87
88class RectCache extends SoftReferenceThreadLocal<Rect> {
89 @Override
90 protected Rect initialValue() {
91 return new Rect();
92 }
93}
94
95class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> {
96 @Override
97 protected BitmapFactory.Options initialValue() {
98 return new BitmapFactory.Options();
99 }
100}
101
102public class WidgetPreviewLoader {
103 static final String TAG = "WidgetPreviewLoader";
Michael Jurka8ff02ca2013-11-01 14:19:27 +0100104 static final String ANDROID_INCREMENTAL_VERSION_NAME_KEY = "android.incremental.version";
Michael Jurka05713af2013-01-23 12:39:24 +0100105
Michael Jurka3f4e0702013-02-05 11:21:28 +0100106 private int mPreviewBitmapWidth;
107 private int mPreviewBitmapHeight;
Michael Jurka05713af2013-01-23 12:39:24 +0100108 private String mSize;
109 private Context mContext;
Michael Jurka05713af2013-01-23 12:39:24 +0100110 private PackageManager mPackageManager;
111 private PagedViewCellLayout mWidgetSpacingLayout;
112
113 // Used for drawing shortcut previews
114 private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache();
115 private PaintCache mCachedShortcutPreviewPaint = new PaintCache();
116 private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache();
117
118 // Used for drawing widget previews
119 private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache();
120 private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache();
121 private RectCache mCachedAppWidgetPreviewDestRect = new RectCache();
122 private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache();
123 private String mCachedSelectQuery;
124 private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache();
125
126 private int mAppIconSize;
127 private IconCache mIconCache;
128
129 private final float sWidgetPreviewIconPaddingPercentage = 0.25f;
130
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100131 private CacheDb mDb;
Michael Jurka05713af2013-01-23 12:39:24 +0100132
Michael Jurka3f4e0702013-02-05 11:21:28 +0100133 private HashMap<String, WeakReference<Bitmap>> mLoadedPreviews;
134 private ArrayList<SoftReference<Bitmap>> mUnusedBitmaps;
Michael Jurka05713af2013-01-23 12:39:24 +0100135 private static HashSet<String> sInvalidPackages;
136
137 static {
Michael Jurka05713af2013-01-23 12:39:24 +0100138 sInvalidPackages = new HashSet<String>();
139 }
140
Chris Wrenfd13c712013-09-27 15:45:19 -0400141 public WidgetPreviewLoader(Context context) {
Winson Chung5f8afe62013-08-12 16:19:28 -0700142 LauncherAppState app = LauncherAppState.getInstance();
143 DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
144
Chris Wrenfd13c712013-09-27 15:45:19 -0400145 mContext = context;
Michael Jurka05713af2013-01-23 12:39:24 +0100146 mPackageManager = mContext.getPackageManager();
Winson Chung5f8afe62013-08-12 16:19:28 -0700147 mAppIconSize = grid.iconSizePx;
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100148 mIconCache = app.getIconCache();
149 mDb = app.getWidgetPreviewCacheDb();
Michael Jurka3f4e0702013-02-05 11:21:28 +0100150 mLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>();
151 mUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>();
Michael Jurka8ff02ca2013-11-01 14:19:27 +0100152
153 SharedPreferences sp = context.getSharedPreferences(
154 LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
155 final String lastVersionName = sp.getString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, null);
156 final String versionName = android.os.Build.VERSION.INCREMENTAL;
157 if (!versionName.equals(lastVersionName)) {
158 // clear all the previews whenever the system version changes, to ensure that previews
159 // are up-to-date for any apps that might have been updated with the system
160 clearDb();
161
162 SharedPreferences.Editor editor = sp.edit();
163 editor.putString(ANDROID_INCREMENTAL_VERSION_NAME_KEY, versionName);
164 editor.commit();
165 }
Michael Jurka3f4e0702013-02-05 11:21:28 +0100166 }
167
168 public void setPreviewSize(int previewWidth, int previewHeight,
169 PagedViewCellLayout widgetSpacingLayout) {
170 mPreviewBitmapWidth = previewWidth;
171 mPreviewBitmapHeight = previewHeight;
172 mSize = previewWidth + "x" + previewHeight;
173 mWidgetSpacingLayout = widgetSpacingLayout;
Michael Jurka05713af2013-01-23 12:39:24 +0100174 }
175
176 public Bitmap getPreview(final Object o) {
Michael Jurkaeb1bb922013-09-26 11:29:01 -0700177 final String name = getObjectName(o);
178 final String packageName = getObjectPackage(o);
Michael Jurka05713af2013-01-23 12:39:24 +0100179 // check if the package is valid
180 boolean packageValid = true;
181 synchronized(sInvalidPackages) {
Michael Jurkaeb1bb922013-09-26 11:29:01 -0700182 packageValid = !sInvalidPackages.contains(packageName);
Michael Jurka05713af2013-01-23 12:39:24 +0100183 }
184 if (!packageValid) {
185 return null;
186 }
187 if (packageValid) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100188 synchronized(mLoadedPreviews) {
Michael Jurka05713af2013-01-23 12:39:24 +0100189 // check if it exists in our existing cache
Michael Jurka3f4e0702013-02-05 11:21:28 +0100190 if (mLoadedPreviews.containsKey(name) && mLoadedPreviews.get(name).get() != null) {
191 return mLoadedPreviews.get(name).get();
Michael Jurka05713af2013-01-23 12:39:24 +0100192 }
193 }
194 }
195
196 Bitmap unusedBitmap = null;
Michael Jurka3f4e0702013-02-05 11:21:28 +0100197 synchronized(mUnusedBitmaps) {
Michael Jurka05713af2013-01-23 12:39:24 +0100198 // not in cache; we need to load it from the db
Michael Jurka3f4e0702013-02-05 11:21:28 +0100199 while ((unusedBitmap == null || !unusedBitmap.isMutable() ||
200 unusedBitmap.getWidth() != mPreviewBitmapWidth ||
201 unusedBitmap.getHeight() != mPreviewBitmapHeight)
202 && mUnusedBitmaps.size() > 0) {
203 unusedBitmap = mUnusedBitmaps.remove(0).get();
Michael Jurka05713af2013-01-23 12:39:24 +0100204 }
205 if (unusedBitmap != null) {
206 final Canvas c = mCachedAppWidgetPreviewCanvas.get();
207 c.setBitmap(unusedBitmap);
208 c.drawColor(0, PorterDuff.Mode.CLEAR);
209 c.setBitmap(null);
210 }
211 }
212
213 if (unusedBitmap == null) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100214 unusedBitmap = Bitmap.createBitmap(mPreviewBitmapWidth, mPreviewBitmapHeight,
Michael Jurka05713af2013-01-23 12:39:24 +0100215 Bitmap.Config.ARGB_8888);
216 }
217
218 Bitmap preview = null;
219
220 if (packageValid) {
221 preview = readFromDb(name, unusedBitmap);
222 }
223
224 if (preview != null) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100225 synchronized(mLoadedPreviews) {
226 mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
Michael Jurka05713af2013-01-23 12:39:24 +0100227 }
228 return preview;
229 } else {
230 // it's not in the db... we need to generate it
231 final Bitmap generatedPreview = generatePreview(o, unusedBitmap);
232 preview = generatedPreview;
233 if (preview != unusedBitmap) {
234 throw new RuntimeException("generatePreview is not recycling the bitmap " + o);
235 }
236
Michael Jurka3f4e0702013-02-05 11:21:28 +0100237 synchronized(mLoadedPreviews) {
238 mLoadedPreviews.put(name, new WeakReference<Bitmap>(preview));
Michael Jurka05713af2013-01-23 12:39:24 +0100239 }
240
241 // write to db on a thread pool... this can be done lazily and improves the performance
242 // of the first time widget previews are loaded
243 new AsyncTask<Void, Void, Void>() {
244 public Void doInBackground(Void ... args) {
245 writeToDb(o, generatedPreview);
246 return null;
247 }
248 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
249
250 return preview;
251 }
252 }
253
Michael Jurkaee8e99f2013-02-07 13:27:06 +0100254 public void recycleBitmap(Object o, Bitmap bitmapToRecycle) {
Michael Jurka05713af2013-01-23 12:39:24 +0100255 String name = getObjectName(o);
Michael Jurka5140cfa2013-02-15 14:50:15 +0100256 synchronized (mLoadedPreviews) {
257 if (mLoadedPreviews.containsKey(name)) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100258 Bitmap b = mLoadedPreviews.get(name).get();
Michael Jurkaee8e99f2013-02-07 13:27:06 +0100259 if (b == bitmapToRecycle) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100260 mLoadedPreviews.remove(name);
Michael Jurkaee8e99f2013-02-07 13:27:06 +0100261 if (bitmapToRecycle.isMutable()) {
Michael Jurka5140cfa2013-02-15 14:50:15 +0100262 synchronized (mUnusedBitmaps) {
263 mUnusedBitmaps.add(new SoftReference<Bitmap>(b));
264 }
Michael Jurka05713af2013-01-23 12:39:24 +0100265 }
266 } else {
267 throw new RuntimeException("Bitmap passed in doesn't match up");
268 }
269 }
270 }
271 }
272
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100273 static class CacheDb extends SQLiteOpenHelper {
Michael Jurkae5919c52013-03-06 17:30:10 +0100274 final static int DB_VERSION = 2;
Michael Jurka05713af2013-01-23 12:39:24 +0100275 final static String DB_NAME = "widgetpreviews.db";
276 final static String TABLE_NAME = "shortcut_and_widget_previews";
277 final static String COLUMN_NAME = "name";
278 final static String COLUMN_SIZE = "size";
279 final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap";
280 Context mContext;
281
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100282 public CacheDb(Context context) {
Michael Jurka05713af2013-01-23 12:39:24 +0100283 super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION);
284 // Store the context for later use
285 mContext = context;
286 }
287
288 @Override
289 public void onCreate(SQLiteDatabase database) {
Michael Jurka32b7a092013-02-07 20:06:49 +0100290 database.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
Michael Jurka05713af2013-01-23 12:39:24 +0100291 COLUMN_NAME + " TEXT NOT NULL, " +
292 COLUMN_SIZE + " TEXT NOT NULL, " +
293 COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " +
294 "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " +
Michael Jurka32b7a092013-02-07 20:06:49 +0100295 ");");
Michael Jurka05713af2013-01-23 12:39:24 +0100296 }
297
298 @Override
299 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Michael Jurkae5919c52013-03-06 17:30:10 +0100300 if (oldVersion != newVersion) {
301 // Delete all the records; they'll be repopulated as this is a cache
302 db.execSQL("DELETE FROM " + TABLE_NAME);
303 }
Michael Jurka05713af2013-01-23 12:39:24 +0100304 }
305 }
306
307 private static final String WIDGET_PREFIX = "Widget:";
308 private static final String SHORTCUT_PREFIX = "Shortcut:";
309
310 private static String getObjectName(Object o) {
311 // should cache the string builder
312 StringBuilder sb = new StringBuilder();
313 String output;
314 if (o instanceof AppWidgetProviderInfo) {
315 sb.append(WIDGET_PREFIX);
316 sb.append(((AppWidgetProviderInfo) o).provider.flattenToString());
317 output = sb.toString();
318 sb.setLength(0);
319 } else {
320 sb.append(SHORTCUT_PREFIX);
321
322 ResolveInfo info = (ResolveInfo) o;
323 sb.append(new ComponentName(info.activityInfo.packageName,
324 info.activityInfo.name).flattenToString());
325 output = sb.toString();
326 sb.setLength(0);
327 }
328 return output;
329 }
330
331 private String getObjectPackage(Object o) {
332 if (o instanceof AppWidgetProviderInfo) {
333 return ((AppWidgetProviderInfo) o).provider.getPackageName();
334 } else {
335 ResolveInfo info = (ResolveInfo) o;
336 return info.activityInfo.packageName;
337 }
338 }
339
340 private void writeToDb(Object o, Bitmap preview) {
341 String name = getObjectName(o);
342 SQLiteDatabase db = mDb.getWritableDatabase();
343 ContentValues values = new ContentValues();
344
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100345 values.put(CacheDb.COLUMN_NAME, name);
Michael Jurka05713af2013-01-23 12:39:24 +0100346 ByteArrayOutputStream stream = new ByteArrayOutputStream();
347 preview.compress(Bitmap.CompressFormat.PNG, 100, stream);
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100348 values.put(CacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray());
349 values.put(CacheDb.COLUMN_SIZE, mSize);
350 db.insert(CacheDb.TABLE_NAME, null, values);
Michael Jurka05713af2013-01-23 12:39:24 +0100351 }
352
Michael Jurka8ff02ca2013-11-01 14:19:27 +0100353 private void clearDb() {
354 SQLiteDatabase db = mDb.getWritableDatabase();
355 // Delete everything
356 db.delete(CacheDb.TABLE_NAME, null, null);
357 }
358
Michael Jurkaeb1bb922013-09-26 11:29:01 -0700359 public static void removePackageFromDb(final CacheDb cacheDb, final String packageName) {
Michael Jurka05713af2013-01-23 12:39:24 +0100360 synchronized(sInvalidPackages) {
361 sInvalidPackages.add(packageName);
362 }
363 new AsyncTask<Void, Void, Void>() {
364 public Void doInBackground(Void ... args) {
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100365 SQLiteDatabase db = cacheDb.getWritableDatabase();
366 db.delete(CacheDb.TABLE_NAME,
367 CacheDb.COLUMN_NAME + " LIKE ? OR " +
368 CacheDb.COLUMN_NAME + " LIKE ?", // SELECT query
Michael Jurka05713af2013-01-23 12:39:24 +0100369 new String[] {
370 WIDGET_PREFIX + packageName + "/%",
371 SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query
372 );
Michael Jurka05713af2013-01-23 12:39:24 +0100373 synchronized(sInvalidPackages) {
374 sInvalidPackages.remove(packageName);
375 }
376 return null;
377 }
378 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
379 }
380
Michael Jurkaeb1bb922013-09-26 11:29:01 -0700381 public static void removeItemFromDb(final CacheDb cacheDb, final String objectName) {
382 new AsyncTask<Void, Void, Void>() {
383 public Void doInBackground(Void ... args) {
384 SQLiteDatabase db = cacheDb.getWritableDatabase();
385 db.delete(CacheDb.TABLE_NAME,
386 CacheDb.COLUMN_NAME + " = ? ", // SELECT query
387 new String[] { objectName }); // args to SELECT query
388 return null;
389 }
390 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
391 }
392
Michael Jurka05713af2013-01-23 12:39:24 +0100393 private Bitmap readFromDb(String name, Bitmap b) {
394 if (mCachedSelectQuery == null) {
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100395 mCachedSelectQuery = CacheDb.COLUMN_NAME + " = ? AND " +
396 CacheDb.COLUMN_SIZE + " = ?";
Michael Jurka05713af2013-01-23 12:39:24 +0100397 }
398 SQLiteDatabase db = mDb.getReadableDatabase();
Michael Jurkad9cb4a12013-03-19 12:01:06 +0100399 Cursor result = db.query(CacheDb.TABLE_NAME,
400 new String[] { CacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return
Michael Jurka05713af2013-01-23 12:39:24 +0100401 mCachedSelectQuery, // select query
402 new String[] { name, mSize }, // args to select query
403 null,
404 null,
405 null,
406 null);
407 if (result.getCount() > 0) {
408 result.moveToFirst();
409 byte[] blob = result.getBlob(0);
410 result.close();
411 final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get();
412 opts.inBitmap = b;
413 opts.inSampleSize = 1;
Michael Jurkaeb1bb922013-09-26 11:29:01 -0700414 try {
415 return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts);
416 } catch (IllegalArgumentException e) {
417 removeItemFromDb(mDb, name);
418 return null;
419 }
Michael Jurka05713af2013-01-23 12:39:24 +0100420 } else {
421 result.close();
422 return null;
423 }
424 }
425
426 public Bitmap generatePreview(Object info, Bitmap preview) {
427 if (preview != null &&
Michael Jurka3f4e0702013-02-05 11:21:28 +0100428 (preview.getWidth() != mPreviewBitmapWidth ||
429 preview.getHeight() != mPreviewBitmapHeight)) {
Michael Jurka05713af2013-01-23 12:39:24 +0100430 throw new RuntimeException("Improperly sized bitmap passed as argument");
431 }
432 if (info instanceof AppWidgetProviderInfo) {
433 return generateWidgetPreview((AppWidgetProviderInfo) info, preview);
434 } else {
435 return generateShortcutPreview(
Michael Jurka3f4e0702013-02-05 11:21:28 +0100436 (ResolveInfo) info, mPreviewBitmapWidth, mPreviewBitmapHeight, preview);
Michael Jurka05713af2013-01-23 12:39:24 +0100437 }
438 }
439
440 public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) {
Chris Wrenfd13c712013-09-27 15:45:19 -0400441 int[] cellSpans = Launcher.getSpanForWidget(mContext, info);
Michael Jurka05713af2013-01-23 12:39:24 +0100442 int maxWidth = maxWidthForWidgetPreview(cellSpans[0]);
443 int maxHeight = maxHeightForWidgetPreview(cellSpans[1]);
444 return generateWidgetPreview(info.provider, info.previewImage, info.icon,
445 cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null);
446 }
447
448 public int maxWidthForWidgetPreview(int spanX) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100449 return Math.min(mPreviewBitmapWidth,
Michael Jurka05713af2013-01-23 12:39:24 +0100450 mWidgetSpacingLayout.estimateCellWidth(spanX));
451 }
452
453 public int maxHeightForWidgetPreview(int spanY) {
Michael Jurka3f4e0702013-02-05 11:21:28 +0100454 return Math.min(mPreviewBitmapHeight,
Michael Jurka05713af2013-01-23 12:39:24 +0100455 mWidgetSpacingLayout.estimateCellHeight(spanY));
456 }
457
458 public Bitmap generateWidgetPreview(ComponentName provider, int previewImage,
459 int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight,
460 Bitmap preview, int[] preScaledWidthOut) {
461 // Load the preview image if possible
462 String packageName = provider.getPackageName();
463 if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE;
464 if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE;
465
466 Drawable drawable = null;
467 if (previewImage != 0) {
468 drawable = mPackageManager.getDrawable(packageName, previewImage, null);
469 if (drawable == null) {
470 Log.w(TAG, "Can't load widget preview drawable 0x" +
471 Integer.toHexString(previewImage) + " for provider: " + provider);
472 }
473 }
474
475 int previewWidth;
476 int previewHeight;
477 Bitmap defaultPreview = null;
478 boolean widgetPreviewExists = (drawable != null);
479 if (widgetPreviewExists) {
480 previewWidth = drawable.getIntrinsicWidth();
481 previewHeight = drawable.getIntrinsicHeight();
482 } else {
483 // Generate a preview image if we couldn't load one
484 if (cellHSpan < 1) cellHSpan = 1;
485 if (cellVSpan < 1) cellVSpan = 1;
486
487 BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources()
Winson Chung6706ed82013-10-02 11:00:15 -0700488 .getDrawable(R.drawable.widget_tile);
Michael Jurka05713af2013-01-23 12:39:24 +0100489 final int previewDrawableWidth = previewDrawable
490 .getIntrinsicWidth();
491 final int previewDrawableHeight = previewDrawable
492 .getIntrinsicHeight();
Winson Chung45cab392013-10-02 17:45:32 -0700493 previewWidth = previewDrawableWidth * cellHSpan;
Michael Jurka05713af2013-01-23 12:39:24 +0100494 previewHeight = previewDrawableHeight * cellVSpan;
495
496 defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight,
497 Config.ARGB_8888);
498 final Canvas c = mCachedAppWidgetPreviewCanvas.get();
499 c.setBitmap(defaultPreview);
500 previewDrawable.setBounds(0, 0, previewWidth, previewHeight);
501 previewDrawable.setTileModeXY(Shader.TileMode.REPEAT,
502 Shader.TileMode.REPEAT);
503 previewDrawable.draw(c);
504 c.setBitmap(null);
505
506 // Draw the icon in the top left corner
507 int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage);
508 int smallestSide = Math.min(previewWidth, previewHeight);
509 float iconScale = Math.min((float) smallestSide
510 / (mAppIconSize + 2 * minOffset), 1f);
511
512 try {
513 Drawable icon = null;
514 int hoffset =
515 (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2);
516 int yoffset =
517 (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2);
518 if (iconId > 0)
519 icon = mIconCache.getFullResIcon(packageName, iconId);
520 if (icon != null) {
521 renderDrawableToBitmap(icon, defaultPreview, hoffset,
522 yoffset, (int) (mAppIconSize * iconScale),
523 (int) (mAppIconSize * iconScale));
524 }
525 } catch (Resources.NotFoundException e) {
526 }
527 }
528
529 // Scale to fit width only - let the widget preview be clipped in the
530 // vertical dimension
531 float scale = 1f;
532 if (preScaledWidthOut != null) {
533 preScaledWidthOut[0] = previewWidth;
534 }
535 if (previewWidth > maxPreviewWidth) {
536 scale = maxPreviewWidth / (float) previewWidth;
537 }
538 if (scale != 1f) {
539 previewWidth = (int) (scale * previewWidth);
540 previewHeight = (int) (scale * previewHeight);
541 }
542
543 // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size
544 if (preview == null) {
545 preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
546 }
547
548 // Draw the scaled preview into the final bitmap
549 int x = (preview.getWidth() - previewWidth) / 2;
550 if (widgetPreviewExists) {
551 renderDrawableToBitmap(drawable, preview, x, 0, previewWidth,
552 previewHeight);
553 } else {
554 final Canvas c = mCachedAppWidgetPreviewCanvas.get();
555 final Rect src = mCachedAppWidgetPreviewSrcRect.get();
556 final Rect dest = mCachedAppWidgetPreviewDestRect.get();
557 c.setBitmap(preview);
558 src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight());
Michael Jurkae5919c52013-03-06 17:30:10 +0100559 dest.set(x, 0, x + previewWidth, previewHeight);
Michael Jurka05713af2013-01-23 12:39:24 +0100560
561 Paint p = mCachedAppWidgetPreviewPaint.get();
562 if (p == null) {
563 p = new Paint();
564 p.setFilterBitmap(true);
565 mCachedAppWidgetPreviewPaint.set(p);
566 }
567 c.drawBitmap(defaultPreview, src, dest, p);
568 c.setBitmap(null);
569 }
570 return preview;
571 }
572
573 private Bitmap generateShortcutPreview(
574 ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) {
575 Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get();
576 final Canvas c = mCachedShortcutPreviewCanvas.get();
577 if (tempBitmap == null ||
578 tempBitmap.getWidth() != maxWidth ||
579 tempBitmap.getHeight() != maxHeight) {
580 tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
581 mCachedShortcutPreviewBitmap.set(tempBitmap);
582 } else {
583 c.setBitmap(tempBitmap);
584 c.drawColor(0, PorterDuff.Mode.CLEAR);
585 c.setBitmap(null);
586 }
587 // Render the icon
588 Drawable icon = mIconCache.getFullResIcon(info);
589
590 int paddingTop = mContext.
591 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top);
592 int paddingLeft = mContext.
593 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left);
594 int paddingRight = mContext.
595 getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right);
596
597 int scaledIconWidth = (maxWidth - paddingLeft - paddingRight);
598
599 renderDrawableToBitmap(
600 icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth);
601
602 if (preview != null &&
603 (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) {
604 throw new RuntimeException("Improperly sized bitmap passed as argument");
605 } else if (preview == null) {
606 preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888);
607 }
608
609 c.setBitmap(preview);
610 // Draw a desaturated/scaled version of the icon in the background as a watermark
611 Paint p = mCachedShortcutPreviewPaint.get();
612 if (p == null) {
613 p = new Paint();
614 ColorMatrix colorMatrix = new ColorMatrix();
615 colorMatrix.setSaturation(0);
616 p.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
617 p.setAlpha((int) (255 * 0.06f));
618 mCachedShortcutPreviewPaint.set(p);
619 }
620 c.drawBitmap(tempBitmap, 0, 0, p);
621 c.setBitmap(null);
622
623 renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize);
624
625 return preview;
626 }
627
628
629 public static void renderDrawableToBitmap(
630 Drawable d, Bitmap bitmap, int x, int y, int w, int h) {
631 renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f);
632 }
633
634 private static void renderDrawableToBitmap(
635 Drawable d, Bitmap bitmap, int x, int y, int w, int h,
636 float scale) {
637 if (bitmap != null) {
638 Canvas c = new Canvas(bitmap);
639 c.scale(scale, scale);
640 Rect oldBounds = d.copyBounds();
641 d.setBounds(x, y, x + w, y + h);
642 d.draw(c);
643 d.setBounds(oldBounds); // Restore the bounds
644 c.setBitmap(null);
645 }
646 }
647
648}