Move window moving and resizing handling to WindowManager

- add a startMoving API to initiate a window move from app, once
  the move starts WindowManager will take over the event handling.

- detect touch events along window's outside border and start
  a resize if necessary

Change-Id: Ic7e8baba34e0aa27a43173e044ffb46e93e219e0
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6367a3d..f7dcf02 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2736,40 +2736,6 @@
     }
 
     /**
-     * Returns the bounds of the task that contains this activity.
-     *
-     * @return Rect The bounds that contains the activity.
-     * @hide
-     */
-    @Override
-    public Rect getActivityBounds() throws RemoteException {
-        return ActivityManagerNative.getDefault().getActivityBounds(mToken);
-    }
-
-    /**
-     * Sets the bounds (size and position) of the task or stack that contains this
-     * activity.
-     * NOTE: The requested bounds might not the fully honored by the system depending
-     * on the window placement policy.
-     *
-     * @param newBounds The new target bounds of the activity in task or stack.
-     * @hide
-     */
-    @Override
-    public void setActivityBounds(Rect newBounds) throws RemoteException {
-        ActivityManagerNative.getDefault().setActivityBounds(mToken, newBounds);
-    }
-
-    /**
-     * Activates this activity, hence bringing it to the top and giving it focus.
-     * Note: This will only work for activities which are located on the freeform desktop.
-     * @hide
-     */
-    public void activateActivity() throws RemoteException {
-        ActivityManagerNative.getDefault().activateActivity(mToken);
-    }
-
-    /**
      * Called to process key events.  You can override this to intercept all
      * key events before they are dispatched to the window.  Be sure to call
      * this implementation for key events that should be handled normally.
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1f1f356..d1c73bc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,24 +752,6 @@
             return true;
         }
 
-        case GET_ACTIVITY_BOUNDS_TRANSACTION: {
-            data.enforceInterface(IActivityManager.descriptor);
-            IBinder token = data.readStrongBinder();
-            Rect r = getActivityBounds(token);
-            reply.writeNoException();
-            r.writeToParcel(reply, 0);
-            return true;
-        }
-
-        case SET_ACTIVITY_BOUNDS_TRANSACTION: {
-            data.enforceInterface(IActivityManager.descriptor);
-            IBinder token = data.readStrongBinder();
-            Rect r = Rect.CREATOR.createFromParcel(data);
-            setActivityBounds(token, r);
-            reply.writeNoException();
-            return true;
-        }
-
         case POSITION_TASK_IN_STACK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int taskId = data.readInt();
@@ -827,14 +809,6 @@
             return true;
         }
 
-        case ACTIVATE_ACTIVITY_TRANSACTION: {
-            data.enforceInterface(IActivityManager.descriptor);
-            IBinder token = data.readStrongBinder();
-            activateActivity(token);
-            reply.writeNoException();
-            return true;
-        }
-
         case SET_FOCUSED_TASK_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int taskId = data.readInt();
@@ -3631,18 +3605,6 @@
         return focusedStackId;
     }
     @Override
-    public void activateActivity(IBinder token) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken(IActivityManager.descriptor);
-        data.writeStrongBinder(token);
-        mRemote.transact(ACTIVATE_ACTIVITY_TRANSACTION, data, reply, 0);
-        reply.readException();
-        data.recycle();
-        reply.recycle();
-    }
-    @Override
     public void setFocusedTask(int taskId) throws RemoteException
     {
         Parcel data = Parcel.obtain();
@@ -5941,35 +5903,6 @@
     }
 
     @Override
-    public void setActivityBounds(IBinder token, Rect r) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken(IActivityManager.descriptor);
-        data.writeStrongBinder(token);
-        r.writeToParcel(data, 0);
-        mRemote.transact(SET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
-        reply.readException();
-        data.recycle();
-        reply.recycle();
-    }
-
-    @Override
-    public Rect getActivityBounds(IBinder token) throws RemoteException
-    {
-        Parcel data = Parcel.obtain();
-        Parcel reply = Parcel.obtain();
-        data.writeInterfaceToken(IActivityManager.descriptor);
-        data.writeStrongBinder(token);
-        mRemote.transact(GET_ACTIVITY_BOUNDS_TRANSACTION, data, reply, 0);
-        reply.readException();
-        Rect rect = Rect.CREATOR.createFromParcel(reply);
-        data.recycle();
-        reply.recycle();
-        return rect;
-    }
-
-    @Override
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 47abf26..66fa796 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -147,7 +147,6 @@
     public boolean isInHomeStack(int taskId) throws RemoteException;
     public void setFocusedStack(int stackId) throws RemoteException;
     public int getFocusedStackId() throws RemoteException;
-    public void activateActivity(IBinder token) throws RemoteException;
     public void setFocusedTask(int taskId) throws RemoteException;
     public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException;
     public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
@@ -491,8 +490,6 @@
             throws RemoteException;
     public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
     public void resizeTask(int taskId, Rect bounds) throws RemoteException;
-    public void setActivityBounds(IBinder token, Rect bounds) throws RemoteException;
-    public Rect getActivityBounds(IBinder token) throws RemoteException;
 
     public Rect getTaskBounds(int taskId) throws RemoteException;
     public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
@@ -891,7 +888,4 @@
     int GET_ACTIVITY_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 343;
     int MOVE_ACTIVITY_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 344;
     int REPORT_SIZE_CONFIGURATIONS = IBinder.FIRST_CALL_TRANSACTION + 345;
-    int GET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 346;
-    int SET_ACTIVITY_BOUNDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 347;
-    int ACTIVATE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 73b4a6e..017364a 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -211,4 +211,12 @@
      * The assumption is that this method will be called rather infrequently.
      */
     void pokeDrawLock(IBinder window);
+
+    /**
+     * Starts a task window move with {startX, startY} as starting point. The amount of move
+     * will be the offset between {startX, startY} and the new cursor position.
+     *
+     * Returns true if the move started successfully; false otherwise.
+     */
+    boolean startMovingTask(IWindow window, float startX, float startY);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f4fc6e7..eb591c1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19750,6 +19750,26 @@
     }
 
     /**
+     * Starts a move from {startX, startY}, the amount of the movement will be the offset
+     * between {startX, startY} and the new cursor positon.
+     * @param startX horizontal coordinate where the move started.
+     * @param startY vertical coordinate where the move started.
+     * @return whether moving was started successfully.
+     * @hide
+     */
+    public final boolean startMovingTask(float startX, float startY) {
+        if (ViewDebug.DEBUG_POSITIONING) {
+            Log.d(VIEW_LOG_TAG, "startMovingTask: {" + startX + "," + startY + "}");
+        }
+        try {
+            return mAttachInfo.mSession.startMovingTask(mAttachInfo.mWindow, startX, startY);
+        } catch (RemoteException e) {
+            Log.e(VIEW_LOG_TAG, "Unable to start moving", e);
+        }
+        return false;
+    }
+
+    /**
      * Handles drag events sent by the system following a call to
      * {@link android.view.View#startDrag(ClipData,DragShadowBuilder,Object,int) startDrag()}.
      *<p>
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8bf53a8..8278335 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -78,6 +78,12 @@
     public static final boolean DEBUG_DRAG = false;
 
     /**
+     * Enables detailed logging of task positioning operations.
+     * @hide
+     */
+    public static final boolean DEBUG_POSITIONING = false;
+
+    /**
      * This annotation can be used to mark fields and methods to be dumped by
      * the view server. Only non-void methods with no arguments can be annotated
      * by this annotation.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5893f4a..b146a51 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -562,28 +562,6 @@
 
         /** Returns the current stack Id for the window. */
         int getWindowStackId() throws RemoteException;
-
-        /**
-         * Returns the bounds of the task that contains this activity.
-         *
-         * @return Rect The bounds that contains the activity.
-         */
-        Rect getActivityBounds() throws RemoteException;
-
-        /**
-         * Sets the bounds (size and position) of the task or stack that contains this
-         * activity.
-         * NOTE: The requested bounds might not the fully honored by the system depending
-         * on the window placement policy.
-         *
-         * @param newBounds The new target bounds of the activity in task or stack.
-         */
-        void setActivityBounds(Rect newBounds) throws RemoteException;
-
-        /**
-         * Activates this activity, hence bringing it to the top and giving it focus.
-         */
-        void activateActivity() throws RemoteException;
     }
 
     public Window(Context context) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 791387d..7f01841 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -175,8 +175,6 @@
     // If the window type does not require such a view, this member might be null.
     NonClientDecorView mNonClientDecorView;
 
-    private boolean mForwardEvents = false;
-
     // The non client decor needs to adapt to the used workspace. Since querying and changing the
     // workspace is expensive, this is the workspace value the window is currently set up for.
     int mWorkspaceId;
@@ -2510,21 +2508,20 @@
         @Override
         public boolean onInterceptTouchEvent(MotionEvent event) {
             int action = event.getAction();
-            // Dispatch the event to the non client decor if the window is resizable and
-            // the event was (starting) outside the window.
-            if (mHasNonClientDecor && mNonClientDecorView.mResizable) {
-                if (mForwardEvents) {
-                    // The non client decor is currently processing the (resize) events.
-                    mForwardEvents = mNonClientDecorView.onTouchEvent(event);
-                    return true;
-                }
+            if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
+                // Don't dispatch ACTION_DOWN to the non client decor if the window is
+                // resizable and the event was (starting) outside the window.
+                // Window resizing events should be handled by WindowManager.
+                // TODO: Investigate how to handle the outside touch in window manager
+                //       without generating these events.
+                //       Currently we receive these because we need to enlarge the window's
+                //       touch region so that the monitor channel receives the events
+                //       in the outside touch area.
                 if (action == MotionEvent.ACTION_DOWN) {
                     final int x = (int) event.getX();
                     final int y = (int) event.getY();
                     if (isOutOfInnerBounds(x, y)) {
-                        // Forward this event to the non client decor.
-                        mForwardEvents = mNonClientDecorView.onTouchEvent(event);
-                        return mForwardEvents;
+                        return true;
                     }
                 }
             }
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
index 7aa1920..6ab306c 100644
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ b/core/java/com/android/internal/widget/NonClientDecorView.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.widget;
 
-import android.app.ActivityThread;
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.RemoteException;
@@ -71,33 +70,11 @@
     // True if the window is being dragged.
     private boolean mDragging = false;
 
-    // The bounds of the window and the absolute mouse pointer coordinates from before we started to
-    // drag the window. They will be used to determine the next window position.
-    private final Rect mWindowOriginalBounds = new Rect();
-    private float mStartDragX;
-    private float mStartDragY;
     // True when the left mouse button got released while dragging.
     private boolean mLeftMouseButtonReleased;
 
-    private static final int NONE = 0;
-    private static final int LEFT = 1;
-    private static final int RIGHT = 2;
-    private static final int TOP = 4;
-    private static final int BOTTOM = 8;
-    private static final int TOP_LEFT = TOP | LEFT;
-    private static final int TOP_RIGHT = TOP | RIGHT;
-    private static final int BOTTOM_LEFT = BOTTOM | LEFT;
-    private static final int BOTTOM_RIGHT = BOTTOM | RIGHT;
-    private int mSizeCorner = NONE;
-
-    // Avoiding re-creation of Rect's by keeping a temporary window drag bound.
-    private final Rect mWindowDragBounds = new Rect();
-
-    // True while the task is resizing itself to avoid overlapping resize operations.
-    private boolean mTaskResizingInProgress = false;
-
     // True if this window is resizable (which is currently only true when the decor is shown).
-    public boolean mResizable = false;
+    public boolean mVisible = false;
 
     // The current focus state of the window for updating the window elevation.
     private boolean mWindowHasFocus = true;
@@ -145,27 +122,14 @@
                     // When there is no decor we should not react to anything.
                     return false;
                 }
-                // Ensure that the activity is active.
-                activateActivity();
                 // A drag action is started if we aren't dragging already and the starting event is
                 // either a left mouse button or any other input device.
                 if (!mDragging &&
                         (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
                                 (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
                     mDragging = true;
-                    mWindowOriginalBounds.set(getActivityBounds());
                     mLeftMouseButtonReleased = false;
-                    mStartDragX = e.getRawX();
-                    mStartDragY = e.getRawY();
-                    // Determine if this is a resizing user action.
-                    final int x = (int) (e.getX());
-                    final int y = (int) (e.getY());
-                    mSizeCorner = (x < 0 ? LEFT : (x >= getWidth() ? RIGHT : NONE)) |
-                            (y < 0 ? TOP : (y >= getHeight() ? BOTTOM : NONE));
-                    if (mSizeCorner != 0) {
-                        // Suppress any configuration changes for now.
-                        ActivityThread.currentActivityThread().suppressConfigurationChanges(true);
-                    }
+                    startMovingTask(e.getRawX(), e.getRawY());
                 }
                 break;
 
@@ -178,79 +142,16 @@
                         mLeftMouseButtonReleased = true;
                         break;
                     }
-                    if (mSizeCorner != NONE) {
-                        // Avoid overlapping resizing operations.
-                        if (mTaskResizingInProgress) {
-                            break;
-                        }
-                        mTaskResizingInProgress = true;
-                        // This is a resizing operation.
-                        final int deltaX = Math.round(e.getRawX() - mStartDragX);
-                        final int deltaY = Math.round(e.getRawY() - mStartDragY);
-                        final int minSizeX = (int)(dipToPx(96));
-                        final int minSizeY = (int)(dipToPx(64));
-                        int left = mWindowOriginalBounds.left;
-                        int top = mWindowOriginalBounds.top;
-                        int right = mWindowOriginalBounds.right;
-                        int bottom = mWindowOriginalBounds.bottom;
-                        if ((mSizeCorner & LEFT) != 0) {
-                            left = Math.min(left + deltaX, right - minSizeX);
-                        }
-                        if ((mSizeCorner & TOP) != 0) {
-                            top = Math.min(top + deltaY, bottom - minSizeY);
-                        }
-                        if ((mSizeCorner & RIGHT) != 0) {
-                            right = Math.max(left + minSizeX, right + deltaX);
-                        }
-                        if ((mSizeCorner & BOTTOM) != 0) {
-                            bottom = Math.max(top + minSizeY, bottom + deltaY);
-                        }
-                        mWindowDragBounds.set(left, top, right, bottom);
-                        setActivityBounds(mWindowDragBounds);
-                        mTaskResizingInProgress = false;
-                    } else {
-                        // This is a moving operation.
-                        mWindowDragBounds.set(mWindowOriginalBounds);
-                        mWindowDragBounds.offset(Math.round(e.getRawX() - mStartDragX),
-                                Math.round(e.getRawY() - mStartDragY));
-                        setActivityBounds(mWindowDragBounds);
-                    }
                 }
                 break;
 
             case MotionEvent.ACTION_UP:
-                if (!mDragging) {
-                    break;
-                }
-                // Finsih the dragging now.
-                mDragging = false;
-                if (mSizeCorner == NONE) {
-                    return true;
-                }
-
-                // Allow configuration changes again.
-                ActivityThread.currentActivityThread().suppressConfigurationChanges(false);
-                // Set the same bounds once more - which might trigger a configuration change now.
-                setActivityBounds(mWindowDragBounds);
-                // Tell the DecorView that we are done with out event interception by
-                // returning false.
-                return false;
-
             case MotionEvent.ACTION_CANCEL:
                 if (!mDragging) {
                     break;
                 }
                 // Abort the ongoing dragging.
                 mDragging = false;
-                // Restore the previous bounds.
-                setActivityBounds(mWindowOriginalBounds);
-                if (mSizeCorner != NONE) {
-                    // ALlow configuration changes again.
-                    ActivityThread.currentActivityThread().suppressConfigurationChanges(false);
-                    // Tell the DecorView that we are done with out event interception by
-                    // returning false.
-                    return false;
-                }
                 return true;
         }
         return mDragging;
@@ -323,7 +224,7 @@
         boolean invisible = isFillingScreen() || !mShowDecor;
         View caption = getChildAt(0);
         caption.setVisibility(invisible ? GONE : VISIBLE);
-        mResizable = !invisible;
+        mVisible = !invisible;
     }
 
     /**
@@ -390,54 +291,4 @@
             }
         }
     }
-
-    /**
-     * Returns the bounds of this activity.
-     * @return Returns bounds of the activity. It will return null if either the window is
-     *     fullscreen or the bounds could not be retrieved.
-     */
-    private Rect getActivityBounds() {
-        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                return callback.getActivityBounds();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to get the activity bounds.");
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Sets the bounds of this Activity on the stack.
-     * @param newBounds The bounds of the activity. Passing null is not allowed.
-     */
-    private void setActivityBounds(Rect newBounds) {
-        if (newBounds == null) {
-            Log.e(TAG, "Failed to set null bounds to the activity.");
-            return;
-        }
-        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                callback.setActivityBounds(newBounds);
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to set the activity bounds.");
-            }
-        }
-    }
-
-    /**
-     * Activates the activity - means setting the focus and moving it to the top of the stack.
-     */
-    private void activateActivity() {
-        Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
-        if (callback != null) {
-            try {
-                callback.activateActivity();
-            } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to activate the activity.");
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 81936ee..14376d1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2730,31 +2730,17 @@
         }
     }
 
-    /**
-     * Activate an activity by bringing it to the top and set the focus on it.
-     * Note: This is only allowed for activities which are on the freeform stack.
-     * @param token The token of the activity calling which will get activated.
-     */
     @Override
-    public void activateActivity(IBinder token) {
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "ActivateActivity token=" + token);
+    public void setFocusedTask(int taskId) {
+        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId);
         long callingId = Binder.clearCallingIdentity();
         try {
             synchronized (ActivityManagerService.this) {
-                final ActivityRecord anyTaskRecord = ActivityRecord.isInStackLocked(token);
-                if (anyTaskRecord == null) {
-                    Slog.w(TAG, "ActivateActivity: token=" + token + " not found");
-                    return;
-                }
-                TaskRecord task = anyTaskRecord.task;
-                final boolean runsOnFreeformStack =
-                        task.stack.getStackId() == FREEFORM_WORKSPACE_STACK_ID;
-                if (!runsOnFreeformStack) {
-                    Slog.w(TAG, "Tried to use activateActivity on a non freeform workspace!");
-                } else if (task != null) {
-                    ActivityRecord topTaskRecord = task.topRunningActivityLocked(null);
-                    if (topTaskRecord != null) {
-                        setFocusedActivityLocked(topTaskRecord, "activateActivity");
+                TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task != null) {
+                    ActivityRecord r = task.topRunningActivityLocked(null);
+                    if (r != null) {
+                        setFocusedActivityLocked(r, "setFocusedTask");
                         mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null);
                     }
                 }
@@ -2764,21 +2750,6 @@
         }
     }
 
-    @Override
-    public void setFocusedTask(int taskId) {
-        if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedTask: taskId=" + taskId);
-        synchronized (ActivityManagerService.this) {
-            TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
-            if (task != null) {
-                ActivityRecord r = task.topRunningActivityLocked(null);
-                if (r != null) {
-                    setFocusedActivityLocked(r, "setFocusedTask");
-                    mStackSupervisor.resumeTopActivitiesLocked(task.stack, null, null);
-                }
-            }
-        }
-    }
-
     /** Sets the task stack listener that gets callbacks when a task stack changes. */
     @Override
     public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException {
@@ -8721,58 +8692,6 @@
     }
 
     @Override
-    public void setActivityBounds(IBinder token, Rect bounds) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (this) {
-                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
-                if (r == null) {
-                    Slog.w(TAG, "setActivityBounds: token=" + token + " not found");
-                    return;
-                }
-                final TaskRecord task = r.task;
-                if (task == null) {
-                    Slog.e(TAG, "setActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
-                    return;
-                }
-                if (task.stack != null && task.stack.mStackId == DOCKED_STACK_ID) {
-                    mStackSupervisor.resizeStackLocked(task.stack.mStackId, bounds);
-                } else {
-                    mStackSupervisor.resizeTaskLocked(task, bounds);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
-    public Rect getActivityBounds(IBinder token) {
-        long ident = Binder.clearCallingIdentity();
-        Rect rect = null;
-        try {
-            synchronized (this) {
-                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
-                if (r == null) {
-                    Slog.w(TAG, "getActivityBounds: token=" + token + " not found");
-                    return rect;
-                }
-                final TaskRecord task = r.task;
-                if (task == null) {
-                    Slog.e(TAG, "getActivityBounds: No TaskRecord for the ActivityRecord r=" + r);
-                    return rect;
-                }
-                if (task.mBounds != null) {
-                    rect = new Rect(task.mBounds);
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return rect;
-    }
-
-    @Override
     public Bitmap getTaskDescriptionIcon(String filename) {
         if (!FileUtils.isValidExtFilename(filename)
                 || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 99e9fe6..325196d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -21,8 +21,10 @@
 import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerService.TAG;
 
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -240,17 +242,85 @@
         return -1;
     }
 
+    /**
+     * Find the id of the task whose outside touch area (for resizing) (x, y)
+     * falls within. Returns -1 if the touch doesn't fall into a resizing area.
+     */
+    int taskIdForControlPoint(int x, int y) {
+        final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP);
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            TaskStack stack = mStacks.get(stackNdx);
+            if (!stack.allowTaskResize()) {
+                break;
+            }
+            final ArrayList<Task> tasks = stack.getTasks();
+            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                final Task task = tasks.get(taskNdx);
+                if (task.isFullscreen()) {
+                    return -1;
+                }
+                task.getBounds(mTmpRect);
+                mTmpRect.inset(-delta, -delta);
+                if (mTmpRect.contains(x, y)) {
+                    mTmpRect.inset(delta, delta);
+                    if (!mTmpRect.contains(x, y)) {
+                        return task.mTaskId;
+                    }
+                    // User touched inside the task. No need to look further,
+                    // focus transfer will be handled in ACTION_UP.
+                    return -1;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private int calculatePixelFromDp(int dp) {
+        final Configuration serviceConfig = mService.mCurConfiguration;
+        // TODO(multidisplay): Update Dp to that of display stack is on.
+        final float density = serviceConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+        return (int)(dp * density);
+    }
+
     void setTouchExcludeRegion(Task focusedTask) {
         mTouchExcludeRegion.set(mBaseDisplayRect);
         WindowList windows = getWindowList();
+        final int delta = calculatePixelFromDp(WindowState.RESIZE_HANDLE_WIDTH_IN_DP);
         for (int i = windows.size() - 1; i >= 0; --i) {
             final WindowState win = windows.get(i);
-            final Task task = win.getTask();
-            if (win.isVisibleLw() && task != null && task != focusedTask) {
-                mTmpRect.set(win.mVisibleFrame);
-                // If no intersection, we need mTmpRect to be unmodified.
-                mTmpRect.intersect(win.mVisibleInsets);
-                mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+            final Task task = win.mAppToken != null ? win.getTask() : null;
+            if (win.isVisibleLw() && task != null) {
+                /**
+                 * Exclusion region is the region that TapDetector doesn't care about.
+                 * Here we want to remove all non-focused tasks from the exclusion region.
+                 * We also remove the outside touch area for resizing for all freeform
+                 * tasks (including the focused).
+                 *
+                 * (For freeform focused task, the below logic will first remove the enlarged
+                 * area, then add back the inner area.)
+                 */
+                final boolean isFreeformed = win.inFreeformWorkspace();
+                if (task != focusedTask || isFreeformed) {
+                    mTmpRect.set(win.mVisibleFrame);
+                    mTmpRect.intersect(win.mVisibleInsets);
+                    /**
+                     * If the task is freeformed, enlarge the area to account for outside
+                     * touch area for resize.
+                     */
+                    if (isFreeformed) {
+                        mTmpRect.inset(-delta, -delta);
+                    }
+                    mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+                }
+                /**
+                 * If we removed the focused task above, add it back and only leave its
+                 * outside touch area in the exclusion. TapDectector is not interested in
+                 * any touch inside the focused task itself.
+                 */
+                if (task == focusedTask && isFreeformed) {
+                    mTmpRect.inset(delta, delta);
+                    mTouchExcludeRegion.op(mTmpRect, Region.Op.UNION);
+                }
             }
         }
         if (mTapDetector != null) {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index e87dcde..8f77176 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -283,11 +283,12 @@
 
         // stop intercepting input
         mService.mDragState.unregister();
-        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
 
         // free our resources and drop all the object references
         mService.mDragState.reset();
         mService.mDragState = null;
+
+        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
     }
 
     void notifyMoveLw(float x, float y) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index b3244ff..9adcafc 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -64,9 +64,9 @@
     public InputMonitor(WindowManagerService service) {
         mService = service;
     }
-    
+
     /* Notifies the window manager about a broken input channel.
-     * 
+     *
      * Called by the InputManager.
      */
     @Override
@@ -83,10 +83,10 @@
             }
         }
     }
-    
+
     /* Notifies the window manager about an application that is not responding.
      * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
-     * 
+     *
      * Called by the InputManager.
      */
     @Override
@@ -257,6 +257,20 @@
             }
         }
 
+        final boolean inPositioning = (mService.mTaskPositioner != null);
+        if (inPositioning) {
+            if (WindowManagerService.DEBUG_TASK_POSITIONING) {
+                Log.d(WindowManagerService.TAG, "Inserting window handle for repositioning");
+            }
+            final InputWindowHandle dragWindowHandle = mService.mTaskPositioner.mDragWindowHandle;
+            if (dragWindowHandle != null) {
+                addInputWindowHandleLw(dragWindowHandle);
+            } else {
+                Slog.e(WindowManagerService.TAG,
+                        "Repositioning is in progress but there is no drag window handle.");
+            }
+        }
+
         boolean addInputConsumerHandle = mService.mInputConsumer != null;
 
         // Add all windows on the default display.
@@ -437,56 +451,56 @@
             if (WindowManagerService.DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
             }
-            
+
             window.paused = true;
             updateInputWindowsLw(true /*force*/);
         }
     }
-    
+
     public void resumeDispatchingLw(WindowToken window) {
         if (window.paused) {
             if (WindowManagerService.DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
             }
-            
+
             window.paused = false;
             updateInputWindowsLw(true /*force*/);
         }
     }
-    
+
     public void freezeInputDispatchingLw() {
         if (! mInputDispatchFrozen) {
             if (WindowManagerService.DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Freezing input dispatching");
             }
-            
+
             mInputDispatchFrozen = true;
             updateInputDispatchModeLw();
         }
     }
-    
+
     public void thawInputDispatchingLw() {
         if (mInputDispatchFrozen) {
             if (WindowManagerService.DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Thawing input dispatching");
             }
-            
+
             mInputDispatchFrozen = false;
             updateInputDispatchModeLw();
         }
     }
-    
+
     public void setEventDispatchingLw(boolean enabled) {
         if (mInputDispatchEnabled != enabled) {
             if (WindowManagerService.DEBUG_INPUT) {
                 Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled);
             }
-            
+
             mInputDispatchEnabled = enabled;
             updateInputDispatchModeLw();
         }
     }
-    
+
     private void updateInputDispatchModeLw() {
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 12f61f9..1f62bc1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -347,6 +347,13 @@
         return true;    // success!
     }
 
+    public boolean startMovingTask(IWindow window, float startX, float startY) {
+        if (WindowManagerService.DEBUG_TASK_POSITIONING) Slog.d(
+                WindowManagerService.TAG, "startMovingTask: {" + startX + "," + startY + "}");
+
+        return mService.startMovingTask(window, startX, startY);
+    }
+
     public void reportDropResult(IWindow window, boolean consumed) {
         IBinder token = window.asBinder();
         if (WindowManagerService.DEBUG_DRAG) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
new file mode 100644
index 0000000..71b83a5
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -0,0 +1,325 @@
+/*
+ * 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.server.wm;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.H;
+import static com.android.server.wm.WindowManagerService.DEBUG_TASK_POSITIONING;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import android.annotation.IntDef;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+class TaskPositioner {
+    private static final String TAG = "TaskPositioner";
+
+    @IntDef(flag = true,
+            value = {
+                    CTRL_NONE,
+                    CTRL_LEFT,
+                    CTRL_RIGHT,
+                    CTRL_TOP,
+                    CTRL_BOTTOM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface CtrlType {}
+
+    private static final int CTRL_NONE   = 0x0;
+    private static final int CTRL_LEFT   = 0x1;
+    private static final int CTRL_RIGHT  = 0x2;
+    private static final int CTRL_TOP    = 0x4;
+    private static final int CTRL_BOTTOM = 0x8;
+
+    private final WindowManagerService mService;
+    private WindowPositionerEventReceiver mInputEventReceiver;
+    private Display mDisplay;
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+
+    private int mTaskId;
+    private final Rect mWindowOriginalBounds = new Rect();
+    private final Rect mWindowDragBounds = new Rect();
+    private float mStartDragX;
+    private float mStartDragY;
+    @CtrlType
+    private int mCtrlType = CTRL_NONE;
+
+    InputChannel mServerChannel;
+    InputChannel mClientChannel;
+    InputApplicationHandle mDragApplicationHandle;
+    InputWindowHandle mDragWindowHandle;
+
+    private final class WindowPositionerEventReceiver extends InputEventReceiver {
+        public WindowPositionerEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            if (!(event instanceof MotionEvent)
+                    || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
+                return;
+            }
+            final MotionEvent motionEvent = (MotionEvent) event;
+            boolean handled = false;
+
+            try {
+                boolean endDrag = false;
+                final float newX = motionEvent.getRawX();
+                final float newY = motionEvent.getRawY();
+
+                switch (motionEvent.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_DOWN @ {" + newX + ", " + newY + "}");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        if (DEBUG_TASK_POSITIONING){
+                            Slog.w(TAG, "ACTION_MOVE @ {" + newX + ", " + newY + "}");
+                        }
+                        synchronized (mService.mWindowMap) {
+                            notifyMoveLocked(newX, newY);
+                        }
+                        try {
+                            mService.mActivityManager.resizeTask(mTaskId, mWindowDragBounds);
+                        } catch(RemoteException e) {}
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_UP @ {" + newX + ", " + newY + "}");
+                        }
+                        endDrag = true;
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_TASK_POSITIONING) {
+                            Slog.w(TAG, "ACTION_CANCEL @ {" + newX + ", " + newY + "}");
+                        }
+                        endDrag = true;
+                    } break;
+                }
+
+                if (endDrag) {
+                    // Post back to WM to handle clean-ups. We still need the input
+                    // event handler for the last finishInputEvent()!
+                    mService.mH.sendEmptyMessage(H.FINISH_TASK_POSITIONING);
+                }
+                handled = true;
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishInputEvent(event, handled);
+            }
+        }
+    }
+
+    TaskPositioner(WindowManagerService service) {
+        mService = service;
+    }
+
+    /**
+     * @param display The Display that the window being dragged is on.
+     */
+    void register(Display display) {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "Registering task positioner");
+        }
+
+        if (mClientChannel != null) {
+            Slog.e(TAG, "Task positioner already registered");
+            return;
+        }
+
+        mDisplay = display;
+        mDisplay.getMetrics(mDisplayMetrics);
+        final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
+        mServerChannel = channels[0];
+        mClientChannel = channels[1];
+        mService.mInputManager.registerInputChannel(mServerChannel, null);
+
+        mInputEventReceiver = new WindowPositionerEventReceiver(mClientChannel,
+                mService.mH.getLooper());
+
+        mDragApplicationHandle = new InputApplicationHandle(null);
+        mDragApplicationHandle.name = TAG;
+        mDragApplicationHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+        mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+                mDisplay.getDisplayId());
+        mDragWindowHandle.name = TAG;
+        mDragWindowHandle.inputChannel = mServerChannel;
+        mDragWindowHandle.layer = getDragLayerLocked();
+        mDragWindowHandle.layoutParamsFlags = 0;
+        mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+        mDragWindowHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mDragWindowHandle.visible = true;
+        mDragWindowHandle.canReceiveKeys = false;
+        mDragWindowHandle.hasFocus = true;
+        mDragWindowHandle.hasWallpaper = false;
+        mDragWindowHandle.paused = false;
+        mDragWindowHandle.ownerPid = Process.myPid();
+        mDragWindowHandle.ownerUid = Process.myUid();
+        mDragWindowHandle.inputFeatures = 0;
+        mDragWindowHandle.scaleFactor = 1.0f;
+
+        // The drag window cannot receive new touches.
+        mDragWindowHandle.touchableRegion.setEmpty();
+
+        // The drag window covers the entire display
+        mDragWindowHandle.frameLeft = 0;
+        mDragWindowHandle.frameTop = 0;
+        final Point p = new Point();
+        mDisplay.getRealSize(p);
+        mDragWindowHandle.frameRight = p.x;
+        mDragWindowHandle.frameBottom = p.y;
+
+        // Pause rotations before a drag.
+        if (WindowManagerService.DEBUG_ORIENTATION) {
+            Slog.d(TAG, "Pausing rotation during re-position");
+        }
+        mService.pauseRotationLocked();
+    }
+
+    void unregister() {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "Unregistering task positioner");
+        }
+
+        if (mClientChannel == null) {
+            Slog.e(TAG, "Task positioner not registered");
+            return;
+        }
+
+        mService.mInputManager.unregisterInputChannel(mServerChannel);
+
+        mInputEventReceiver.dispose();
+        mInputEventReceiver = null;
+        mClientChannel.dispose();
+        mServerChannel.dispose();
+        mClientChannel = null;
+        mServerChannel = null;
+
+        mDragWindowHandle = null;
+        mDragApplicationHandle = null;
+        mDisplay = null;
+
+        // Resume rotations after a drag.
+        if (WindowManagerService.DEBUG_ORIENTATION) {
+            Slog.d(TAG, "Resuming rotation after re-position");
+        }
+        mService.resumeRotationLocked();
+    }
+
+    void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
+        if (DEBUG_TASK_POSITIONING) {Slog.d(TAG,
+                "startDragLocked: win=" + win + ", resize=" + resize
+                + ", {" + startX + ", " + startY + "}");
+        }
+        mCtrlType = CTRL_NONE;
+        if (resize) {
+            final Rect visibleFrame = win.mVisibleFrame;
+            if (startX < visibleFrame.left) {
+                mCtrlType |= CTRL_LEFT;
+            }
+            if (startX > visibleFrame.right) {
+                mCtrlType |= CTRL_RIGHT;
+            }
+            if (startY < visibleFrame.top) {
+                mCtrlType |= CTRL_TOP;
+            }
+            if (startY > visibleFrame.bottom) {
+                mCtrlType |= CTRL_BOTTOM;
+            }
+        }
+
+        final Task task = win.getTask();
+        mTaskId = task.mTaskId;
+        mStartDragX = startX;
+        mStartDragY = startY;
+
+        mService.getTaskBounds(mTaskId, mWindowOriginalBounds);
+    }
+
+    private void notifyMoveLocked(float x, float y) {
+        if (DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "notifyMoveLw: {" + x + "," + y + "}");
+        }
+
+        if (mCtrlType != CTRL_NONE) {
+            // This is a resizing operation.
+            final int deltaX = Math.round(x - mStartDragX);
+            final int deltaY = Math.round(y - mStartDragY);
+            // TODO: fix the min sizes when we have mininum width/height support,
+            //       use hard-coded min sizes for now.
+            final int minSizeX = (int)(dipToPx(96));
+            final int minSizeY = (int)(dipToPx(64));
+            int left = mWindowOriginalBounds.left;
+            int top = mWindowOriginalBounds.top;
+            int right = mWindowOriginalBounds.right;
+            int bottom = mWindowOriginalBounds.bottom;
+            if ((mCtrlType & CTRL_LEFT) != 0) {
+                left = Math.min(left + deltaX, right - minSizeX);
+            }
+            if ((mCtrlType & CTRL_TOP) != 0) {
+                top = Math.min(top + deltaY, bottom - minSizeY);
+            }
+            if ((mCtrlType & CTRL_RIGHT) != 0) {
+                right = Math.max(left + minSizeX, right + deltaX);
+            }
+            if ((mCtrlType & CTRL_BOTTOM) != 0) {
+                bottom = Math.max(top + minSizeY, bottom + deltaY);
+            }
+            mWindowDragBounds.set(left, top, right, bottom);
+        } else {
+            // This is a moving operation.
+            mWindowDragBounds.set(mWindowOriginalBounds);
+            mWindowDragBounds.offset(Math.round(x - mStartDragX),
+                    Math.round(y - mStartDragY));
+        }
+    }
+
+    private int getDragLayerLocked() {
+        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    private float dipToPx(float dip) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip, mDisplayMetrics);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index c3a8486..ae3bb9b 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -93,6 +93,11 @@
         }
     }
 
+    boolean allowTaskResize() {
+        return mStackId == FREEFORM_WORKSPACE_STACK_ID
+                || mStackId == DOCKED_STACK_ID;
+    }
+
     /**
      * Set the bounds of the stack and its containing tasks.
      * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index c97d12f..ce1b785 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -47,12 +47,23 @@
     public void onPointerEvent(MotionEvent motionEvent) {
         final int action = motionEvent.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
-            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_DOWN: {
                 mPointerId = motionEvent.getPointerId(0);
                 mDownX = motionEvent.getX();
                 mDownY = motionEvent.getY();
+
+                final int x = (int) mDownX;
+                final int y = (int) mDownY;
+                synchronized (this) {
+                    if (!mTouchExcludeRegion.contains(x, y)) {
+                        mService.mH.obtainMessage(H.TAP_DOWN_OUTSIDE_TASK, x, y,
+                                mDisplayContent).sendToTarget();
+                    }
+                }
                 break;
-            case MotionEvent.ACTION_MOVE:
+            }
+
+            case MotionEvent.ACTION_MOVE: {
                 if (mPointerId >= 0) {
                     int index = motionEvent.findPointerIndex(mPointerId);
                     if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC
@@ -63,6 +74,8 @@
                     }
                 }
                 break;
+            }
+
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_POINTER_UP: {
                 int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 84e0b65..d345aca 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -207,6 +207,7 @@
     static final boolean DEBUG_SURFACE_TRACE = false;
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
+    static final boolean DEBUG_TASK_POSITIONING = false;
     static final boolean DEBUG_STACK = false;
     static final boolean DEBUG_DISPLAY = false;
     static final boolean DEBUG_POWER = false;
@@ -598,6 +599,7 @@
     // Whether or not a layout can cause a wake up when theater mode is enabled.
     boolean mAllowTheaterModeWakeFromLayout;
 
+    TaskPositioner mTaskPositioner;
     DragState mDragState = null;
 
     // For frozen screen animations.
@@ -6817,6 +6819,88 @@
         }
     }
 
+    boolean startMovingTask(IWindow window, float startX, float startY) {
+        WindowState callingWin = null;
+        synchronized (mWindowMap) {
+            callingWin = windowForClientLocked(null, window, false);
+            if (!startPositioningLocked(callingWin, false /*resize*/, startX, startY)) {
+                return false;
+            }
+        }
+        try {
+            mActivityManager.setFocusedTask(callingWin.getTask().mTaskId);
+        } catch(RemoteException e) {}
+        return true;
+    }
+
+    private void startResizingTask(DisplayContent displayContent, int startX, int startY) {
+        int taskId = -1;
+        AppWindowToken atoken = null;
+        synchronized (mWindowMap) {
+            taskId = displayContent.taskIdForControlPoint(startX, startY);
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null || task.mAppTokens == null) {
+                return;
+            }
+            AppTokenList tokens = task.mAppTokens;
+            atoken = tokens.get(tokens.size() - 1);
+            WindowState win = atoken.findMainWindow();
+            if (!startPositioningLocked(win, true /*resize*/, startX, startY)) {
+                return;
+            }
+        }
+        try {
+            mActivityManager.setFocusedTask(taskId);
+        } catch(RemoteException e) {}
+    }
+
+    private boolean startPositioningLocked(
+            WindowState win, boolean resize, float startX, float startY) {
+        if (WindowManagerService.DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "startPositioningLocked: win=" + win +
+                    ", resize=" + resize + ", {" + startX + ", " + startY + "}");
+        }
+        if (win == null || win.getAppToken() == null || !win.inFreeformWorkspace()) {
+            Slog.w(TAG, "startPositioningLocked: Bad window " + win);
+            return false;
+        }
+
+        final DisplayContent displayContent = win.getDisplayContent();
+        if (displayContent == null) {
+            Slog.w(TAG, "startPositioningLocked: Invalid display content " + win);
+            return false;
+        }
+
+        Display display = displayContent.getDisplay();
+        mTaskPositioner = new TaskPositioner(this);
+        mTaskPositioner.register(display);
+        mInputMonitor.updateInputWindowsLw(true /*force*/);
+        if (!mInputManager.transferTouchFocus(
+                win.mInputChannel, mTaskPositioner.mServerChannel)) {
+            Slog.e(TAG, "startPositioningLocked: Unable to transfer touch focus");
+            mTaskPositioner.unregister();
+            mTaskPositioner = null;
+            mInputMonitor.updateInputWindowsLw(true /*force*/);
+            return false;
+        }
+
+        mTaskPositioner.startDragLocked(win, resize, startX, startY);
+        return true;
+    }
+
+    private void finishPositioning() {
+        if (WindowManagerService.DEBUG_TASK_POSITIONING) {
+            Slog.d(TAG, "finishPositioning");
+        }
+        synchronized (mWindowMap) {
+            if (mTaskPositioner != null) {
+                mTaskPositioner.unregister();
+                mTaskPositioner = null;
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+            }
+        }
+    }
+
     // -------------------------------------------------------------
     // Drag and drop
     // -------------------------------------------------------------
@@ -7082,6 +7166,9 @@
         public static final int RESET_ANR_MESSAGE = 38;
         public static final int WALLPAPER_DRAW_PENDING_TIMEOUT = 39;
 
+        public static final int TAP_DOWN_OUTSIDE_TASK = 40;
+        public static final int FINISH_TASK_POSITIONING = 41;
+
         @Override
         public void handleMessage(Message msg) {
             if (DEBUG_WINDOW_TRACE) {
@@ -7542,6 +7629,17 @@
                     }
                 }
                 break;
+
+                case TAP_DOWN_OUTSIDE_TASK: {
+                    startResizingTask((DisplayContent)msg.obj, msg.arg1, msg.arg2);
+                }
+                break;
+
+                case FINISH_TASK_POSITIONING: {
+                    finishPositioning();
+                }
+                break;
+
                 case NOTIFY_ACTIVITY_DRAWN:
                     try {
                         mActivityManager.notifyActivityDrawn((IBinder) msg.obj);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 779f342..789354d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -86,7 +86,7 @@
 
     // The thickness of a window resize handle outside the window bounds on the free form workspace
     // to capture touch events in that area.
-    private static final int RESIZE_HANDLE_WIDTH_IN_DP = 10;
+    static final int RESIZE_HANDLE_WIDTH_IN_DP = 10;
 
     static final boolean BOUNDS_FOR_TOUCH = true;
 
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index bea1f86..9824fa1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -148,6 +148,13 @@
     }
 
     @Override
+    public boolean startMoving(IWindow window, float startX, float startY)
+            throws RemoteException {
+        // pass for now
+        return false;
+    }
+
+    @Override
     public void reportDropResult(IWindow window, boolean consumed) throws RemoteException {
         // pass for now
     }