Adding support for showing the widget preview based on the provided RemoteViews

Bug: 35811129
Change-Id: I336e48cd00cfec2e617ac73bd8a81419b0944aa7
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index 689cc9b..0e91062 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -22,6 +22,7 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.UserHandle;
 import android.support.v4.graphics.ColorUtils;
@@ -89,14 +90,17 @@
      *
      * @return a request id which can be used to cancel the request.
      */
-    public PreviewLoadRequest getPreview(WidgetItem item, int previewWidth,
+    public CancellationSignal getPreview(WidgetItem item, int previewWidth,
             int previewHeight, WidgetCell caller) {
         String size = previewWidth + "x" + previewHeight;
         WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size);
 
         PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller);
         task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR);
-        return new PreviewLoadRequest(task);
+
+        CancellationSignal signal = new CancellationSignal();
+        signal.setOnCancelListener(task);
+        return signal;
     }
 
     /**
@@ -510,42 +514,8 @@
         }
     }
 
-    /**
-     * A request Id which can be used by the client to cancel any request.
-     */
-    public class PreviewLoadRequest {
-
-        @Thunk final PreviewLoadTask mTask;
-
-        public PreviewLoadRequest(PreviewLoadTask task) {
-            mTask = task;
-        }
-
-        public void cleanup() {
-            if (mTask != null) {
-                mTask.cancel(true);
-            }
-
-            // This only handles the case where the PreviewLoadTask is cancelled after the task has
-            // successfully completed (including having written to disk when necessary).  In the
-            // other cases where it is cancelled while the task is running, it will be cleaned up
-            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
-            // disk, it will be cancelled in the task's onPostExecute() call.
-            if (mTask.mBitmapToRecycle != null) {
-                mWorkerHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (mUnusedBitmaps) {
-                            mUnusedBitmaps.add(mTask.mBitmapToRecycle);
-                        }
-                        mTask.mBitmapToRecycle = null;
-                    }
-                });
-            }
-        }
-    }
-
-    public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap> {
+    public class PreviewLoadTask extends AsyncTask<Void, Void, Bitmap>
+            implements CancellationSignal.OnCancelListener {
         @Thunk final WidgetCacheKey mKey;
         private final WidgetItem mInfo;
         private final int mPreviewHeight;
@@ -661,6 +631,28 @@
                 });
             }
         }
+
+        @Override
+        public void onCancel() {
+            cancel(true);
+
+            // This only handles the case where the PreviewLoadTask is cancelled after the task has
+            // successfully completed (including having written to disk when necessary).  In the
+            // other cases where it is cancelled while the task is running, it will be cleaned up
+            // in the tasks's onCancelled() call, and if cancelled while the task is writing to
+            // disk, it will be cancelled in the task's onPostExecute() call.
+            if (mBitmapToRecycle != null) {
+                mWorkerHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (mUnusedBitmaps) {
+                            mUnusedBitmaps.add(mBitmapToRecycle);
+                        }
+                        mBitmapToRecycle = null;
+                    }
+                });
+            }
+        }
     }
 
     private static final class WidgetCacheKey extends ComponentKey {
diff --git a/src/com/android/launcher3/compat/PinItemRequestCompat.java b/src/com/android/launcher3/compat/PinItemRequestCompat.java
index f76b776..550bcc3 100644
--- a/src/com/android/launcher3/compat/PinItemRequestCompat.java
+++ b/src/com/android/launcher3/compat/PinItemRequestCompat.java
@@ -76,6 +76,14 @@
         }
     }
 
+    public Bundle getExtras() {
+        try {
+            return (Bundle) mObject.getClass().getDeclaredMethod("getExtras").invoke(mObject);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
     private Object invokeMethod(String methodName) {
         try {
             return mObject.getClass().getDeclaredMethod(methodName).invoke(mObject);
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index b80baf3..c2a4820 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -27,10 +27,8 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.*;
@@ -47,7 +45,6 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.WidgetHostViewLoader;
 import com.android.launcher3.widget.WidgetImageView;
 
@@ -65,7 +62,7 @@
     private LauncherAppState mApp;
     private InvariantDeviceProfile mIdp;
 
-    private WidgetCell mWidgetCell;
+    private LivePreviewWidgetCell mWidgetCell;
 
     // Widget request specific options.
     private AppWidgetHost mAppWidgetHost;
@@ -92,7 +89,7 @@
         mDeviceProfile = mIdp.getDeviceProfile(getApplicationContext());
 
         setContentView(R.layout.add_item_confirmation_activity);
-        mWidgetCell = (WidgetCell) findViewById(R.id.widget_cell);
+        mWidgetCell = (LivePreviewWidgetCell) findViewById(R.id.widget_cell);
 
         if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_SHORTCUT) {
             setupShortcut();
@@ -169,6 +166,7 @@
             // Cannot add widget
             return false;
         }
+        mWidgetCell.setPreview(PinItemDragListener.getPreview(mRequest));
 
         mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);
         mAppWidgetHost = new AppWidgetHost(this, Launcher.APPWIDGET_HOST_ID);
diff --git a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
new file mode 100644
index 0000000..36a0292
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -0,0 +1,99 @@
+package com.android.launcher3.dragndrop;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+
+import com.android.launcher3.BaseActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.WidgetCell;
+
+/**
+ * Extension of {@link WidgetCell} which supports generating previews from {@link RemoteViews}
+ */
+public class LivePreviewWidgetCell extends WidgetCell {
+
+    private RemoteViews mPreview;
+
+    public LivePreviewWidgetCell(Context context) {
+        this(context, null);
+    }
+
+    public LivePreviewWidgetCell(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LivePreviewWidgetCell(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setPreview(RemoteViews view) {
+        mPreview = view;
+    }
+
+    @Override
+    public void ensurePreview() {
+        if (mPreview != null && mActiveRequest == null) {
+            Bitmap preview = generateFromRemoteViews(
+                    mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
+            if (preview != null) {
+                applyPreview(preview);
+                return;
+            }
+        }
+        super.ensurePreview();
+    }
+
+    /**
+     * Generates a bitmap by inflating {@param views}.
+     * @see com.android.launcher3.WidgetPreviewLoader#generateWidgetPreview
+     *
+     * TODO: Consider moving this to the background thread.
+     */
+    public static Bitmap generateFromRemoteViews(BaseActivity activity, RemoteViews views,
+            LauncherAppWidgetProviderInfo info, int previewSize, int[] preScaledWidthOut) {
+
+        DeviceProfile dp = activity.getDeviceProfile();
+        int viewWidth = dp.cellWidthPx * info.spanX;
+        int viewHeight = dp.cellHeightPx * info.spanY;
+
+        final View v;
+        try {
+            v = views.apply(activity, new FrameLayout(activity));
+            v.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+            viewWidth = v.getMeasuredWidth();
+            viewHeight = v.getMeasuredHeight();
+            v.layout(0, 0, viewWidth, viewHeight);
+        } catch (Exception e) {
+            return null;
+        }
+
+        preScaledWidthOut[0] = viewWidth;
+        final int bitmapWidth, bitmapHeight;
+        final float scale;
+        if (viewWidth > previewSize) {
+            scale = ((float) previewSize) / viewWidth;
+            bitmapWidth = previewSize;
+            bitmapHeight = (int) (viewHeight * scale);
+        } else {
+            scale = 1;
+            bitmapWidth = viewWidth;
+            bitmapHeight = viewHeight;
+        }
+
+        Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(preview);
+        c.scale(scale, scale);
+        v.draw(c);
+        c.setBitmap(null);
+        return preview;
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 6e5318f..fd252a2 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -16,9 +16,11 @@
 
 package com.android.launcher3.dragndrop;
 
+import android.appwidget.AppWidgetManager;
 import android.content.ClipDescription;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Parcel;
@@ -27,6 +29,7 @@
 import android.util.Log;
 import android.view.DragEvent;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import com.android.launcher3.DeleteDropTarget;
 import com.android.launcher3.DragSource;
@@ -171,7 +174,12 @@
         // and the absolute position (position relative to the screen) of drag event is same
         // across windows, using drag position here give a good estimate for relative position
         // to source window.
-        new PendingItemDragHelper(view).startDrag(new Rect(mPreviewRect),
+        PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
+        if (mRequest.getRequestType() == PinItemRequestCompat.REQUEST_TYPE_APPWIDGET) {
+            dragHelper.setPreview(getPreview(mRequest));
+        }
+
+        dragHelper.startDrag(new Rect(mPreviewRect),
                 mPreviewBitmapWidth, mPreviewViewWidth, downPos,  this, options);
         mDragStartTime = SystemClock.uptimeMillis();
         return true;
@@ -250,6 +258,15 @@
         }
     }
 
+    public static RemoteViews getPreview(PinItemRequestCompat request) {
+        Bundle extras = request.getExtras();
+        if (extras != null &&
+                extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW) instanceof RemoteViews) {
+            return (RemoteViews) extras.get(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW);
+        }
+        return null;
+    }
+
     public static final Parcelable.Creator<PinItemDragListener> CREATOR =
             new Parcelable.Creator<PinItemDragListener>() {
                 public PinItemDragListener createFromParcel(Parcel source) {
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 6f4c286..b411879 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -23,6 +23,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.view.View;
+import android.widget.RemoteViews;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
@@ -32,6 +33,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.LivePreviewWidgetCell;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
 import com.android.launcher3.graphics.LauncherIcons;
@@ -47,12 +49,17 @@
     private final PendingAddItemInfo mAddInfo;
 
     private Bitmap mPreviewBitmap;
+    private RemoteViews mPreview;
 
     public PendingItemDragHelper(View view) {
         super(view);
         mAddInfo = (PendingAddItemInfo) view.getTag();
     }
 
+    public void setPreview(RemoteViews preview) {
+        mPreview = preview;
+    }
+
     /**
      * Starts the drag for the pending item associated with the view.
      *
@@ -67,7 +74,7 @@
         final Launcher launcher = Launcher.getLauncher(mView.getContext());
         LauncherAppState app = LauncherAppState.getInstance(launcher);
 
-        final Bitmap preview;
+        Bitmap preview = null;
         final float scale;
         final Point dragOffset;
         final Rect dragRegion;
@@ -80,8 +87,15 @@
             int maxWidth = Math.min((int) (previewBitmapWidth * MAX_WIDGET_SCALE), size[0]);
 
             int[] previewSizeBeforeScale = new int[1];
-            preview = app.getWidgetCache() .generateWidgetPreview(
-                    launcher, createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
+
+            if (mPreview != null) {
+                preview = LivePreviewWidgetCell.generateFromRemoteViews(launcher, mPreview,
+                        createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+            }
+            if (preview == null) {
+                preview = app.getWidgetCache().generateWidgetPreview(
+                        launcher, createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale);
+            }
 
             if (previewSizeBeforeScale[0] < previewBitmapWidth) {
                 // The icon has extra padding around it.
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 455ec4e..3bf622e 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.os.CancellationSignal;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -34,7 +35,6 @@
 import com.android.launcher3.SimpleOnStylusPressListener;
 import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
-import com.android.launcher3.WidgetPreviewLoader.PreviewLoadRequest;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.model.WidgetItem;
 
@@ -60,20 +60,21 @@
     /** Widget preview width is calculated by multiplying this factor to the widget cell width. */
     private static final float PREVIEW_SCALE = 0.8f;
 
-    private int mPresetPreviewSize;
+    protected int mPresetPreviewSize;
     private int mCellSize;
 
     private WidgetImageView mWidgetImage;
     private TextView mWidgetName;
     private TextView mWidgetDims;
 
-    private WidgetItem mItem;
+    protected WidgetItem mItem;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
-    private PreviewLoadRequest mActiveRequest;
     private StylusEventHelper mStylusEventHelper;
 
-    private final BaseActivity mActivity;
+    protected CancellationSignal mActiveRequest;
+
+    protected final BaseActivity mActivity;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -123,7 +124,7 @@
         mWidgetDims.setText(null);
 
         if (mActiveRequest != null) {
-            mActiveRequest.cleanup();
+            mActiveRequest.cancel();
             mActiveRequest = null;
         }
     }