Add support for singleTaskInstance displays
singleTaskInstance displays will only contain on task. This is mostly
used by ActivityView for use cases like bubbles.
Bug: 121047677
Test: atest ActivityManagerMultiDisplayTests#testSingleTaskInstanceDisplay
Change-Id: I5166015d8ecfa2845b4ffaa6c16d21a30a56b8a8
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 973499f..1f638c7 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -37,6 +37,7 @@
import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID;
import static com.android.server.am.ActivityDisplayProto.ID;
import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY;
+import static com.android.server.am.ActivityDisplayProto.SINGLE_TASK_INSTANCE;
import static com.android.server.am.ActivityDisplayProto.STACKS;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
import static com.android.server.wm.ActivityStackSupervisor.TAG_TASKS;
@@ -120,6 +121,9 @@
*/
private boolean mRemoved;
+ /** The display can only contain one task. */
+ private boolean mSingleTaskInstance;
+
/**
* A focusable stack that is purposely to be positioned at the top. Although the stack may not
* have the topmost index, it is used as a preferred candidate to prevent being unable to resume
@@ -244,6 +248,10 @@
final ActivityStack prevFocusedStack = updateLastFocusedStackReason != null
? getFocusedStack() : null;
final boolean wasContained = mStacks.remove(stack);
+ if (mSingleTaskInstance && getChildCount() > 0) {
+ throw new IllegalStateException(
+ "positionChildAt: Can only have one child on display=" + this);
+ }
final int insertPosition = getTopInsertPosition(stack, position);
mStacks.add(insertPosition, stack);
@@ -403,6 +411,14 @@
*/
<T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
+ if (mSingleTaskInstance && getChildCount() > 0) {
+ // Create stack on default display instead since this display can only contain 1 stack.
+ // TODO: Kinda a hack, but better that having the decision at each call point. Hoping
+ // this goes away once ActivityView is no longer using virtual displays.
+ return mRootActivityContainer.getDefaultDisplay().createStack(
+ windowingMode, activityType, onTop);
+ }
+
if (activityType == ACTIVITY_TYPE_UNDEFINED) {
// Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
// anything else should be passing it in anyways...
@@ -1337,8 +1353,31 @@
}
}
+ void setDisplayToSingleTaskInstance() {
+ final int childCount = getChildCount();
+ if (childCount > 1) {
+ throw new IllegalArgumentException("Display already has multiple stacks. display="
+ + this);
+ }
+ if (childCount > 0) {
+ final ActivityStack stack = getChildAt(0);
+ if (stack.getChildCount() > 1) {
+ throw new IllegalArgumentException("Display stack already has multiple tasks."
+ + " display=" + this + " stack=" + stack);
+ }
+ }
+
+ mSingleTaskInstance = true;
+ }
+
+ /** Returns true if the display can only contain one task */
+ boolean isSingleTaskInstance() {
+ return mSingleTaskInstance;
+ }
+
public void dump(PrintWriter pw, String prefix) {
- pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size());
+ pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size()
+ + (mSingleTaskInstance ? " mSingleTaskInstance" : ""));
final String myPrefix = prefix + " ";
if (mHomeStack != null) {
pw.println(myPrefix + "mHomeStack=" + mHomeStack);
@@ -1373,6 +1412,7 @@
final long token = proto.start(fieldId);
super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */);
proto.write(ID, mDisplayId);
+ proto.write(SINGLE_TASK_INSTANCE, mSingleTaskInstance);
final ActivityStack focusedStack = getFocusedStack();
if (focusedStack != null) {
proto.write(FOCUSED_STACK_ID, focusedStack.mStackId);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 6755c73..6292c4a 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1142,6 +1142,12 @@
}
}
+ /** @return true if the stack can only contain one task */
+ boolean isSingleTaskInstance() {
+ final ActivityDisplay display = getDisplay();
+ return display != null && display.isSingleTaskInstance();
+ }
+
final void removeActivitiesFromLRUListLocked(TaskRecord task) {
for (ActivityRecord r : task.mActivities) {
mLRUActivities.remove(r);
@@ -5348,6 +5354,10 @@
String reason) {
// TODO: Is this remove really needed? Need to look into the call path for the other addTask
mTaskHistory.remove(task);
+ if (isSingleTaskInstance() && !mTaskHistory.isEmpty()) {
+ throw new IllegalStateException("Can only have one child on stack=" + this);
+ }
+
position = getAdjustedPositionForTask(task, position, null /* starting */);
final boolean toTop = position >= mTaskHistory.size();
final ActivityStack prevStack = preAddTask(task, reason, toTop);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index c4be1ba537..86c5d4d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4569,6 +4569,26 @@
}
}
+ /**
+ * Makes the display with the given id a single task instance display. I.e the display can only
+ * contain one task.
+ */
+ @Override
+ public void setDisplayToSingleTaskInstance(int displayId) {
+ mAmInternal.enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS,
+ "setDisplayToSingleTaskInstance");
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ final ActivityDisplay display =
+ mRootActivityContainer.getActivityDisplayOrCreate(displayId);
+ if (display != null) {
+ display.setDisplayToSingleTaskInstance();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
void dumpLastANRLocked(PrintWriter pw) {
pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
if (mLastANRState == null) {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index 6f92e64..b7ea1d7 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -915,6 +915,13 @@
+ " to its current displayId=" + displayId);
}
+ if (activityDisplay.isSingleTaskInstance() && activityDisplay.getChildCount() > 0) {
+ // We don't allow moving stacks to single instance display that already has a child.
+ Slog.e(TAG, "Can not move stack=" + stack
+ + " to single task instance display=" + activityDisplay);
+ return;
+ }
+
stack.reparent(activityDisplay, onTop, false /* displayRemoved */);
// TODO(multi-display): resize stacks properly if moved from split-screen.
}