diff options
Diffstat (limited to 'src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java')
-rw-r--r-- | src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java | 744 |
1 files changed, 147 insertions, 597 deletions
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java index 4ec7e60c86..784f4f050e 100644 --- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java +++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java @@ -16,95 +16,60 @@ package com.android.launcher3.widget; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; -import android.content.ComponentName; -import android.content.ContentValues; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; -import android.database.Cursor; -import android.database.SQLException; -import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.CancellationSignal; +import android.os.Handler; import android.os.Process; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; -import android.util.LongSparseArray; -import android.util.Pair; import android.util.Size; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.android.launcher3.BaseActivity; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.icons.GraphicsUtils; -import com.android.launcher3.icons.IconCache; +import com.android.launcher3.icons.BitmapRenderer; +import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.ShadowGenerator; +import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pm.ShortcutConfigActivityInfo; -import com.android.launcher3.pm.UserCache; -import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Executors; -import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Preconditions; -import com.android.launcher3.util.SQLiteCacheHelper; -import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.ActivityContext; import com.android.launcher3.widget.util.WidgetSizes; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; -/** {@link WidgetPreviewLoader} that loads preview images from a {@link CacheDb}. */ -public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { +/** Utility class to load widget previews */ +public class DatabaseWidgetPreviewLoader { private static final String TAG = "WidgetPreviewLoader"; - private static final boolean DEBUG = false; - - private final HashMap<String, long[]> mPackageVersions = new HashMap<>(); - - /** - * Weak reference objects, do not prevent their referents from being made finalizable, - * finalized, and then reclaimed. - * Note: synchronized block used for this variable is expensive and the block should always - * be posted to a background thread. - */ - @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>()); private final Context mContext; - private final IconCache mIconCache; - private final UserCache mUserCache; - private final CacheDb mDb; private final float mPreviewBoxCornerRadius; - public DatabaseWidgetPreviewLoader(Context context, IconCache iconCache) { + private final UserHandle mMyUser = Process.myUserHandle(); + private final ArrayMap<UserHandle, Bitmap> mUserBadges = new ArrayMap<>(); + + public DatabaseWidgetPreviewLoader(Context context) { mContext = context; - mIconCache = iconCache; - mUserCache = UserCache.INSTANCE.get(context); - mDb = new CacheDb(context); float previewCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context); mPreviewBoxCornerRadius = previewCornerRadius > 0 ? previewCornerRadius @@ -117,268 +82,88 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { * * @return a request id which can be used to cancel the request. */ - @Override @NonNull - public CancellationSignal loadPreview( - @NonNull BaseActivity activity, + public HandlerRunnable loadPreview( @NonNull WidgetItem item, @NonNull Size previewSize, - @NonNull WidgetPreviewLoadedCallback callback) { - int previewWidth = previewSize.getWidth(); - int previewHeight = previewSize.getHeight(); - String size = previewWidth + "x" + previewHeight; - WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); - - PreviewLoadTask task = - new PreviewLoadTask(activity, key, item, previewWidth, previewHeight, callback); - task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); - - CancellationSignal signal = new CancellationSignal(); - signal.setOnCancelListener(task); - return signal; - } - - /** Clears the database storing previews. */ - public void refresh() { - mDb.clear(); + @NonNull Consumer<Bitmap> callback) { + Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler(); + HandlerRunnable<Bitmap> request = new HandlerRunnable<>(handler, + () -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()), + MAIN_EXECUTOR, + callback); + Utilities.postAsyncCallback(handler, request); + return request; } /** - * The DB holds the generated previews for various components. Previews can also have different - * sizes (landscape vs portrait). + * Returns a generated preview for a widget and if the preview should be saved in persistent + * storage. */ - private static class CacheDb extends SQLiteCacheHelper { - private static final int DB_VERSION = 9; - - private static final String TABLE_NAME = "shortcut_and_widget_previews"; - private static final String COLUMN_COMPONENT = "componentName"; - private static final String COLUMN_USER = "profileId"; - private static final String COLUMN_SIZE = "size"; - private static final String COLUMN_PACKAGE = "packageName"; - private static final String COLUMN_LAST_UPDATED = "lastUpdated"; - private static final String COLUMN_VERSION = "version"; - private static final String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; - - CacheDb(Context context) { - super(context, LauncherFiles.WIDGET_PREVIEWS_DB, DB_VERSION, TABLE_NAME); - } - - @Override - public void onCreateTable(SQLiteDatabase database) { - database.execSQL("CREATE TABLE IF NOT EXISTS " - + TABLE_NAME - + " (" - + COLUMN_COMPONENT - + " TEXT NOT NULL, " - + COLUMN_USER - + " INTEGER NOT NULL, " - + COLUMN_SIZE - + " TEXT NOT NULL, " - + COLUMN_PACKAGE - + " TEXT NOT NULL, " - + COLUMN_LAST_UPDATED - + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_VERSION - + " INTEGER NOT NULL DEFAULT 0, " - + COLUMN_PREVIEW_BITMAP - + " BLOB, " - + "PRIMARY KEY (" - + COLUMN_COMPONENT - + ", " - + COLUMN_USER - + ", " - + COLUMN_SIZE - + ") " - + - ");"); - } - } - - @Thunk void writeToDb(WidgetCacheKey key, long[] versions, Bitmap preview) { - ContentValues values = new ContentValues(); - values.put(CacheDb.COLUMN_COMPONENT, key.componentName.flattenToShortString()); - values.put(CacheDb.COLUMN_USER, mUserCache.getSerialNumberForUser(key.user)); - values.put(CacheDb.COLUMN_SIZE, key.mSize); - values.put(CacheDb.COLUMN_PACKAGE, key.componentName.getPackageName()); - values.put(CacheDb.COLUMN_VERSION, versions[0]); - values.put(CacheDb.COLUMN_LAST_UPDATED, versions[1]); - values.put(CacheDb.COLUMN_PREVIEW_BITMAP, GraphicsUtils.flattenBitmap(preview)); - mDb.insertOrReplace(values); - } - - /** Removes the package from the preview database. */ - public void removePackage(String packageName, UserHandle user) { - removePackage(packageName, user, mUserCache.getSerialNumberForUser(user)); - } - - /** Removes the package from the preview database. */ - public void removePackage(String packageName, UserHandle user, long userSerial) { - synchronized (mPackageVersions) { - mPackageVersions.remove(packageName); + private Bitmap generatePreview(WidgetItem item, int previewWidth, int previewHeight) { + if (item.widgetInfo != null) { + return generateWidgetPreview(item.widgetInfo, previewWidth, null); + } else { + return generateShortcutPreview(item.activityInfo, previewWidth, previewHeight); } - - mDb.delete( - CacheDb.COLUMN_PACKAGE + " = ? AND " + CacheDb.COLUMN_USER + " = ?", - new String[]{packageName, Long.toString(userSerial)}); } /** - * Updates the persistent DB: - * 1. Any preview generated for an old package version is removed - * 2. Any preview for an absent package is removed - * This ensures that we remove entries for packages which changed while the launcher was dead. - * - * @param packageUser if provided, specifies that list only contains previews for the - * given package/user, otherwise the list contains all previews + * Returns a drawable that can be used as a badge for the user or null. */ - public void removeObsoletePreviews(ArrayList<? extends ComponentKey> list, - @Nullable PackageUserKey packageUser) { - Preconditions.assertWorkerThread(); - - LongSparseArray<HashSet<String>> validPackages = new LongSparseArray<>(); - - for (ComponentKey key : list) { - final long userId = mUserCache.getSerialNumberForUser(key.user); - HashSet<String> packages = validPackages.get(userId); - if (packages == null) { - packages = new HashSet<>(); - validPackages.put(userId, packages); - } - packages.add(key.componentName.getPackageName()); + // @UiThread + public Drawable getBadgeForUser(UserHandle user, int badgeSize) { + if (mMyUser.equals(user)) { + return null; } - LongSparseArray<HashSet<String>> packagesToDelete = new LongSparseArray<>(); - long passedUserId = packageUser == null ? 0 - : mUserCache.getSerialNumberForUser(packageUser.mUser); - Cursor c = null; - try { - c = mDb.query( - new String[]{CacheDb.COLUMN_USER, CacheDb.COLUMN_PACKAGE, - CacheDb.COLUMN_LAST_UPDATED, CacheDb.COLUMN_VERSION}, - null, null); - while (c.moveToNext()) { - long userId = c.getLong(0); - String pkg = c.getString(1); - long lastUpdated = c.getLong(2); - long version = c.getLong(3); - - if (packageUser != null && (!pkg.equals(packageUser.mPackageName) - || userId != passedUserId)) { - // This preview is associated with a different package/user, no need to remove. - continue; - } - - HashSet<String> packages = validPackages.get(userId); - if (packages != null && packages.contains(pkg)) { - long[] versions = getPackageVersion(pkg); - if (versions[0] == version && versions[1] == lastUpdated) { - // Every thing checks out - continue; - } - } + Bitmap badgeBitmap = getUserBadge(user, badgeSize); + FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); + d.setFilterBitmap(true); + d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); + return d; + } - // We need to delete this package. - packages = packagesToDelete.get(userId); - if (packages == null) { - packages = new HashSet<>(); - packagesToDelete.put(userId, packages); - } - packages.add(pkg); + private Bitmap getUserBadge(UserHandle user, int badgeSize) { + synchronized (mUserBadges) { + Bitmap badgeBitmap = mUserBadges.get(user); + if (badgeBitmap != null) { + return badgeBitmap; } - for (int i = 0; i < packagesToDelete.size(); i++) { - long userId = packagesToDelete.keyAt(i); - UserHandle user = mUserCache.getUserForSerialNumber(userId); - for (String pkg : packagesToDelete.valueAt(i)) { - removePackage(pkg, user, userId); - } - } - } catch (SQLException e) { - Log.e(TAG, "Error updating widget previews", e); - } finally { - if (c != null) { - c.close(); - } - } - } + final Resources res = mContext.getResources(); + badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - /** - * Reads the preview bitmap from the DB or null if the preview is not in the DB. - */ - @Thunk Bitmap readFromDb(WidgetCacheKey key, Bitmap recycle, PreviewLoadTask loadTask) { - Cursor cursor = null; - try { - cursor = mDb.query( - new String[]{CacheDb.COLUMN_PREVIEW_BITMAP}, - CacheDb.COLUMN_COMPONENT + " = ? AND " + CacheDb.COLUMN_USER + " = ? AND " - + CacheDb.COLUMN_SIZE + " = ?", - new String[]{ - key.componentName.flattenToShortString(), - Long.toString(mUserCache.getSerialNumberForUser(key.user)), - key.mSize - }); - // If cancelled, skip getting the blob and decoding it into a bitmap - if (loadTask.isCancelled()) { - return null; - } - if (cursor.moveToNext()) { - byte[] blob = cursor.getBlob(0); - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inBitmap = recycle; - try { - if (!loadTask.isCancelled()) { - return BitmapFactory.decodeByteArray(blob, 0, blob.length, opts); - } - } catch (Exception e) { - return null; - } - } - } catch (SQLException e) { - Log.w(TAG, "Error loading preview from DB", e); - } finally { - if (cursor != null) { - cursor.close(); + Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity( + new BitmapDrawable(res, badgeBitmap), user, + new Rect(0, 0, badgeSize, badgeSize), + 0); + if (drawable instanceof BitmapDrawable) { + badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + badgeBitmap.eraseColor(Color.TRANSPARENT); + Canvas c = new Canvas(badgeBitmap); + drawable.setBounds(0, 0, badgeSize, badgeSize); + drawable.draw(c); + c.setBitmap(null); } - } - return null; - } - /** - * Returns a generated preview for a widget and if the preview should be saved in persistent - * storage. - * @param launcher - * @param item - * @param recycle - * @param previewWidth - * @param previewHeight - * @return Pair<Bitmap, Boolean> - */ - private Pair<Bitmap, Boolean> generatePreview(BaseActivity launcher, WidgetItem item, - Bitmap recycle, - int previewWidth, int previewHeight) { - if (item.widgetInfo != null) { - return generateWidgetPreview(launcher, item.widgetInfo, - previewWidth, recycle, null); - } else { - return new Pair<>(generateShortcutPreview(launcher, item.activityInfo, - previewWidth, previewHeight, recycle), false); + mUserBadges.put(user, badgeBitmap); + return badgeBitmap; } } + /** * Generates the widget preview from either the {@link WidgetManagerHelper} or cache * and add badge at the bottom right corner. * - * @param launcher * @param info information about the widget * @param maxPreviewWidth width of the preview on either workspace or tray - * @param preview bitmap that can be recycled * @param preScaledWidthOut return the width of the returned bitmap - * @return Pair<Bitmap (the preview) , Boolean (should be stored in db)> */ - public Pair<Bitmap, Boolean> generateWidgetPreview(BaseActivity launcher, - LauncherAppWidgetProviderInfo info, - int maxPreviewWidth, Bitmap preview, int[] preScaledWidthOut) { + public Bitmap generateWidgetPreview(LauncherAppWidgetProviderInfo info, + int maxPreviewWidth, int[] preScaledWidthOut) { // Load the preview image if possible if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE; @@ -409,117 +194,97 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { int previewWidth; int previewHeight; - boolean savePreviewImage = widgetPreviewExists || info.previewImage == 0; + DeviceProfile dp = ActivityContext.lookupContext(mContext).getDeviceProfile(); if (widgetPreviewExists && drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) { previewWidth = drawable.getIntrinsicWidth(); previewHeight = drawable.getIntrinsicHeight(); } else { - DeviceProfile dp = launcher.getDeviceProfile(); Size widgetSize = WidgetSizes.getWidgetPaddedSizePx(mContext, info.provider, dp, spanX, spanY); previewWidth = widgetSize.getWidth(); previewHeight = widgetSize.getHeight(); } - // Scale to fit width only - let the widget preview be clipped in the - // vertical dimension - float scale = 1f; if (preScaledWidthOut != null) { preScaledWidthOut[0] = previewWidth; } - if (previewWidth > maxPreviewWidth) { - scale = maxPreviewWidth / (float) (previewWidth); - } + // Scale to fit width only - let the widget preview be clipped in the + // vertical dimension + final float scale = previewWidth > maxPreviewWidth + ? (maxPreviewWidth / (float) (previewWidth)) : 1f; if (scale != 1f) { previewWidth = Math.max((int) (scale * previewWidth), 1); previewHeight = Math.max((int) (scale * previewHeight), 1); } - final Canvas c = new Canvas(); - if (preview == null) { - // If no bitmap was provided, then allocate a new one with the right size. - preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); - c.setBitmap(preview); - } else { - // If a bitmap was passed in, attempt to reconfigure the bitmap to the same dimensions - // as the preview. - try { - preview.reconfigure(previewWidth, previewHeight, preview.getConfig()); - } catch (IllegalArgumentException e) { - // This occurs if the preview can't be reconfigured for any reason. In this case, - // allocate a new bitmap with the right size. - preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); - } - - c.setBitmap(preview); - c.drawColor(0, PorterDuff.Mode.CLEAR); - } - - // Draw the scaled preview into the final bitmap - if (widgetPreviewExists) { - drawable.setBounds(0, 0, previewWidth, previewHeight); - drawable.draw(c); - } else { - RectF boxRect; - - // Draw horizontal and vertical lines to represent individual columns. - final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); + final int previewWidthF = previewWidth; + final int previewHeightF = previewHeight; + final Drawable drawableF = drawable; - if (Utilities.ATLEAST_S) { - boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */ - previewWidth, /* bottom= */ previewHeight); - - p.setStyle(Paint.Style.FILL); - p.setColor(Color.WHITE); - float roundedCorner = mContext.getResources().getDimension( - android.R.dimen.system_app_widget_background_radius); - c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p); + return BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, c -> { + // Draw the scaled preview into the final bitmap + if (widgetPreviewExists) { + drawableF.setBounds(0, 0, previewWidthF, previewHeightF); + drawableF.draw(c); } else { - boxRect = drawBoxWithShadow(c, previewWidth, previewHeight); - } + RectF boxRect; + + // Draw horizontal and vertical lines to represent individual columns. + final Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); + + if (Utilities.ATLEAST_S) { + boxRect = new RectF(/* left= */ 0, /* top= */ 0, /* right= */ + previewWidthF, /* bottom= */ previewHeightF); + + p.setStyle(Paint.Style.FILL); + p.setColor(Color.WHITE); + float roundedCorner = mContext.getResources().getDimension( + android.R.dimen.system_app_widget_background_radius); + c.drawRoundRect(boxRect, roundedCorner, roundedCorner, p); + } else { + boxRect = drawBoxWithShadow(c, previewWidthF, previewHeightF); + } - p.setStyle(Paint.Style.STROKE); - p.setStrokeWidth(mContext.getResources() - .getDimension(R.dimen.widget_preview_cell_divider_width)); - p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + p.setStyle(Paint.Style.STROKE); + p.setStrokeWidth(mContext.getResources() + .getDimension(R.dimen.widget_preview_cell_divider_width)); + p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - float t = boxRect.left; - float tileSize = boxRect.width() / spanX; - for (int i = 1; i < spanX; i++) { - t += tileSize; - c.drawLine(t, 0, t, previewHeight, p); - } + float t = boxRect.left; + float tileSize = boxRect.width() / spanX; + for (int i = 1; i < spanX; i++) { + t += tileSize; + c.drawLine(t, 0, t, previewHeightF, p); + } - t = boxRect.top; - tileSize = boxRect.height() / spanY; - for (int i = 1; i < spanY; i++) { - t += tileSize; - c.drawLine(0, t, previewWidth, t, p); - } + t = boxRect.top; + tileSize = boxRect.height() / spanY; + for (int i = 1; i < spanY; i++) { + t += tileSize; + c.drawLine(0, t, previewWidthF, t, p); + } - // Draw icon in the center. - try { - Drawable icon = - mIconCache.getFullResIcon(info.provider.getPackageName(), info.icon); - if (icon != null) { - int appIconSize = launcher.getDeviceProfile().iconSizePx; - int iconSize = (int) Math.min(appIconSize * scale, - Math.min(boxRect.width(), boxRect.height())); - - icon = mutateOnMainThread(icon); - int hoffset = (previewWidth - iconSize) / 2; - int yoffset = (previewHeight - iconSize) / 2; - icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize); - icon.draw(c); + // Draw icon in the center. + try { + Drawable icon = LauncherAppState.getInstance(mContext).getIconCache() + .getFullResIcon(info.provider.getPackageName(), info.icon); + if (icon != null) { + int appIconSize = dp.iconSizePx; + int iconSize = (int) Math.min(appIconSize * scale, + Math.min(boxRect.width(), boxRect.height())); + + icon = mutateOnMainThread(icon); + int hoffset = (previewWidthF - iconSize) / 2; + int yoffset = (previewHeightF - iconSize) / 2; + icon.setBounds(hoffset, yoffset, hoffset + iconSize, yoffset + iconSize); + icon.draw(c); + } + } catch (Resources.NotFoundException e) { } - } catch (Resources.NotFoundException e) { - savePreviewImage = false; } - c.setBitmap(null); - } - return new Pair<>(preview, savePreviewImage); + }); } private RectF drawBoxWithShadow(Canvas c, int width, int height) { @@ -537,42 +302,29 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { return builder.bounds; } - private Bitmap generateShortcutPreview(BaseActivity launcher, ShortcutConfigActivityInfo info, - int maxWidth, int maxHeight, Bitmap preview) { - int iconSize = launcher.getDeviceProfile().allAppsIconSizePx; - int padding = launcher.getResources() + private Bitmap generateShortcutPreview( + ShortcutConfigActivityInfo info, int maxWidth, int maxHeight) { + int iconSize = ActivityContext.lookupContext(mContext).getDeviceProfile().allAppsIconSizePx; + int padding = mContext.getResources() .getDimensionPixelSize(R.dimen.widget_preview_shortcut_padding); int size = iconSize + 2 * padding; if (maxHeight < size || maxWidth < size) { throw new RuntimeException("Max size is too small for preview"); } - final Canvas c = new Canvas(); - if (preview == null || preview.getWidth() < size || preview.getHeight() < size) { - preview = Bitmap.createBitmap(size, size, Config.ARGB_8888); - c.setBitmap(preview); - } else { - if (preview.getWidth() > size || preview.getHeight() > size) { - preview.reconfigure(size, size, preview.getConfig()); - } - - // Reusing bitmap. Clear it. - c.setBitmap(preview); - c.drawColor(0, PorterDuff.Mode.CLEAR); - } - - drawBoxWithShadow(c, size, size); - - LauncherIcons li = LauncherIcons.obtain(mContext); - Drawable icon = li.createBadgedIconBitmap( - mutateOnMainThread(info.getFullResIcon(mIconCache)), - Process.myUserHandle(), 0).newIcon(launcher); - li.recycle(); - - icon.setBounds(padding, padding, padding + iconSize, padding + iconSize); - icon.draw(c); - c.setBitmap(null); - return preview; + return BitmapRenderer.createHardwareBitmap(size, size, c -> { + drawBoxWithShadow(c, size, size); + + LauncherIcons li = LauncherIcons.obtain(mContext); + Drawable icon = li.createBadgedIconBitmap( + mutateOnMainThread(info.getFullResIcon( + LauncherAppState.getInstance(mContext).getIconCache())), + Process.myUserHandle(), 0).newIcon(mContext); + li.recycle(); + + icon.setBounds(padding, padding, padding + iconSize, padding + iconSize); + icon.draw(c); + }); } private Drawable mutateOnMainThread(final Drawable drawable) { @@ -585,206 +337,4 @@ public class DatabaseWidgetPreviewLoader implements WidgetPreviewLoader { throw new RuntimeException(e); } } - - /** - * @return an array of containing versionCode and lastUpdatedTime for the package. - */ - @Thunk long[] getPackageVersion(String packageName) { - synchronized (mPackageVersions) { - long[] versions = mPackageVersions.get(packageName); - if (versions == null) { - versions = new long[2]; - try { - PackageInfo info = mContext.getPackageManager().getPackageInfo(packageName, - PackageManager.GET_UNINSTALLED_PACKAGES); - versions[0] = info.versionCode; - versions[1] = info.lastUpdateTime; - } catch (NameNotFoundException e) { - Log.e(TAG, "PackageInfo not found", e); - } - mPackageVersions.put(packageName, versions); - } - return versions; - } - } - - private class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> - implements CancellationSignal.OnCancelListener { - @Thunk final WidgetCacheKey mKey; - private final WidgetItem mInfo; - private final int mPreviewHeight; - private final int mPreviewWidth; - private final WidgetPreviewLoadedCallback mCallback; - private final BaseActivity mActivity; - @Thunk long[] mVersions; - @Thunk Bitmap mBitmapToRecycle; - - @Nullable private Bitmap mUnusedPreviewBitmap; - private boolean mSaveToDB = false; - - PreviewLoadTask(BaseActivity activity, WidgetCacheKey key, WidgetItem info, - int previewWidth, int previewHeight, WidgetPreviewLoadedCallback callback) { - mActivity = activity; - mKey = key; - mInfo = info; - mPreviewHeight = previewHeight; - mPreviewWidth = previewWidth; - mCallback = callback; - if (DEBUG) { - Log.d(TAG, String.format("%s, %s, %d, %d", - mKey, mInfo, mPreviewHeight, mPreviewWidth)); - } - } - - @Override - protected Bitmap doInBackground(Void... params) { - Bitmap unusedBitmap = null; - - // If already cancelled before this gets to run in the background, then return early - if (isCancelled()) { - return null; - } - synchronized (mUnusedBitmaps) { - // Check if we can re-use a bitmap - for (Bitmap candidate : mUnusedBitmaps) { - if (candidate != null && candidate.isMutable() - && candidate.getWidth() == mPreviewWidth - && candidate.getHeight() == mPreviewHeight) { - unusedBitmap = candidate; - mUnusedBitmaps.remove(unusedBitmap); - break; - } - } - } - - // creating a bitmap is expensive. Do not do this inside synchronized block. - if (unusedBitmap == null) { - unusedBitmap = Bitmap.createBitmap(mPreviewWidth, mPreviewHeight, Config.ARGB_8888); - } - // If cancelled now, don't bother reading the preview from the DB - if (isCancelled()) { - return unusedBitmap; - } - Bitmap preview = readFromDb(mKey, unusedBitmap, this); - // Only consider generating the preview if we have not cancelled the task already - if (!isCancelled() && preview == null) { - // Fetch the version info before we generate the preview, so that, in-case the - // app was updated while we are generating the preview, we use the old version info, - // which would gets re-written next time. - boolean persistable = mInfo.activityInfo == null - || mInfo.activityInfo.isPersistable(); - mVersions = persistable ? getPackageVersion(mKey.componentName.getPackageName()) - : null; - - // it's not in the db... we need to generate it - Pair<Bitmap, Boolean> pair = generatePreview(mActivity, mInfo, unusedBitmap, - mPreviewWidth, mPreviewHeight); - preview = pair.first; - - if (preview != unusedBitmap) { - mUnusedPreviewBitmap = unusedBitmap; - } - - this.mSaveToDB = pair.second; - } - return preview; - } - - @Override - protected void onPostExecute(final Bitmap preview) { - mCallback.onPreviewLoaded(preview); - - // Write the generated preview to the DB in the worker thread - if (mVersions != null) { - MODEL_EXECUTOR.post(new Runnable() { - @Override - public void run() { - if (mUnusedPreviewBitmap != null) { - // If we didn't end up using the bitmap, it can be added back into the - // recycled set. - synchronized (mUnusedBitmaps) { - mUnusedBitmaps.add(mUnusedPreviewBitmap); - } - } - - if (!isCancelled() && mSaveToDB) { - // If we are still using this preview, then write it to the DB and then - // let the normal clear mechanism recycle the bitmap - writeToDb(mKey, mVersions, preview); - mBitmapToRecycle = preview; - } else { - // If we've already cancelled, then skip writing the bitmap to the DB - // and manually add the bitmap back to the recycled set - synchronized (mUnusedBitmaps) { - mUnusedBitmaps.add(preview); - } - } - } - }); - } else { - // If we don't need to write to disk, then ensure the preview gets recycled by - // the normal clear mechanism - mBitmapToRecycle = preview; - } - } - - @Override - protected void onCancelled(final Bitmap preview) { - // If we've cancelled while the task is running, then can return the bitmap to the - // recycled set immediately. Otherwise, it will be recycled after the preview is written - // to disk. - if (preview != null) { - MODEL_EXECUTOR.post(new Runnable() { - @Override - public void run() { - synchronized (mUnusedBitmaps) { - mUnusedBitmaps.add(preview); - } - } - }); - } - } - - @Override - public void onCancel() { - cancel(true); - - // This only handles the case where the PreviewLoadTask is cancelled after the task has - // successfully completed (including having written to disk when necessary). In the - // other cases where it is cancelled while the task is running, it will be cleaned up - // in the tasks's onCancelled() call, and if cancelled while the task is writing to - // disk, it will be cancelled in the task's onPostExecute() call. - if (mBitmapToRecycle != null) { - MODEL_EXECUTOR.post(new Runnable() { - @Override - public void run() { - synchronized (mUnusedBitmaps) { - mUnusedBitmaps.add(mBitmapToRecycle); - } - mBitmapToRecycle = null; - } - }); - } - } - } - - private static final class WidgetCacheKey extends ComponentKey { - - @Thunk final String mSize; - - WidgetCacheKey(ComponentName componentName, UserHandle user, String size) { - super(componentName, user); - this.mSize = size; - } - - @Override - public int hashCode() { - return super.hashCode() ^ mSize.hashCode(); - } - - @Override - public boolean equals(Object o) { - return super.equals(o) && ((WidgetCacheKey) o).mSize.equals(mSize); - } - } } |