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/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;