diff options
author | Michael Jurka <mikejurka@google.com> | 2013-01-23 12:39:24 +0100 |
---|---|---|
committer | Michael Jurka <mikejurka@google.com> | 2013-02-04 20:32:49 +0100 |
commit | 05713af127d765cc28a8b2fd548a90347c90d6cb (patch) | |
tree | 71b8301d523d919a0195f8473527f12bdd7ffe39 /src/com/android/launcher2 | |
parent | 892d023c548a5e49b67b4c81ff1e3e9d02004e6e (diff) | |
download | android_packages_apps_Trebuchet-05713af127d765cc28a8b2fd548a90347c90d6cb.tar.gz android_packages_apps_Trebuchet-05713af127d765cc28a8b2fd548a90347c90d6cb.tar.bz2 android_packages_apps_Trebuchet-05713af127d765cc28a8b2fd548a90347c90d6cb.zip |
Cache widget previews in a DB
- Smoother All Apps scroll performance
Change-Id: Id2d31a45e71c63d05a46f580667ad94403730616
Diffstat (limited to 'src/com/android/launcher2')
-rw-r--r-- | src/com/android/launcher2/AppsCustomizePagedView.java | 336 | ||||
-rw-r--r-- | src/com/android/launcher2/DragController.java | 12 | ||||
-rw-r--r-- | src/com/android/launcher2/LauncherModel.java | 2 | ||||
-rw-r--r-- | src/com/android/launcher2/PackageChangedReceiver.java | 18 | ||||
-rw-r--r-- | src/com/android/launcher2/PagedViewWidget.java | 7 | ||||
-rw-r--r-- | src/com/android/launcher2/WidgetPreviewLoader.java | 606 |
6 files changed, 672 insertions, 309 deletions
diff --git a/src/com/android/launcher2/AppsCustomizePagedView.java b/src/com/android/launcher2/AppsCustomizePagedView.java index 1817c824f..bfc2db02c 100644 --- a/src/com/android/launcher2/AppsCustomizePagedView.java +++ b/src/com/android/launcher2/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; @@ -62,7 +54,6 @@ import android.widget.Toast; import com.android.launcher.R; import com.android.launcher2.DropTarget.DragObject; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -83,16 +74,6 @@ class AsyncTaskPageData { LoadWidgetPreviewData } - AsyncTaskPageData(int p, ArrayList<Object> l, ArrayList<Bitmap> si, AsyncTaskCallback bgR, - AsyncTaskCallback postR) { - page = p; - items = l; - sourceImages = si; - generatedImages = new ArrayList<Bitmap>(); - maxImageWidth = maxImageHeight = -1; - doInBackgroundCallback = bgR; - postExecuteCallback = postR; - } AsyncTaskPageData(int p, ArrayList<Object> l, int cw, int ch, AsyncTaskCallback bgR, AsyncTaskCallback postR) { page = p; @@ -105,18 +86,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(); @@ -167,64 +140,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. */ @@ -267,11 +182,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 float sWidgetPreviewIconPaddingPercentage = 0.25f; private PagedViewCellLayout mWidgetSpacingLayout; private int mNumAppsPages; private int mNumWidgetPages; @@ -323,6 +236,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen RectCache mCachedAppWidgetPreviewDestRect = new RectCache(); PaintCache mCachedAppWidgetPreviewPaint = new PaintCache(); + WidgetPreviewLoader mWidgetPreviewLoader; + public AppsCustomizePagedView(Context context, AttributeSet attrs) { super(context, attrs); mLayoutInflater = LayoutInflater.from(context); @@ -334,9 +249,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mRunningTasks = new ArrayList<AppsCustomizeAsyncTask>(); // Save the default widget preview background - Resources resources = context.getResources(); - 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); @@ -769,6 +681,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. @@ -788,19 +702,25 @@ 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 = (float) mv[0]; + + 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) v.getTag(); Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.shortcutActivityInfo); @@ -809,7 +729,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen 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); @@ -828,7 +748,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; @@ -1215,181 +1135,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 * sWidgetPreviewIconPaddingPercentage); - 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) { - } - } - - // 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 syncWidgetPageItems(final int page, final boolean immediate) { int numItemsPerPage = mWidgetCountX * mWidgetCountY; @@ -1475,6 +1220,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); @@ -1513,23 +1263,7 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen task.syncThreadPriority(); } - Object rawInfo = items.get(i); - if (rawInfo instanceof AppWidgetProviderInfo) { - AppWidgetProviderInfo info = (AppWidgetProviderInfo) rawInfo; - 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 (rawInfo instanceof ResolveInfo) { - // Fill in the shortcuts information - ResolveInfo info = (ResolveInfo) rawInfo; - images.add(getShortcutPreview(info, data.maxImageWidth, data.maxImageHeight)); - } + images.add(mWidgetPreviewLoader.getPreview(items.get(i))); } } @@ -1707,8 +1441,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/android/launcher2/DragController.java b/src/com/android/launcher2/DragController.java index 4c4295319..3e586bf61 100644 --- a/src/com/android/launcher2/DragController.java +++ b/src/com/android/launcher2/DragController.java @@ -175,16 +175,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/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java index ccf83282c..9a5bd47b3 100644 --- a/src/com/android/launcher2/LauncherModel.java +++ b/src/com/android/launcher2/LauncherModel.java @@ -1988,6 +1988,7 @@ public class LauncherModel extends BroadcastReceiver { for (int i=0; i<N; i++) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); mBgAllAppsList.updatePackage(context, packages[i]); + WidgetPreviewLoader.removeFromDb(context, packages[i]); } break; case OP_REMOVE: @@ -1995,6 +1996,7 @@ public class LauncherModel extends BroadcastReceiver { for (int i=0; i<N; i++) { if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); mBgAllAppsList.removePackage(packages[i]); + WidgetPreviewLoader.removeFromDb(context, packages[i]); } break; } diff --git a/src/com/android/launcher2/PackageChangedReceiver.java b/src/com/android/launcher2/PackageChangedReceiver.java new file mode 100644 index 000000000..ce08b3acd --- /dev/null +++ b/src/com/android/launcher2/PackageChangedReceiver.java @@ -0,0 +1,18 @@ +package com.android.launcher2; + +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/android/launcher2/PagedViewWidget.java b/src/com/android/launcher2/PagedViewWidget.java index b804ab0a2..1ee1c4ac0 100644 --- a/src/com/android/launcher2/PagedViewWidget.java +++ b/src/com/android/launcher2/PagedViewWidget.java @@ -46,6 +46,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); @@ -88,8 +89,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); } @@ -99,6 +100,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); @@ -116,6 +118,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); image.setContentDescription(label); diff --git a/src/com/android/launcher2/WidgetPreviewLoader.java b/src/com/android/launcher2/WidgetPreviewLoader.java new file mode 100644 index 000000000..2d1096778 --- /dev/null +++ b/src/com/android/launcher2/WidgetPreviewLoader.java @@ -0,0 +1,606 @@ +package com.android.launcher2; + +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.android.launcher.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); + } + } + +} |