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();