diff options
author | Michael Jurka <mikejurka@google.com> | 2013-01-23 12:39:24 +0100 |
---|---|---|
committer | Steve Kondik <shade@chemlab.org> | 2013-08-17 02:35:26 -0700 |
commit | 5a0686bd65fcb249be11f1053849013750637e84 (patch) | |
tree | dce16a39b95291c967187e0a5aa800b4eaf67ce6 /src/com | |
parent | 03cddc8bdd8f3cb3a0a5f66fa22b561a7eaf29eb (diff) | |
download | android_packages_apps_Trebuchet-5a0686bd65fcb249be11f1053849013750637e84.tar.gz android_packages_apps_Trebuchet-5a0686bd65fcb249be11f1053849013750637e84.tar.bz2 android_packages_apps_Trebuchet-5a0686bd65fcb249be11f1053849013750637e84.zip |
Cache widget previews in a DB
- Smoother All Apps scroll performance
Change-Id: Id2d31a45e71c63d05a46f580667ad94403730616
Diffstat (limited to 'src/com')
6 files changed, 676 insertions, 315 deletions
diff --git a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java index d04cbb07e..6f6b7bd5f 100755 --- a/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java +++ b/src/com/cyanogenmod/trebuchet/AppsCustomizePagedView.java @@ -30,17 +30,9 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.graphics.Canvas; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PorterDuff; +import android.graphics.Point; import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; @@ -63,7 +55,6 @@ import org.cyanogenmod.support.ui.LiveFolder; import com.cyanogenmod.trebuchet.DropTarget.DragObject; import com.cyanogenmod.trebuchet.preference.PreferencesProvider; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -96,18 +87,10 @@ class AsyncTaskPageData { } void cleanup(boolean cancelled) { // Clean up any references to source/generated bitmaps - if (sourceImages != null) { - if (cancelled) { - for (Bitmap b : sourceImages) { - b.recycle(); - } - } - sourceImages.clear(); - } if (generatedImages != null) { if (cancelled) { - for (Bitmap b : generatedImages) { - b.recycle(); + for (int i = 0; i < generatedImages.size(); i++) { + WidgetPreviewLoader.releaseBitmap(items.get(i), generatedImages.get(i)); } } generatedImages.clear(); @@ -160,64 +143,6 @@ class AppsCustomizeAsyncTask extends AsyncTask<AsyncTaskPageData, Void, AsyncTas int threadPriority; } -abstract class WeakReferenceThreadLocal<T> { - private ThreadLocal<WeakReference<T>> mThreadLocal; - public WeakReferenceThreadLocal() { - mThreadLocal = new ThreadLocal<WeakReference<T>>(); - } - - abstract T initialValue(); - - public void set(T t) { - mThreadLocal.set(new WeakReference<T>(t)); - } - - public T get() { - WeakReference<T> reference = mThreadLocal.get(); - T obj; - if (reference == null) { - obj = initialValue(); - mThreadLocal.set(new WeakReference<T>(obj)); - return obj; - } else { - obj = reference.get(); - if (obj == null) { - obj = initialValue(); - mThreadLocal.set(new WeakReference<T>(obj)); - } - return obj; - } - } -} - -class CanvasCache extends WeakReferenceThreadLocal<Canvas> { - @Override - protected Canvas initialValue() { - return new Canvas(); - } -} - -class PaintCache extends WeakReferenceThreadLocal<Paint> { - @Override - protected Paint initialValue() { - return null; - } -} - -class BitmapCache extends WeakReferenceThreadLocal<Bitmap> { - @Override - protected Bitmap initialValue() { - return null; - } -} - -class RectCache extends WeakReferenceThreadLocal<Rect> { - @Override - protected Rect initialValue() { - return new Rect(); - } -} - /** * The Apps/Customize page that displays all the applications, widgets, and shortcuts. */ @@ -279,12 +204,9 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen // Dimens private int mContentWidth; - private int mAppIconSize; private int mMaxAppCellCountX, mMaxAppCellCountY; private int mWidgetCountX, mWidgetCountY; private int mWidgetWidthGap, mWidgetHeightGap; - private final int mWidgetPreviewIconPaddedDimension; - private static final float WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE = 0.25f; private PagedViewCellLayout mWidgetSpacingLayout; private int mNumAppsPages = 0; private int mNumWidgetPages = 0; @@ -364,6 +286,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen private static final int SCROLLING_INDICATOR_TOP = 1; private static final int SCROLLING_INDICATOR_BOTTOM = 0; + WidgetPreviewLoader mWidgetPreviewLoader; + public AppsCustomizePagedView(Context context, AttributeSet attrs) { super(context, attrs); mLayoutInflater = LayoutInflater.from(context); @@ -408,8 +332,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen } // Save the default widget preview background - mAppIconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size); - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppsCustomizePagedView, 0, 0); mMaxAppCellCountX = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountX, -1); mMaxAppCellCountY = a.getInt(R.styleable.AppsCustomizePagedView_maxAppCellCountY, -1); @@ -423,11 +345,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mClingFocusedY = a.getInt(R.styleable.AppsCustomizePagedView_clingFocusedY, 0); a.recycle(); mWidgetSpacingLayout = new PagedViewCellLayout(getContext()); - - // The padding on the non-matched dimension for the default widget preview icons - // (top + bottom) - mWidgetPreviewIconPaddedDimension = - (int) (mAppIconSize * (1 + (2 * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE))); } @Override @@ -911,6 +828,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen Bitmap preview; Bitmap outline; float scale = 1f; + Point previewPadding = null; + if (createItemInfo instanceof PendingAddWidgetInfo) { // This can happen in some weird cases involving multi-touch. We can't start dragging // the widget if this is null, so we break out. @@ -930,43 +849,39 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen int maxWidth, maxHeight; maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]); maxHeight = Math.min((int) (previewDrawable.getIntrinsicHeight() * minScale), size[1]); - preview = getWidgetPreview(createWidgetInfo.componentName, createWidgetInfo.previewImage, - createWidgetInfo.icon, spanX, spanY, maxWidth, maxHeight); - - // Determine the image view drawable scale relative to the preview - float[] mv = new float[9]; - Matrix m = new Matrix(); - m.setRectToRect( - new RectF(0f, 0f, (float) preview.getWidth(), (float) preview.getHeight()), - new RectF(0f, 0f, (float) previewDrawable.getIntrinsicWidth(), - (float) previewDrawable.getIntrinsicHeight()), - Matrix.ScaleToFit.START); - m.getValues(mv); - scale = mv[0]; - } else if (createItemInfo instanceof PendingAddShortcutInfo) { + + int[] previewSizeBeforeScale = new int[1]; + + preview = mWidgetPreviewLoader.generateWidgetPreview(createWidgetInfo.componentName, + createWidgetInfo.previewImage, createWidgetInfo.icon, spanX, spanY, + maxWidth, maxHeight, null, previewSizeBeforeScale); + + // Compare the size of the drag preview to the preview in the AppsCustomize tray + int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0], + mWidgetPreviewLoader.maxWidthForWidgetPreview(spanX)); + scale = previewWidthInAppsCustomize / (float) preview.getWidth(); + + // The bitmap in the AppsCustomize tray is always the the same size, so there + // might be extra pixels around the preview itself - this accounts for that + if (previewWidthInAppsCustomize < previewDrawable.getIntrinsicWidth()) { + int padding = + (previewDrawable.getIntrinsicWidth() - previewWidthInAppsCustomize) / 2; + previewPadding = new Point(padding, 0); + } + } else { PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) createItemInfo; + Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo); preview = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); mCanvas.setBitmap(preview); mCanvas.save(); - renderDrawableToBitmap(icon, preview, 0, 0, + WidgetPreviewLoader.renderDrawableToBitmap(icon, preview, 0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); mCanvas.restore(); mCanvas.setBitmap(null); createItemInfo.spanX = createItemInfo.spanY = 1; - } else { - // Workaround for the fact that we don't keep the original ResolveInfo associated with - // the shortcut around. To get the icon, we just render the preview image (which has - // the shortcut icon) to a new drag bitmap that clips the non-icon space. - preview = Bitmap.createBitmap(mWidgetPreviewIconPaddedDimension, - mWidgetPreviewIconPaddedDimension, Bitmap.Config.ARGB_8888); - Drawable d = image.getDrawable(); - mCanvas.setBitmap(preview); - d.draw(mCanvas); - mCanvas.setBitmap(null); - createItemInfo.spanX = createItemInfo.spanY = 1; } // Don't clip alpha values for the drag outline if we're using the default widget preview @@ -981,7 +896,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mLauncher.lockScreenOrientation(); mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha); mDragController.startDrag(image, preview, this, createItemInfo, - DragController.DRAG_ACTION_COPY, null, scale); + DragController.DRAG_ACTION_COPY, previewPadding, scale); outline.recycle(); preview.recycle(); return true; @@ -1388,180 +1303,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen layout.measure(widthSpec, heightSpec); } - private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h) { - renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); - } - - private void renderDrawableToBitmap(Drawable d, Bitmap bitmap, int x, int y, int w, int h, - float scale) { - if (bitmap != null) { - Canvas c = new Canvas(bitmap); - c.scale(scale, scale); - Rect oldBounds = d.copyBounds(); - d.setBounds(x, y, x + w, y + h); - d.draw(c); - d.setBounds(oldBounds); // Restore the bounds - c.setBitmap(null); - } - } - - private Bitmap getShortcutPreview(ResolveInfo info, int maxWidth, int maxHeight) { - Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get(); - final Canvas c = mCachedShortcutPreviewCanvas.get(); - if (tempBitmap == null || - tempBitmap.getWidth() != maxWidth || - tempBitmap.getHeight() != maxHeight) { - tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); - mCachedShortcutPreviewBitmap.set(tempBitmap); - } else { - c.setBitmap(tempBitmap); - c.drawColor(0, PorterDuff.Mode.CLEAR); - c.setBitmap(null); - } - // Render the icon - Drawable icon = mIconCache.getFullResIcon(info); - - int paddingTop = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); - int paddingLeft = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left); - int paddingRight = - getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right); - - int scaledIconWidth = (maxWidth - paddingLeft - paddingRight); - - renderDrawableToBitmap( - icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth); - - Bitmap preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); - c.setBitmap(preview); - Paint p = mCachedShortcutPreviewPaint.get(); - if (p == null) { - p = new Paint(); - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.setSaturation(0); - p.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); - p.setAlpha((int) (255 * 0.06f)); - //float density = 1f; - //p.setMaskFilter(new BlurMaskFilter(15*density, BlurMaskFilter.Blur.NORMAL)); - mCachedShortcutPreviewPaint.set(p); - } - c.drawBitmap(tempBitmap, 0, 0, p); - c.setBitmap(null); - - renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize); - - return preview; - } - - private Bitmap getWidgetPreview(ComponentName provider, int previewImage, int iconId, - int cellHSpan, int cellVSpan, int maxWidth, int maxHeight) { - // Load the preview image if possible - String packageName = provider.getPackageName(); - if (maxWidth < 0) maxWidth = Integer.MAX_VALUE; - if (maxHeight < 0) maxHeight = Integer.MAX_VALUE; - - Drawable drawable = null; - if (previewImage != 0) { - drawable = mPackageManager.getDrawable(packageName, previewImage, null); - if (drawable == null) { - Log.w(TAG, "Can't load widget preview drawable 0x" + - Integer.toHexString(previewImage) + " for provider: " + provider); - } - } - - int bitmapWidth; - int bitmapHeight; - Bitmap defaultPreview = null; - boolean widgetPreviewExists = (drawable != null); - if (widgetPreviewExists) { - bitmapWidth = drawable.getIntrinsicWidth(); - bitmapHeight = drawable.getIntrinsicHeight(); - } else { - // Generate a preview image if we couldn't load one - if (cellHSpan < 1) cellHSpan = 1; - if (cellVSpan < 1) cellVSpan = 1; - - BitmapDrawable previewDrawable = (BitmapDrawable) getResources() - .getDrawable(R.drawable.widget_preview_tile); - final int previewDrawableWidth = previewDrawable - .getIntrinsicWidth(); - final int previewDrawableHeight = previewDrawable - .getIntrinsicHeight(); - bitmapWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips - bitmapHeight = previewDrawableHeight * cellVSpan; - - defaultPreview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, - Config.ARGB_8888); - final Canvas c = mCachedAppWidgetPreviewCanvas.get(); - c.setBitmap(defaultPreview); - previewDrawable.setBounds(0, 0, bitmapWidth, bitmapHeight); - previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, - Shader.TileMode.REPEAT); - previewDrawable.draw(c); - c.setBitmap(null); - - // Draw the icon in the top left corner - int minOffset = (int) (mAppIconSize * WIDGET_PREVIEW_ICON_PADDING_PERCENTAGE); - int smallestSide = Math.min(bitmapWidth, bitmapHeight); - float iconScale = Math.min((float) smallestSide - / (mAppIconSize + 2 * minOffset), 1f); - - try { - Drawable icon = null; - int hoffset = - (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); - int yoffset = - (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); - if (iconId > 0) - icon = mIconCache.getFullResIcon(packageName, iconId); - if (icon != null) { - renderDrawableToBitmap(icon, defaultPreview, hoffset, - yoffset, (int) (mAppIconSize * iconScale), - (int) (mAppIconSize * iconScale)); - } - } catch (Resources.NotFoundException e) { - // Ignore - } - } - - // Scale to fit width only - let the widget preview be clipped in the - // vertical dimension - float scale = 1f; - if (bitmapWidth > maxWidth) { - scale = maxWidth / (float) bitmapWidth; - } - if (scale != 1f) { - bitmapWidth = (int) (scale * bitmapWidth); - bitmapHeight = (int) (scale * bitmapHeight); - } - - Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, - Config.ARGB_8888); - - // Draw the scaled preview into the final bitmap - if (widgetPreviewExists) { - renderDrawableToBitmap(drawable, preview, 0, 0, bitmapWidth, - bitmapHeight); - } else { - final Canvas c = mCachedAppWidgetPreviewCanvas.get(); - final Rect src = mCachedAppWidgetPreviewSrcRect.get(); - final Rect dest = mCachedAppWidgetPreviewDestRect.get(); - c.setBitmap(preview); - src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); - dest.set(0, 0, preview.getWidth(), preview.getHeight()); - - Paint p = mCachedAppWidgetPreviewPaint.get(); - if (p == null) { - p = new Paint(); - p.setFilterBitmap(true); - mCachedAppWidgetPreviewPaint.set(p); - } - c.drawBitmap(defaultPreview, src, dest, p); - c.setBitmap(null); - } - return preview; - } public void syncWidgetPages() { // Ensure that we have the right number of pages Context context = getContext(); @@ -1664,6 +1405,11 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen maxPreviewWidth = maxSize[0]; maxPreviewHeight = maxSize[1]; } + + if (mWidgetPreviewLoader == null) { + mWidgetPreviewLoader = new WidgetPreviewLoader( + maxPreviewWidth, maxPreviewHeight, mLauncher, mWidgetSpacingLayout); + } if (immediate) { AsyncTaskPageData data = new AsyncTaskPageData(page, items, maxPreviewWidth, maxPreviewHeight, null, null); @@ -1692,7 +1438,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen // Load each of the widget/shortcut previews ArrayList<Object> items = data.items; ArrayList<Bitmap> images = data.generatedImages; - for (Object item : items) { + int count = items.size(); + for (int i = 0; i < count; ++i) { if (task != null) { // Ensure we haven't been cancelled yet if (task.isCancelled()) break; @@ -1701,22 +1448,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen task.syncThreadPriority(); } - if (item instanceof AppWidgetProviderInfo) { - AppWidgetProviderInfo info = (AppWidgetProviderInfo) item; - int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); - - int maxWidth = Math.min(data.maxImageWidth, - mWidgetSpacingLayout.estimateCellWidth(cellSpans[0])); - int maxHeight = Math.min(data.maxImageHeight, - mWidgetSpacingLayout.estimateCellHeight(cellSpans[1])); - Bitmap b = getWidgetPreview(info.provider, info.previewImage, info.icon, - cellSpans[0], cellSpans[1], maxWidth, maxHeight); - images.add(b); - } else if (item instanceof ResolveInfo) { - // Fill in the shortcuts information - ResolveInfo info = (ResolveInfo) item; - images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight)); - } + images.add(mWidgetPreviewLoader.getPreview(items.get(i))); } } @@ -2024,8 +1756,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen } } - int newLeft = -1; - int newRight = -1; for (int i = 0; i < screenCount; i++) { final View layout = (View) getPageAt(i); diff --git a/src/com/cyanogenmod/trebuchet/DragController.java b/src/com/cyanogenmod/trebuchet/DragController.java index 91adb403a..f7e45fad3 100644 --- a/src/com/cyanogenmod/trebuchet/DragController.java +++ b/src/com/cyanogenmod/trebuchet/DragController.java @@ -173,16 +173,18 @@ public class DragController { * Makes dragging feel more precise, e.g. you can clip out a transparent border */ public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo, int dragAction, - Rect dragRegion, float initialDragViewScale) { + Point extraPadding, float initialDragViewScale) { int[] loc = mCoordinatesTemp; mLauncher.getDragLayer().getLocationInDragLayer(v, loc); - int dragLayerX = loc[0] + v.getPaddingLeft() + + int viewExtraPaddingLeft = extraPadding != null ? extraPadding.x : 0; + int viewExtraPaddingTop = extraPadding != null ? extraPadding.y : 0; + int dragLayerX = loc[0] + v.getPaddingLeft() + viewExtraPaddingLeft + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2); - int dragLayerY = loc[1] + v.getPaddingTop() + + int dragLayerY = loc[1] + v.getPaddingTop() + viewExtraPaddingTop + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); - startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, dragRegion, - initialDragViewScale); + startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, + null, initialDragViewScale); if (dragAction == DRAG_ACTION_MOVE) { v.setVisibility(View.GONE); diff --git a/src/com/cyanogenmod/trebuchet/LauncherModel.java b/src/com/cyanogenmod/trebuchet/LauncherModel.java index 7c1a7b976..a4308f517 100644 --- a/src/com/cyanogenmod/trebuchet/LauncherModel.java +++ b/src/com/cyanogenmod/trebuchet/LauncherModel.java @@ -2128,6 +2128,7 @@ public class LauncherModel extends BroadcastReceiver { for (String p : packages) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + p); mBgAllAppsList.updatePackage(context, p); + WidgetPreviewLoader.removeFromDb(context, p); } break; case OP_REMOVE: @@ -2135,6 +2136,7 @@ public class LauncherModel extends BroadcastReceiver { for (String p : packages) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + p); mBgAllAppsList.removePackage(p); + WidgetPreviewLoader.removeFromDb(context, p); } break; } diff --git a/src/com/cyanogenmod/trebuchet/PackageChangedReceiver.java b/src/com/cyanogenmod/trebuchet/PackageChangedReceiver.java new file mode 100644 index 000000000..25b423965 --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/PackageChangedReceiver.java @@ -0,0 +1,18 @@ +package com.cyanogenmod.trebuchet; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class PackageChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(final Context context, Intent intent) { + final String packageName = intent.getData().getSchemeSpecificPart(); + + if (packageName == null || packageName.length() == 0) { + // they sent us a bad intent + return; + } + WidgetPreviewLoader.removeFromDb(context, packageName); + } +} diff --git a/src/com/cyanogenmod/trebuchet/PagedViewWidget.java b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java index 61173cd5e..d2705778c 100644 --- a/src/com/cyanogenmod/trebuchet/PagedViewWidget.java +++ b/src/com/cyanogenmod/trebuchet/PagedViewWidget.java @@ -44,6 +44,7 @@ public class PagedViewWidget extends LinearLayout { static PagedViewWidget sShortpressTarget = null; boolean mIsAppWidget; private final Rect mOriginalImagePadding = new Rect(); + private Object mInfo; public PagedViewWidget(Context context) { this(context, null); @@ -86,8 +87,8 @@ public class PagedViewWidget extends LinearLayout { final ImageView image = (ImageView) findViewById(R.id.widget_preview); if (image != null) { FastBitmapDrawable preview = (FastBitmapDrawable) image.getDrawable(); - if (preview != null && preview.getBitmap() != null) { - preview.getBitmap().recycle(); + if (mInfo != null && preview != null && preview.getBitmap() != null) { + WidgetPreviewLoader.releaseBitmap(mInfo, preview.getBitmap()); } image.setImageDrawable(null); } @@ -97,6 +98,7 @@ public class PagedViewWidget extends LinearLayout { public void applyFromAppWidgetProviderInfo(AppWidgetProviderInfo info, int maxWidth, int[] cellSpan) { mIsAppWidget = true; + mInfo = info; final ImageView image = (ImageView) findViewById(R.id.widget_preview); if (maxWidth > -1) { image.setMaxWidth(maxWidth); @@ -113,6 +115,7 @@ public class PagedViewWidget extends LinearLayout { public void applyFromResolveInfo(PackageManager pm, ResolveInfo info) { mIsAppWidget = false; + mInfo = info; CharSequence label = info.loadLabel(pm); final ImageView image = (ImageView) findViewById(R.id.widget_preview); final TextView name = (TextView) findViewById(R.id.widget_name); diff --git a/src/com/cyanogenmod/trebuchet/WidgetPreviewLoader.java b/src/com/cyanogenmod/trebuchet/WidgetPreviewLoader.java new file mode 100644 index 000000000..5852ae86e --- /dev/null +++ b/src/com/cyanogenmod/trebuchet/WidgetPreviewLoader.java @@ -0,0 +1,606 @@ +package com.cyanogenmod.trebuchet; + +import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.util.Log; + +import com.cyanogenmod.trebuchet.R; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +abstract class SoftReferenceThreadLocal<T> { + private ThreadLocal<SoftReference<T>> mThreadLocal; + public SoftReferenceThreadLocal() { + mThreadLocal = new ThreadLocal<SoftReference<T>>(); + } + + abstract T initialValue(); + + public void set(T t) { + mThreadLocal.set(new SoftReference<T>(t)); + } + + public T get() { + SoftReference<T> reference = mThreadLocal.get(); + T obj; + if (reference == null) { + obj = initialValue(); + mThreadLocal.set(new SoftReference<T>(obj)); + return obj; + } else { + obj = reference.get(); + if (obj == null) { + obj = initialValue(); + mThreadLocal.set(new SoftReference<T>(obj)); + } + return obj; + } + } +} + +class CanvasCache extends SoftReferenceThreadLocal<Canvas> { + @Override + protected Canvas initialValue() { + return new Canvas(); + } +} + +class PaintCache extends SoftReferenceThreadLocal<Paint> { + @Override + protected Paint initialValue() { + return null; + } +} + +class BitmapCache extends SoftReferenceThreadLocal<Bitmap> { + @Override + protected Bitmap initialValue() { + return null; + } +} + +class RectCache extends SoftReferenceThreadLocal<Rect> { + @Override + protected Rect initialValue() { + return new Rect(); + } +} + +class BitmapFactoryOptionsCache extends SoftReferenceThreadLocal<BitmapFactory.Options> { + @Override + protected BitmapFactory.Options initialValue() { + return new BitmapFactory.Options(); + } +} + +public class WidgetPreviewLoader { + static final String TAG = "WidgetPreviewLoader"; + + private int mPreviewBitmapMaxWidth; + private int mPreviewBitmapMaxHeight; + private String mSize; + private Context mContext; + private Launcher mLauncher; + private PackageManager mPackageManager; + private PagedViewCellLayout mWidgetSpacingLayout; + + // Used for drawing shortcut previews + private BitmapCache mCachedShortcutPreviewBitmap = new BitmapCache(); + private PaintCache mCachedShortcutPreviewPaint = new PaintCache(); + private CanvasCache mCachedShortcutPreviewCanvas = new CanvasCache(); + + // Used for drawing widget previews + private CanvasCache mCachedAppWidgetPreviewCanvas = new CanvasCache(); + private RectCache mCachedAppWidgetPreviewSrcRect = new RectCache(); + private RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); + private PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); + private String mCachedSelectQuery; + private BitmapFactoryOptionsCache mCachedBitmapFactoryOptions = new BitmapFactoryOptionsCache(); + + private int mAppIconSize; + private IconCache mIconCache; + + private final float sWidgetPreviewIconPaddingPercentage = 0.25f; + + private WidgetPreviewCacheDb mDb; + + private static HashMap<String, WeakReference<Bitmap>> sLoadedPreviews; + private static ArrayList<SoftReference<Bitmap>> sUnusedBitmaps; + private static HashSet<String> sInvalidPackages; + + static { + sLoadedPreviews = new HashMap<String, WeakReference<Bitmap>>(); + sUnusedBitmaps = new ArrayList<SoftReference<Bitmap>>(); + sInvalidPackages = new HashSet<String>(); + } + + public WidgetPreviewLoader(int previewWidth, int previewHeight, + Launcher launcher, PagedViewCellLayout widgetSpacingLayout) { + mPreviewBitmapMaxWidth = previewWidth; + mPreviewBitmapMaxHeight = previewHeight; + mSize = previewWidth + "x" + previewHeight; + mContext = mLauncher = launcher; + mPackageManager = mContext.getPackageManager(); + mAppIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.app_icon_size); + mIconCache = ((LauncherApplication) launcher.getApplicationContext()).getIconCache(); + mWidgetSpacingLayout = widgetSpacingLayout; + mDb = new WidgetPreviewCacheDb(mContext); + } + + public Bitmap getPreview(final Object o) { + String name = getObjectName(o); + // check if the package is valid + boolean packageValid = true; + synchronized(sInvalidPackages) { + packageValid = !sInvalidPackages.contains(getObjectPackage(o)); + } + if (!packageValid) { + return null; + } + if (packageValid) { + synchronized(sLoadedPreviews) { + // check if it exists in our existing cache + if (sLoadedPreviews.containsKey(name) && sLoadedPreviews.get(name).get() != null) { + return sLoadedPreviews.get(name).get(); + } + } + } + + Bitmap unusedBitmap = null; + synchronized(sUnusedBitmaps) { + // not in cache; we need to load it from the db + while ((unusedBitmap == null || !unusedBitmap.isMutable()) + && sUnusedBitmaps.size() > 0) { + unusedBitmap = sUnusedBitmaps.remove(0).get(); + } + if (unusedBitmap != null) { + final Canvas c = mCachedAppWidgetPreviewCanvas.get(); + c.setBitmap(unusedBitmap); + c.drawColor(0, PorterDuff.Mode.CLEAR); + c.setBitmap(null); + } + } + + if (unusedBitmap == null) { + unusedBitmap = Bitmap.createBitmap(mPreviewBitmapMaxWidth, mPreviewBitmapMaxHeight, + Bitmap.Config.ARGB_8888); + } + + Bitmap preview = null; + + if (packageValid) { + preview = readFromDb(name, unusedBitmap); + } + + if (preview != null) { + synchronized(sLoadedPreviews) { + sLoadedPreviews.put(name, new WeakReference<Bitmap>(preview)); + } + return preview; + } else { + // it's not in the db... we need to generate it + final Bitmap generatedPreview = generatePreview(o, unusedBitmap); + preview = generatedPreview; + if (preview != unusedBitmap) { + throw new RuntimeException("generatePreview is not recycling the bitmap " + o); + } + + synchronized(sLoadedPreviews) { + sLoadedPreviews.put(name, new WeakReference<Bitmap>(preview)); + } + + // write to db on a thread pool... this can be done lazily and improves the performance + // of the first time widget previews are loaded + new AsyncTask<Void, Void, Void>() { + public Void doInBackground(Void ... args) { + writeToDb(o, generatedPreview); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); + + return preview; + } + } + + public static void releaseBitmap(Object o, Bitmap bitmapToFree) { + // enable this code when doDecode doesn't force Bitmaps to become immutable + String name = getObjectName(o); + synchronized(sLoadedPreviews) { + synchronized(sUnusedBitmaps) { + Bitmap b = sLoadedPreviews.get(name).get(); + if (b == bitmapToFree) { + sLoadedPreviews.remove(name); + if (bitmapToFree.isMutable()) { + sUnusedBitmaps.add(new SoftReference<Bitmap>(b)); + } + } else { + throw new RuntimeException("Bitmap passed in doesn't match up"); + } + } + } + } + + static class WidgetPreviewCacheDb extends SQLiteOpenHelper { + final static int DB_VERSION = 1; + final static String DB_NAME = "widgetpreviews.db"; + final static String TABLE_NAME = "shortcut_and_widget_previews"; + final static String COLUMN_NAME = "name"; + final static String COLUMN_SIZE = "size"; + final static String COLUMN_PREVIEW_BITMAP = "preview_bitmap"; + Context mContext; + + public WidgetPreviewCacheDb(Context context) { + super(context, new File(context.getCacheDir(), DB_NAME).getPath(), null, DB_VERSION); + // Store the context for later use + mContext = context; + } + + @Override + public void onCreate(SQLiteDatabase database) { + database.execSQL("CREATE TABLE " + TABLE_NAME + " (" + + COLUMN_NAME + " TEXT NOT NULL, " + + COLUMN_SIZE + " TEXT NOT NULL, " + + COLUMN_PREVIEW_BITMAP + " BLOB NOT NULL, " + + "PRIMARY KEY (" + COLUMN_NAME + ", " + COLUMN_SIZE + ") " + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // We are still only on version 1 + } + } + + private static final String WIDGET_PREFIX = "Widget:"; + private static final String SHORTCUT_PREFIX = "Shortcut:"; + + private static String getObjectName(Object o) { + // should cache the string builder + StringBuilder sb = new StringBuilder(); + String output; + if (o instanceof AppWidgetProviderInfo) { + sb.append(WIDGET_PREFIX); + sb.append(((AppWidgetProviderInfo) o).provider.flattenToString()); + output = sb.toString(); + sb.setLength(0); + } else { + sb.append(SHORTCUT_PREFIX); + + ResolveInfo info = (ResolveInfo) o; + sb.append(new ComponentName(info.activityInfo.packageName, + info.activityInfo.name).flattenToString()); + output = sb.toString(); + sb.setLength(0); + } + return output; + } + + private String getObjectPackage(Object o) { + if (o instanceof AppWidgetProviderInfo) { + return ((AppWidgetProviderInfo) o).provider.getPackageName(); + } else { + ResolveInfo info = (ResolveInfo) o; + return info.activityInfo.packageName; + } + } + + private void writeToDb(Object o, Bitmap preview) { + String name = getObjectName(o); + SQLiteDatabase db = mDb.getWritableDatabase(); + ContentValues values = new ContentValues(); + + values.put(WidgetPreviewCacheDb.COLUMN_NAME, name); + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + preview.compress(Bitmap.CompressFormat.PNG, 100, stream); + values.put(WidgetPreviewCacheDb.COLUMN_PREVIEW_BITMAP, stream.toByteArray()); + values.put(WidgetPreviewCacheDb.COLUMN_SIZE, mSize); + db.insert(WidgetPreviewCacheDb.TABLE_NAME, null, values); + } + + public static void removeFromDb(final Context mContext, final String packageName) { + synchronized(sInvalidPackages) { + sInvalidPackages.add(packageName); + } + new AsyncTask<Void, Void, Void>() { + public Void doInBackground(Void ... args) { + SQLiteDatabase db = new WidgetPreviewCacheDb(mContext).getWritableDatabase(); + db.delete(WidgetPreviewCacheDb.TABLE_NAME, + WidgetPreviewCacheDb.COLUMN_NAME + " LIKE ? OR " + + WidgetPreviewCacheDb.COLUMN_NAME + " LIKE ?", // SELECT query + new String[] { + WIDGET_PREFIX + packageName + "/%", + SHORTCUT_PREFIX + packageName + "/%"} // args to SELECT query + ); + db.close(); + synchronized(sInvalidPackages) { + sInvalidPackages.remove(packageName); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); + } + + private Bitmap readFromDb(String name, Bitmap b) { + if (mCachedSelectQuery == null) { + mCachedSelectQuery = WidgetPreviewCacheDb.COLUMN_NAME + " = ? AND " + + WidgetPreviewCacheDb.COLUMN_SIZE + " = ?"; + } + SQLiteDatabase db = mDb.getReadableDatabase(); + Cursor result = db.query(WidgetPreviewCacheDb.TABLE_NAME, + new String[] { WidgetPreviewCacheDb.COLUMN_PREVIEW_BITMAP }, // cols to return + mCachedSelectQuery, // select query + new String[] { name, mSize }, // args to select query + null, + null, + null, + null); + if (result.getCount() > 0) { + result.moveToFirst(); + byte[] blob = result.getBlob(0); + result.close(); + final BitmapFactory.Options opts = mCachedBitmapFactoryOptions.get(); + opts.inBitmap = b; + opts.inSampleSize = 1; + Bitmap out = BitmapFactory.decodeByteArray(blob, 0, blob.length, opts); + return out; + } else { + result.close(); + return null; + } + } + + public Bitmap generatePreview(Object info, Bitmap preview) { + if (preview != null && + (preview.getWidth() != mPreviewBitmapMaxWidth || + preview.getHeight() != mPreviewBitmapMaxHeight)) { + throw new RuntimeException("Improperly sized bitmap passed as argument"); + } + if (info instanceof AppWidgetProviderInfo) { + return generateWidgetPreview((AppWidgetProviderInfo) info, preview); + } else { + return generateShortcutPreview( + (ResolveInfo) info, mPreviewBitmapMaxWidth, mPreviewBitmapMaxHeight, preview); + } + } + + public Bitmap generateWidgetPreview(AppWidgetProviderInfo info, Bitmap preview) { + // should just read what's in the DB, and return it + // what's the method that's used? + // going to have to track down more or less how the DB stuff works + // when you long click on something, we're going to have to load the bigger preview at that + // time (worry about it later) + int[] cellSpans = Launcher.getSpanForWidget(mLauncher, info); + int maxWidth = maxWidthForWidgetPreview(cellSpans[0]); + int maxHeight = maxHeightForWidgetPreview(cellSpans[1]); + return generateWidgetPreview(info.provider, info.previewImage, info.icon, + cellSpans[0], cellSpans[1], maxWidth, maxHeight, preview, null); + } + + public int maxWidthForWidgetPreview(int spanX) { + return Math.min(mPreviewBitmapMaxWidth, + mWidgetSpacingLayout.estimateCellWidth(spanX)); + } + + public int maxHeightForWidgetPreview(int spanY) { + return Math.min(mPreviewBitmapMaxHeight, + mWidgetSpacingLayout.estimateCellHeight(spanY)); + } + + public Bitmap generateWidgetPreview(ComponentName provider, int previewImage, + int iconId, int cellHSpan, int cellVSpan, int maxPreviewWidth, int maxPreviewHeight, + Bitmap preview, int[] preScaledWidthOut) { + // Load the preview image if possible + String packageName = provider.getPackageName(); + if (maxPreviewWidth < 0) maxPreviewWidth = Integer.MAX_VALUE; + if (maxPreviewHeight < 0) maxPreviewHeight = Integer.MAX_VALUE; + + Drawable drawable = null; + if (previewImage != 0) { + drawable = mPackageManager.getDrawable(packageName, previewImage, null); + if (drawable == null) { + Log.w(TAG, "Can't load widget preview drawable 0x" + + Integer.toHexString(previewImage) + " for provider: " + provider); + } + } + + int previewWidth; + int previewHeight; + Bitmap defaultPreview = null; + boolean widgetPreviewExists = (drawable != null); + if (widgetPreviewExists) { + previewWidth = drawable.getIntrinsicWidth(); + previewHeight = drawable.getIntrinsicHeight(); + } else { + // Generate a preview image if we couldn't load one + if (cellHSpan < 1) cellHSpan = 1; + if (cellVSpan < 1) cellVSpan = 1; + + BitmapDrawable previewDrawable = (BitmapDrawable) mContext.getResources() + .getDrawable(R.drawable.widget_preview_tile); + final int previewDrawableWidth = previewDrawable + .getIntrinsicWidth(); + final int previewDrawableHeight = previewDrawable + .getIntrinsicHeight(); + previewWidth = previewDrawableWidth * cellHSpan; // subtract 2 dips + previewHeight = previewDrawableHeight * cellVSpan; + + defaultPreview = Bitmap.createBitmap(previewWidth, previewHeight, + Config.ARGB_8888); + final Canvas c = mCachedAppWidgetPreviewCanvas.get(); + c.setBitmap(defaultPreview); + previewDrawable.setBounds(0, 0, previewWidth, previewHeight); + previewDrawable.setTileModeXY(Shader.TileMode.REPEAT, + Shader.TileMode.REPEAT); + previewDrawable.draw(c); + c.setBitmap(null); + + // Draw the icon in the top left corner + int minOffset = (int) (mAppIconSize * sWidgetPreviewIconPaddingPercentage); + int smallestSide = Math.min(previewWidth, previewHeight); + float iconScale = Math.min((float) smallestSide + / (mAppIconSize + 2 * minOffset), 1f); + + try { + Drawable icon = null; + int hoffset = + (int) ((previewDrawableWidth - mAppIconSize * iconScale) / 2); + int yoffset = + (int) ((previewDrawableHeight - mAppIconSize * iconScale) / 2); + if (iconId > 0) + icon = mIconCache.getFullResIcon(packageName, iconId); + if (icon != null) { + renderDrawableToBitmap(icon, defaultPreview, hoffset, + yoffset, (int) (mAppIconSize * iconScale), + (int) (mAppIconSize * iconScale)); + } + } catch (Resources.NotFoundException e) { + } + } + + // 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; + } + if (scale != 1f) { + previewWidth = (int) (scale * previewWidth); + previewHeight = (int) (scale * previewHeight); + } + + // If a bitmap is passed in, we use it; otherwise, we create a bitmap of the right size + if (preview == null) { + preview = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888); + } + + // Draw the scaled preview into the final bitmap + int x = (preview.getWidth() - previewWidth) / 2; + if (widgetPreviewExists) { + renderDrawableToBitmap(drawable, preview, x, 0, previewWidth, + previewHeight); + } else { + final Canvas c = mCachedAppWidgetPreviewCanvas.get(); + final Rect src = mCachedAppWidgetPreviewSrcRect.get(); + final Rect dest = mCachedAppWidgetPreviewDestRect.get(); + c.setBitmap(preview); + src.set(0, 0, defaultPreview.getWidth(), defaultPreview.getHeight()); + dest.set(x, 0, previewWidth, previewHeight); + + Paint p = mCachedAppWidgetPreviewPaint.get(); + if (p == null) { + p = new Paint(); + p.setFilterBitmap(true); + mCachedAppWidgetPreviewPaint.set(p); + } + c.drawBitmap(defaultPreview, src, dest, p); + c.setBitmap(null); + } + return preview; + } + + private Bitmap generateShortcutPreview( + ResolveInfo info, int maxWidth, int maxHeight, Bitmap preview) { + Bitmap tempBitmap = mCachedShortcutPreviewBitmap.get(); + final Canvas c = mCachedShortcutPreviewCanvas.get(); + if (tempBitmap == null || + tempBitmap.getWidth() != maxWidth || + tempBitmap.getHeight() != maxHeight) { + tempBitmap = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); + mCachedShortcutPreviewBitmap.set(tempBitmap); + } else { + c.setBitmap(tempBitmap); + c.drawColor(0, PorterDuff.Mode.CLEAR); + c.setBitmap(null); + } + // Render the icon + Drawable icon = mIconCache.getFullResIcon(info); + + int paddingTop = mContext. + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_top); + int paddingLeft = mContext. + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_left); + int paddingRight = mContext. + getResources().getDimensionPixelOffset(R.dimen.shortcut_preview_padding_right); + + int scaledIconWidth = (maxWidth - paddingLeft - paddingRight); + + renderDrawableToBitmap( + icon, tempBitmap, paddingLeft, paddingTop, scaledIconWidth, scaledIconWidth); + + if (preview != null && + (preview.getWidth() != maxWidth || preview.getHeight() != maxHeight)) { + throw new RuntimeException("Improperly sized bitmap passed as argument"); + } else if (preview == null) { + preview = Bitmap.createBitmap(maxWidth, maxHeight, Config.ARGB_8888); + } + + c.setBitmap(preview); + // Draw a desaturated/scaled version of the icon in the background as a watermark + Paint p = mCachedShortcutPreviewPaint.get(); + if (p == null) { + p = new Paint(); + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.setSaturation(0); + p.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); + p.setAlpha((int) (255 * 0.06f)); + mCachedShortcutPreviewPaint.set(p); + } + c.drawBitmap(tempBitmap, 0, 0, p); + c.setBitmap(null); + + renderDrawableToBitmap(icon, preview, 0, 0, mAppIconSize, mAppIconSize); + + return preview; + } + + + public static void renderDrawableToBitmap( + Drawable d, Bitmap bitmap, int x, int y, int w, int h) { + renderDrawableToBitmap(d, bitmap, x, y, w, h, 1f); + } + + private static void renderDrawableToBitmap( + Drawable d, Bitmap bitmap, int x, int y, int w, int h, + float scale) { + if (bitmap != null) { + Canvas c = new Canvas(bitmap); + c.scale(scale, scale); + Rect oldBounds = d.copyBounds(); + d.setBounds(x, y, x + w, y + h); + d.draw(c); + d.setBounds(oldBounds); // Restore the bounds + c.setBitmap(null); + } + } + +} |