Widget cell improvement

> Using a custom view for preview, instead of extensing image view
> Preventing relayout when applying preview
> Removing unnecessary method calls

Bug: 21133230
Change-Id: Iab12fa1e5c871ee43a9fb0e6b6af897fecfb345f
diff --git a/res/layout/widget_cell.xml b/res/layout/widget_cell.xml
index 500cf10..7fefeba 100644
--- a/res/layout/widget_cell.xml
+++ b/res/layout/widget_cell.xml
@@ -39,16 +39,15 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:gravity="start"
-            android:singleLine="true"
             android:ellipsize="end"
             android:fadingEdge="horizontal"
-            android:textColor="@color/widgets_view_item_text_color"
-            android:textSize="14sp"
-            android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
+            android:gravity="start"
+            android:shadowColor="#B0000000"
             android:shadowRadius="2.0"
-            android:shadowColor="#B0000000" />
+            android:singleLine="true"
+            android:textColor="@color/widgets_view_item_text_color"
+            android:textSize="14sp" />
 
         <!-- The original dimensions of the widget (can't be the same text as above due to different
              style. -->
@@ -58,22 +57,18 @@
             android:layout_height="wrap_content"
             android:layout_marginStart="5dp"
             android:layout_marginLeft="5dp"
-            android:layout_weight="0"
-            android:gravity="start"
             android:textColor="@color/widgets_view_item_text_color"
             android:textSize="14sp"
-            android:textAlignment="viewStart"
             android:fontFamily="sans-serif-condensed"
             android:shadowRadius="2.0"
             android:shadowColor="#B0000000" />
     </LinearLayout>
 
-    <!-- The image of the widget. -->
+    <!-- The image of the widget. This view does not support padding. Any placement adjustment
+         should be done using margins. -->
     <com.android.launcher3.widget.WidgetImageView
         android:id="@+id/widget_preview"
-        style="@style/WidgetImageView"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:scaleType="matrix" />
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" />
 </com.android.launcher3.widget.WidgetCell>
\ No newline at end of file
diff --git a/res/values-v17/styles.xml b/res/values-v17/styles.xml
deleted file mode 100644
index 3589e80..0000000
--- a/res/values-v17/styles.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
-    <style name="WidgetImageView">
-        <item name="android:paddingStart">@dimen/widget_preview_horizontal_padding</item>
-    </style>
-</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f95debe..1496da9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -86,9 +86,4 @@
         <item name="ringOutset">4dp</item>
     </style>
 
-    <!-- Overridden in device overlays -->
-    <style name="WidgetImageView">
-        <item name="android:paddingLeft">@dimen/widget_preview_horizontal_padding</item>
-    </style>
-
 </resources>
diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java
index f5c29ae..2191455 100644
--- a/src/com/android/launcher3/DragController.java
+++ b/src/com/android/launcher3/DragController.java
@@ -174,19 +174,18 @@
      * @param dragInfo The data associated with the object that is being dragged
      * @param dragAction The drag action: either {@link #DRAG_ACTION_MOVE} or
      *        {@link #DRAG_ACTION_COPY}
+     * @param viewImageBounds the position of the image inside the view
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          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,
-            Point extraPadding, float initialDragViewScale) {
+    public void startDrag(View v, Bitmap bmp, DragSource source, Object dragInfo,
+            Rect viewImageBounds, int dragAction, float initialDragViewScale) {
         int[] loc = mCoordinatesTemp;
         mLauncher.getDragLayer().getLocationInDragLayer(v, loc);
-        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() + viewExtraPaddingTop +
-                (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
+        int dragLayerX = loc[0] + viewImageBounds.left
+                + (int) ((initialDragViewScale * bmp.getWidth() - bmp.getWidth()) / 2);
+        int dragLayerY = loc[1] + viewImageBounds.top
+                + (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2);
 
         startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null,
                 null, initialDragViewScale, false);
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index e3eb76c..dcaf1f2 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -21,17 +21,14 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnLayoutChangeListener;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
@@ -41,7 +38,13 @@
 import com.android.launcher3.compat.AppWidgetManagerCompat;
 
 /**
- * Represents the individual cell of the widget inside the widget tray.
+ * Represents the individual cell of the widget inside the widget tray. The preview is drawn
+ * horizontally centered, and scaled down if needed.
+ *
+ * This view does not support padding. Since the image is scaled down to fit the view, padding will
+ * further decrease the scaling factor. Drag-n-drop uses the view bounds for showing a smooth
+ * transition from the view to drag view, so when adding padding support, DnD would need to
+ * consider the appropriate scaling factor.
  */
 public class WidgetCell extends LinearLayout implements OnLayoutChangeListener {
 
@@ -59,13 +62,11 @@
     private int mPresetPreviewSize;
     int cellSize;
 
-    private ImageView mWidgetImage;
+    private WidgetImageView mWidgetImage;
     private TextView mWidgetName;
     private TextView mWidgetDims;
-    private final Rect mOrigImgPadding = new Rect();
 
     private String mDimensionsFormatString;
-    private boolean mIsAppWidget;
     private Object mInfo;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
@@ -101,12 +102,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mWidgetImage = (ImageView) findViewById(R.id.widget_preview);
-        mOrigImgPadding.left = mWidgetImage.getPaddingLeft();
-        mOrigImgPadding.top = mWidgetImage.getPaddingTop();
-        mOrigImgPadding.right = mWidgetImage.getPaddingRight();
-        mOrigImgPadding.bottom = mWidgetImage.getPaddingBottom();
-
+        mWidgetImage = (WidgetImageView) findViewById(R.id.widget_preview);
         mWidgetName = ((TextView) findViewById(R.id.widget_name));
         mWidgetDims = ((TextView) findViewById(R.id.widget_dims));
     }
@@ -119,7 +115,7 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setImageDrawable(null);
+        mWidgetImage.setBitmap(null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
 
@@ -133,15 +129,11 @@
      * Apply the widget provider info to the view.
      */
     public void applyFromAppWidgetProviderInfo(LauncherAppWidgetProviderInfo info,
-            int maxWidth, WidgetPreviewLoader loader) {
+            WidgetPreviewLoader loader) {
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
 
-        mIsAppWidget = true;
         mInfo = info;
-        if (maxWidth > -1) {
-            mWidgetImage.setMaxWidth(maxWidth);
-        }
         // TODO(hyunyoungs): setup a cache for these labels.
         mWidgetName.setText(AppWidgetManagerCompat.getInstance(getContext()).loadLabel(info));
         int hSpan = Math.min(info.spanX, grid.numColumns);
@@ -155,7 +147,6 @@
      */
     public void applyFromResolveInfo(
             PackageManager pm, ResolveInfo info, WidgetPreviewLoader loader) {
-        mIsAppWidget = false;
         mInfo = info;
         CharSequence label = info.loadLabel(pm);
         mWidgetName.setText(label);
@@ -172,20 +163,8 @@
     }
 
     public void applyPreview(Bitmap bitmap) {
-        FastBitmapDrawable preview = new FastBitmapDrawable(bitmap);
-
-        if (preview != null) {
-            mWidgetImage.setImageDrawable(preview);
-
-            if (mIsAppWidget) {
-                // center horizontally
-                int[] imageSize = getPreviewSize();
-                int centerAmount = (imageSize[0] - preview.getIntrinsicWidth()) / 2;
-                mWidgetImage.setPadding(mOrigImgPadding.left + centerAmount,
-                        mOrigImgPadding.top,
-                        mOrigImgPadding.right,
-                        mOrigImgPadding.bottom);
-            }
+        if (bitmap != null) {
+            mWidgetImage.setBitmap(bitmap);
             mWidgetImage.setAlpha(0f);
             mWidgetImage.animate().alpha(1.0f).setDuration(FADE_IN_DURATION_MS);
         }
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index f1eaf64..6f8fd89 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,25 +17,73 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.util.AttributeSet;
-import android.widget.ImageView;
+import android.view.View;
 
-public class WidgetImageView extends ImageView {
+/**
+ * View that draws a bitmap horizontally centered. If the image width is greater than the view
+ * width, the image is scaled down appropriately.
+ */
+public class WidgetImageView extends View {
+
+    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+    private final RectF mDstRectF = new RectF();
+    private Bitmap mBitmap;
+
+    public WidgetImageView(Context context) {
+        super(context);
+    }
 
     public WidgetImageView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
+    public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        mBitmap = bitmap;
+        invalidate();
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
-        canvas.save();
-        canvas.clipRect(getScrollX() + getPaddingLeft(),
-                getScrollY() + getPaddingTop(),
-                getScrollX() + getRight() - getLeft() - getPaddingRight(),
-                getScrollY() + getBottom() - getTop() - getPaddingBottom());
+        if (mBitmap != null) {
+            updateDstRectF();
+            canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+        }
+    }
 
-        super.onDraw(canvas);
-        canvas.restore();
+    private void updateDstRectF() {
+        if (mBitmap.getWidth() > getWidth()) {
+            float scale = ((float) getWidth()) / mBitmap.getWidth();
+            mDstRectF.set(0, 0, getWidth(), scale * mBitmap.getHeight());
+        } else {
+            mDstRectF.set(
+                    (getWidth() - mBitmap.getWidth()) * 0.5f,
+                    0,
+                    (getWidth() + mBitmap.getWidth()) * 0.5f,
+                    mBitmap.getHeight());
+        }
+    }
+
+    /**
+     * @return the bounds where the image was drawn.
+     */
+    public Rect getBitmapBounds() {
+        updateDstRectF();
+        Rect rect = new Rect();
+        mDstRectF.round(rect);
+        return rect;
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 00fb225..181c08a 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v7.widget.LinearLayoutManager;
@@ -28,8 +27,8 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.Toast;
+
 import com.android.launcher3.BaseContainerView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeleteDropTarget;
@@ -37,10 +36,8 @@
 import com.android.launcher3.DragController;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Folder;
 import com.android.launcher3.IconCache;
-import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
@@ -222,20 +219,19 @@
 
     private boolean beginDraggingWidget(WidgetCell v) {
         // Get the widget preview as the drag representation
-        ImageView image = (ImageView) v.findViewById(R.id.widget_preview);
+        WidgetImageView image = (WidgetImageView) v.findViewById(R.id.widget_preview);
         PendingAddItemInfo createItemInfo = (PendingAddItemInfo) v.getTag();
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (image.getDrawable() == null) {
+        if (image.getBitmap() == null) {
             return false;
         }
 
         // Compose the drag image
         Bitmap preview;
-        Bitmap outline;
         float scale = 1f;
-        Point previewPadding = null;
+        final Rect bounds = image.getBitmapBounds();
 
         if (createItemInfo instanceof PendingAddWidgetInfo) {
             // This can happen in some weird cases involving multi-touch. We can't start dragging
@@ -244,25 +240,25 @@
             PendingAddWidgetInfo createWidgetInfo = (PendingAddWidgetInfo) createItemInfo;
             int[] size = mLauncher.getWorkspace().estimateItemSize(createWidgetInfo, true);
 
-            FastBitmapDrawable previewDrawable = (FastBitmapDrawable) image.getDrawable();
+            Bitmap icon = image.getBitmap();
             float minScale = 1.25f;
-            int maxWidth = Math.min((int) (previewDrawable.getIntrinsicWidth() * minScale), size[0]);
+            int maxWidth = Math.min((int) (icon.getWidth() * minScale), size[0]);
 
             int[] previewSizeBeforeScale = new int[1];
             preview = getWidgetPreviewLoader().generateWidgetPreview(createWidgetInfo.info,
                     maxWidth, null, previewSizeBeforeScale);
-            // Compare the size of the drag preview to the preview in the AppsCustomize tray
-            int previewWidthInAppsCustomize = Math.min(previewSizeBeforeScale[0],
-                    v.getActualItemWidth());
-            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);
+            if (previewSizeBeforeScale[0] < icon.getWidth()) {
+                // The icon has extra padding around it.
+                int padding = (icon.getWidth() - previewSizeBeforeScale[0]) / 2;
+                if (icon.getWidth() > image.getWidth()) {
+                    padding = padding * image.getWidth() / icon.getWidth();
+                }
+
+                bounds.left += padding;
+                bounds.right -= padding;
             }
+            scale = bounds.width() / (float) preview.getWidth();
         } else {
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) v.getTag();
             Drawable icon = mIconCache.getFullResIcon(createShortcutInfo.activityInfo);
@@ -274,16 +270,12 @@
         boolean clipAlpha = !(createItemInfo instanceof PendingAddWidgetInfo &&
                 (((PendingAddWidgetInfo) createItemInfo).previewImage == 0));
 
-        // Save the preview for the outline generation, then dim the preview
-        outline = Bitmap.createScaledBitmap(preview, preview.getWidth(), preview.getHeight(),
-                false);
-
         // Start the drag
         mLauncher.lockScreenOrientation();
-        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, outline, clipAlpha);
+        mLauncher.getWorkspace().onDragStartedWithItem(createItemInfo, preview, clipAlpha);
         mDragController.startDrag(image, preview, this, createItemInfo,
-                DragController.DRAG_ACTION_COPY, previewPadding, scale);
-        outline.recycle();
+                bounds, DragController.DRAG_ACTION_COPY, scale);
+
         preview.recycle();
         return true;
     }
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index 397d177..2f733dc 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -18,9 +18,9 @@
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
 import android.os.Build;
 import android.support.v7.widget.RecyclerView;
-import android.content.res.Resources;
 import android.support.v7.widget.RecyclerView.Adapter;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -29,15 +29,16 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.LinearLayout;
+
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DynamicGrid;
-import com.android.launcher3.IconCache;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+
 import java.util.List;
 
 /**
@@ -56,7 +57,6 @@
     private Context mContext;
     private Launcher mLauncher;
     private LayoutInflater mLayoutInflater;
-    private IconCache mIconCache;
 
     private WidgetsModel mWidgetsModel;
     private WidgetPreviewLoader mWidgetPreviewLoader;
@@ -76,9 +76,7 @@
 
         mIconClickListener = iconClickListener;
         mIconLongClickListener = iconLongClickListener;
-
         mLauncher = launcher;
-        mIconCache = LauncherAppState.getInstance().getIconCache();
 
         setContainerHeight();
     }
@@ -143,7 +141,7 @@
                 LauncherAppWidgetProviderInfo info = (LauncherAppWidgetProviderInfo) infoList.get(i);
                 PendingAddWidgetInfo pawi = new PendingAddWidgetInfo(info, null);
                 widget.setTag(pawi);
-                widget.applyFromAppWidgetProviderInfo(info, -1, mWidgetPreviewLoader);
+                widget.applyFromAppWidgetProviderInfo(info, mWidgetPreviewLoader);
             } else if (infoList.get(i) instanceof ResolveInfo) {
                 ResolveInfo info = (ResolveInfo) infoList.get(i);
                 PendingAddShortcutInfo pasi = new PendingAddShortcutInfo(info.activityInfo);