Add shell command to move activity stacks between displays

Also rename "stack movetask" command to be consistent with other
shell commands.

Test: New CTS tests coming soon.
Change-Id: I3d7e04e0ae8ea76c27c3e4c1e286d5cd4539870c
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 15f6361..141c899 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -752,6 +752,15 @@
             return true;
         }
 
+        case MOVE_STACK_TO_DISPLAY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int stackId = data.readInt();
+            int displayId = data.readInt();
+            moveStackToDisplay(stackId, displayId);
+            reply.writeNoException();
+            return true;
+        }
+
         case MOVE_TASK_TO_FRONT_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int task = data.readInt();
@@ -3885,6 +3894,19 @@
         reply.recycle();
         return list;
     }
+    public void moveStackToDisplay(int stackId, int displayId) throws RemoteException
+    {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(stackId);
+        data.writeInt(displayId);
+        mRemote.transact(MOVE_STACK_TO_DISPLAY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+    @Override
     public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException
     {
         Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index c075ed6..cada1b8 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -448,13 +448,6 @@
             mGuard.open("release");
         }
 
-        void attachToDisplay(int displayId) {
-            try {
-                mIActivityContainer.attachToDisplay(displayId);
-            } catch (RemoteException e) {
-            }
-        }
-
         void setSurface(Surface surface, int width, int height, int density)
                 throws RemoteException {
             mIActivityContainer.setSurface(surface, width, height, density);
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index 170aff3..1ff3c87 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -25,7 +25,7 @@
 
 /** @hide */
 interface IActivityContainer {
-    void attachToDisplay(int displayId);
+    void addToDisplay(int displayId);
     void setSurface(in Surface surface, int width, int height, int density);
     int startActivity(in Intent intent);
     int startActivityIntentSender(in IIntentSender intentSender);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 0323651..7166789 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -143,6 +143,7 @@
     public List<RunningServiceInfo> getServices(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
             throws RemoteException;
+    public void moveStackToDisplay(int stackId, int displayId) throws RemoteException;
     public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
     public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
     public void moveTaskBackwards(int task) throws RemoteException;
@@ -1100,4 +1101,5 @@
     int REQUEST_ACTIVITY_RELAUNCH = IBinder.FIRST_CALL_TRANSACTION+400;
     int UPDATE_DISPLAY_OVERRIDE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 401;
     int UNREGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+402;
+    int MOVE_STACK_TO_DISPLAY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 403;
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5d41d36..5aee770 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9513,6 +9513,22 @@
     }
 
     @Override
+    public void moveStackToDisplay(int stackId, int displayId) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveStackToDisplay()");
+
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG_STACK) Slog.d(TAG_STACK, "moveStackToDisplay: moving stackId=" + stackId
+                        + " to displayId=" + displayId);
+                mStackSupervisor.moveStackToDisplayLocked(stackId, displayId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public boolean removeTask(int taskId) {
         enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()");
         synchronized (this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8234f35..3efd300 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -215,6 +215,8 @@
                     return runGetInactive(pw);
                 case "send-trim-memory":
                     return runSendTrimMemory(pw);
+                case "display":
+                    return runDisplay(pw);
                 case "stack":
                     return runStack(pw);
                 case "task":
@@ -1631,12 +1633,23 @@
         return 0;
     }
 
+    int runDisplay(PrintWriter pw) throws RemoteException {
+        String op = getNextArgRequired();
+        switch (op) {
+            case "move-stack":
+                return runDisplayMoveStack(pw);
+            default:
+                getErrPrintWriter().println("Error: unknown command '" + op + "'");
+                return -1;
+        }
+    }
+
     int runStack(PrintWriter pw) throws RemoteException {
         String op = getNextArgRequired();
         switch (op) {
             case "start":
                 return runStackStart(pw);
-            case "movetask":
+            case "move-task":
                 return runStackMoveTask(pw);
             case "resize":
                 return runStackResize(pw);
@@ -1691,6 +1704,15 @@
         return new Rect(left, top, right, bottom);
     }
 
+    int runDisplayMoveStack(PrintWriter pw) throws RemoteException {
+        String stackIdStr = getNextArgRequired();
+        int stackId = Integer.parseInt(stackIdStr);
+        String displayIdStr = getNextArgRequired();
+        int displayId = Integer.parseInt(displayIdStr);
+        mInterface.moveStackToDisplay(stackId, displayId);
+        return 0;
+    }
+
     int runStackStart(PrintWriter pw) throws RemoteException {
         String displayIdStr = getNextArgRequired();
         int displayId = Integer.parseInt(displayIdStr);
@@ -2450,10 +2472,13 @@
             pw.println("  send-trim-memory [--user <USER_ID>] <PROCESS>");
             pw.println("          [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]");
             pw.println("      Send a memory trim event to a <PROCESS>.");
+            pw.println("  display [COMMAND] [...]: sub-commands for operating on displays.");
+            pw.println("       move-stack <STACK_ID> <DISPLAY_ID>");
+            pw.println("           Move <STACK_ID> from its current display to <DISPLAY_ID>.");
             pw.println("  stack [COMMAND] [...]: sub-commands for operating on activity stacks.");
             pw.println("       start <DISPLAY_ID> <INTENT>");
             pw.println("           Start a new activity on <DISPLAY_ID> using <INTENT>");
-            pw.println("       movetask <TASK_ID> <STACK_ID> [true|false]");
+            pw.println("       move-task <TASK_ID> <STACK_ID> [true|false]");
             pw.println("           Move <TASK_ID> from its current stack to the top (true) or");
             pw.println("           bottom (false) of <STACK_ID>.");
             pw.println("       resize <STACK_ID> <LEFT,TOP,RIGHT,BOTTOM>");
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1c3a885..0d79980 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -449,10 +449,23 @@
                 ? new LaunchingTaskPositioner() : null;
     }
 
-    void attachDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+    /** Adds the stack to specified display and calls WindowManager to do the same. */
+    void addToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+        final Rect bounds = mWindowManager.addStackToDisplay(mStackId, activityDisplay.mDisplayId,
+                onTop);
+        postAddToDisplay(activityDisplay, bounds);
+    }
+
+    /**
+     * Updates internal state after adding to new display.
+     * @param activityDisplay New display to which this stack was attached.
+     * @param bounds Updated bounds.
+     */
+    private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
+            Rect bounds) {
         mDisplayId = activityDisplay.mDisplayId;
         mStacks = activityDisplay.mStacks;
-        mBounds = mWindowManager.attachStack(mStackId, activityDisplay.mDisplayId, onTop);
+        mBounds = bounds;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
             mTaskPositioner.setDisplay(activityDisplay.mDisplay);
@@ -468,7 +481,21 @@
         }
     }
 
-    void remove() {
+    /**
+     * Moves the stack to specified display.
+     * @param activityDisplay Target display to move the stack to.
+     */
+    void moveToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay) {
+        removeFromDisplay();
+        final Rect bounds = mWindowManager.moveStackToDisplay(mStackId, activityDisplay.mDisplayId);
+        postAddToDisplay(activityDisplay, bounds);
+    }
+
+    /**
+     * Updates the inner state of the stack to remove it from its current parent, so it can be
+     * either destroyed completely or re-parented.
+     */
+    private void removeFromDisplay() {
         mDisplayId = Display.INVALID_DISPLAY;
         mStacks = null;
         if (mTaskPositioner != null) {
@@ -480,6 +507,11 @@
             mStackSupervisor.resizeDockedStackLocked(
                     null, null, null, null, null, PRESERVE_WINDOWS);
         }
+    }
+
+    /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
+    void remove() {
+        removeFromDisplay();
         mStackSupervisor.deleteActivityContainerRecord(mStackId);
         mWindowManager.removeStack(mStackId);
         onParentChanged();
@@ -696,11 +728,7 @@
         }
 
         mStacks.add(addIndex, this);
-
-        // TODO(multi-display): Needs to also work if focus is moving to the non-home display.
-        if (isOnHomeDisplay()) {
-            mStackSupervisor.setFocusStackUnchecked(reason, this);
-        }
+        mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
             insertTaskAtTop(task, null);
             return;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index eed8dd7..6167671 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2300,7 +2300,7 @@
 
         ActivityContainer activityContainer = new ActivityContainer(stackId);
         mActivityContainers.put(stackId, activityContainer);
-        activityContainer.attachToDisplayLocked(activityDisplay, onTop);
+        activityContainer.addToDisplayLocked(activityDisplay, onTop);
         return activityContainer.mStack;
     }
 
@@ -2364,6 +2364,40 @@
     }
 
     /**
+     * Move stack with all its existing content to specified display.
+     * @param stackId Id of stack to move.
+     * @param displayId Id of display to move stack to.
+     */
+    void moveStackToDisplayLocked(int stackId, int displayId) {
+        final ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+        if (activityDisplay == null) {
+            throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
+                    + displayId);
+        }
+        final ActivityContainer activityContainer = mActivityContainers.get(stackId);
+        if (activityContainer != null) {
+            if (activityContainer.isAttachedLocked()) {
+                if (activityContainer.getDisplayId() == displayId) {
+                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
+                            + " to its current displayId=" + displayId);
+                }
+
+                activityContainer.moveToDisplayLocked(activityDisplay);
+            } else {
+                throw new IllegalStateException("moveStackToDisplayLocked: Stack with stackId="
+                        + stackId + " is not attached to any display.");
+            }
+        } else {
+            throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
+                    + stackId);
+        }
+
+        ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */,
+                !PRESERVE_WINDOWS);
+        // TODO(multi-display): resize stacks properly if moved from split-screen.
+    }
+
+    /**
      * Moves the specified task record to the input stack id.
      * WARNING: This method performs an unchecked/raw move of the task and
      * can leave the system in an unstable state if used incorrectly.
@@ -4032,22 +4066,33 @@
             }
         }
 
-        void attachToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
-            if (DEBUG_STACK) Slog.d(TAG_STACK, "attachToDisplayLocked: " + this
+        /**
+         * Adds the stack to specified display. Also calls WindowManager to do the same from
+         * {@link ActivityStack#addToDisplay(ActivityDisplay, boolean)}.
+         * @param activityDisplay The display to add the stack to.
+         * @param onTop If true the stack will be place at the top of the display, else at the
+         *              bottom.
+         */
+        void addToDisplayLocked(ActivityDisplay activityDisplay, boolean onTop) {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "addToDisplayLocked: " + this
                     + " to display=" + activityDisplay + " onTop=" + onTop);
+            if (mActivityDisplay != null) {
+                throw new IllegalStateException("ActivityContainer is already attached, " +
+                        "displayId=" + mActivityDisplay.mDisplayId);
+            }
             mActivityDisplay = activityDisplay;
-            mStack.attachDisplay(activityDisplay, onTop);
+            mStack.addToDisplay(activityDisplay, onTop);
             activityDisplay.attachActivities(mStack, onTop);
         }
 
         @Override
-        public void attachToDisplay(int displayId) {
+        public void addToDisplay(int displayId) {
             synchronized (mService) {
                 ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
                 if (activityDisplay == null) {
                     return;
                 }
-                attachToDisplayLocked(activityDisplay, true);
+                addToDisplayLocked(activityDisplay, true);
             }
         }
 
@@ -4103,16 +4148,44 @@
             }
         }
 
+        /** Remove the stack completely. */
         void removeLocked() {
             if (DEBUG_STACK) Slog.d(TAG_STACK, "removeLocked: " + this + " from display="
                     + mActivityDisplay + " Callers=" + Debug.getCallers(2));
             if (mActivityDisplay != null) {
-                mActivityDisplay.detachActivitiesLocked(mStack);
-                mActivityDisplay = null;
+                removeFromDisplayLocked();
             }
             mStack.remove();
         }
 
+        /**
+         * Remove the stack from its current {@link ActivityDisplay}, so it can be either destroyed
+         * completely or re-parented.
+         */
+        private void removeFromDisplayLocked() {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "removeFromDisplayLocked: " + this
+                    + " current displayId=" + mActivityDisplay.mDisplayId);
+
+            mActivityDisplay.detachActivitiesLocked(mStack);
+            mActivityDisplay = null;
+        }
+
+        /**
+         * Move the stack to specified display.
+         * @param activityDisplay Target display to move the stack to.
+         */
+        void moveToDisplayLocked(ActivityDisplay activityDisplay) {
+            if (DEBUG_STACK) Slog.d(TAG_STACK, "moveToDisplayLocked: " + this + " from display="
+                    + mActivityDisplay + " to display=" + activityDisplay
+                    + " Callers=" + Debug.getCallers(2));
+
+            removeFromDisplayLocked();
+
+            mActivityDisplay = activityDisplay;
+            mStack.moveToDisplay(activityDisplay);
+            activityDisplay.attachActivities(mStack, ON_TOP);
+        }
+
         @Override
         public final int startActivity(Intent intent) {
             return mService.startActivity(intent, this);
@@ -4232,7 +4305,7 @@
                         new VirtualActivityDisplay(width, height, density);
                 mActivityDisplay = virtualActivityDisplay;
                 mActivityDisplays.put(virtualActivityDisplay.mDisplayId, virtualActivityDisplay);
-                attachToDisplayLocked(virtualActivityDisplay, true);
+                addToDisplayLocked(virtualActivityDisplay, true);
             }
 
             if (mSurface != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 243c4a5..450ed20 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -74,6 +74,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
@@ -184,6 +185,7 @@
     // Accessed directly by all users.
     private boolean mLayoutNeeded;
     int pendingLayoutChanges;
+    // TODO(multi-display): remove some of the usages.
     final boolean isDefaultDisplay;
 
     /** Window tokens that are in the process of exiting, but still on screen for animations. */
@@ -535,9 +537,72 @@
         out.set(mContentRect);
     }
 
-    /** Refer to {@link WindowManagerService#attachStack(int, int, boolean)} */
-    void attachStack(TaskStack stack, boolean onTop) {
-        mTaskStackContainers.attachStack(stack, onTop);
+    /**
+     * Adds the stack to this display.
+     * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+     */
+    Rect addStackToDisplay(int stackId, boolean onTop) {
+        boolean attachedToDisplay = false;
+        TaskStack stack = mService.mStackIdToStack.get(stackId);
+        if (stack == null) {
+            if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+                    + mDisplayId);
+
+            stack = getStackById(stackId);
+            if (stack != null) {
+                // It's already attached to the display...clear mDeferRemoval and move stack to
+                // appropriate z-order on display as needed.
+                stack.mDeferRemoval = false;
+                moveStack(stack, onTop);
+                attachedToDisplay = true;
+            } else {
+                stack = new TaskStack(mService, stackId);
+            }
+
+            mService.mStackIdToStack.put(stackId, stack);
+            if (stackId == DOCKED_STACK_ID) {
+                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
+            }
+        } else {
+            final DisplayContent currentDC = stack.getDisplayContent();
+            if (currentDC != null) {
+                throw new IllegalStateException("Trying to add stackId=" + stackId
+                        + "to displayId=" + mDisplayId + ", but it's already attached to displayId="
+                        + currentDC.getDisplayId());
+            }
+        }
+
+        if (!attachedToDisplay) {
+            mTaskStackContainers.addStackToDisplay(stack, onTop);
+        }
+
+        if (stack.getRawFullscreen()) {
+            return null;
+        }
+        final Rect bounds = new Rect();
+        stack.getRawBounds(bounds);
+        return bounds;
+    }
+
+    /** Removes the stack from the display and prepares for changing the parent. */
+    private void removeStackFromDisplay(TaskStack stack) {
+        mTaskStackContainers.removeStackFromDisplay(stack);
+    }
+
+    /** Moves the stack to this display and returns the updated bounds. */
+    Rect moveStackToDisplay(TaskStack stack) {
+        final DisplayContent currentDisplayContent = stack.getDisplayContent();
+        if (currentDisplayContent == null) {
+            throw new IllegalStateException("Trying to move stackId=" + stack.mStackId
+                    + " which is not currently attached to any display");
+        }
+        if (stack.getDisplayContent().getDisplayId() == mDisplayId) {
+            throw new IllegalArgumentException("Trying to move stackId=" + stack.mStackId
+                    + " to its current displayId=" + mDisplayId);
+        }
+
+        currentDisplayContent.removeStackFromDisplay(stack);
+        return addStackToDisplay(stack.mStackId, true /* onTop */);
     }
 
     void moveStack(TaskStack stack, boolean toTop) {
@@ -2394,7 +2459,11 @@
             if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
                     pendingLayoutChanges);
 
-            if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+            // TODO(multi-display): For now adjusting wallpaper only on primary display to avoid
+            // the wallpaper window jumping across displays.
+            // Remove check for default display when there will be support for multiple wallpaper
+            // targets (on different displays).
+            if (isDefaultDisplay && (pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
                 adjustWallpaperWindows();
             }
 
@@ -3078,7 +3147,11 @@
      */
     private class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
 
-        void attachStack(TaskStack stack, boolean onTop) {
+        /**
+         * Adds the stack to this container.
+         * @see WindowManagerService#addStackToDisplay(int, int, boolean)
+         */
+        void addStackToDisplay(TaskStack stack, boolean onTop) {
             if (stack.mStackId == HOME_STACK_ID) {
                 if (mHomeStack != null) {
                     throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
@@ -3089,6 +3162,21 @@
             stack.onDisplayChanged(DisplayContent.this);
         }
 
+        /** Removes the stack from its container and prepare for changing the parent. */
+        void removeStackFromDisplay(TaskStack stack) {
+            removeChild(stack);
+            stack.onRemovedFromDisplay();
+            // TODO: remove when window list will be gone.
+            // Manually remove records from window list and tap excluded windows list.
+            for (int i = mWindows.size() - 1; i >= 0; --i) {
+                final WindowState windowState = mWindows.get(i);
+                if (stack == windowState.getStack()) {
+                    mWindows.remove(i);
+                    mTapExcludedWindows.remove(windowState);
+                }
+            }
+        }
+
         void moveStack(TaskStack stack, boolean toTop) {
             if (StackId.isAlwaysOnTop(stack.mStackId) && !toTop) {
                 // This stack is always-on-top silly...
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6325cda..f038135 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -215,49 +215,6 @@
         return dc;
     }
 
-    /** Adds the input stack id to the input display id and returns the bounds of the added stack.*/
-    Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
-        final DisplayContent dc = getDisplayContent(displayId);
-        if (dc == null) {
-            Slog.w(TAG_WM, "addStackToDisplay: Trying to add stackId=" + stackId
-                    + " to unknown displayId=" + displayId + " callers=" + Debug.getCallers(6));
-            return null;
-        }
-
-        boolean attachedToDisplay = false;
-        TaskStack stack = mService.mStackIdToStack.get(stackId);
-        if (stack == null) {
-            if (DEBUG_STACK) Slog.d(TAG_WM, "attachStack: stackId=" + stackId);
-
-            stack = dc.getStackById(stackId);
-            if (stack != null) {
-                // It's already attached to the display...clear mDeferRemoval and move stack to
-                // appropriate z-order on display as needed.
-                stack.mDeferRemoval = false;
-                dc.moveStack(stack, onTop);
-                attachedToDisplay = true;
-            } else {
-                stack = new TaskStack(mService, stackId);
-            }
-
-            mService.mStackIdToStack.put(stackId, stack);
-            if (stackId == DOCKED_STACK_ID) {
-                dc.mDividerControllerLocked.notifyDockedStackExistsChanged(true);
-            }
-        }
-
-        if (!attachedToDisplay) {
-            dc.attachStack(stack, onTop);
-        }
-
-        if (stack.getRawFullscreen()) {
-            return null;
-        }
-        final Rect bounds = new Rect();
-        stack.getRawBounds(bounds);
-        return bounds;
-    }
-
     boolean isLayoutNeeded() {
         final int numDisplays = mChildren.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 60ba025..a0270c6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -793,6 +793,14 @@
     void removeImmediately() {
         super.removeImmediately();
 
+        onRemovedFromDisplay();
+    }
+
+    /**
+     * Removes the stack it from its current parent, so it can be either destroyed completely or
+     * re-parented.
+     */
+    void onRemovedFromDisplay() {
         mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
@@ -800,15 +808,13 @@
             mAnimationBackgroundSurface.destroySurface();
             mAnimationBackgroundSurface = null;
         }
-        final DockedStackDividerController dividerController =
-                mDisplayContent.mDividerControllerLocked;
-        mDisplayContent = null;
-
-        mService.mWindowPlacerLocked.requestTraversal();
 
         if (mStackId == DOCKED_STACK_ID) {
-            dividerController.notifyDockedStackExistsChanged(false);
+            mDisplayContent.mDividerControllerLocked.notifyDockedStackExistsChanged(false);
         }
+
+        mDisplayContent = null;
+        mService.mWindowPlacerLocked.requestTraversal();
     }
 
     void resetAnimationBackgroundAnimator() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0abcd9f..91663b0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2721,11 +2721,11 @@
     }
 
     void setFocusTaskRegionLocked() {
-        if (mFocusedApp != null) {
-            final Task task = mFocusedApp.mTask;
-            final DisplayContent displayContent = task.getDisplayContent();
+        final Task focusedTask = mFocusedApp != null ? mFocusedApp.mTask : null;
+        if (focusedTask != null) {
+            final DisplayContent displayContent = focusedTask.getDisplayContent();
             if (displayContent != null) {
-                displayContent.setTouchExcludeRegion(task);
+                displayContent.setTouchExcludeRegion(focusedTask);
             }
         }
     }
@@ -3425,24 +3425,63 @@
     }
 
     /**
-     * Create a new TaskStack and place it on a DisplayContent.
+     * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
+     * specified stackId.
      * @param stackId The unique identifier of the new stack.
      * @param displayId The unique identifier of the DisplayContent.
      * @param onTop If true the stack will be place at the top of the display,
-     *              else at the bottom
-     * @return The initial bounds the stack was created with. null means fullscreen.
+     *              else at the bottom.
+     * @return The bounds that the stack has after adding. null means fullscreen.
      */
-    public Rect attachStack(int stackId, int displayId, boolean onTop) {
+    public Rect addStackToDisplay(int stackId, int displayId, boolean onTop) {
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mWindowMap) {
-                return mRoot.addStackToDisplay(stackId, displayId, onTop);
+                final DisplayContent dc = mRoot.getDisplayContent(displayId);
+                if (dc == null) {
+                    throw new IllegalArgumentException("Trying to add stackId=" + stackId
+                            + " to unknown displayId=" + displayId);
+                }
+
+                return dc.addStackToDisplay(stackId, onTop);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    /**
+     * Move a TaskStack from current DisplayContent to specified one.
+     * @param stackId The unique identifier of the new stack.
+     * @param displayId The unique identifier of the new display.
+     */
+    public Rect moveStackToDisplay(int stackId, int displayId) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                TaskStack stack = mStackIdToStack.get(stackId);
+                if (stack == null) {
+                    throw new IllegalArgumentException("Trying to move unknown stackId=" + stackId
+                            + " to displayId=" + displayId);
+                }
+
+                final DisplayContent targetDisplayContent = mRoot.getDisplayContent(displayId);
+                if (targetDisplayContent == null) {
+                    throw new IllegalArgumentException("Trying to move stackId=" + stackId
+                            + " to unknown displayId=" + displayId);
+                }
+
+                return targetDisplayContent.moveStackToDisplay(stack);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * Remove a TaskStack completely.
+     * @param stackId The unique identifier of the stack.
+     */
     public void removeStack(int stackId) {
         synchronized (mWindowMap) {
             final TaskStack stack = mStackIdToStack.get(stackId);