Initial changes to drag and drop to docked task.

Change-Id: I5e7a435a4061d902e5de0a54cc386388bc2b565e
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 5c61c5ca6..284d540 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -41,6 +42,8 @@
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
 import com.android.systemui.recents.events.ui.ResizeTaskEvent;
 import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -612,6 +615,18 @@
         getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
     }
 
+    public final void onBusEvent(DragStartEvent event) {
+        // Lock the orientation while dragging
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+
+        // TODO: docking requires custom accessibility actions
+    }
+
+    public final void onBusEvent(DragEndEvent event) {
+        // Unlock the orientation when dragging completes
+        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
+    }
+
     private void refreshSearchWidgetView() {
         if (mSearchWidgetInfo != null) {
             SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 52b9521..59c590c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -22,6 +22,7 @@
 import android.provider.Settings;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 
 /**
  * Application resources that can be retrieved from the application context and are not specifically
@@ -70,6 +71,7 @@
     public final int smallestWidth;
 
     /** Misc **/
+    public boolean hasDockedTasks;
     public boolean useHardwareLayers;
     public boolean fakeShadows;
     public int svelteLevel;
@@ -114,6 +116,7 @@
         lockToAppEnabled = ssp.getSystemSetting(context,
                 Settings.System.LOCK_TO_APP_ENABLED) != 0;
         multiWindowEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window"));
+        hasDockedTasks = ssp.hasDockedTask();
 
         // Recompute some values based on the given state, since we can not rely on the resource
         // system to get certain values.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java
new file mode 100644
index 0000000..f2c3c33
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+/**
+ * This event is sent when a user drag enters or exits a dock region.
+ */
+public class DragDockStateChangedEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskStack.DockState dockState;
+
+    public DragDockStateChangedEvent(Task task, TaskStack.DockState dockState) {
+        this.task = task;
+        this.dockState = dockState;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
new file mode 100644
index 0000000..827998d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.DragView;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This event is sent whenever a drag ends.
+ */
+public class DragEndEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskView taskView;
+    public final DragView dragView;
+    public final TaskStack.DockState dockState;
+    public final ReferenceCountedTrigger postAnimationTrigger;
+
+    public DragEndEvent(Task task, TaskView taskView, DragView dragView,
+            TaskStack.DockState dockState, ReferenceCountedTrigger postAnimationTrigger) {
+        this.task = task;
+        this.taskView = taskView;
+        this.dragView = dragView;
+        this.dockState = dockState;
+        this.postAnimationTrigger = postAnimationTrigger;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
new file mode 100644
index 0000000..2d42a0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui.dragndrop;
+
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.views.DragView;
+import com.android.systemui.recents.views.TaskView;
+
+/**
+ * This event is sent whenever a drag starts.
+ */
+public class DragStartEvent extends EventBus.Event {
+
+    public final Task task;
+    public final TaskView taskView;
+    public final DragView dragView;
+
+    public DragStartEvent(Task task, TaskView taskView, DragView dragView) {
+        this.task = task;
+        this.taskView = taskView;
+        this.dragView = dragView;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index e0f820d..119a6f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -327,6 +327,21 @@
         return mAm.isInHomeStack(taskId);
     }
 
+    /**
+     * @return whether there are any docked tasks.
+     */
+    public boolean hasDockedTask() {
+        if (mIam == null) return false;
+
+        ActivityManager.StackInfo stackInfo = null;
+        try {
+            stackInfo = mIam.getStackInfo(ActivityManager.DOCKED_STACK_ID);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        return stackInfo != null;
+    }
+
     /** Returns the top task thumbnail for the given task id */
     public Bitmap getTaskThumbnail(int taskId) {
         if (mAm == null) return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index e810410..93c5ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -21,12 +21,29 @@
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.View;
+import android.view.ViewParent;
 
 import java.util.ArrayList;
 
 /* Common code */
 public class Utilities {
 
+    /**
+     * @return the first parent walking up the view hierarchy that has the given class type.
+     *
+     * @param parentClass must be a class derived from {@link View}
+     */
+    public static <T extends View> T findParent(View v, Class<T> parentClass) {
+        ViewParent parent = v.getParent();
+        while (parent != null) {
+            if (parent.getClass().equals(parentClass)) {
+                return (T) parent;
+            }
+            parent = parent.getParent();
+        }
+        return null;
+    }
+
     /** Scales a rect about its centroid */
     public static void scaleRectAboutCenter(Rect r, float scale) {
         if (scale != 1.0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 8eec87e..0fb235b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.recents.model;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.misc.NamedCounter;
@@ -30,6 +33,9 @@
 import java.util.List;
 import java.util.Random;
 
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+
 
 /**
  * An interface for a task filter to query whether a particular task should show in a stack.
@@ -174,6 +180,63 @@
         public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
     }
 
+
+    public enum DockState {
+        LEFT(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+                new RectF(0, 0, 0.25f, 1), new RectF(0, 0, 0.5f, 1), new RectF(0.5f, 0, 1, 1)),
+        TOP(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+                new RectF(0, 0, 1, 0.25f), new RectF(0, 0, 1, 0.5f), new RectF(0, 0.5f, 1, 1)),
+        RIGHT(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+                new RectF(0.75f, 0, 1, 1), new RectF(0.5f, 0, 1, 1), new RectF(0, 0, 0.5f, 1)),
+        BOTTOM(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT,
+                new RectF(0, 0.75f, 1, 1), new RectF(0, 0.5f, 1, 1), new RectF(0, 0, 1, 0.5f));
+
+        public final int createMode;
+        private final RectF touchArea;
+        private final RectF dockArea;
+        private final RectF stackArea;
+
+        /**
+         * @param createMode used to pass to ActivityManager to dock the task
+         * @param touchArea the area in which touch will initiate this dock state
+         * @param stackArea the area for the stack if a task is docked
+         */
+        DockState(int createMode, RectF touchArea, RectF dockArea, RectF stackArea) {
+            this.createMode = createMode;
+            this.touchArea = touchArea;
+            this.dockArea = dockArea;
+            this.stackArea = stackArea;
+        }
+
+        /**
+         * Returns whether {@param x} and {@param y} are contained in the touch area scaled to the
+         * given {@param width} and {@param height}.
+         */
+        public boolean touchAreaContainsPoint(int width, int height, float x, float y) {
+            int left = (int) (touchArea.left * width);
+            int top = (int) (touchArea.top * height);
+            int right = (int) (touchArea.right * width);
+            int bottom = (int) (touchArea.bottom * height);
+            return x >= left && y >= top && x <= right && y <= bottom;
+        }
+
+        /**
+         * Returns the docked task bounds with the given {@param width} and {@param height}.
+         */
+        public Rect getDockedBounds(int width, int height) {
+            return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height),
+                    (int) (dockArea.right * width), (int) (dockArea.bottom * height));
+        }
+
+        /**
+         * Returns the stack bounds with the given {@param width} and {@param height}.
+         */
+        public Rect getStackBounds(int width, int height) {
+            return new Rect((int) (stackArea.left * width), (int) (stackArea.top * height),
+                    (int) (stackArea.right * width), (int) (stackArea.bottom * height));
+        }
+    }
+
     // The task offset to apply to a task id as a group affiliation
     static final int IndividualTaskIdOffset = 1 << 16;
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
new file mode 100644
index 0000000..96dfaac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 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.systemui.recents.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.widget.ImageView;
+
+public class DragView extends ImageView {
+
+    // The offset from the top-left of the dragBitmap
+    Point mTopLeftOffset;
+
+    public DragView(Context context, Bitmap dragBitmap, Point tlOffset) {
+        super(context);
+
+        mTopLeftOffset = tlOffset;
+        setImageBitmap(dragBitmap);
+    }
+
+    public Point getTopLeftOffset() {
+        return mTopLeftOffset;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index de8730d..e406155 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -22,6 +22,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
@@ -30,19 +31,25 @@
 import android.util.SparseArray;
 import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManagerGlobal;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 import com.android.internal.logging.MetricsLogger;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsAppWidgetHostView;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
@@ -78,6 +85,11 @@
     TaskStackView mTaskStackView;
     RecentsAppWidgetHostView mSearchBar;
     RecentsViewCallbacks mCb;
+
+    RecentsViewTouchHandler mTouchHandler;
+    DragView mDragView;
+    ColorDrawable mDockRegionOverlay;
+
     Interpolator mFastOutSlowInInterpolator;
 
     Rect mSystemInsets = new Rect();
@@ -96,10 +108,14 @@
 
     public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setWillNotDraw(false);
         mConfig = RecentsConfiguration.getInstance();
         mInflater = LayoutInflater.from(context);
         mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
                 com.android.internal.R.interpolator.fast_out_slow_in);
+        mTouchHandler = new RecentsViewTouchHandler(this);
+        mDockRegionOverlay = new ColorDrawable(0x66000000);
+        mDockRegionOverlay.setAlpha(0);
     }
 
     /** Sets the callbacks */
@@ -200,6 +216,7 @@
             ArrayList<Task> tasks = stack.getTasks();
 
             // Find the launch task in the stack
+            // TODO: replace this with an event from RecentsActivity
             if (!tasks.isEmpty()) {
                 int taskCount = tasks.size();
                 for (int j = 0; j < taskCount; j++) {
@@ -267,6 +284,20 @@
         }
     }
 
+    @Override
+    protected void onAttachedToWindow() {
+        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+        super.onAttachedToWindow();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        EventBus.getDefault().unregister(this);
+        EventBus.getDefault().unregister(mTouchHandler);
+    }
+
     /**
      * This is called with the full size of the window since we are handling our own insets.
      */
@@ -293,6 +324,12 @@
             mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
         }
 
+        if (mDragView != null) {
+            mDragView.measure(
+                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+        }
+
         setMeasuredDimension(width, height);
     }
 
@@ -314,6 +351,11 @@
         if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
             mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
         }
+
+        if (mDragView != null) {
+            mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
+                    top + mDragView.getMeasuredHeight());
+        }
     }
 
     @Override
@@ -323,6 +365,24 @@
         return insets.consumeSystemWindowInsets();
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return mTouchHandler.onInterceptTouchEvent(ev);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        return mTouchHandler.onTouchEvent(ev);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mDockRegionOverlay.getAlpha() > 0) {
+            mDockRegionOverlay.draw(canvas);
+        }
+    }
+
     /** Notifies each task view of the user interaction. */
     public void onUserInteraction() {
         // Get the first stack view
@@ -697,4 +757,64 @@
                     .start();
         }
     }
+
+    /**** EventBus Events ****/
+
+    public final void onBusEvent(DragStartEvent event) {
+        // Add the drag view
+        mDragView = event.dragView;
+        addView(mDragView);
+    }
+
+    public final void onBusEvent(DragDockStateChangedEvent event) {
+        // Update the task stack positions, and then
+        if (event.dockState != null) {
+            // Draw an overlay on the bounds of the dock task
+            mDockRegionOverlay.setBounds(
+                    event.dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight()));
+            mDockRegionOverlay.setAlpha(255);
+        } else {
+            mDockRegionOverlay.setAlpha(0);
+        }
+        invalidate();
+    }
+
+    public final void onBusEvent(final DragEndEvent event) {
+        event.postAnimationTrigger.increment();
+        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // Remove the drag view
+                removeView(mDragView);
+                mDragView = null;
+                mDockRegionOverlay.setAlpha(0);
+                invalidate();
+
+                // Dock the new task if we are hovering over a valid dock state
+                if (event.dockState != null) {
+                    SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy();
+                    ssp.setTaskResizeable(event.task.key.id);
+                    ssp.dockTask(event.task.key.id, event.dockState.createMode);
+                    launchTask(event.task, null);
+                }
+            }
+        });
+        if (event.dockState == null) {
+            // Animate the alpha back to what it was before
+            Rect taskBounds = mTaskStackView.getStackAlgorithm().getUntransformedTaskViewBounds();
+            int left = taskBounds.left + (int) ((1f - event.taskView.getScaleX()) * taskBounds.width()) / 2;
+            int top = taskBounds.top + (int) ((1f - event.taskView.getScaleY()) * taskBounds.height()) / 2;
+            event.dragView.animate()
+                    .alpha(1f)
+                    .translationX(left + event.taskView.getTranslationX())
+                    .translationY(top + event.taskView.getTranslationY())
+                    .setDuration(175)
+                    .setInterpolator(new AccelerateInterpolator(1.5f))
+                    .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
+                    .withLayer()
+                    .start();
+        } else {
+            event.postAnimationTrigger.decrement();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
new file mode 100644
index 0000000..8dea0cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 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.systemui.recents.views;
+
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.view.MotionEvent;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.ReferenceCountedTrigger;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+
+/**
+ * Represents the dock regions for each orientation.
+ */
+class DockRegion {
+    public static TaskStack.DockState[] LANDSCAPE = {
+            TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT
+    };
+    public static TaskStack.DockState[] PORTRAIT = {
+            TaskStack.DockState.TOP, TaskStack.DockState.BOTTOM
+    };
+}
+
+/**
+ * Handles touch events for a RecentsView.
+ */
+class RecentsViewTouchHandler {
+
+    private RecentsView mRv;
+
+    private Task mDragTask;
+    private TaskView mTaskView;
+    private DragView mDragView;
+
+    private Point mDownPos = new Point();
+    private boolean mDragging;
+    private TaskStack.DockState mLastDockState;
+
+    public RecentsViewTouchHandler(RecentsView rv) {
+        mRv = rv;
+    }
+
+    /** Touch preprocessing for handling below */
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+        }
+        return mDragging;
+    }
+
+    /** Handles touch events once we have intercepted them */
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (!mDragging) return false;
+
+        boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
+        int action = ev.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                break;
+            case MotionEvent.ACTION_MOVE: {
+                int width = mRv.getMeasuredWidth();
+                int height = mRv.getMeasuredHeight();
+                float evX = ev.getX();
+                float evY = ev.getY();
+                float x = evX - mDragView.getTopLeftOffset().x;
+                float y = evY - mDragView.getTopLeftOffset().y;
+
+                // Update the dock state
+                TaskStack.DockState[] dockStates = isLandscape ?
+                        DockRegion.LANDSCAPE : DockRegion.PORTRAIT;
+                TaskStack.DockState foundDockState = null;
+                for (int i = 0; i < dockStates.length; i++) {
+                    TaskStack.DockState state = dockStates[i];
+                    if (state.touchAreaContainsPoint(width, height, evX, evY)) {
+                        foundDockState = state;
+                        break;
+                    }
+                }
+                if (mLastDockState != foundDockState) {
+                    mLastDockState = foundDockState;
+                    EventBus.getDefault().send(new DragDockStateChangedEvent(mDragTask,
+                            foundDockState));
+                }
+
+                mDragView.setTranslationX(x);
+                mDragView.setTranslationY(y);
+                break;
+            }
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger(
+                        mRv.getContext(), null, null, null);
+                postAnimationTrigger.increment();
+                EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
+                        mLastDockState, postAnimationTrigger));
+                postAnimationTrigger.decrement();
+                break;
+            }
+        }
+        return true;
+    }
+
+    /**** Events ****/
+
+    public final void onBusEvent(DragStartEvent event) {
+        mRv.getParent().requestDisallowInterceptTouchEvent(true);
+        mDragging = true;
+        mDragTask = event.task;
+        mTaskView = event.taskView;
+        mDragView = event.dragView;
+
+        float x = mDownPos.x - mDragView.getTopLeftOffset().x;
+        float y = mDownPos.y - mDragView.getTopLeftOffset().y;
+        mDragView.setTranslationX(x);
+        mDragView.setTranslationY(y);
+    }
+
+    public final void onBusEvent(DragEndEvent event) {
+        mDragging = false;
+        mDragTask = null;
+        mTaskView = null;
+        mDragView = null;
+        mLastDockState = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 67e3f82..bc1f481 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -1296,7 +1296,7 @@
         RecentsTaskLoader.getInstance().loadTaskData(task);
 
         // If the doze trigger has already fired, then update the state for this task view
-        if (mUIDozeTrigger.hasTriggered()) {
+        if (mUIDozeTrigger.hasTriggered() || mConfig.multiWindowEnabled) {
             tv.setNoUserInteractionState();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 910db87..f213a10 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -21,13 +21,17 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.animation.AccelerateInterpolator;
@@ -36,17 +40,22 @@
 import android.widget.FrameLayout;
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.RecentsActivity;
 import com.android.systemui.recents.RecentsActivityLaunchState;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DismissTaskEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
+import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 
 /* A task view */
 public class TaskView extends FrameLayout implements Task.TaskCallbacks,
-        View.OnClickListener {
+        View.OnClickListener, View.OnLongClickListener {
 
     /** The TaskView callbacks */
     interface TaskViewCallbacks {
@@ -79,6 +88,8 @@
     View mActionButtonView;
     TaskViewCallbacks mCb;
 
+    Point mDownTouchPos = new Point();
+
     Interpolator mFastOutSlowInInterpolator;
     Interpolator mFastOutLinearInInterpolator;
     Interpolator mQuintOutInterpolator;
@@ -169,6 +180,14 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY()));
+        }
+        return super.onInterceptTouchEvent(ev);
+    }
+
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
@@ -719,6 +738,7 @@
             mHeaderView.rebindToTask(mTask);
             // Rebind any listeners
             mActionButtonView.setOnClickListener(this);
+            setOnLongClickListener(mConfig.hasDockedTasks ? null : this);
         }
         mTaskDataLoaded = true;
     }
@@ -753,4 +773,69 @@
             mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView));
         }
     }
+
+    /**** View.OnLongClickListener Implementation ****/
+
+    @Override
+    public boolean onLongClick(View v) {
+        if (v == this) {
+            // Start listening for drag events
+            setClipViewInStack(false);
+
+            int width = (int) (getScaleX() * getWidth());
+            int height = (int) (getScaleY() * getHeight());
+            Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(dragBitmap);
+            c.scale(getScaleX(), getScaleY());
+            mThumbnailView.draw(c);
+            mHeaderView.draw(c);
+            c.setBitmap(null);
+
+            // Initiate the drag
+            final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
+            dragView.setOutlineProvider(mViewBounds);
+            dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View v) {
+                    // Hide this task view after the drag view is attached
+                    setVisibility(View.INVISIBLE);
+                    // Animate the alpha slightly to indicate dragging
+                    dragView.setElevation(getElevation());
+                    dragView.setTranslationZ(getTranslationZ());
+                    dragView.animate()
+                            .alpha(0.75f)
+                            .setDuration(175)
+                            .setInterpolator(new AccelerateInterpolator(1.5f))
+                            .withLayer()
+                            .start();
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View v) {
+                }
+            });
+            EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
+            EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView));
+            return true;
+        }
+        return false;
+    }
+
+    /**** Events ****/
+
+    public final void onBusEvent(DragEndEvent event) {
+        event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+            @Override
+            public void run() {
+                // If docked state == null:
+                // Animate the drag view back from where it is, to the view location, then after it returns,
+                // update the clip state
+                setClipViewInStack(true);
+
+                // Show this task view
+                setVisibility(View.VISIBLE);
+            }
+        });
+        EventBus.getDefault().unregister(this);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 7de8b7b..a71fdaa 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -471,7 +471,7 @@
             // In accessibility, a single click on the focused app info button will show it
             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
         } else if (v == mDismissButton) {
-            TaskView tv = (TaskView) getParent().getParent();
+            TaskView tv = Utilities.findParent(this, TaskView.class);
             tv.dismissTask();
 
             // Keep track of deletions by the dismiss button