Support for setting stack windowing mode

We currently create a in stack in the windowing mode we want anytime we
want to change the windowing mode of a task which isn't optimal.
We now change the current stack windowing mode for some cases vs.
creating a new stack.

Test: go/wm-smoke
Bug: 64146578
Fixes: 68948655
Fixes: 65739235
Fixes: 29068935
Change-Id: I9fceb7d608e86610c1fb5ba59b15087a96e7401e
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 8cc584e..cfb1035 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
+import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -95,7 +98,6 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityController;
@@ -345,6 +347,7 @@
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
+    private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
@@ -452,8 +455,8 @@
         mStackId = stackId;
         mCurrentUser = mService.mUserController.getCurrentUserId();
         mTmpRect2.setEmpty();
-        setWindowingMode(windowingMode);
         setActivityType(activityType);
+        setWindowingMode(windowingMode);
         mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
                 mTmpRect2);
         postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -479,6 +482,125 @@
     }
 
     @Override
+    public void setWindowingMode(int windowingMode) {
+        setWindowingMode(windowingMode, false /* animate */);
+    }
+
+    void setWindowingMode(int preferredWindowingMode, boolean animate) {
+        final int currentMode = getWindowingMode();
+        final ActivityDisplay display = getDisplay();
+        final TaskRecord topTask = topTask();
+        final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack();
+        mTmpOptions.setLaunchWindowingMode(preferredWindowingMode);
+
+        // Need to make sure windowing mode is supported.
+        int windowingMode = display.resolveWindowingMode(
+                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+        if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            // Resolution to split-screen secondary for the primary split-screen stack means we want
+            // to go fullscreen.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        }
+
+        // Take any required action due to us not supporting the preferred windowing mode.
+        if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
+            if (display.hasSplitScreenPrimaryStack()
+                    && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                    || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
+                // Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
+                // and display a warning toast about it.
+                mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
+                display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            }
+        }
+
+        if (currentMode == windowingMode) {
+            // You are already in the window mode silly...
+            return;
+        }
+
+        final WindowManagerService wm = mService.mWindowManager;
+        final ActivityRecord topActivity = getTopActivity();
+
+        if (windowingMode != WINDOWING_MODE_FULLSCREEN && topActivity != null
+                && topActivity.isNonResizableOrForcedResizable() && !topActivity.noDisplay) {
+            // Inform the user that they are starting an app that may not work correctly in
+            // multi-window mode.
+            final String packageName = topActivity.appInfo.packageName;
+            mService.mTaskChangeNotificationController.notifyActivityForcedResizable(
+                    topTask.taskId, FORCED_RESIZEABLE_REASON_SPLIT_SCREEN, packageName);
+        }
+
+        wm.deferSurfaceLayout();
+        try {
+            if (!animate && topActivity != null) {
+                mNoAnimActivities.add(topActivity);
+            }
+            super.setWindowingMode(windowingMode);
+
+            if (mWindowContainerController == null) {
+                // Nothing else to do if we don't have a window container yet. E.g. call from ctor.
+                return;
+            }
+
+            if (windowingMode == WINDOWING_MODE_PINNED || currentMode == WINDOWING_MODE_PINNED) {
+                // TODO: Need to remove use of PinnedActivityStack for this to be supported.
+                // NOTE: Need to ASS.scheduleUpdatePictureInPictureModeIfNeeded() in
+                // setWindowModeUnchecked() when this support is added. See TaskRecord.reparent()
+                throw new IllegalArgumentException(
+                        "Changing pinned windowing mode not currently supported");
+            }
+
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && splitScreenStack != null) {
+                // We already have a split-screen stack in this display, so just move the tasks over.
+                // TODO: Figure-out how to do all the stuff in
+                // AMS.setTaskWindowingModeSplitScreenPrimary
+                throw new IllegalArgumentException("Setting primary split-screen windowing mode"
+                        + " while there is already one isn't currently supported");
+                //return;
+            }
+
+            mTmpRect2.setEmpty();
+            if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
+                mWindowContainerController.getRawBounds(mTmpRect2);
+                if (windowingMode == WINDOWING_MODE_FREEFORM) {
+                    if (topTask != null) {
+                        // TODO: Can we consolidate this and other sites that call this methods?
+                        Rect bounds = topTask().getLaunchBounds();
+                        if (bounds != null) {
+                            mTmpRect2.set(bounds);
+                        }
+                    }
+                }
+            }
+
+            if (!Objects.equals(mBounds, mTmpRect2)) {
+                resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
+            }
+        } finally {
+            if (mDisplayId == DEFAULT_DISPLAY
+                    && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                // Make sure recents stack exist when creating a dock stack as it normally needs to
+                // be on the other side of the docked stack and we make visibility decisions based
+                // on that.
+                // TODO: This is only here to help out with the case where recents stack doesn't
+                // exist yet. For that case the initial size of the split-screen stack will be the
+                // the one where the home stack is visible since recents isn't visible yet, but the
+                // divider will be off. I think we should just make the initial bounds that of home
+                // so that the divider matches and remove this logic.
+                display.getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+                        ACTIVITY_TYPE_RECENTS, true /* onTop */);
+                // If task moved to docked stack - show recents if needed.
+                mService.mWindowManager.showRecentApps(false /* fromHome */);
+            }
+            wm.continueSurfaceLayout();
+        }
+
+        mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+        mStackSupervisor.resumeFocusedStackTopActivityLocked();
+    }
+
+    @Override
     public boolean isCompatible(int windowingMode, int activityType) {
         // TODO: Should we just move this to ConfigurationContainer?
         if (activityType == ACTIVITY_TYPE_UNDEFINED) {
@@ -538,12 +660,6 @@
      * either destroyed completely or re-parented.
      */
     private void removeFromDisplay() {
-        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            // If we removed a docked stack we want to resize it so it resizes all other stacks
-            // in the system to fullscreen.
-            mStackSupervisor.resizeDockedStackLocked(
-                    null, null, null, null, null, PRESERVE_WINDOWS);
-        }
         final ActivityDisplay display = getDisplay();
         if (display != null) {
             display.removeChild(this);
@@ -730,14 +846,11 @@
         return null;
     }
 
-    final ActivityRecord topActivity() {
+    ActivityRecord getTopActivity() {
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
-            ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                if (!r.finishing) {
-                    return r;
-                }
+            final ActivityRecord r = mTaskHistory.get(taskNdx).getTopActivity();
+            if (r != null) {
+                return r;
             }
         }
         return null;
@@ -751,7 +864,7 @@
         return null;
     }
 
-    final TaskRecord bottomTask() {
+    private TaskRecord bottomTask() {
         if (mTaskHistory.isEmpty()) {
             return null;
         }
@@ -1607,8 +1720,6 @@
             }
 
             final int otherWindowingMode = other.getWindowingMode();
-            // TODO: Can be removed once we are no longer using returnToType for back functionality
-            final ActivityStack stackBehind = i > 0 ? display.getChildAt(i - 1) : null;
 
             if (otherWindowingMode == WINDOWING_MODE_FULLSCREEN) {
                 if (other.isStackTranslucent(starting)) {
@@ -1802,6 +1913,13 @@
         return inPinnedWindowingMode();
     }
 
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
+        final TaskRecord topTask = topTask();
+        return super.supportsSplitScreenWindowingMode()
+                && (topTask == null || topTask.supportsSplitScreenWindowingMode());
+    }
+
     /** @return True if the resizing of the primary-split-screen stack affects this stack size. */
     boolean affectedBySplitScreenResize() {
         if (!supportsSplitScreenWindowingMode()) {
@@ -4299,7 +4417,7 @@
         if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
 
         final ActivityStack topStack = getDisplay().getTopStack();
-        final ActivityRecord topActivity = topStack != null ? topStack.topActivity() : null;
+        final ActivityRecord topActivity = topStack != null ? topStack.getTopActivity() : null;
         final int numTasks = mTaskHistory.size();
         final int index = mTaskHistory.indexOf(tr);
         if (numTasks == 0 || index < 0)  {
@@ -4502,6 +4620,7 @@
             final TaskRecord task = mTaskHistory.get(i);
             if (task.isResizeable()) {
                 if (inFreeformWindowingMode()) {
+                    // TODO: Can be removed now since each freeform task is in its own stack.
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.