1/n Move TaskContainers out of DisplayContent class
No changes in logic, just moving the code around.
Bug: 152116619
Test: WM CTS and unit tests
Change-Id: I4aa142e07e39950d077c685bb157b4abb2172886
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 0ec0c7b..3c083e1 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -19,8 +19,6 @@
import android.content.res.Resources;
import android.text.TextUtils;
-import com.android.server.wm.DisplayContent.TaskContainers;
-
/**
* Policy that manages DisplayAreas.
*/
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 88c9b2c..fa4ad7a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -31,9 +31,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -79,7 +77,6 @@
import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
-import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -108,7 +105,6 @@
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SINGLE_TASK_INSTANCE;
import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
-import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
@@ -289,7 +285,7 @@
/** The containers below are the only child containers {@link #mWindowContainers} can have. */
// Contains all window containers that are related to apps (Activities)
- private final TaskContainers mTaskContainers = new TaskContainers(mWmService);
+ private final TaskContainers mTaskContainers = new TaskContainers(this, mWmService);
// Contains all IME window containers. Note that the z-ordering of the IME windows will depend
// on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -579,10 +575,6 @@
// Last systemUiVisibility we dispatched to windows.
private int mLastDispatchedSystemUiVisibility = 0;
- private final ArrayList<ActivityStack> mTmpAlwaysOnTopStacks = new ArrayList<>();
- private final ArrayList<ActivityStack> mTmpNormalStacks = new ArrayList<>();
- private final ArrayList<ActivityStack> mTmpHomeStacks = new ArrayList<>();
-
/** Corner radius that windows should have in order to match the display. */
private final float mWindowCornerRadius;
@@ -4275,531 +4267,6 @@
}
}
- /**
- * Window container class that contains all containers on this display relating to Apps.
- * I.e Activities.
- */
- final class TaskContainers extends DisplayArea<ActivityStack> {
- /**
- * A control placed at the appropriate level for transitions to occur.
- */
- SurfaceControl mAppAnimationLayer = null;
- SurfaceControl mBoostedAppAnimationLayer = null;
- SurfaceControl mHomeAppAnimationLayer = null;
-
- /**
- * Given that the split-screen divider does not have an AppWindowToken, it
- * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
- * it will need to be interleaved with some of our children, appearing on top of
- * both docked stacks but underneath any assistant stacks.
- *
- * To solve this problem we have this anchor control, which will always exist so
- * we can always assign it the correct value in our {@link #assignChildLayers}.
- * Likewise since it always exists, we can always
- * assign the divider a layer relative to it. This way we prevent linking lifecycle
- * events between tasks and the divider window.
- */
- SurfaceControl mSplitScreenDividerAnchor = null;
-
- // Cached reference to some special tasks we tend to get a lot so we don't need to loop
- // through the list to find them.
- private ActivityStack mRootHomeTask = null;
- private ActivityStack mRootPinnedTask = null;
- private ActivityStack mRootSplitScreenPrimaryTask = null;
-
- TaskContainers(WindowManagerService service) {
- super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
- }
-
- /**
- * Returns the topmost stack on the display that is compatible with the input windowing mode
- * and activity type. Null is no compatible stack on the display.
- */
- ActivityStack getStack(int windowingMode, int activityType) {
- if (activityType == ACTIVITY_TYPE_HOME) {
- return mRootHomeTask;
- }
- if (windowingMode == WINDOWING_MODE_PINNED) {
- return mRootPinnedTask;
- } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return mRootSplitScreenPrimaryTask;
- }
- for (int i = mTaskContainers.getChildCount() - 1; i >= 0; --i) {
- final ActivityStack stack = mTaskContainers.getChildAt(i);
- if (activityType == ACTIVITY_TYPE_UNDEFINED
- && windowingMode == stack.getWindowingMode()) {
- // Passing in undefined type means we want to match the topmost stack with the
- // windowing mode.
- return stack;
- }
- if (stack.isCompatible(windowingMode, activityType)) {
- return stack;
- }
- }
- return null;
- }
-
- @VisibleForTesting
- ActivityStack getTopStack() {
- final int count = mTaskContainers.getChildCount();
- return count > 0 ? mTaskContainers.getChildAt(count - 1) : null;
- }
-
- int getIndexOf(ActivityStack stack) {
- return mTaskContainers.mChildren.indexOf(stack);
- }
-
- ActivityStack getRootHomeTask() {
- return mRootHomeTask;
- }
-
- ActivityStack getRootPinnedTask() {
- return mRootPinnedTask;
- }
-
- ActivityStack getRootSplitScreenPrimaryTask() {
- return mRootSplitScreenPrimaryTask;
- }
-
- ArrayList<Task> getVisibleTasks() {
- final ArrayList<Task> visibleTasks = new ArrayList<>();
- forAllTasks(task -> {
- if (task.isLeafTask() && task.isVisible()) {
- visibleTasks.add(task);
- }
- });
- return visibleTasks;
- }
-
- void onStackWindowingModeChanged(ActivityStack stack) {
- removeStackReferenceIfNeeded(stack);
- addStackReferenceIfNeeded(stack);
- if (stack == mRootPinnedTask && getTopStack() != stack) {
- // Looks like this stack changed windowing mode to pinned. Move it to the top.
- positionChildAt(POSITION_TOP, stack, false /* includingParents */);
- }
- }
-
- private void addStackReferenceIfNeeded(ActivityStack stack) {
- if (stack.isActivityTypeHome()) {
- if (mRootHomeTask != null) {
- if (!stack.isDescendantOf(mRootHomeTask)) {
- throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
- + mRootHomeTask + " already exist on display=" + this
- + " stack=" + stack);
- }
- } else {
- mRootHomeTask = stack;
- }
- }
-
- if (!stack.isRootTask()) {
- return;
- }
- final int windowingMode = stack.getWindowingMode();
- if (windowingMode == WINDOWING_MODE_PINNED) {
- if (mRootPinnedTask != null) {
- throw new IllegalArgumentException(
- "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask
- + " already exist on display=" + this + " stack=" + stack);
- }
- mRootPinnedTask = stack;
- } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- if (mRootSplitScreenPrimaryTask != null) {
- throw new IllegalArgumentException(
- "addStackReferenceIfNeeded: split screen primary stack="
- + mRootSplitScreenPrimaryTask
- + " already exist on display=" + this + " stack=" + stack);
- }
- mRootSplitScreenPrimaryTask = stack;
- }
- }
-
- void removeStackReferenceIfNeeded(ActivityStack stack) {
- if (stack == mRootHomeTask) {
- mRootHomeTask = null;
- } else if (stack == mRootPinnedTask) {
- mRootPinnedTask = null;
- } else if (stack == mRootSplitScreenPrimaryTask) {
- mRootSplitScreenPrimaryTask = null;
- }
- }
-
- @Override
- void addChild(ActivityStack stack, int position) {
- addStackReferenceIfNeeded(stack);
- position = findPositionForStack(position, stack, true /* adding */);
-
- super.addChild(stack, position);
- mAtmService.updateSleepIfNeededLocked();
-
- // The reparenting case is handled in WindowContainer.
- if (!stack.mReparenting) {
- setLayoutNeeded();
- }
- }
-
- @Override
- protected void removeChild(ActivityStack stack) {
- super.removeChild(stack);
- mDisplayContent.onStackRemoved(stack);
- mAtmService.updateSleepIfNeededLocked();
- removeStackReferenceIfNeeded(stack);
- }
-
- @Override
- boolean isOnTop() {
- // Considered always on top
- return true;
- }
-
- @Override
- void positionChildAt(int position, ActivityStack child, boolean includingParents) {
- final boolean moveToTop = (position == POSITION_TOP || position == getChildCount());
- final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0);
- if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) {
- // This stack is always-on-top, override the default behavior.
- Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
-
- // Moving to its current position, as we must call super but we don't want to
- // perform any meaningful action.
- final int currentPosition = mChildren.indexOf(child);
- super.positionChildAt(currentPosition, child, false /* includingParents */);
- return;
- }
- // We don't allow untrusted display to top when task stack moves to top,
- // until user tapping this display to change display position as top intentionally.
- if (isUntrustedVirtualDisplay() && !getParent().isOnTop()) {
- includingParents = false;
- }
- final int targetPosition = findPositionForStack(position, child, false /* adding */);
- super.positionChildAt(targetPosition, child, false /* includingParents */);
-
- if (includingParents && (moveToTop || moveToBottom)) {
- // The DisplayContent children do not re-order, but we still want to move the
- // display of this stack container because the intention of positioning is to have
- // higher z-order to gain focus.
- positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
- true /* includingParents */);
- }
-
- child.updateTaskMovement(moveToTop);
-
- setLayoutNeeded();
- }
-
- /**
- * When stack is added or repositioned, find a proper position for it.
- * This will make sure that pinned stack always stays on top.
- * @param requestedPosition Position requested by caller.
- * @param stack Stack to be added or positioned.
- * @param adding Flag indicates whether we're adding a new stack or positioning an existing.
- * @return The proper position for the stack.
- */
- private int findPositionForStack(int requestedPosition, ActivityStack stack,
- boolean adding) {
- if (stack.isActivityTypeDream()) {
- return POSITION_TOP;
- }
-
- if (stack.inPinnedWindowingMode()) {
- return POSITION_TOP;
- }
-
- final int topChildPosition = mChildren.size() - 1;
- int belowAlwaysOnTopPosition = POSITION_BOTTOM;
- for (int i = topChildPosition; i >= 0; --i) {
- // Since a stack could be repositioned while being one of the child, return
- // current index if that's the same stack we are positioning and it is always on
- // top.
- final boolean sameStack = getStacks().get(i) == stack;
- if ((sameStack && stack.isAlwaysOnTop())
- || (!sameStack && !getStacks().get(i).isAlwaysOnTop())) {
- belowAlwaysOnTopPosition = i;
- break;
- }
- }
-
- // The max possible position we can insert the stack at.
- int maxPosition = POSITION_TOP;
- // The min possible position we can insert the stack at.
- int minPosition = POSITION_BOTTOM;
-
- if (stack.isAlwaysOnTop()) {
- if (hasPinnedTask()) {
- // Always-on-top stacks go below the pinned stack.
- maxPosition = getStacks().indexOf(mRootPinnedTask) - 1;
- }
- // Always-on-top stacks need to be above all other stacks.
- minPosition = belowAlwaysOnTopPosition !=
- POSITION_BOTTOM ? belowAlwaysOnTopPosition : topChildPosition;
- } else {
- // Other stacks need to be below the always-on-top stacks.
- maxPosition = belowAlwaysOnTopPosition !=
- POSITION_BOTTOM ? belowAlwaysOnTopPosition : 0;
- }
-
- // Cap the requested position to something reasonable for the previous position check
- // below.
- if (requestedPosition == POSITION_TOP) {
- requestedPosition = mChildren.size();
- } else if (requestedPosition == POSITION_BOTTOM) {
- requestedPosition = 0;
- }
-
- int targetPosition = requestedPosition;
- targetPosition = Math.min(targetPosition, maxPosition);
- targetPosition = Math.max(targetPosition, minPosition);
-
- int prevPosition = getStacks().indexOf(stack);
- // The positions we calculated above (maxPosition, minPosition) do not take into
- // consideration the following edge cases.
- // 1) We need to adjust the position depending on the value "adding".
- // 2) When we are moving a stack to another position, we also need to adjust the
- // position depending on whether the stack is moving to a higher or lower position.
- if ((targetPosition != requestedPosition) &&
- (adding || targetPosition < prevPosition)) {
- targetPosition++;
- }
-
- return targetPosition;
- }
-
- @Override
- boolean forAllWindows(ToBooleanFunction<WindowState> callback,
- boolean traverseTopToBottom) {
- if (traverseTopToBottom) {
- if (super.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
- return true;
- }
- } else {
- if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
- return true;
- }
- if (super.forAllWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean forAllExitingAppTokenWindows(ToBooleanFunction<WindowState> callback,
- boolean traverseTopToBottom) {
- // For legacy reasons we process the TaskStack.mExitingActivities first here before the
- // app tokens.
- // TODO: Investigate if we need to continue to do this or if we can just process them
- // in-order.
- if (traverseTopToBottom) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final List<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- if (activities.get(j).forAllWindowsUnchecked(callback,
- traverseTopToBottom)) {
- return true;
- }
- }
- }
- } else {
- final int count = mChildren.size();
- for (int i = 0; i < count; ++i) {
- final List<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
- final int appTokensCount = activities.size();
- for (int j = 0; j < appTokensCount; j++) {
- if (activities.get(j).forAllWindowsUnchecked(callback,
- traverseTopToBottom)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- void setExitingTokensHasVisible(boolean hasVisible) {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final ArrayList<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- activities.get(j).hasVisible = hasVisible;
- }
- }
- }
-
- void removeExistingAppTokensIfPossible() {
- for (int i = mChildren.size() - 1; i >= 0; --i) {
- final ArrayList<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
- for (int j = activities.size() - 1; j >= 0; --j) {
- final ActivityRecord activity = activities.get(j);
- if (!activity.hasVisible && !mClosingApps.contains(activity)
- && (!activity.mIsExiting || activity.isEmpty())) {
- // Make sure there is no animation running on this activity, so any windows
- // associated with it will be removed as soon as their animations are
- // complete.
- cancelAnimation();
- ProtoLog.v(WM_DEBUG_ADD_REMOVE,
- "performLayout: Activity exiting now removed %s", activity);
- activity.removeIfPossible();
- }
- }
- }
- }
-
- @Override
- int getOrientation(int candidate) {
- if (isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
- // Apps and their containers are not allowed to specify an orientation while using
- // root tasks...except for the home stack if it is not resizable and currently
- // visible (top of) its root task.
- if (mRootHomeTask != null && mRootHomeTask.isVisible()) {
- final Task topMost = mRootHomeTask.getTopMostTask();
- final boolean resizable = topMost != null && topMost.isResizeable();
- if (!(resizable && mRootHomeTask.matchParentBounds())) {
- final int orientation = mRootHomeTask.getOrientation();
- if (orientation != SCREEN_ORIENTATION_UNSET) {
- return orientation;
- }
- }
- }
- return SCREEN_ORIENTATION_UNSPECIFIED;
- }
-
- final int orientation = super.getOrientation(candidate);
- if (orientation != SCREEN_ORIENTATION_UNSET
- && orientation != SCREEN_ORIENTATION_BEHIND) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "App is requesting an orientation, return %d for display id=%d",
- orientation, mDisplayId);
- return orientation;
- }
-
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "No app is requesting an orientation, return %d for display id=%d",
- getLastOrientation(), mDisplayId);
- // The next app has not been requested to be visible, so we keep the current orientation
- // to prevent freezing/unfreezing the display too early.
- return getLastOrientation();
- }
-
- @Override
- void assignChildLayers(SurfaceControl.Transaction t) {
- assignStackOrdering(t);
-
- for (int i = 0; i < mChildren.size(); i++) {
- final ActivityStack s = mChildren.get(i);
- s.assignChildLayers(t);
- }
- }
-
- void assignStackOrdering(SurfaceControl.Transaction t) {
- if (getParent() == null) {
- return;
- }
- mTmpAlwaysOnTopStacks.clear();
- mTmpHomeStacks.clear();
- mTmpNormalStacks.clear();
- for (int i = 0; i < mChildren.size(); ++i) {
- final ActivityStack s = mChildren.get(i);
- if (s.isAlwaysOnTop()) {
- mTmpAlwaysOnTopStacks.add(s);
- } else if (s.isActivityTypeHome()) {
- mTmpHomeStacks.add(s);
- } else {
- mTmpNormalStacks.add(s);
- }
- }
-
- int layer = 0;
- // Place home stacks to the bottom.
- for (int i = 0; i < mTmpHomeStacks.size(); i++) {
- mTmpHomeStacks.get(i).assignLayer(t, layer++);
- }
- // The home animation layer is between the home stacks and the normal stacks.
- final int layerForHomeAnimationLayer = layer++;
- int layerForSplitScreenDividerAnchor = layer++;
- int layerForAnimationLayer = layer++;
- for (int i = 0; i < mTmpNormalStacks.size(); i++) {
- final ActivityStack s = mTmpNormalStacks.get(i);
- s.assignLayer(t, layer++);
- if (s.inSplitScreenWindowingMode()) {
- // The split screen divider anchor is located above the split screen window.
- layerForSplitScreenDividerAnchor = layer++;
- }
- if (s.isTaskAnimating() || s.isAppTransitioning()) {
- // The animation layer is located above the highest animating stack and no
- // higher.
- layerForAnimationLayer = layer++;
- }
- }
- // The boosted animation layer is between the normal stacks and the always on top
- // stacks.
- final int layerForBoostedAnimationLayer = layer++;
- for (int i = 0; i < mTmpAlwaysOnTopStacks.size(); i++) {
- mTmpAlwaysOnTopStacks.get(i).assignLayer(t, layer++);
- }
-
- t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
- t.setLayer(mAppAnimationLayer, layerForAnimationLayer);
- t.setLayer(mSplitScreenDividerAnchor, layerForSplitScreenDividerAnchor);
- t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
- }
-
- @Override
- SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
- switch (animationLayer) {
- case ANIMATION_LAYER_BOOSTED:
- return mBoostedAppAnimationLayer;
- case ANIMATION_LAYER_HOME:
- return mHomeAppAnimationLayer;
- case ANIMATION_LAYER_STANDARD:
- default:
- return mAppAnimationLayer;
- }
- }
-
- SurfaceControl getSplitScreenDividerAnchor() {
- return mSplitScreenDividerAnchor;
- }
-
- @Override
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- if (getParent() != null) {
- super.onParentChanged(newParent, oldParent, () -> {
- mAppAnimationLayer = makeChildSurface(null)
- .setName("animationLayer")
- .build();
- mBoostedAppAnimationLayer = makeChildSurface(null)
- .setName("boostedAnimationLayer")
- .build();
- mHomeAppAnimationLayer = makeChildSurface(null)
- .setName("homeAnimationLayer")
- .build();
- mSplitScreenDividerAnchor = makeChildSurface(null)
- .setName("splitScreenDividerAnchor")
- .build();
- getPendingTransaction()
- .show(mAppAnimationLayer)
- .show(mBoostedAppAnimationLayer)
- .show(mHomeAppAnimationLayer)
- .show(mSplitScreenDividerAnchor);
- });
- } else {
- super.onParentChanged(newParent, oldParent);
- mWmService.mTransactionFactory.get()
- .remove(mAppAnimationLayer)
- .remove(mBoostedAppAnimationLayer)
- .remove(mHomeAppAnimationLayer)
- .remove(mSplitScreenDividerAnchor)
- .apply();
- mAppAnimationLayer = null;
- mBoostedAppAnimationLayer = null;
- mHomeAppAnimationLayer = null;
- mSplitScreenDividerAnchor = null;
- }
- }
- }
-
private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> {
private final String mName;
diff --git a/services/core/java/com/android/server/wm/TaskContainers.java b/services/core/java/com/android/server/wm/TaskContainers.java
new file mode 100644
index 0000000..a959942
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskContainers.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
+
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.util.Slog;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ToBooleanFunction;
+import com.android.server.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Window container class that contains all containers on this display relating to Apps.
+ * I.e Activities.
+ */
+final class TaskContainers extends DisplayArea<ActivityStack> {
+ private DisplayContent mDisplayContent;
+ /**
+ * A control placed at the appropriate level for transitions to occur.
+ */
+ private SurfaceControl mAppAnimationLayer;
+ private SurfaceControl mBoostedAppAnimationLayer;
+ private SurfaceControl mHomeAppAnimationLayer;
+
+ /**
+ * Given that the split-screen divider does not have an AppWindowToken, it
+ * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
+ * it will need to be interleaved with some of our children, appearing on top of
+ * both docked stacks but underneath any assistant stacks.
+ *
+ * To solve this problem we have this anchor control, which will always exist so
+ * we can always assign it the correct value in our {@link #assignChildLayers}.
+ * Likewise since it always exists, we can always
+ * assign the divider a layer relative to it. This way we prevent linking lifecycle
+ * events between tasks and the divider window.
+ */
+ private SurfaceControl mSplitScreenDividerAnchor;
+
+ // Cached reference to some special tasks we tend to get a lot so we don't need to loop
+ // through the list to find them.
+ private ActivityStack mRootHomeTask;
+ private ActivityStack mRootPinnedTask;
+ private ActivityStack mRootSplitScreenPrimaryTask;
+
+ private final ArrayList<ActivityStack> mTmpAlwaysOnTopStacks = new ArrayList<>();
+ private final ArrayList<ActivityStack> mTmpNormalStacks = new ArrayList<>();
+ private final ArrayList<ActivityStack> mTmpHomeStacks = new ArrayList<>();
+
+ TaskContainers(DisplayContent displayContent, WindowManagerService service) {
+ super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
+ mDisplayContent = displayContent;
+ }
+
+ /**
+ * Returns the topmost stack on the display that is compatible with the input windowing mode
+ * and activity type. Null is no compatible stack on the display.
+ */
+ ActivityStack getStack(int windowingMode, int activityType) {
+ if (activityType == ACTIVITY_TYPE_HOME) {
+ return mRootHomeTask;
+ }
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ return mRootPinnedTask;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ return mRootSplitScreenPrimaryTask;
+ }
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityStack stack = getChildAt(i);
+ if (activityType == ACTIVITY_TYPE_UNDEFINED
+ && windowingMode == stack.getWindowingMode()) {
+ // Passing in undefined type means we want to match the topmost stack with the
+ // windowing mode.
+ return stack;
+ }
+ if (stack.isCompatible(windowingMode, activityType)) {
+ return stack;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ ActivityStack getTopStack() {
+ final int count = getChildCount();
+ return count > 0 ? getChildAt(count - 1) : null;
+ }
+
+ int getIndexOf(ActivityStack stack) {
+ return mChildren.indexOf(stack);
+ }
+
+ ActivityStack getRootHomeTask() {
+ return mRootHomeTask;
+ }
+
+ ActivityStack getRootPinnedTask() {
+ return mRootPinnedTask;
+ }
+
+ ActivityStack getRootSplitScreenPrimaryTask() {
+ return mRootSplitScreenPrimaryTask;
+ }
+
+ ArrayList<Task> getVisibleTasks() {
+ final ArrayList<Task> visibleTasks = new ArrayList<>();
+ forAllTasks(task -> {
+ if (task.isLeafTask() && task.isVisible()) {
+ visibleTasks.add(task);
+ }
+ });
+ return visibleTasks;
+ }
+
+ void onStackWindowingModeChanged(ActivityStack stack) {
+ removeStackReferenceIfNeeded(stack);
+ addStackReferenceIfNeeded(stack);
+ if (stack == mRootPinnedTask && getTopStack() != stack) {
+ // Looks like this stack changed windowing mode to pinned. Move it to the top.
+ positionChildAt(POSITION_TOP, stack, false /* includingParents */);
+ }
+ }
+
+ void addStackReferenceIfNeeded(ActivityStack stack) {
+ if (stack.isActivityTypeHome()) {
+ if (mRootHomeTask != null) {
+ if (!stack.isDescendantOf(mRootHomeTask)) {
+ throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
+ + mRootHomeTask + " already exist on display=" + this
+ + " stack=" + stack);
+ }
+ } else {
+ mRootHomeTask = stack;
+ }
+ }
+
+ if (!stack.isRootTask()) {
+ return;
+ }
+ final int windowingMode = stack.getWindowingMode();
+ if (windowingMode == WINDOWING_MODE_PINNED) {
+ if (mRootPinnedTask != null) {
+ throw new IllegalArgumentException(
+ "addStackReferenceIfNeeded: pinned stack=" + mRootPinnedTask
+ + " already exist on display=" + this + " stack=" + stack);
+ }
+ mRootPinnedTask = stack;
+ } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+ if (mRootSplitScreenPrimaryTask != null) {
+ throw new IllegalArgumentException(
+ "addStackReferenceIfNeeded: split screen primary stack="
+ + mRootSplitScreenPrimaryTask
+ + " already exist on display=" + this + " stack=" + stack);
+ }
+ mRootSplitScreenPrimaryTask = stack;
+ }
+ }
+
+ void removeStackReferenceIfNeeded(ActivityStack stack) {
+ if (stack == mRootHomeTask) {
+ mRootHomeTask = null;
+ } else if (stack == mRootPinnedTask) {
+ mRootPinnedTask = null;
+ } else if (stack == mRootSplitScreenPrimaryTask) {
+ mRootSplitScreenPrimaryTask = null;
+ }
+ }
+
+ @Override
+ void addChild(ActivityStack stack, int position) {
+ addStackReferenceIfNeeded(stack);
+ position = findPositionForStack(position, stack, true /* adding */);
+
+ super.addChild(stack, position);
+ mDisplayContent.mAtmService.updateSleepIfNeededLocked();
+
+ // The reparenting case is handled in WindowContainer.
+ if (!stack.mReparenting) {
+ mDisplayContent.setLayoutNeeded();
+ }
+ }
+
+ @Override
+ protected void removeChild(ActivityStack stack) {
+ super.removeChild(stack);
+ mDisplayContent.onStackRemoved(stack);
+ mDisplayContent.mAtmService.updateSleepIfNeededLocked();
+ removeStackReferenceIfNeeded(stack);
+ }
+
+ @Override
+ boolean isOnTop() {
+ // Considered always on top
+ return true;
+ }
+
+ @Override
+ void positionChildAt(int position, ActivityStack child, boolean includingParents) {
+ final boolean moveToTop = (position == POSITION_TOP || position == getChildCount());
+ final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0);
+ if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) {
+ // This stack is always-on-top, override the default behavior.
+ Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom");
+
+ // Moving to its current position, as we must call super but we don't want to
+ // perform any meaningful action.
+ final int currentPosition = mChildren.indexOf(child);
+ super.positionChildAt(currentPosition, child, false /* includingParents */);
+ return;
+ }
+ // We don't allow untrusted display to top when task stack moves to top,
+ // until user tapping this display to change display position as top intentionally.
+ if (mDisplayContent.isUntrustedVirtualDisplay() && !getParent().isOnTop()) {
+ includingParents = false;
+ }
+ final int targetPosition = findPositionForStack(position, child, false /* adding */);
+ super.positionChildAt(targetPosition, child, false /* includingParents */);
+
+ if (includingParents && (moveToTop || moveToBottom)) {
+ // The DisplayContent children do not re-order, but we still want to move the
+ // display of this stack container because the intention of positioning is to have
+ // higher z-order to gain focus.
+ mDisplayContent.positionDisplayAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
+ true /* includingParents */);
+ }
+
+ child.updateTaskMovement(moveToTop);
+
+ mDisplayContent.setLayoutNeeded();
+ }
+
+ /**
+ * When stack is added or repositioned, find a proper position for it.
+ * This will make sure that pinned stack always stays on top.
+ * @param requestedPosition Position requested by caller.
+ * @param stack Stack to be added or positioned.
+ * @param adding Flag indicates whether we're adding a new stack or positioning an existing.
+ * @return The proper position for the stack.
+ */
+ private int findPositionForStack(int requestedPosition, ActivityStack stack,
+ boolean adding) {
+ if (stack.isActivityTypeDream()) {
+ return POSITION_TOP;
+ }
+
+ if (stack.inPinnedWindowingMode()) {
+ return POSITION_TOP;
+ }
+
+ final int topChildPosition = mChildren.size() - 1;
+ int belowAlwaysOnTopPosition = POSITION_BOTTOM;
+ for (int i = topChildPosition; i >= 0; --i) {
+ // Since a stack could be repositioned while being one of the child, return
+ // current index if that's the same stack we are positioning and it is always on
+ // top.
+ final boolean sameStack = mDisplayContent.getStacks().get(i) == stack;
+ if ((sameStack && stack.isAlwaysOnTop())
+ || (!sameStack && !mDisplayContent.getStacks().get(i).isAlwaysOnTop())) {
+ belowAlwaysOnTopPosition = i;
+ break;
+ }
+ }
+
+ // The max possible position we can insert the stack at.
+ int maxPosition = POSITION_TOP;
+ // The min possible position we can insert the stack at.
+ int minPosition = POSITION_BOTTOM;
+
+ if (stack.isAlwaysOnTop()) {
+ if (mDisplayContent.hasPinnedTask()) {
+ // Always-on-top stacks go below the pinned stack.
+ maxPosition = mDisplayContent.getStacks().indexOf(mRootPinnedTask) - 1;
+ }
+ // Always-on-top stacks need to be above all other stacks.
+ minPosition = belowAlwaysOnTopPosition
+ != POSITION_BOTTOM ? belowAlwaysOnTopPosition : topChildPosition;
+ } else {
+ // Other stacks need to be below the always-on-top stacks.
+ maxPosition = belowAlwaysOnTopPosition
+ != POSITION_BOTTOM ? belowAlwaysOnTopPosition : 0;
+ }
+
+ // Cap the requested position to something reasonable for the previous position check
+ // below.
+ if (requestedPosition == POSITION_TOP) {
+ requestedPosition = mChildren.size();
+ } else if (requestedPosition == POSITION_BOTTOM) {
+ requestedPosition = 0;
+ }
+
+ int targetPosition = requestedPosition;
+ targetPosition = Math.min(targetPosition, maxPosition);
+ targetPosition = Math.max(targetPosition, minPosition);
+
+ int prevPosition = mDisplayContent.getStacks().indexOf(stack);
+ // The positions we calculated above (maxPosition, minPosition) do not take into
+ // consideration the following edge cases.
+ // 1) We need to adjust the position depending on the value "adding".
+ // 2) When we are moving a stack to another position, we also need to adjust the
+ // position depending on whether the stack is moving to a higher or lower position.
+ if ((targetPosition != requestedPosition) && (adding || targetPosition < prevPosition)) {
+ targetPosition++;
+ }
+
+ return targetPosition;
+ }
+
+ @Override
+ boolean forAllWindows(ToBooleanFunction<WindowState> callback,
+ boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ if (super.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ } else {
+ if (forAllExitingAppTokenWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ if (super.forAllWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean forAllExitingAppTokenWindows(ToBooleanFunction<WindowState> callback,
+ boolean traverseTopToBottom) {
+ // For legacy reasons we process the TaskStack.mExitingActivities first here before the
+ // app tokens.
+ // TODO: Investigate if we need to continue to do this or if we can just process them
+ // in-order.
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final List<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
+ for (int j = activities.size() - 1; j >= 0; --j) {
+ if (activities.get(j).forAllWindowsUnchecked(callback,
+ traverseTopToBottom)) {
+ return true;
+ }
+ }
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; ++i) {
+ final List<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
+ final int appTokensCount = activities.size();
+ for (int j = 0; j < appTokensCount; j++) {
+ if (activities.get(j).forAllWindowsUnchecked(callback,
+ traverseTopToBottom)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ void setExitingTokensHasVisible(boolean hasVisible) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final ArrayList<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
+ for (int j = activities.size() - 1; j >= 0; --j) {
+ activities.get(j).hasVisible = hasVisible;
+ }
+ }
+ }
+
+ void removeExistingAppTokensIfPossible() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final ArrayList<ActivityRecord> activities = mChildren.get(i).mExitingActivities;
+ for (int j = activities.size() - 1; j >= 0; --j) {
+ final ActivityRecord activity = activities.get(j);
+ if (!activity.hasVisible && !mDisplayContent.mClosingApps.contains(activity)
+ && (!activity.mIsExiting || activity.isEmpty())) {
+ // Make sure there is no animation running on this activity, so any windows
+ // associated with it will be removed as soon as their animations are
+ // complete.
+ cancelAnimation();
+ ProtoLog.v(WM_DEBUG_ADD_REMOVE,
+ "performLayout: Activity exiting now removed %s", activity);
+ activity.removeIfPossible();
+ }
+ }
+ }
+ }
+
+ @Override
+ int getOrientation(int candidate) {
+ if (mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
+ // Apps and their containers are not allowed to specify an orientation while using
+ // root tasks...except for the home stack if it is not resizable and currently
+ // visible (top of) its root task.
+ if (mRootHomeTask != null && mRootHomeTask.isVisible()) {
+ final Task topMost = mRootHomeTask.getTopMostTask();
+ final boolean resizable = topMost != null && topMost.isResizeable();
+ if (!(resizable && mRootHomeTask.matchParentBounds())) {
+ final int orientation = mRootHomeTask.getOrientation();
+ if (orientation != SCREEN_ORIENTATION_UNSET) {
+ return orientation;
+ }
+ }
+ }
+ return SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ final int orientation = super.getOrientation(candidate);
+ if (orientation != SCREEN_ORIENTATION_UNSET
+ && orientation != SCREEN_ORIENTATION_BEHIND) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "App is requesting an orientation, return %d for display id=%d",
+ orientation, mDisplayContent.mDisplayId);
+ return orientation;
+ }
+
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "No app is requesting an orientation, return %d for display id=%d",
+ mDisplayContent.getLastOrientation(), mDisplayContent.mDisplayId);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return mDisplayContent.getLastOrientation();
+ }
+
+ @Override
+ void assignChildLayers(SurfaceControl.Transaction t) {
+ assignStackOrdering(t);
+
+ for (int i = 0; i < mChildren.size(); i++) {
+ final ActivityStack s = mChildren.get(i);
+ s.assignChildLayers(t);
+ }
+ }
+
+ void assignStackOrdering(SurfaceControl.Transaction t) {
+ if (getParent() == null) {
+ return;
+ }
+ mTmpAlwaysOnTopStacks.clear();
+ mTmpHomeStacks.clear();
+ mTmpNormalStacks.clear();
+ for (int i = 0; i < mChildren.size(); ++i) {
+ final ActivityStack s = mChildren.get(i);
+ if (s.isAlwaysOnTop()) {
+ mTmpAlwaysOnTopStacks.add(s);
+ } else if (s.isActivityTypeHome()) {
+ mTmpHomeStacks.add(s);
+ } else {
+ mTmpNormalStacks.add(s);
+ }
+ }
+
+ int layer = 0;
+ // Place home stacks to the bottom.
+ for (int i = 0; i < mTmpHomeStacks.size(); i++) {
+ mTmpHomeStacks.get(i).assignLayer(t, layer++);
+ }
+ // The home animation layer is between the home stacks and the normal stacks.
+ final int layerForHomeAnimationLayer = layer++;
+ int layerForSplitScreenDividerAnchor = layer++;
+ int layerForAnimationLayer = layer++;
+ for (int i = 0; i < mTmpNormalStacks.size(); i++) {
+ final ActivityStack s = mTmpNormalStacks.get(i);
+ s.assignLayer(t, layer++);
+ if (s.inSplitScreenWindowingMode()) {
+ // The split screen divider anchor is located above the split screen window.
+ layerForSplitScreenDividerAnchor = layer++;
+ }
+ if (s.isTaskAnimating() || s.isAppTransitioning()) {
+ // The animation layer is located above the highest animating stack and no
+ // higher.
+ layerForAnimationLayer = layer++;
+ }
+ }
+ // The boosted animation layer is between the normal stacks and the always on top
+ // stacks.
+ final int layerForBoostedAnimationLayer = layer++;
+ for (int i = 0; i < mTmpAlwaysOnTopStacks.size(); i++) {
+ mTmpAlwaysOnTopStacks.get(i).assignLayer(t, layer++);
+ }
+
+ t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
+ t.setLayer(mAppAnimationLayer, layerForAnimationLayer);
+ t.setLayer(mSplitScreenDividerAnchor, layerForSplitScreenDividerAnchor);
+ t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
+ }
+
+ @Override
+ SurfaceControl getAppAnimationLayer(@AnimationLayer int animationLayer) {
+ switch (animationLayer) {
+ case ANIMATION_LAYER_BOOSTED:
+ return mBoostedAppAnimationLayer;
+ case ANIMATION_LAYER_HOME:
+ return mHomeAppAnimationLayer;
+ case ANIMATION_LAYER_STANDARD:
+ default:
+ return mAppAnimationLayer;
+ }
+ }
+
+ SurfaceControl getSplitScreenDividerAnchor() {
+ return mSplitScreenDividerAnchor;
+ }
+
+ @Override
+ void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
+ if (getParent() != null) {
+ super.onParentChanged(newParent, oldParent, () -> {
+ mAppAnimationLayer = makeChildSurface(null)
+ .setName("animationLayer")
+ .build();
+ mBoostedAppAnimationLayer = makeChildSurface(null)
+ .setName("boostedAnimationLayer")
+ .build();
+ mHomeAppAnimationLayer = makeChildSurface(null)
+ .setName("homeAnimationLayer")
+ .build();
+ mSplitScreenDividerAnchor = makeChildSurface(null)
+ .setName("splitScreenDividerAnchor")
+ .build();
+ getPendingTransaction()
+ .show(mAppAnimationLayer)
+ .show(mBoostedAppAnimationLayer)
+ .show(mHomeAppAnimationLayer)
+ .show(mSplitScreenDividerAnchor);
+ });
+ } else {
+ super.onParentChanged(newParent, oldParent);
+ mWmService.mTransactionFactory.get()
+ .remove(mAppAnimationLayer)
+ .remove(mBoostedAppAnimationLayer)
+ .remove(mHomeAppAnimationLayer)
+ .remove(mSplitScreenDividerAnchor)
+ .apply();
+ mAppAnimationLayer = null;
+ mBoostedAppAnimationLayer = null;
+ mHomeAppAnimationLayer = null;
+ mSplitScreenDividerAnchor = null;
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index 3120631..32d7a07 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -78,7 +78,7 @@
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
- DisplayContent.TaskContainers taskContainers) {
+ TaskContainers taskContainers) {
throw new RuntimeException("test stub");
}
}