Disable multi-resumed activities for pre-Q app

Only resume the top-most visible activities for pre-Q
app since these applications may have the assumption
that there is only one activity being resumed.

Bug: 122429803
Test: atest android.server.am.lifecycle
Test: atest ActivityManagerMultiDisplayTests
Test: atest ActivityLifecycleFreeformTests
Test: atest ActivityManagerSplitScreenTests

Change-Id: I696a4bf41684d5837a6b23818bd9f71df8947e22
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index e817dd4..5b3a2fe 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1267,6 +1267,15 @@
         positionChildAt(stack, Math.max(0, insertIndex));
     }
 
+    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
+            boolean preserveWindows, boolean notifyClients) {
+        for (int stackNdx = getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = getChildAt(stackNdx);
+            stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows,
+                    notifyClients);
+        }
+    }
+
     void moveHomeStackToFront(String reason) {
         if (mHomeStack != null) {
             mHomeStack.moveToFront(reason);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index aa0c62c..cf4017ff 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3395,6 +3395,16 @@
                 stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */);
     }
 
+    /**
+     * Check if this activity is able to resume. For pre-Q apps, only the topmost activities of each
+     * process are allowed to be resumed.
+     *
+     * @return true if this activity can be resumed.
+     */
+    boolean canResumeByCompat() {
+        return app == null || app.updateTopResumingActivityInProcessIfNeeded(this);
+    }
+
     boolean getTurnScreenOnFlag() {
         return mTurnScreenOn;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 16c44aa..6fc2014 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2597,6 +2597,10 @@
             return false;
         }
 
+        if (!next.canResumeByCompat()) {
+            return false;
+        }
+
         // If we are sleeping, and there is no resumed activity, and the top
         // activity is paused, well that is the state we want.
         if (shouldSleepOrShutDownActivities()
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index c8a150b..c0fe6e9 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -720,6 +720,11 @@
 
             r.setProcess(proc);
 
+            // Ensure activity is allowed to be resumed after process has set.
+            if (andResume && !r.canResumeByCompat()) {
+                andResume = false;
+            }
+
             if (getKeyguardController().isKeyguardLocked()) {
                 r.notifyUnknownVisibilityLaunched();
             }
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 624fdc2..ecab1f1 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -777,11 +777,8 @@
             // First the front stacks. In case any are not fullscreen and are in front of home.
             for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
                 final ActivityDisplay display = mActivityDisplays.get(displayNdx);
-                for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
-                    final ActivityStack stack = display.getChildAt(stackNdx);
-                    stack.ensureActivitiesVisibleLocked(starting, configChanges, preserveWindows,
-                            notifyClients);
-                }
+                display.ensureActivitiesVisible(starting, configChanges, preserveWindows,
+                        notifyClients);
             }
         } finally {
             mStackSupervisor.getKeyguardController().endActivityVisibilityUpdate();
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index dd94af6..4ff552e 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -22,6 +22,8 @@
 import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
@@ -86,6 +88,16 @@
                 if (parent != null && parent.getTopChild() != mDisplayContent) {
                     parent.positionChildAt(WindowContainer.POSITION_TOP, mDisplayContent,
                             true /* includingParents */);
+                    // For compatibility, only the topmost activity is allowed to be resumed for
+                    // pre-Q app. Ensure the topmost activities are resumed whenever a display is
+                    // moved to top.
+                    // TODO(b/123761773): Investigate whether we can move this into
+                    // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is
+                    // risky to do so because it seems possible to resume activities as part of a
+                    // larger transaction and it's too early to resume based on current order
+                    // when performing updateTopResumedActivityIfNeeded().
+                    mDisplayContent.mAcitvityDisplay.ensureActivitiesVisible(null /* starting */,
+                            0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */);
                 }
             }
         };
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8da39b6..da3da1e 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.Build.VERSION_CODES.Q;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -32,11 +33,11 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.ActivityTaskManagerService
-        .INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS;
+import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS;
 import static com.android.server.wm.ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 
+import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
@@ -155,6 +156,8 @@
     private final ArrayList<ActivityRecord> mActivities = new ArrayList<>();
     // any tasks this process had run root activities in
     private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<>();
+    // The most recent top-most activity that was resumed in the process for pre-Q app.
+    private ActivityRecord mPreQTopResumedActivity = null;
 
     // Last configuration that was reported to the process.
     private final Configuration mLastReportedConfiguration;
@@ -469,6 +472,59 @@
         }
     }
 
+    /**
+     * Update the top resuming activity in process for pre-Q apps, only the top-most visible
+     * activities are allowed to be resumed per process.
+     * @return {@code true} if the activity is allowed to be resumed by compatibility
+     * restrictions, which the activity was the topmost visible activity in process or the app is
+     * targeting after Q.
+     */
+    boolean updateTopResumingActivityInProcessIfNeeded(@NonNull ActivityRecord activity) {
+        if (mInfo.targetSdkVersion >= Q || mPreQTopResumedActivity == activity) {
+            return true;
+        }
+
+        final ActivityDisplay display = activity.getDisplay();
+        if (display == null) {
+            // No need to update if the activity hasn't attach to any display.
+            return false;
+        }
+
+        boolean canUpdate = false;
+        final ActivityDisplay topDisplay =
+                mPreQTopResumedActivity != null ? mPreQTopResumedActivity.getDisplay() : null;
+        // Update the topmost activity if current top activity was not on any display or no
+        // longer visible.
+        if (topDisplay == null || !mPreQTopResumedActivity.visible) {
+            canUpdate = true;
+        }
+
+        // Update the topmost activity if the current top activity wasn't on top of the other one.
+        if (!canUpdate && topDisplay.mDisplayContent.compareTo(display.mDisplayContent) < 0) {
+            canUpdate = true;
+        }
+
+        // Compare the z-order of ActivityStacks if both activities landed on same display.
+        if (display == topDisplay
+                && mPreQTopResumedActivity.getActivityStack().mTaskStack.compareTo(
+                activity.getActivityStack().mTaskStack) <= 0) {
+            canUpdate = true;
+        }
+
+        if (canUpdate) {
+            // Make sure the previous top activity in the process no longer be resumed.
+            if (mPreQTopResumedActivity != null && mPreQTopResumedActivity.isState(RESUMED)) {
+                final ActivityStack stack = mPreQTopResumedActivity.getActivityStack();
+                if (stack != null) {
+                    stack.startPausingLocked(false /* userLeaving */, false /* uiSleeping */,
+                            null /* resuming */, false /* pauseImmediately */);
+                }
+            }
+            mPreQTopResumedActivity = activity;
+        }
+        return canUpdate;
+    }
+
     public void stopFreezingActivities() {
         synchronized (mAtm.mGlobalLock) {
             int i = mActivities.size();