Merge "Resize task to fullscreen when moving from pinned stack."
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6be5775..300c7df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,8 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -144,6 +146,8 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import static java.lang.Integer.MAX_VALUE;
+
 import android.Manifest;
 import android.Manifest.permission;
 import android.annotation.NonNull;
@@ -9808,14 +9812,19 @@
                 } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
                     stackId = FREEFORM_WORKSPACE_STACK_ID;
                 }
+
+                // Reparent the task to the right stack if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
                 if (stackId != task.getStackId()) {
-                    mStackSupervisor.moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
-                            !FORCE_FOCUS, "resizeTask");
+                    // Defer resume until the task is resized below
+                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+                            DEFER_RESUME, "resizeTask");
                     preserveWindow = false;
                 }
 
-                task.resize(bounds, resizeMode, preserveWindow, false /* deferResume */);
+                // After reparenting (which only resizes the task to the stack bounds), resize the
+                // task to the actual bounds provided
+                task.resize(bounds, resizeMode, preserveWindow, !DEFER_RESUME);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10194,14 +10203,16 @@
                     throw new IllegalArgumentException(
                             "exitFreeformMode: No activity record matching token=" + token);
                 }
-                final ActivityStack stack = r.getStackLocked(token);
+
+                final ActivityStack stack = r.getStack();
                 if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
                     throw new IllegalStateException(
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
-                mStackSupervisor.moveTaskToStackLocked(r.task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
-                        ON_TOP, !FORCE_FOCUS, "exitFreeformMode", ANIMATE);
+                r.task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT,
+                        ANIMATE, !DEFER_RESUME, "exitFreeformMode");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10218,15 +10229,22 @@
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
+                    return;
+                }
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
                         + " to stackId=" + stackId + " toTop=" + toTop);
                 if (stackId == DOCKED_STACK_ID) {
                     mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
                             null /* initialBounds */);
                 }
-                boolean result = mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop,
-                        !FORCE_FOCUS, "moveTaskToStack", ANIMATE);
-                if (result && stackId == DOCKED_STACK_ID) {
+
+                final boolean successful = task.reparent(stackId, toTop,
+                        REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
+                if (successful && stackId == DOCKED_STACK_ID) {
                     // If task moved to docked stack - show recents if needed.
                     mWindowManager.showRecentApps(false /* fromHome */);
                 }
@@ -10258,22 +10276,23 @@
                 // TODO: App transition
                 mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
 
-                // Defer the resume so resume/pausing while moving stacks is dangerous.
-                mStackSupervisor.moveTaskToStackLocked(topTask.taskId, DOCKED_STACK_ID,
-                        false /* toTop */, !FORCE_FOCUS, "swapDockedAndFullscreenStack",
-                        ANIMATE, true /* deferResume */);
+                // Defer the resume until we move all the docked tasks to the fullscreen stack below
+                topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+                        DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
                 final int size = tasks.size();
                 for (int i = 0; i < size; i++) {
                     final int id = tasks.get(i).taskId;
                     if (id == topTask.taskId) {
                         continue;
                     }
-                    mStackSupervisor.moveTaskToStackLocked(id,
-                            FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */, !FORCE_FOCUS,
-                            "swapDockedAndFullscreenStack", ANIMATE, true /* deferResume */);
+
+                    // Defer the resume until after all the tasks have been moved
+                    tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+                            REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
+                            "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
                 }
 
-                // Because we deferred the resume, to avoid conflicts with stack switches while
+                // Because we deferred the resume to avoid conflicts with stack switches while
                 // resuming, we need to do it after all the tasks are moved.
                 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -10306,12 +10325,20 @@
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
+                    return false;
+                }
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateState(createMode, initialBounds);
-                final boolean moved = mStackSupervisor.moveTaskToStackLocked(
-                        taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
-                        animate, DEFER_RESUME);
+
+                // Defer resuming until we move the home stack to the front below
+                final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+                        REPARENT_KEEP_STACK_AT_FRONT, animate, DEFER_RESUME,
+                        "moveTaskToDockedStack");
                 if (moved) {
                     if (moveHomeStackFront) {
                         mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
@@ -10445,7 +10472,8 @@
                     stack.positionChildAt(task, position);
                 } else {
                     // Reparent to new stack.
-                    task.reparent(stackId, position, "positionTaskInStack");
+                    task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
+                            !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 10055c8..c79ed2a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -797,6 +797,15 @@
                     + " is already the parent of r=" + this);
         }
 
+        // TODO: Ensure that we do not directly reparent activities across stacks, as that may leave
+        //       the stacks in strange states. For now, we should use Task.reparent() to ensure that
+        //       the stack is left in an OK state.
+        if (prevTask != null && newTask != null && prevTask.getStack() != newTask.getStack()) {
+            throw new IllegalArgumentException(reason + ": task=" + newTask
+                    + " is in a different stack (" + newTask.getStackId() + ") than the parent of"
+                    + " r=" + this + " (" + prevTask.getStackId() + ")");
+        }
+
         // Must reparent first in window manager
         mWindowContainerController.reparent(newTask.getWindowContainerController(), position);
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 827a41e..5d4bff9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -5077,45 +5077,6 @@
         moveToFront(reason);
     }
 
-    /**
-     * Moves the input activity from its current stack to this one.
-     * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is
-     * created on this stack which the activity is added to.
-     * */
-    void moveActivityToStack(ActivityRecord r) {
-        final ActivityStack prevStack = r.getStack();
-        if (prevStack.mStackId == mStackId) {
-            // You are already in the right stack silly...
-            return;
-        }
-
-        final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
-                && (mStackSupervisor.topRunningActivityLocked() == r);
-        final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
-        final boolean wasPaused = prevStack.mPausingActivity == r;
-
-        // Create a new task for the activity to be parented in
-        final TaskRecord task = createTaskRecord(
-                mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
-                r.info, r.intent, null, null, true, r.mActivityType);
-        // This is a new task, so reparenting it to position 0 will move it to the top
-        r.reparent(task, 0 /* position */, "moveActivityToStack");
-
-        // Notify the task actiivties if it was moved to/from a pinned stack
-        mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
-
-        // Resume the activity if necessary after it has moved
-        moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
-                "moveActivityToStack");
-        if (wasResumed) {
-            prevStack.mResumedActivity = null;
-        }
-        if (wasPaused) {
-            prevStack.mPausingActivity = null;
-            prevStack.removeTimeoutsForActivityLocked(r);
-        }
-    }
-
     public int getStackId() {
         return mStackId;
     }
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d3ad057..6e138a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -89,6 +89,9 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
 import static java.lang.Integer.MAX_VALUE;
 
@@ -1992,8 +1995,8 @@
                     stackId = task.getLaunchStackId();
                 }
                 if (stackId != currentStack.mStackId) {
-                    currentStack = moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
-                            !FORCE_FOCUS, reason);
+                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+                            DEFER_RESUME, "findTaskToMoveToFrontLocked");
                     stackId = currentStack.mStackId;
                     // moveTaskToStackUncheckedLocked() should already placed the task on top,
                     // still need moveTaskToFrontLocked() below for any transition settings.
@@ -2278,6 +2281,7 @@
             if (onTop) {
                 for (int i = 0; i < size; i++) {
                     final TaskRecord task = tasks.get(i);
+                    final boolean isTopTask = i == (size - 1);
                     if (fromStackId == PINNED_STACK_ID) {
                         // Update the return-to to reflect where the pinned stack task was moved
                         // from so that we retain the stack that was previously visible if the
@@ -2287,22 +2291,25 @@
                         MetricsLogger.action(mService.mContext,
                                 MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
                     }
-                    moveTaskToStackLocked(tasks.get(i).taskId,
-                            FULLSCREEN_WORKSPACE_STACK_ID, onTop, onTop /*forceFocus*/,
-                            "moveTasksToFullscreenStack - onTop", ANIMATE, DEFER_RESUME);
+                    // Defer resume until all the tasks have been moved to the fullscreen stack
+                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+                            REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
+                            "moveTasksToFullscreenStack - onTop");
                 }
-
-                ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
-                resumeFocusedStackTopActivityLocked();
             } else {
                 for (int i = 0; i < size; i++) {
                     final TaskRecord task = tasks.get(i);
-                    final int position = fullscreenStack != null ?
-                            Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+                    final int position = fullscreenStack != null
+                            ? Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+                    // Defer resume until all the tasks have been moved to the fullscreen stack
                     task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, position,
-                            "moveTasksToFullscreenStack - NOT_onTop");
+                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+                            DEFER_RESUME, "moveTasksToFullscreenStack - NOT_onTop");
                 }
             }
+
+            ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+            resumeFocusedStackTopActivityLocked();
         } finally {
             mAllowDockedStackResize = true;
             mWindowManager.continueSurfaceLayout();
@@ -2445,14 +2452,16 @@
                             ? Math.max(0, fullscreenStack.getChildCount() - 1)
                             : fullscreenStack.getChildCount();
                     final TaskRecord task = tasks.get(i);
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition, "removeStack");
+                    // Defer resume until we remove all the tasks
+                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition,
+                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, "removeStack");
                 }
                 ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 resumeFocusedStackTopActivityLocked();
             } else {
                 // If there is no fullscreen stack, then create the stack and move all the tasks
                 // onto the stack
-                moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */);
+                moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
             }
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -2657,168 +2666,52 @@
     }
 
     /**
-     * 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.
-     * Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
-     * @param task Task to move.
-     * @param stackId Id of stack to move task to.
-     * @param toTop True if the task should be placed at the top of the stack.
-     * @param forceFocus if focus should be moved to the new stack
-     * @param reason Reason the task is been moved.
-     * @return The stack the task was moved to.
+     * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
+     * the various checks on tasks that are going to be reparented from one stack to another.
      */
-    ActivityStack moveTaskToStackUncheckedLocked(TaskRecord task, int stackId, boolean toTop,
-            boolean forceFocus, String reason) {
-
-        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
-            throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
-                    + "support multi-window task=" + task + " to stackId=" + stackId);
-        }
-
-        final ActivityRecord r = task.topRunningActivityLocked();
+    ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
-        final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
-        final boolean wasResumed = prevStack.mResumedActivity == r;
-        final boolean wasPaused = prevStack.mPausingActivity == r;
-        // In some cases the focused stack isn't the front stack. E.g. pinned stack.
-        // Whenever we are moving the top activity from the front stack we want to make sure to move
-        // the stack to the front.
-        final boolean wasFront = isFrontStackOnDisplay(prevStack)
-                && (prevStack.topRunningActivityLocked() == r);
 
-        if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
-            // We don't allow moving a unresizeable task to the docked stack since the docked
-            // stack is used for split-screen mode and will cause things like the docked divider to
-            // show up. We instead leave the task in its current stack or move it to the fullscreen
-            // stack if it isn't currently in a stack.
-            stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
-            Slog.w(TAG, "Can not move unresizeable task=" + task
-                    + " to docked stack. Moving to stackId=" + stackId + " instead.");
+        // Check that we aren't reparenting to the same stack that the task is already in
+        if (prevStack != null && prevStack.mStackId == stackId) {
+            Slog.w(TAG, "Can not reparent to same stack, task=" + task
+                    + " already in stackId=" + stackId);
+            return prevStack;
         }
 
-        // Temporarily disable resizeablility of task we are moving. We don't want it to be resized
-        // if a docked stack is created below which will lead to the stack we are moving from and
-        // its resizeable tasks being resized.
-        task.mTemporarilyUnresizable = true;
-        final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
-        task.mTemporarilyUnresizable = false;
-        task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
-
-        // Reset the resumed activity on the previous stack
-        if (wasResumed) {
-            prevStack.mResumedActivity = null;
-        }
-        // Reset the paused activity on the previous stack
-        if (wasPaused) {
-            prevStack.mPausingActivity = null;
-            prevStack.removeTimeoutsForActivityLocked(r);
+        // Ensure that we aren't trying to move into a multi-window stack without multi-window
+        // support
+        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+            throw new IllegalArgumentException("Device doesn't support multi-window, can not"
+                    + " reparent task=" + task + " to stackId=" + stackId);
         }
 
-        // If the task had focus before (or we're requested to move focus),
-        // move focus to the new stack by moving the stack to the front.
-        stack.moveToFrontAndResumeStateIfNeeded(r, forceFocus || wasFocused || wasFront, wasResumed,
-                wasPaused, reason);
-
-        return stack;
-    }
-
-    boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
-            String reason, boolean animate) {
-        return moveTaskToStackLocked(taskId, stackId, toTop, forceFocus, reason, animate,
-                false /* deferResume */);
-    }
-
-    boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
-            String reason, boolean animate, boolean deferResume) {
-        final TaskRecord task = anyTaskForIdLocked(taskId);
-        if (task == null) {
-            Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
-            return false;
-        }
-
-        final ActivityStack currentStack = task.getStack();
-        if (currentStack != null && currentStack.mStackId == stackId) {
-            // You are already in the right stack silly...
-            Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
-            return true;
-        }
-
+        // Ensure that we aren't trying to move into a freeform stack without freeform
+        // support
         if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
-            throw new IllegalArgumentException("moveTaskToStack:"
-                    + "Attempt to move task " + taskId + " to unsupported freeform stack");
+            throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
+                    + " task=" + task);
         }
 
-        final ActivityRecord topActivity = task.getTopActivity();
-        final int sourceStackId = task.getStackId();
-        final boolean mightReplaceWindow =
-                StackId.replaceWindowsOnTaskMove(sourceStackId, stackId) && topActivity != null;
-        if (mightReplaceWindow) {
-            // We are about to relaunch the activity because its configuration changed due to
-            // being maximized, i.e. size change. The activity will first remove the old window
-            // and then add a new one. This call will tell window manager about this, so it can
-            // preserve the old window until the new one is drawn. This prevents having a gap
-            // between the removal and addition, in which no window is visible. We also want the
-            // entrance of the new window to be properly animated.
-            // Note here we always set the replacing window first, as the flags might be needed
-            // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
-            mWindowManager.setWillReplaceWindow(topActivity.appToken, animate);
+        // We don't allow moving a unresizeable task to the docked stack since the docked stack is
+        // used for split-screen mode and will cause things like the docked divider to show up. We
+        // instead leave the task in its current stack or move it to the fullscreen stack if it
+        // isn't currently in a stack.
+        if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
+            stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+            Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
+                    + " Moving to stackId=" + stackId + " instead.");
         }
 
-        mWindowManager.deferSurfaceLayout();
-        final int preferredLaunchStackId = stackId;
-        boolean kept = true;
+        // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
+        // example, a docked stack is created which will lead to the stack we are moving from being
+        // resized and and its resizeable tasks being resized.
         try {
-            final ActivityStack stack = moveTaskToStackUncheckedLocked(
-                    task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
-            stackId = stack.mStackId;
-
-            if (!animate) {
-                stack.mNoAnimActivities.add(topActivity);
-            }
-
-            // We might trigger a configuration change. Save the current task bounds for freezing.
-            // TODO: Should this call be moved inside the resize method in WM?
-            stack.prepareFreezingTaskBounds();
-
-            // Make sure the task has the appropriate bounds/size for the stack it is in.
-            if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                    && !Objects.equals(task.mBounds, stack.mBounds)) {
-                kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
-                        deferResume);
-            } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
-                Rect bounds = task.getLaunchBounds();
-                if (bounds == null) {
-                    stack.layoutTaskInStack(task, null);
-                    bounds = task.mBounds;
-                }
-                kept = task.resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
-            } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-                kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
-                        deferResume);
-            }
+            task.mTemporarilyUnresizable = true;
+            return getStack(stackId, CREATE_IF_NEEDED, toTop);
         } finally {
-            mWindowManager.continueSurfaceLayout();
+            task.mTemporarilyUnresizable = false;
         }
-
-        if (mightReplaceWindow) {
-            // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
-            // window), we need to clear the replace window settings. Otherwise, we schedule a
-            // timeout to remove the old window if the replacing window is not coming in time.
-            mWindowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
-        }
-
-        if (!deferResume) {
-
-            // The task might have already been running and its visibility needs to be synchronized with
-            // the visibility of the stack / windows.
-            ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
-            resumeFocusedStackTopActivityLocked();
-        }
-
-        handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);
-
-        return (preferredLaunchStackId == stackId);
     }
 
     boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
@@ -2849,8 +2742,8 @@
 
     void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
             boolean moveHomeStackToFront) {
-
         mWindowManager.deferSurfaceLayout();
+
         // Need to make sure the pinned stack exist so we can resize it below...
         final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
 
@@ -2882,12 +2775,26 @@
                     // was launched from home so home should be visible behind it.
                     moveHomeStackToFront(reason);
                 }
-                moveTaskToStackLocked(
-                        task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+                // Defer resume until below
+                task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                        DEFER_RESUME, reason);
             } else {
                 // There are multiple activities in the task and moving the top activity should
-                // reveal/leave the other activities in their original task
-                stack.moveActivityToStack(r);
+                // reveal/leave the other activities in their original task.
+
+                // Currently, we don't support reparenting activities across tasks in two different
+                // stacks, so instead, just create a new task in the same stack, reparent the
+                // activity into that task, and then reparent the whole task to the new stack. This
+                // ensures that all the necessary work to migrate states in the old and new stacks
+                // is also done.
+                final TaskRecord newTask = task.getStack().createTaskRecord(
+                        getNextTaskIdForUserLocked(r.userId), r.info, r.intent, null, null, true,
+                        r.mActivityType);
+                r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
+
+                // Defer resume until below
+                newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                        DEFER_RESUME, reason);
             }
 
             // Reset the state that indicates it can enter PiP while pausing after we've moved it
@@ -4880,9 +4787,8 @@
 
         if (launchStackId != INVALID_STACK_ID) {
             if (task.getStackId() != launchStackId) {
-                moveTaskToStackLocked(
-                        taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
-                        ANIMATE);
+                task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
+                        DEFER_RESUME, "startActivityFromRecents");
             }
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 857241a..2e33c62 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -77,11 +77,16 @@
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
+import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
 import static com.android.server.am.EventLogTags.AM_NEW_INTENT;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+
+import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -1489,9 +1494,9 @@
                         if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                             // If we want to launch adjacent and mTargetStack is not the computed
                             // launch stack - move task to top of computed stack.
-                            mSupervisor.moveTaskToStackLocked(intentActivity.task.taskId,
-                                    launchStack.mStackId, ON_TOP, FORCE_FOCUS, "launchToSide",
-                                    ANIMATE);
+                            intentActivity.task.reparent(launchStack.mStackId, ON_TOP,
+                                    REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                                    "launchToSide");
                         } else {
                             // TODO: This should be reevaluated in MW v2.
                             // We choose to move task to front instead of launching it adjacent
@@ -1706,8 +1711,8 @@
         if (mTargetStack == null) {
             mTargetStack = sourceStack;
         } else if (mTargetStack != sourceStack) {
-            mSupervisor.moveTaskToStackLocked(sourceTask.taskId, mTargetStack.mStackId,
-                    ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE);
+            sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                    !ANIMATE, DEFER_RESUME, "launchToSide");
         }
 
         final TaskRecord topTask = mTargetStack.topTask();
@@ -1773,9 +1778,9 @@
             mInTask.updateOverrideConfiguration(mLaunchBounds);
             int stackId = mInTask.getLaunchStackId();
             if (stackId != mInTask.getStackId()) {
-                final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(mInTask,
-                        stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
-                stackId = stack.mStackId;
+                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+                        DEFER_RESUME, "inTaskToFront");
+                stackId = mInTask.getStackId();
             }
             if (StackId.resizeStackWithLaunchBounds(stackId)) {
                 mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 2410424..3f33f41 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -54,6 +55,7 @@
 import com.android.server.wm.StackWindowController;
 import com.android.server.wm.TaskWindowContainerController;
 import com.android.server.wm.TaskWindowContainerListener;
+import com.android.server.wm.WindowManagerService;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,10 +64,13 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Objects;
 
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -107,9 +112,10 @@
 import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 
+import static java.lang.Integer.MAX_VALUE;
+
 final class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -160,6 +166,24 @@
     static final int INVALID_TASK_ID = -1;
     private static final int INVALID_MIN_SIZE = -1;
 
+    /**
+     * The modes to control how the stack is moved to the front when calling
+     * {@link TaskRecord#reparent}.
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            REPARENT_MOVE_STACK_TO_FRONT,
+            REPARENT_KEEP_STACK_AT_FRONT,
+            REPARENT_LEAVE_STACK_IN_PLACE
+    })
+    public @interface ReparentMoveStackMode {}
+    // Moves the stack to the front if it was not at the front
+    public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+    // Only moves the stack to the front if it was focused or front most already
+    public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+    // Do not move the stack as a part of reparenting
+    public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+
     final int taskId;       // Unique identifier for this task.
     String affinity;        // The affinity name for this task, or null; may change identity.
     String rootAffinity;    // Initial base affinity, or null; does not change from initial root.
@@ -537,36 +561,159 @@
         mWindowContainerController.getBounds(bounds);
     }
 
-    // TODO: Should we be doing all the stuff in ASS.moveTaskToStackLocked?
-    void reparent(int stackId, int position, String reason) {
-        mService.mWindowManager.deferSurfaceLayout();
+    /**
+     * Convenience method to reparent a task to the top or bottom position of the stack.
+     */
+    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
+            boolean animate, boolean deferResume, String reason) {
+        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+                deferResume, reason);
+    }
 
+    /**
+     * Reparents the task into a preferred stack, creating it if necessary.
+     *
+     * @param preferredStackId the stack id of the target stack to move this task
+     * @param position the position to place this task in the new stack
+     * @param animate whether or not we should wait for the new window created as a part of the
+     *                reparenting to be drawn and animated in
+     * @param moveStackMode whether or not to move the stack to the front always, only if it was
+     *                      previously focused & in front, or never
+     * @param deferResume whether or not to update the visibility of other tasks and stacks that may
+     *                    have changed as a result of this reparenting
+     * @param reason the caller of this reparenting
+     * @return
+     */
+    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
+            boolean animate, boolean deferResume, String reason) {
+        final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+        final WindowManagerService windowManager = mService.mWindowManager;
+        final ActivityStack sourceStack = getStack();
+        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+                position == MAX_VALUE);
+        if (toStack == sourceStack) {
+            return false;
+        }
+
+        final int sourceStackId = getStackId();
+        final int stackId = toStack.getStackId();
+        final ActivityRecord topActivity = getTopActivity();
+
+        final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
+                && topActivity != null;
+        if (mightReplaceWindow) {
+            // We are about to relaunch the activity because its configuration changed due to
+            // being maximized, i.e. size change. The activity will first remove the old window
+            // and then add a new one. This call will tell window manager about this, so it can
+            // preserve the old window until the new one is drawn. This prevents having a gap
+            // between the removal and addition, in which no window is visible. We also want the
+            // entrance of the new window to be properly animated.
+            // Note here we always set the replacing window first, as the flags might be needed
+            // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
+            windowManager.setWillReplaceWindow(topActivity.appToken, animate);
+        }
+
+        windowManager.deferSurfaceLayout();
+        boolean kept = true;
         try {
-            final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
-            final ActivityStack newStack = supervisor.getStack(stackId,
-                    CREATE_IF_NEEDED, false /* toTop */);
+            final ActivityRecord r = topRunningActivityLocked();
+            final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+                    && (topRunningActivityLocked() == r);
+            final boolean wasResumed = sourceStack.mResumedActivity == r;
+            final boolean wasPaused = sourceStack.mPausingActivity == r;
+
+            // In some cases the focused stack isn't the front stack. E.g. pinned stack.
+            // Whenever we are moving the top activity from the front stack we want to make sure to
+            // move the stack to the front.
+            final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+                    && (sourceStack.topRunningActivityLocked() == r);
+
             // Adjust the position for the new parent stack as needed.
-            position = newStack.getAdjustedPositionForTask(this, position, null /* starting */);
+            position = toStack.getAdjustedPositionForTask(this, position, null /* starting */);
 
             // Must reparent first in window manager to avoid a situation where AM can delete the
             // we are coming from in WM before we reparent because it became empty.
-            mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
+            mWindowContainerController.reparent(toStack.getWindowContainerController(), position);
 
-            final ActivityStack prevStack = mStack;
-            prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
-            newStack.addTask(this, position, reason);
+            // Reset the resumed activity on the previous stack
+            if (wasResumed) {
+                sourceStack.mResumedActivity = null;
+            }
 
-            supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, prevStack);
+            // Reset the paused activity on the previous stack
+            if (wasPaused) {
+                sourceStack.mPausingActivity = null;
+                sourceStack.removeTimeoutsForActivityLocked(r);
+            }
 
+            // Move the task
+            sourceStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
+            toStack.addTask(this, position, reason);
+
+            // TODO: Ensure that this is actually necessary here
+            // Notify of picture-in-picture mode changes
+            supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, sourceStack);
+
+            // TODO: Ensure that this is actually necessary here
+            // Notify the voice session if required
             if (voiceSession != null) {
                 try {
                     voiceSession.taskStarted(intent, taskId);
                 } catch (RemoteException e) {
                 }
             }
+
+            // If the task had focus before (or we're requested to move focus), move focus to the
+            // new stack by moving the stack to the front.
+            final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
+                    || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
+            toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
+                    reason);
+            if (!animate) {
+                toStack.mNoAnimActivities.add(topActivity);
+            }
+
+            // We might trigger a configuration change. Save the current task bounds for freezing.
+            // TODO: Should this call be moved inside the resize method in WM?
+            toStack.prepareFreezingTaskBounds();
+
+            // Make sure the task has the appropriate bounds/size for the stack it is in.
+            if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+                    && !Objects.equals(mBounds, toStack.mBounds)) {
+                kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+                        deferResume);
+            } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+                Rect bounds = getLaunchBounds();
+                if (bounds == null) {
+                    toStack.layoutTaskInStack(this, null);
+                    bounds = mBounds;
+                }
+                kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
+            } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
+                kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+                        deferResume);
+            }
         } finally {
-            mService.mWindowManager.continueSurfaceLayout();
+            windowManager.continueSurfaceLayout();
         }
+
+        if (mightReplaceWindow) {
+            // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
+            // window), we need to clear the replace window settings. Otherwise, we schedule a
+            // timeout to remove the old window if the replacing window is not coming in time.
+            windowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
+        }
+
+        if (!deferResume) {
+            // The task might have already been running and its visibility needs to be synchronized
+            // with the visibility of the stack / windows.
+            supervisor.ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
+            supervisor.resumeFocusedStackTopActivityLocked();
+        }
+
+        supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, stackId);
+
+        return (preferredStackId == stackId);
     }
 
     void cancelWindowTransition() {