Adding support for multiwindow drag and drop

Change-Id: I95b46e3c3f1238307d3ef5a6c81a8e530ba0987a
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 2dde7ca..cf8abae 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -259,7 +259,8 @@
             }
         };
         dragLayer.animateView(d.dragView, from, to, scale, 1f, 1f, 0.1f, 0.1f,
-                DRAG_VIEW_DROP_DURATION, new DecelerateInterpolator(2),
+                mLauncher.getDragController().isExternalDrag() ? 1 : DRAG_VIEW_DROP_DURATION,
+                new DecelerateInterpolator(2),
                 new LinearInterpolator(), onAnimationEndRunnable,
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 9d7be16..fb93743 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -158,7 +158,7 @@
      */
     Intent promisedIntent;
 
-    ShortcutInfo() {
+    public ShortcutInfo() {
         itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
     }
 
diff --git a/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
new file mode 100644
index 0000000..156941a
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AnotherWindowDragSource.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
+/**
+ * DragSource used when the drag started at another window.
+ */
+public class AnotherWindowDragSource implements DragSource {
+
+    private final Context mContext;
+
+    AnotherWindowDragSource(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean supportsFlingToDelete() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsAppInfoDropTarget() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDeleteDropTarget() {
+        return false;
+    }
+
+    @Override
+    public float getIntrinsicIconScaleFactor() {
+        return 1;
+    }
+
+    @Override
+    public void onFlingToDeleteCompleted() {
+    }
+
+    @Override
+    public void onDropCompleted(View target, DragObject d,
+            boolean isFlingToDelete, boolean success) {
+        if (!success) {
+            Launcher.getLauncher(mContext).exitSpringLoadedDragModeDelayed(false, 0, null);
+        }
+
+    }
+
+    @Override
+    public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
+        // TODO: Probably log something
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 29e33e9..a93ee90 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
-import android.util.Log;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -213,6 +212,12 @@
         }
         mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
 
+        mOptions = options;
+        if (mOptions.systemDndStartPoint != null) {
+            mMotionDownX = mOptions.systemDndStartPoint.x;
+            mMotionDownY = mOptions.systemDndStartPoint.y;
+        }
+
         final int registrationX = mMotionDownX - dragLayerX;
         final int registrationY = mMotionDownY - dragLayerY;
 
@@ -221,7 +226,6 @@
 
         mLastDropTarget = null;
 
-        mOptions = options;
         mDragObject = new DropTarget.DragObject();
 
         final Resources res = mLauncher.getResources();
@@ -241,7 +245,7 @@
             mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
             mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
 
-            mDragDriver = DragDriver.create(this, dragInfo, dragView);
+            mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
         }
 
         mDragObject.dragSource = source;
@@ -293,6 +297,10 @@
         return mDragDriver != null || (mOptions != null && mOptions.isAccessibleDrag);
     }
 
+    public boolean isExternalDrag() {
+        return (mOptions != null && mOptions.systemDndStartPoint != null);
+    }
+
     /**
      * Stop dragging without dropping.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 2164708..4db8c07 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -17,18 +17,19 @@
 package com.android.launcher3.dragndrop;
 
 import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
 import android.content.Intent;
-import android.graphics.Canvas;
-import android.graphics.Point;
 import android.view.DragEvent;
 import android.view.MotionEvent;
-import android.view.View;
 
-import com.android.launcher3.AnotherWindowDropTarget;
 import com.android.launcher3.DropTarget;
-import com.android.launcher3.ItemInfo;
+import com.android.launcher3.DropTarget.DragObject;
+import com.android.launcher3.InstallShortcutReceiver;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
+
+import java.util.ArrayList;
 
 /**
  * Base class for driving a drag/drop operation.
@@ -50,7 +51,7 @@
     /**
      * Handles ending of the DragView animation.
      */
-    public abstract void onDragViewAnimationEnd();
+    public void onDragViewAnimationEnd() { }
 
     public boolean onTouchEvent(MotionEvent ev) {
         final int action = ev.getAction();
@@ -89,100 +90,46 @@
         return true;
     }
 
-    public static DragDriver create(
-            DragController dragController, ItemInfo dragInfo, DragView dragView) {
-        if (FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER && Utilities.isNycOrAbove()) {
-            return new SystemDragDriver(dragController, dragInfo.getIntent(), dragView);
+    public static DragDriver create(Context context, DragController dragController,
+            DragObject dragObject, DragOptions options) {
+        if (Utilities.isNycOrAbove() && options.systemDndStartPoint != null) {
+            return new SystemDragDriver(dragController, context, dragObject);
         } else {
             return new InternalDragDriver(dragController);
         }
     }
-
-};
+}
 
 /**
  * Class for driving a system (i.e. framework) drag/drop operation.
  */
 class SystemDragDriver extends DragDriver {
-    /** Intent associated with the drag operation, or null is there no associated intent.  */
-    private final Intent mDragIntent;
 
-    private final DragView mDragView;
-    boolean mIsFrameworkDragActive = false;
+    private final DragObject mDragObject;
+    private final Context mContext;
+
     boolean mReceivedDropEvent = false;
     float mLastX = 0;
     float mLastY = 0;
 
-    public SystemDragDriver(DragController dragController, Intent dragIntent, DragView dragView) {
+    public SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
         super(dragController);
-        mDragIntent = dragIntent;
-        mDragView = dragView;
-    }
-
-    private static class ShadowBuilder extends View.DragShadowBuilder {
-        final DragView mDragView;
-
-        public ShadowBuilder(DragView dragView) {
-            mDragView = dragView;
-        }
-
-        @Override
-        public void onProvideShadowMetrics (Point size, Point touch) {
-            mDragView.provideDragShadowMetrics(size, touch);
-        }
-
-        @Override
-        public void onDrawShadow(Canvas canvas) {
-            mDragView.drawDragShadow(canvas);
-        }
-    };
-
-    @Override
-    public void onDragViewAnimationEnd() {
-        // Clip data for the drag operation. If there is an intent, create an intent-based ClipData,
-        // which will be passed to a global DND.
-        // If there is no intent, craft a fake ClipData and start a local DND operation; this
-        // ClipData will be ignored.
-        final ClipData dragData = mDragIntent != null ?
-                ClipData.newIntent("", mDragIntent) :
-                ClipData.newPlainText("", "");
-
-        View.DragShadowBuilder shadowBuilder = new ShadowBuilder(mDragView);
-        // TODO: DND flags are in flux, once settled, use the appropriate constant.
-        final int flagGlobal = 1 << 0;
-        final int flagOpaque = 1 << 9;
-        final int flags = (mDragIntent != null ? flagGlobal : 0) | flagOpaque;
-
-        mIsFrameworkDragActive = true;
-
-        if (!mDragView.startDrag(dragData, shadowBuilder, null, flags)) {
-            mIsFrameworkDragActive = false;
-            mEventListener.onDriverDragCancel();
-            return;
-        }
-
-        // Starting from this point, the driver takes over showing the drag shadow, so hiding the
-        // drag view.
-        mDragView.setVisibility(View.INVISIBLE);
+        mDragObject = dragObject;
+        mContext = context;
     }
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        return !mIsFrameworkDragActive && super.onTouchEvent(ev);
+        return false;
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return !mIsFrameworkDragActive && super.onInterceptTouchEvent(ev);
+        return false;
     }
 
     @Override
     public boolean onDragEvent (DragEvent event) {
-        if (!mIsFrameworkDragActive) {
-            // We are interested only in drag events started by this driver.
-            return false;
-        }
-
         final int action = event.getAction();
 
         switch (action) {
@@ -192,8 +139,6 @@
                 return true;
 
             case DragEvent.ACTION_DRAG_ENTERED:
-                mLastX = event.getX();
-                mLastY = event.getY();
                 return true;
 
             case DragEvent.ACTION_DRAG_LOCATION:
@@ -205,35 +150,66 @@
             case DragEvent.ACTION_DROP:
                 mLastX = event.getX();
                 mLastY = event.getY();
-                mReceivedDropEvent = true;
-                return true;
+                mReceivedDropEvent =
+                        updateInfoFromClipData(event.getClipData(), event.getClipDescription());
+                return mReceivedDropEvent;
 
             case DragEvent.ACTION_DRAG_EXITED:
-                mLastX = event.getX();
-                mLastY = event.getY();
                 mEventListener.onDriverDragExitWindow();
                 return true;
 
             case DragEvent.ACTION_DRAG_ENDED:
-                final boolean dragAccepted = event.getResult();
-                final boolean acceptedByAnotherWindow = dragAccepted && !mReceivedDropEvent;
-
-                // When the system drag ends, its drag shadow disappears. Resume showing the drag
-                // view for the possible final animation.
-                mDragView.setVisibility(View.VISIBLE);
-
-                final DropTarget dropTargetOverride = acceptedByAnotherWindow ?
-                        new AnotherWindowDropTarget(mDragView.getContext()) : null;
-
-                mEventListener.onDriverDragEnd(mLastX, mLastY, dropTargetOverride);
-                mIsFrameworkDragActive = false;
+                if (mReceivedDropEvent) {
+                    mEventListener.onDriverDragEnd(mLastX, mLastY, null);
+                } else {
+                    mEventListener.onDriverDragCancel();
+                }
                 return true;
 
             default:
                 return false;
         }
     }
-};
+
+    private boolean updateInfoFromClipData(ClipData data, ClipDescription desc) {
+        if (data == null) {
+            return false;
+        }
+        ArrayList<Intent> intents = new ArrayList<>();
+        int itemCount = data.getItemCount();
+        for (int i = 0; i < itemCount; i++) {
+            Intent intent = data.getItemAt(i).getIntent();
+            if (intent == null) {
+                continue;
+            }
+
+            // Give preference to shortcut intents.
+            if (!Intent.ACTION_CREATE_SHORTCUT.equals(intent.getAction())) {
+                intents.add(intent);
+                continue;
+            }
+            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, intent);
+            if (info != null) {
+                mDragObject.dragInfo = info;
+                return true;
+            }
+            return true;
+        }
+
+        // Try creating shortcuts just using the intent and label
+        Intent fullIntent = new Intent().putExtra(Intent.EXTRA_SHORTCUT_NAME, desc.getLabel());
+        for (Intent intent : intents) {
+            fullIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, intent);
+            ShortcutInfo info = InstallShortcutReceiver.fromShortcutIntent(mContext, fullIntent);
+            if (info != null) {
+                mDragObject.dragInfo = info;
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
 
 /**
  * Class for driving an internal (i.e. not using framework) drag/drop operation.
@@ -244,8 +220,5 @@
     }
 
     @Override
-    public void onDragViewAnimationEnd() {}
-
-    @Override
     public boolean onDragEvent (DragEvent event) { return false; }
 };
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 5863828..016347b 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -21,14 +21,22 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.TargetApi;
+import android.content.ClipDescription;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
+import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -45,12 +53,14 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTargetBar;
 import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetHostView;
 import com.android.launcher3.PinchToOverviewListener;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsTransitionController;
@@ -62,6 +72,7 @@
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 
 /**
@@ -431,8 +442,46 @@
         return false;
     }
 
+    @TargetApi(Build.VERSION_CODES.N)
+    private void handleSystemDragStart(DragEvent event) {
+        if (!FeatureFlags.LAUNCHER3_USE_SYSTEM_DRAG_DRIVER || !Utilities.isNycOrAbove()) {
+            return;
+        }
+        if (mLauncher.isWorkspaceLocked()) {
+            return;
+        }
+
+        ClipDescription description = event.getClipDescription();
+        if (!description.hasMimeType(ClipDescription.MIMETYPE_TEXT_INTENT)) {
+            return;
+        }
+        ShortcutInfo info = new ShortcutInfo();
+        // Set a dummy intent until we get the final value
+        info.intent = new Intent();
+
+        // Since we are not going through the workspace for starting the drag, set drag related
+        // information on the workspace before starting the drag.
+        ExternalDragPreviewProvider previewProvider =
+                new ExternalDragPreviewProvider(mLauncher, info);
+        mLauncher.getWorkspace().prepareDragWithProvider(previewProvider);
+
+        DragOptions options = new DragOptions();
+        options.systemDndStartPoint = new Point((int) event.getX(), (int) event.getY());
+
+        int halfPadding = previewProvider.previewPadding / 2;
+        mDragController.startDrag(
+                Bitmap.createBitmap(1, 1, Config.ARGB_8888),
+                0, 0,
+                new AnotherWindowDragSource(mLauncher), info,
+                new Point(- halfPadding, halfPadding),
+                previewProvider.getPreviewBounds(), 1f, options);
+    }
+
     @Override
     public boolean onDragEvent (DragEvent event) {
+        if (event.getAction() == DragEvent.ACTION_DRAG_STARTED) {
+            handleSystemDragStart(event);
+        }
         return mDragController.onDragEvent(event);
     }
 
@@ -758,11 +807,15 @@
 
         // If duration < 0, this is a cue to compute the duration based on the distance
         if (duration < 0) {
-            duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
-            if (dist < maxDist) {
-                duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+            if (mDragController != null && mDragController.isExternalDrag()) {
+                duration = 1;
+            } else {
+                duration = res.getInteger(R.integer.config_dropAnimMaxDuration);
+                if (dist < maxDist) {
+                    duration *= mCubicEaseOutInterpolator.getInterpolation(dist / maxDist);
+                }
+                duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
             }
-            duration = Math.max(duration, res.getInteger(R.integer.config_dropAnimMinDuration));
         }
 
         // Fall back to cubic ease out interpolator for the animation if none is specified
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index a7f2872..3d52a48 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.dragndrop;
 
+import android.graphics.Point;
+
 /**
  * Set of options to control the drag and drop behavior.
  */
@@ -23,4 +25,7 @@
 
     /** Whether or not an accessible drag operation is in progress. */
     public boolean isAccessibleDrag = false;
+
+    /** Specifies the start location for the system DnD, null when using internal DnD */
+    public Point systemDndStartPoint = null;
 }
diff --git a/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
new file mode 100644
index 0000000..6b14be7
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/ExternalDragPreviewProvider.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dragndrop;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.HolographicOutlineHelper;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.graphics.DragPreviewProvider;
+
+/**
+ * Extension of {@link DragPreviewProvider} which provides a dummy outline when drag starts from
+ * a different window.
+ * It just draws an empty circle to a placeholder outline.
+ */
+public class ExternalDragPreviewProvider extends DragPreviewProvider {
+
+    private final Launcher mLauncher;
+    private final ItemInfo mAddInfo;
+
+    private final int[] mOutlineSize;
+
+    public ExternalDragPreviewProvider(Launcher launcher, ItemInfo addInfo) {
+        super(null);
+        mLauncher = launcher;
+        mAddInfo = addInfo;
+
+        mOutlineSize = mLauncher.getWorkspace().estimateItemSize(mAddInfo, false);
+    }
+
+    public Rect getPreviewBounds() {
+        Rect rect = new Rect();
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        rect.left = DRAG_BITMAP_PADDING / 2;
+        rect.top = (mOutlineSize[1] - dp.cellHeightPx) / 2;
+        rect.right = rect.left + dp.iconSizePx;
+        rect.bottom = rect.top + dp.iconSizePx;
+        return rect;
+    }
+
+    @Override
+    public Bitmap createDragOutline(Canvas canvas) {
+        final Bitmap b = Bitmap.createBitmap(mOutlineSize[0], mOutlineSize[1], Bitmap.Config.ALPHA_8);
+        canvas.setBitmap(b);
+
+        Paint paint = new Paint();
+        paint.setColor(Color.WHITE);
+        paint.setStyle(Paint.Style.FILL);
+
+        // Use 0.9f times the radius for the actual circle to account for icon normalization.
+        float radius = getPreviewBounds().width() * 0.5f;
+        canvas.drawCircle(DRAG_BITMAP_PADDING / 2 + radius,
+                DRAG_BITMAP_PADDING / 2 + radius, radius * 0.9f, paint);
+
+        HolographicOutlineHelper.obtain(mLauncher).applyExpensiveOutlineWithBlur(b, canvas);
+        canvas.setBitmap(null);
+        return b;
+    }
+}