blob: 4d85e7bda90008deaa6db9ef59e94dd8e987e2e1 [file] [log] [blame]
/*
* 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.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
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 com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK;
import static com.android.server.wm.DisplayContent.alwaysCreateRootTask;
import static com.android.server.wm.Task.ActivityState.RESUMED;
import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.os.UserHandle;
import android.util.IntArray;
import android.util.Slog;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* {@link DisplayArea} that represents a section of a screen that contains app window containers.
*
* The children can be either {@link Task} or {@link TaskDisplayArea}.
*/
final class TaskDisplayArea extends DisplayArea<WindowContainer> {
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 root tasks but underneath any assistant root tasks.
*
* 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 Task mRootHomeTask;
private Task mRootPinnedTask;
private Task mRootSplitScreenPrimaryTask;
// TODO(b/159029784): Remove when getStack() behavior is cleaned-up
private Task mRootRecentsTask;
private final ArrayList<WindowContainer> mTmpAlwaysOnTopChildren = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpNormalChildren = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpHomeChildren = new ArrayList<>();
private final IntArray mTmpNeedsZBoostIndexes = new IntArray();
private int mTmpLayerForSplitScreenDividerAnchor;
private int mTmpLayerForAnimationLayer;
private ArrayList<Task> mTmpTasks = new ArrayList<>();
private ActivityTaskManagerService mAtmService;
private RootWindowContainer mRootWindowContainer;
// Launch root tasks by activityType then by windowingMode.
static private class LaunchRootTaskDef {
Task task;
int[] windowingModes;
int[] activityTypes;
boolean contains(int windowingMode, int activityType) {
return ArrayUtils.contains(windowingModes, windowingMode)
&& ArrayUtils.contains(activityTypes, activityType);
}
}
private final ArrayList<LaunchRootTaskDef> mLaunchRootTasks = new ArrayList<>();
/**
* A focusable root task that is purposely to be positioned at the top. Although the root
* task may not have the topmost index, it is used as a preferred candidate to prevent being
* unable to resume target root task properly when there are other focusable always-on-top
* root tasks.
*/
Task mPreferredTopFocusableRootTask;
/**
* If this is the same as {@link #getFocusedRootTask} then the activity on the top of the
* focused root task has been resumed. If root tasks are changing position this will hold the
* old root task until the new root task becomes resumed after which it will be set to
* current focused root task.
*/
Task mLastFocusedRootTask;
/**
* All of the root tasks on this display. Order matters, topmost root task is in front of all
* other root tasks, bottommost behind. Accessed directly by ActivityManager package classes.
* Any calls changing the list should also call {@link #onRootTaskOrderChanged(Task)}.
*/
private ArrayList<OnRootTaskOrderChangedListener> mRootTaskOrderChangedCallbacks =
new ArrayList<>();
/**
* The task display area is removed from the system and we are just waiting for all activities
* on it to be finished before removing this object.
*/
private boolean mRemoved;
/**
* The id of a leaf task that most recently being moved to front.
*/
private int mLastLeafTaskToFrontId;
/**
* Whether this TaskDisplayArea was created by a {@link android.window.DisplayAreaOrganizer}.
* If {@code true}, this will be removed when the organizer is unregistered.
*/
final boolean mCreatedByOrganizer;
/**
* True if this TaskDisplayArea can have a home task
* {@link WindowConfiguration#ACTIVITY_TYPE_HOME}
*/
private final boolean mCanHostHomeTask;
TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
int displayAreaFeature) {
this(displayContent, service, name, displayAreaFeature, false /* createdByOrganizer */,
true /* canHostHomeTask */);
}
TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
int displayAreaFeature, boolean createdByOrganizer) {
this(displayContent, service, name, displayAreaFeature, createdByOrganizer,
true /* canHostHomeTask */);
}
TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
int displayAreaFeature, boolean createdByOrganizer,
boolean canHostHomeTask) {
super(service, Type.ANY, name, displayAreaFeature);
mDisplayContent = displayContent;
mRootWindowContainer = service.mRoot;
mAtmService = service.mAtmService;
mCreatedByOrganizer = createdByOrganizer;
mCanHostHomeTask = canHostHomeTask;
}
/**
* Returns the topmost root task on the display that is compatible with the input windowing mode
* and activity type. Null is no compatible root task on the display.
*/
@Nullable
Task getRootTask(int windowingMode, int activityType) {
if (activityType == ACTIVITY_TYPE_HOME) {
return mRootHomeTask;
} else if (activityType == ACTIVITY_TYPE_RECENTS) {
return mRootRecentsTask;
}
if (windowingMode == WINDOWING_MODE_PINNED) {
return mRootPinnedTask;
} else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
return mRootSplitScreenPrimaryTask;
}
return getRootTask(rootTask -> {
if (activityType == ACTIVITY_TYPE_UNDEFINED
&& windowingMode == rootTask.getWindowingMode()) {
// Passing in undefined type means we want to match the topmost root task with the
// windowing mode.
return true;
}
return rootTask.isCompatible(windowingMode, activityType);
});
}
@VisibleForTesting
Task getTopRootTask() {
return getRootTask(t -> true);
}
@Nullable
Task getRootHomeTask() {
return mRootHomeTask;
}
@Nullable
Task getRootRecentsTask() {
return mRootRecentsTask;
}
Task getRootPinnedTask() {
return mRootPinnedTask;
}
Task getRootSplitScreenPrimaryTask() {
return mRootSplitScreenPrimaryTask;
}
Task getRootSplitScreenSecondaryTask() {
// Only check the direct child Task for now, since the primary is also a direct child Task.
for (int i = mChildren.size() - 1; i >= 0; --i) {
final Task task = mChildren.get(i).asTask();
if (task != null && task.inSplitScreenSecondaryWindowingMode()) {
return task;
}
}
return null;
}
ArrayList<Task> getVisibleTasks() {
final ArrayList<Task> visibleTasks = new ArrayList<>();
forAllTasks(task -> {
if (task.isLeafTask() && task.isVisible()) {
visibleTasks.add(task);
}
});
return visibleTasks;
}
void onRootTaskWindowingModeChanged(Task rootTask) {
removeRootTaskReferenceIfNeeded(rootTask);
addRootTaskReferenceIfNeeded(rootTask);
if (rootTask == mRootPinnedTask && getTopRootTask() != rootTask) {
// Looks like this root task changed windowing mode to pinned. Move it to the top.
positionChildAt(POSITION_TOP, rootTask, false /* includingParents */);
}
}
void addRootTaskReferenceIfNeeded(Task rootTask) {
if (rootTask.isActivityTypeHome()) {
if (mRootHomeTask != null) {
if (!rootTask.isDescendantOf(mRootHomeTask)) {
throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root home"
+ " task=" + mRootHomeTask + " already exist on display=" + this
+ " rootTask=" + rootTask);
}
} else {
mRootHomeTask = rootTask;
}
} else if (rootTask.isActivityTypeRecents()) {
if (mRootRecentsTask != null) {
if (!rootTask.isDescendantOf(mRootRecentsTask)) {
throw new IllegalArgumentException("addRootTaskReferenceIfNeeded: root recents"
+ " task=" + mRootRecentsTask + " already exist on display=" + this
+ " rootTask=" + rootTask);
}
} else {
mRootRecentsTask = rootTask;
}
}
if (!rootTask.isRootTask()) {
return;
}
final int windowingMode = rootTask.getWindowingMode();
if (windowingMode == WINDOWING_MODE_PINNED) {
if (mRootPinnedTask != null) {
throw new IllegalArgumentException(
"addRootTaskReferenceIfNeeded: root pinned task=" + mRootPinnedTask
+ " already exist on display=" + this + " rootTask=" + rootTask);
}
mRootPinnedTask = rootTask;
} else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
if (mRootSplitScreenPrimaryTask != null) {
throw new IllegalArgumentException(
"addRootTaskReferenceIfNeeded: root split screen primary task="
+ mRootSplitScreenPrimaryTask
+ " already exist on display=" + this + " rootTask=" + rootTask);
}
mRootSplitScreenPrimaryTask = rootTask;
}
}
void removeRootTaskReferenceIfNeeded(Task rootTask) {
if (rootTask == mRootHomeTask) {
mRootHomeTask = null;
} else if (rootTask == mRootRecentsTask) {
mRootRecentsTask = null;
} else if (rootTask == mRootPinnedTask) {
mRootPinnedTask = null;
} else if (rootTask == mRootSplitScreenPrimaryTask) {
mRootSplitScreenPrimaryTask = null;
}
}
@Override
void addChild(WindowContainer child, int position) {
if (child.asTaskDisplayArea() != null) {
if (DEBUG_ROOT_TASK) {
Slog.d(TAG_WM, "Set TaskDisplayArea=" + child + " on taskDisplayArea=" + this);
}
super.addChild(child, position);
} else if (child.asTask() != null) {
addChildTask(child.asTask(), position);
} else {
throw new IllegalArgumentException(
"TaskDisplayArea can only add Task and TaskDisplayArea, but found "
+ child);
}
}
private void addChildTask(Task task, int position) {
if (DEBUG_ROOT_TASK) Slog.d(TAG_WM, "Set task=" + task + " on taskDisplayArea=" + this);
addRootTaskReferenceIfNeeded(task);
position = findPositionForRootTask(position, task, true /* adding */);
super.addChild(task, position);
if (mPreferredTopFocusableRootTask != null
&& task.isFocusable()
&& mPreferredTopFocusableRootTask.compareTo(task) < 0) {
// Clear preferred top because the adding focusable task has a higher z-order.
mPreferredTopFocusableRootTask = null;
}
mAtmService.updateSleepIfNeededLocked();
onRootTaskOrderChanged(task);
}
@Override
protected void removeChild(WindowContainer child) {
if (child.asTaskDisplayArea() != null) {
super.removeChild(child);
} else if (child.asTask() != null) {
removeChildTask(child.asTask());
} else {
throw new IllegalArgumentException(
"TaskDisplayArea can only remove Task and TaskDisplayArea, but found "
+ child);
}
}
private void removeChildTask(Task task) {
super.removeChild(task);
onRootTaskRemoved(task);
mAtmService.updateSleepIfNeededLocked();
removeRootTaskReferenceIfNeeded(task);
}
@Override
boolean isOnTop() {
// Considered always on top
return true;
}
@Override
void positionChildAt(int position, WindowContainer child, boolean includingParents) {
if (child.asTaskDisplayArea() != null) {
super.positionChildAt(position, child, includingParents);
} else if (child.asTask() != null) {
positionChildTaskAt(position, child.asTask(), includingParents);
} else {
throw new IllegalArgumentException(
"TaskDisplayArea can only position Task and TaskDisplayArea, but found "
+ child);
}
}
private void positionChildTaskAt(int position, Task child, boolean includingParents) {
final boolean moveToTop = position >= getChildCount() - 1;
final boolean moveToBottom = position <= 0;
final int oldPosition = mChildren.indexOf(child);
if (child.isAlwaysOnTop() && !moveToTop) {
// This root task is always-on-top, override the default behavior.
Slog.w(TAG_WM, "Ignoring move of always-on-top root task=" + this + " to bottom");
// Moving to its current position, as we must call super but we don't want to
// perform any meaningful action.
super.positionChildAt(oldPosition, child, false /* includingParents */);
return;
}
// We don't allow untrusted display to top when root task moves to top,
// until user tapping this display to change display position as top intentionally.
//
// Displays with {@code mDontMoveToTop} property set to {@code true} won't be
// allowed to top neither.
if ((!mDisplayContent.isTrusted() || mDisplayContent.mDontMoveToTop)
&& !getParent().isOnTop()) {
includingParents = false;
}
final int targetPosition = findPositionForRootTask(position, child, false /* adding */);
super.positionChildAt(targetPosition, child, false /* includingParents */);
if (includingParents && getParent() != null && (moveToTop || moveToBottom)) {
getParent().positionChildAt(moveToTop ? POSITION_TOP : POSITION_BOTTOM,
this /* child */, true /* includingParents */);
}
child.updateTaskMovement(moveToTop, targetPosition);
mDisplayContent.layoutAndAssignWindowLayersIfNeeded();
// The insert position may be adjusted to non-top when there is always-on-top root task.
// Since the original position is preferred to be top, the root task should have higher
// priority when we are looking for top focusable root task. The condition {@code
// wasContained} restricts the preferred root task is set only when moving an existing
// root task to top instead of adding a new root task that may be too early (e.g. in the
// middle of launching or reparenting).
if (moveToTop && child.isFocusableAndVisible()) {
mPreferredTopFocusableRootTask = child;
} else if (mPreferredTopFocusableRootTask == child) {
mPreferredTopFocusableRootTask = null;
}
// Update the top resumed activity because the preferred top focusable task may be changed.
mAtmService.mTaskSupervisor.updateTopResumedActivityIfNeeded();
final ActivityRecord r = child.getResumedActivity();
if (r != null && r == mRootWindowContainer.getTopResumedActivity()) {
mAtmService.setResumedActivityUncheckLocked(r, "positionChildAt");
}
if (mChildren.indexOf(child) != oldPosition) {
onRootTaskOrderChanged(child);
}
}
void onLeafTaskRemoved(int taskId) {
if (mLastLeafTaskToFrontId == taskId) {
mLastLeafTaskToFrontId = INVALID_TASK_ID;
}
}
void onLeafTaskMoved(Task t, boolean toTop) {
if (!toTop) {
if (t.mTaskId == mLastLeafTaskToFrontId) {
mLastLeafTaskToFrontId = INVALID_TASK_ID;
}
return;
}
if (t.mTaskId == mLastLeafTaskToFrontId || t.topRunningActivityLocked() == null) {
return;
}
mLastLeafTaskToFrontId = t.mTaskId;
EventLogTags.writeWmTaskToFront(t.mUserId, t.mTaskId);
// Notifying only when a leaf task moved to front. Or the listeners would be notified
// couple times from the leaf task all the way up to the root task.
mAtmService.getTaskChangeNotificationController().notifyTaskMovedToFront(t.getTaskInfo());
}
@Override
void onChildPositionChanged(WindowContainer child) {
super.onChildPositionChanged(child);
mRootWindowContainer.invalidateTaskLayers();
}
@Override
boolean forAllTaskDisplayAreas(Function<TaskDisplayArea, Boolean> callback,
boolean traverseTopToBottom) {
// Apply the callback to all TDAs at or below this container. If the callback returns true,
// stop early.
if (traverseTopToBottom) {
// When it is top to bottom, run on child TDA first as they are on top of the parent.
return super.forAllTaskDisplayAreas(callback, traverseTopToBottom)
|| callback.apply(this);
}
return callback.apply(this) || super.forAllTaskDisplayAreas(callback, traverseTopToBottom);
}
@Override
void forAllTaskDisplayAreas(Consumer<TaskDisplayArea> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
super.forAllTaskDisplayAreas(callback, traverseTopToBottom);
callback.accept(this);
} else {
callback.accept(this);
super.forAllTaskDisplayAreas(callback, traverseTopToBottom);
}
}
@Nullable
@Override
<R> R reduceOnAllTaskDisplayAreas(BiFunction<TaskDisplayArea, R, R> accumulator,
@Nullable R initValue, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
final R result =
super.reduceOnAllTaskDisplayAreas(accumulator, initValue, traverseTopToBottom);
return accumulator.apply(this, result);
} else {
final R result = accumulator.apply(this, initValue);
return super.reduceOnAllTaskDisplayAreas(accumulator, result, traverseTopToBottom);
}
}
@Nullable
@Override
<R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
final R item = super.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);
return item != null ? item : callback.apply(this);
} else {
final R item = callback.apply(this);
return item != null
? item
: super.getItemFromTaskDisplayAreas(callback, traverseTopToBottom);
}
}
/**
* Assigns a priority number to root task types. This priority defines an order between the
* types of root task that are added to the task display area.
*
* Higher priority number indicates that the root task should have a higher z-order.
*
* For child {@link TaskDisplayArea}, it will be the priority of its top child.
*
* @return the priority of the root task
*/
private int getPriority(WindowContainer child) {
final TaskDisplayArea tda = child.asTaskDisplayArea();
if (tda != null) {
// Use the top child priority as the TaskDisplayArea priority.
return tda.getPriority(tda.getTopChild());
}
final Task rootTask = child.asTask();
if (mWmService.mAssistantOnTopOfDream && rootTask.isActivityTypeAssistant()) return 4;
if (rootTask.isActivityTypeDream()) return 3;
if (rootTask.inPinnedWindowingMode()) return 2;
if (rootTask.isAlwaysOnTop()) return 1;
return 0;
}
private int findMinPositionForRootTask(Task rootTask) {
int minPosition = POSITION_BOTTOM;
for (int i = 0; i < mChildren.size(); ++i) {
if (getPriority(mChildren.get(i)) < getPriority(rootTask)) {
minPosition = i;
} else {
break;
}
}
if (rootTask.isAlwaysOnTop()) {
// Since a root task could be repositioned while still being one of the children, we
// check if this always-on-top root task already exists and if so, set the minPosition
// to its previous position.
// Use mChildren.indexOf instead of getTaskIndexOf because we need to place the rootTask
// as a direct child.
final int currentIndex = mChildren.indexOf(rootTask);
if (currentIndex > minPosition) {
minPosition = currentIndex;
}
}
return minPosition;
}
private int findMaxPositionForRootTask(Task rootTask) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer curr = mChildren.get(i);
// Since a root task could be repositioned while still being one of the children, we
// check if 'curr' is the same root task and skip it if so
final boolean sameRootTask = curr == rootTask;
if (getPriority(curr) <= getPriority(rootTask) && !sameRootTask) {
return i;
}
}
return 0;
}
/**
* When root task is added or repositioned, find a proper position for it.
*
* The order is defined as:
* - Dream is on top of everything
* - PiP is directly below the Dream
* - always-on-top root tasks are directly below PiP; new always-on-top root tasks are added
* above existing ones
* - other non-always-on-top root tasks come directly below always-on-top root tasks; new
* non-always-on-top root tasks are added directly below always-on-top root tasks and above
* existing non-always-on-top root tasks
* - if {@link #mAssistantOnTopOfDream} is enabled, then Assistant is on top of everything
* (including the Dream); otherwise, it is a normal non-always-on-top root task
*
* @param requestedPosition Position requested by caller.
* @param rootTask Root task to be added or positioned.
* @param adding Flag indicates whether we're adding a new root task or positioning
* an existing.
* @return The proper position for the root task.
*/
private int findPositionForRootTask(int requestedPosition, Task rootTask, boolean adding) {
// The max possible position we can insert the root task at.
int maxPosition = findMaxPositionForRootTask(rootTask);
// The min possible position we can insert the root task at.
int minPosition = findMinPositionForRootTask(rootTask);
// 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 = mChildren.indexOf(rootTask);
// 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 root task to another position, we also need to adjust the
// position depending on whether the root task 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 RootTask.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) {
// Only run on those of direct Task child, because child TaskDisplayArea has run on
// its child in #forAllWindows()
if (mChildren.get(i).asTask() == null) {
continue;
}
final List<ActivityRecord> activities =
mChildren.get(i).asTask().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) {
// Only run on those of direct Task child, because child TaskDisplayArea has run on
// its child in #forAllWindows()
if (mChildren.get(i).asTask() == null) {
continue;
}
final List<ActivityRecord> activities =
mChildren.get(i).asTask().mExitingActivities;
final int appTokensCount = activities.size();
for (int j = 0; j < appTokensCount; j++) {
if (activities.get(j).forAllWindowsUnchecked(callback,
traverseTopToBottom)) {
return true;
}
}
}
}
return false;
}
@Override
int getOrientation(int candidate) {
mLastOrientationSource = null;
if (mIgnoreOrientationRequest) {
return SCREEN_ORIENTATION_UNSET;
}
if (!canSpecifyOrientation()) {
// We only respect orientation of the focused TDA, which can be a child of this TDA.
return reduceOnAllTaskDisplayAreas((taskDisplayArea, orientation) -> {
if (taskDisplayArea == this || orientation != SCREEN_ORIENTATION_UNSET) {
return orientation;
}
return taskDisplayArea.getOrientation(candidate);
}, SCREEN_ORIENTATION_UNSET);
}
if (isRootTaskVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
// Apps and their containers are not allowed to specify an orientation while using
// root tasks...except for the root home task if it is not resizable and currently
// visible (top of) its root task.
if (mRootHomeTask != null && !mRootHomeTask.isResizeable()) {
// Manually nest one-level because because getOrientation() checks fillsParent()
// which checks that requestedOverrideBounds() is empty. However, in this case,
// it is not empty because it's been overridden to maintain the fullscreen size
// within a smaller split-root.
final Task topHomeTask = mRootHomeTask.getTopMostTask();
final ActivityRecord topHomeActivity = topHomeTask.getTopNonFinishingActivity();
// If a home activity is in the process of launching and isn't yet visible we
// should still respect the root task's preferred orientation to ensure rotation
// occurs before the home activity finishes launching.
final boolean isHomeActivityLaunching = topHomeActivity != null
&& topHomeActivity.mVisibleRequested;
if (topHomeTask.isVisible() || isHomeActivityLaunching) {
final int orientation = topHomeTask.getOrientation();
if (orientation != SCREEN_ORIENTATION_UNSET) {
return orientation;
}
}
}
return SCREEN_ORIENTATION_UNSPECIFIED;
} else {
// Apps and their containers are not allowed to specify an orientation of full screen
// tasks created by organizer. The organizer handles the orientation instead.
final Task task = getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
if (task != null && task.isVisible() && task.mCreatedByOrganizer) {
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) {
assignRootTaskOrdering(t);
for (int i = 0; i < mChildren.size(); i++) {
mChildren.get(i).assignChildLayers(t);
}
}
void assignRootTaskOrdering(SurfaceControl.Transaction t) {
if (getParent() == null) {
return;
}
mTmpAlwaysOnTopChildren.clear();
mTmpHomeChildren.clear();
mTmpNormalChildren.clear();
for (int i = 0; i < mChildren.size(); ++i) {
final WindowContainer child = mChildren.get(i);
final TaskDisplayArea childTda = child.asTaskDisplayArea();
if (childTda != null) {
final Task childTdaTopRootTask = childTda.getTopRootTask();
if (childTdaTopRootTask == null) {
mTmpNormalChildren.add(childTda);
} else if (childTdaTopRootTask.isAlwaysOnTop()) {
mTmpAlwaysOnTopChildren.add(childTda);
} else if (childTdaTopRootTask.isActivityTypeHome()) {
mTmpHomeChildren.add(childTda);
} else {
mTmpNormalChildren.add(childTda);
}
continue;
}
final Task childTask = child.asTask();
if (childTask.isAlwaysOnTop()) {
mTmpAlwaysOnTopChildren.add(childTask);
} else if (childTask.isActivityTypeHome()) {
mTmpHomeChildren.add(childTask);
} else {
mTmpNormalChildren.add(childTask);
}
}
int layer = 0;
// Place root home tasks to the bottom.
layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer, false /* normalRootTasks */);
// The home animation layer is between the root home tasks and the normal root tasks.
final int layerForHomeAnimationLayer = layer++;
mTmpLayerForSplitScreenDividerAnchor = layer++;
mTmpLayerForAnimationLayer = layer++;
layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer, true /* normalRootTasks */);
// The boosted animation layer is between the normal root tasks and the always on top
// root tasks.
final int layerForBoostedAnimationLayer = layer++;
adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer, false /* normalRootTasks */);
t.setLayer(mHomeAppAnimationLayer, layerForHomeAnimationLayer);
t.setLayer(mAppAnimationLayer, mTmpLayerForAnimationLayer);
t.setLayer(mSplitScreenDividerAnchor, mTmpLayerForSplitScreenDividerAnchor);
t.setLayer(mBoostedAppAnimationLayer, layerForBoostedAnimationLayer);
}
private int adjustNormalRootTaskLayer(WindowContainer child, int layer) {
if (child.asTask() != null && child.inSplitScreenWindowingMode()) {
// The split screen divider anchor is located above the split screen window.
mTmpLayerForSplitScreenDividerAnchor = layer++;
}
if ((child.asTask() != null && child.asTask().isAnimatingByRecents())
|| child.isAppTransitioning()) {
// The animation layer is located above the highest animating root task and no
// higher.
mTmpLayerForAnimationLayer = layer++;
}
return layer;
}
/**
* Adjusts the layer of the root task which belongs to the same group.
* Note that there are three root task groups: home rootTasks, always on top rootTasks, and
* normal rootTasks.
*
* @param startLayer The beginning layer of this group of rootTasks.
* @param normalRootTasks Set {@code true} if this group is neither home nor always on top.
* @return The adjusted layer value.
*/
private int adjustRootTaskLayer(SurfaceControl.Transaction t,
ArrayList<WindowContainer> children, int startLayer, boolean normalRootTasks) {
mTmpNeedsZBoostIndexes.clear();
final int childCount = children.size();
for (int i = 0; i < childCount; i++) {
final WindowContainer child = children.get(i);
final TaskDisplayArea childTda = child.asTaskDisplayArea();
boolean childNeedsZBoost = childTda != null
? childTda.childrenNeedZBoost()
: child.needsZBoost();
if (!childNeedsZBoost) {
child.assignLayer(t, startLayer++);
if (normalRootTasks) {
startLayer = adjustNormalRootTaskLayer(child, startLayer);
}
} else {
mTmpNeedsZBoostIndexes.add(i);
}
}
final int zBoostSize = mTmpNeedsZBoostIndexes.size();
for (int i = 0; i < zBoostSize; i++) {
final WindowContainer child = children.get(mTmpNeedsZBoostIndexes.get(i));
child.assignLayer(t, startLayer++);
if (normalRootTasks) {
startLayer = adjustNormalRootTaskLayer(child, startLayer);
}
}
return startLayer;
}
private boolean childrenNeedZBoost() {
final boolean[] needsZBoost = new boolean[1];
forAllRootTasks(task -> {
needsZBoost[0] |= task.needsZBoost();
});
return needsZBoost[0];
}
@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;
}
}
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
final ActivityRecord activity = getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
SurfaceControl getSplitScreenDividerAnchor() {
return mSplitScreenDividerAnchor;
}
@Override
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
if (getParent() != null) {
super.onParentChanged(newParent, oldParent, () -> {
mAppAnimationLayer = makeChildSurface(null)
.setName("animationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
mBoostedAppAnimationLayer = makeChildSurface(null)
.setName("boostedAnimationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
mHomeAppAnimationLayer = makeChildSurface(null)
.setName("homeAnimationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
mSplitScreenDividerAnchor = makeChildSurface(null)
.setName("splitScreenDividerAnchor")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
getSyncTransaction()
.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;
}
}
@Override
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
super.migrateToNewSurfaceControl(t);
if (mAppAnimationLayer == null) {
return;
}
// As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
t.reparent(mAppAnimationLayer, mSurfaceControl);
t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
reassignLayer(t);
scheduleAnimation();
}
void onRootTaskRemoved(Task rootTask) {
if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
Slog.v(TAG_ROOT_TASK, "onRootTaskRemoved: detaching " + rootTask + " from displayId="
+ mDisplayContent.mDisplayId);
}
if (mPreferredTopFocusableRootTask == rootTask) {
mPreferredTopFocusableRootTask = null;
}
mDisplayContent.releaseSelfIfNeeded();
onRootTaskOrderChanged(rootTask);
}
/**
* Moves/reparents `task` to the back of whatever container the root home task is in. This is
* for when we just want to move a task to "the back" vs. a specific place. The primary use-case
* is to make sure that moved-to-back apps go into secondary split when in split-screen mode.
*/
void positionTaskBehindHome(Task task) {
final Task home = getOrCreateRootHomeTask();
final WindowContainer homeParent = home.getParent();
final Task homeParentTask = homeParent != null ? homeParent.asTask() : null;
if (homeParentTask == null) {
// reparent throws if parent didn't change...
if (task.getParent() == this) {
positionChildAt(POSITION_BOTTOM, task, false /*includingParents*/);
} else {
task.reparent(this, false /* onTop */);
}
} else if (homeParentTask == task.getParent()) {
// Apparently reparent early-outs if same root task, so we have to explicitly reorder.
homeParentTask.positionChildAtBottom(task);
} else {
task.reparent(homeParentTask, false /* toTop */,
Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE, false /* animate */,
false /* deferResume */, "positionTaskBehindHome");
}
}
/**
* Returns an existing root task compatible with the windowing mode and activity type or
* creates one if a compatible root task doesn't exist.
*
* @see #getOrCreateRootTask(int, int, boolean, Intent, Task, ActivityOptions)
*/
Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop) {
return getOrCreateRootTask(windowingMode, activityType, onTop, null /* intent */,
null /* candidateTask */, null /* options */);
}
/**
* When two level tasks are required for given windowing mode and activity type, returns an
* existing compatible root task or creates a new one.
* For one level task, the candidate task would be reused to also be the root task or create
* a new root task if no candidate task.
*
* @see #getRootTask(int, int)
* @see #createRootTask(int, int, boolean)
*/
Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
Intent intent, Task candidateTask, ActivityOptions options) {
// Need to pass in a determined windowing mode to see if a new root task should be created,
// so use its parent's windowing mode if it is undefined.
if (!alwaysCreateRootTask(
windowingMode != WINDOWING_MODE_UNDEFINED ? windowingMode : getWindowingMode(),
activityType)) {
Task rootTask = getRootTask(windowingMode, activityType);
if (rootTask != null) {
return rootTask;
}
} else if (candidateTask != null) {
final Task rootTask = candidateTask;
final int position = onTop ? POSITION_TOP : POSITION_BOTTOM;
final Task launchRootTask = getLaunchRootTask(windowingMode, activityType, options);
if (launchRootTask != null) {
if (rootTask.getParent() == null) {
launchRootTask.addChild(rootTask, position);
} else if (rootTask.getParent() != launchRootTask) {
rootTask.reparent(launchRootTask, position);
}
} else if (rootTask.getDisplayArea() != this || !rootTask.isRootTask()) {
if (rootTask.getParent() == null) {
addChild(rootTask, position);
} else {
rootTask.reparent(this, onTop);
}
}
// Update windowing mode if necessary, e.g. moving a pinned task to fullscreen.
if (candidateTask.getWindowingMode() != windowingMode) {
candidateTask.setWindowingMode(windowingMode);
}
return rootTask;
}
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
.setOnTop(onTop)
.setParent(this)
.setIntent(intent)
.setActivityOptions(options)
.build();
}
/**
* Returns an existing root task compatible with the input params or creates one
* if a compatible root task doesn't exist.
*
* @see #getOrCreateRootTask(int, int, boolean)
*/
Task getOrCreateRootTask(@Nullable ActivityRecord r,
@Nullable ActivityOptions options, @Nullable Task candidateTask,
@Nullable LaunchParams launchParams, int activityType, boolean onTop) {
int windowingMode = WINDOWING_MODE_UNDEFINED;
if (launchParams != null) {
// If launchParams isn't null, windowing mode is already resolved.
windowingMode = launchParams.mWindowingMode;
} else if (options != null) {
// If launchParams is null and options isn't let's use the windowing mode in the
// options.
windowingMode = options.getLaunchWindowingMode();
}
// Validate that our desired windowingMode will work under the current conditions.
// UNDEFINED windowing mode is a valid result and means that the new root task will inherit
// it's display's windowing mode.
windowingMode = validateWindowingMode(windowingMode, r, candidateTask, activityType);
return getOrCreateRootTask(windowingMode, activityType, onTop, null /* intent */,
candidateTask, options);
}
@VisibleForTesting
int getNextRootTaskId() {
return mAtmService.mTaskSupervisor.getNextTaskIdForUser();
}
Task createRootTask(int windowingMode, int activityType, boolean onTop) {
return createRootTask(windowingMode, activityType, onTop, null /* activityOptions */);
}
/**
* A convinenit method of creating a root task by providing windowing mode and activity type
* on this display.
*
* @param windowingMode The windowing mode the root task should be created in. If
* {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the
* root task will inherit its parent's windowing mode.
* @param activityType The activityType the root task should be created in. If
* {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the
* root task will be created in
* {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
* @param onTop If true the root task will be created at the top of the display,
* else at the bottom.
* @param opts The activity options.
* @return The newly created root task.
*/
Task createRootTask(int windowingMode, int activityType, boolean onTop, ActivityOptions opts) {
return new Task.Builder(mAtmService)
.setWindowingMode(windowingMode)
.setActivityType(activityType)
.setParent(this)
.setOnTop(onTop)
.setActivityOptions(opts)
.build();
}
// TODO: Also clear when task is removed from system?
void setLaunchRootTask(Task rootTask, int[] windowingModes, int[] activityTypes) {
if (!rootTask.mCreatedByOrganizer) {
throw new IllegalArgumentException(
"Can't set not mCreatedByOrganizer as launch root tr=" + rootTask);
}
LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
if (def != null) {
// Remove so we add to the end of the list.
mLaunchRootTasks.remove(def);
} else {
def = new LaunchRootTaskDef();
def.task = rootTask;
}
def.activityTypes = activityTypes;
def.windowingModes = windowingModes;
if (!ArrayUtils.isEmpty(windowingModes) || !ArrayUtils.isEmpty(activityTypes)) {
mLaunchRootTasks.add(def);
}
}
void removeLaunchRootTask(Task rootTask) {
LaunchRootTaskDef def = getLaunchRootTaskDef(rootTask);
if (def != null) {
mLaunchRootTasks.remove(def);
}
}
private @Nullable LaunchRootTaskDef getLaunchRootTaskDef(Task rootTask) {
LaunchRootTaskDef def = null;
for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
if (mLaunchRootTasks.get(i).task.mTaskId != rootTask.mTaskId) continue;
def = mLaunchRootTasks.get(i);
break;
}
return def;
}
Task getLaunchRootTask(int windowingMode, int activityType, ActivityOptions options) {
// Try to use the launch root task in options if available.
if (options != null) {
final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask());
// We only allow this for created by organizer tasks.
if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) {
return launchRootTask;
}
}
for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
if (mLaunchRootTasks.get(i).contains(windowingMode, activityType)) {
return mLaunchRootTasks.get(i).task;
}
}
return null;
}
/**
* Get the preferred focusable root task in priority. If the preferred root task does not exist,
* find a focusable and visible root task from the top of root tasks in this display.
*/
Task getFocusedRootTask() {
if (mPreferredTopFocusableRootTask != null) {
return mPreferredTopFocusableRootTask;
}
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (child.asTaskDisplayArea() != null) {
final Task rootTask = child.asTaskDisplayArea().getFocusedRootTask();
if (rootTask != null) {
return rootTask;
}
continue;
}
final Task rootTask = mChildren.get(i).asTask();
if (rootTask.isFocusableAndVisible()) {
return rootTask;
}
}
return null;
}
Task getNextFocusableRootTask(Task currentFocus, boolean ignoreCurrent) {
final int currentWindowingMode = currentFocus != null
? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED;
Task candidate = null;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (child.asTaskDisplayArea() != null) {
final Task rootTask = child.asTaskDisplayArea()
.getNextFocusableRootTask(currentFocus, ignoreCurrent);
if (rootTask != null) {
return rootTask;
}
continue;
}
final Task rootTask = mChildren.get(i).asTask();
if (ignoreCurrent && rootTask == currentFocus) {
continue;
}
if (!rootTask.isFocusableAndVisible()) {
continue;
}
if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
&& candidate == null && rootTask.inSplitScreenPrimaryWindowingMode()) {
// If the currently focused root task is in split-screen secondary we save off the
// top primary split-screen root task as a candidate for focus because we might
// prefer focus to move to an other root task to avoid primary split-screen root
// task overlapping with a fullscreen root task when a fullscreen root task is
// higher in z than the next split-screen root task. Assistant root task, I am
// looking at you...
// We only move the focus to the primary-split screen root task if there isn't a
// better alternative.
candidate = rootTask;
continue;
}
if (candidate != null && rootTask.inSplitScreenSecondaryWindowingMode()) {
// Use the candidate root task since we are now at the secondary split-screen.
return candidate;
}
return rootTask;
}
return candidate;
}
ActivityRecord getFocusedActivity() {
final Task focusedRootTask = getFocusedRootTask();
if (focusedRootTask == null) {
return null;
}
// TODO(b/111541062): Move this into Task#getResumedActivity()
// Check if the focused root task has the resumed activity
ActivityRecord resumedActivity = focusedRootTask.getResumedActivity();
if (resumedActivity == null || resumedActivity.app == null) {
// If there is no registered resumed activity in the root task or it is not running -
// try to use previously resumed one.
resumedActivity = focusedRootTask.getPausingActivity();
if (resumedActivity == null || resumedActivity.app == null) {
// If previously resumed activity doesn't work either - find the topmost running
// activity that can be focused.
resumedActivity = focusedRootTask.topRunningActivity(true /* focusableOnly */);
}
}
return resumedActivity;
}
Task getLastFocusedRootTask() {
return mLastFocusedRootTask;
}
void updateLastFocusedRootTask(Task prevFocusedTask, String updateLastFocusedTaskReason) {
if (updateLastFocusedTaskReason == null) {
return;
}
final Task currentFocusedTask = getFocusedRootTask();
if (currentFocusedTask == prevFocusedTask) {
return;
}
// Clear last paused activity if focused root task changed while sleeping, so that the
// top activity of current focused task can be resumed.
if (mDisplayContent.isSleeping()) {
currentFocusedTask.mLastPausedActivity = null;
}
mLastFocusedRootTask = prevFocusedTask;
EventLogTags.writeWmFocusedRootTask(mRootWindowContainer.mCurrentUser,
mDisplayContent.mDisplayId,
currentFocusedTask == null ? -1 : currentFocusedTask.getRootTaskId(),
mLastFocusedRootTask == null ? -1 : mLastFocusedRootTask.getRootTaskId(),
updateLastFocusedTaskReason);
}
boolean allResumedActivitiesComplete() {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (child.asTaskDisplayArea() != null) {
if (!child.asTaskDisplayArea().allResumedActivitiesComplete()) {
return false;
}
continue;
}
final ActivityRecord r = mChildren.get(i).asTask().getResumedActivity();
if (r != null && !r.isState(RESUMED)) {
return false;
}
}
final Task currentFocusedRootTask = getFocusedRootTask();
if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
Slog.d(TAG_ROOT_TASK, "allResumedActivitiesComplete: currentFocusedRootTask "
+ "changing from=" + mLastFocusedRootTask + " to=" + currentFocusedRootTask);
}
mLastFocusedRootTask = currentFocusedRootTask;
return true;
}
/**
* Pause all activities in either all of the root tasks or just the back root tasks. This is
* done before resuming a new activity and to make sure that previously active activities are
* paused in root tasks that are no longer visible or in pinned windowing mode. This does not
* pause activities in visible root tasks, so if an activity is launched within the same root
* task, hen we should explicitly pause that root task's top activity.
*
* @param resuming The resuming activity.
* @return {@code true} if any activity was paused as a result of this call.
*/
boolean pauseBackTasks(ActivityRecord resuming) {
final int[] someActivityPaused = {0};
forAllLeafTasks((task) -> {
final ActivityRecord resumedActivity = task.getResumedActivity();
if (resumedActivity != null
&& (task.getVisibility(resuming) != TASK_VISIBILITY_VISIBLE
|| !task.isTopActivityFocusable())) {
ProtoLog.d(WM_DEBUG_STATES, "pauseBackTasks: task=%s "
+ "mResumedActivity=%s", task, resumedActivity);
if (task.startPausingLocked(false /* uiSleeping*/,
resuming, "pauseBackTasks")) {
someActivityPaused[0]++;
}
}
}, true /* traverseTopToBottom */);
return someActivityPaused[0] > 0;
}
void onSplitScreenModeDismissed() {
// The focused task could be a non-resizeable fullscreen root task that is on top of the
// other split-screen tasks, therefore had to dismiss split-screen, make sure the current
// focused root task can still be on top after dismissal
final Task rootTask = getFocusedRootTask();
final Task toTop =
rootTask != null && !rootTask.inSplitScreenWindowingMode() ? rootTask : null;
onSplitScreenModeDismissed(toTop);
}
void onSplitScreenModeDismissed(Task toTop) {
mAtmService.deferWindowLayout();
try {
moveSplitScreenTasksToFullScreen();
} finally {
final Task topFullscreenRootTask = toTop != null
? toTop : getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
final Task rootHomeTask = getOrCreateRootHomeTask();
if (rootHomeTask != null && ((topFullscreenRootTask != null && !isTopRootTask(
rootHomeTask)) || toTop != null)) {
// Whenever split-screen is dismissed we want the root home task directly behind the
// current top fullscreen root task so it shows up when the top root task is
// finished. Or, if the caller specified a root task to be on top after
// split-screen is dismissed.
// TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
// ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
// once we have that.
rootHomeTask.moveToFront("onSplitScreenModeDismissed");
topFullscreenRootTask.moveToFront("onSplitScreenModeDismissed");
}
mAtmService.continueWindowLayout();
}
}
private void moveSplitScreenTasksToFullScreen() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mTmpTasks.clear();
forAllTasks(task -> {
if (task.mCreatedByOrganizer && task.inSplitScreenWindowingMode() && task.hasChild()) {
mTmpTasks.add(task);
}
});
for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
final Task root = mTmpTasks.get(i);
for (int j = 0; j < root.getChildCount(); j++) {
wct.reparent(root.getChildAt(j).mRemoteToken.toWindowContainerToken(),
null, true /* toTop */);
}
}
mAtmService.mWindowOrganizerController.applyTransaction(wct);
}
/**
* Returns true if the {@param windowingMode} is supported based on other parameters passed in.
*
* @param windowingMode The windowing mode we are checking support for.
* @param supportsMultiWindow If we should consider support for multi-window mode in general.
* @param supportsSplitScreen If we should consider support for split-screen multi-window.
* @param supportsFreeform If we should consider support for freeform multi-window.
* @param supportsPip If we should consider support for picture-in-picture mutli-window.
* @param activityType The activity type under consideration.
* @return true if the windowing mode is supported.
*/
static boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
int activityType) {
if (windowingMode == WINDOWING_MODE_UNDEFINED
|| windowingMode == WINDOWING_MODE_FULLSCREEN) {
return true;
}
if (!supportsMultiWindow) {
return false;
}
if (windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
return true;
}
if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
return supportsSplitScreen
&& WindowConfiguration.supportSplitScreenWindowingMode(activityType);
}
if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
return false;
}
if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
return false;
}
return true;
}
/**
* Resolves the windowing mode that an {@link ActivityRecord} would be in if started on this
* display with the provided parameters.
*
* @param r The ActivityRecord in question.
* @param options Options to start with.
* @param task The task within-which the activity would start.
* @param activityType The type of activity to start.
* @return The resolved (not UNDEFINED) windowing-mode that the activity would be in.
*/
int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
@Nullable Task task, int activityType) {
// First preference if the windowing mode in the activity options if set.
int windowingMode = (options != null)
? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
// If windowing mode is unset, then next preference is the candidate task, then the
// activity record.
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
if (task != null) {
windowingMode = task.getWindowingMode();
}
if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
windowingMode = r.getWindowingMode();
}
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
// Use the display's windowing mode.
windowingMode = getWindowingMode();
}
}
windowingMode = validateWindowingMode(windowingMode, r, task, activityType);
return windowingMode != WINDOWING_MODE_UNDEFINED
? windowingMode : WINDOWING_MODE_FULLSCREEN;
}
/**
* Check if the requested windowing-mode is appropriate for the specified task and/or activity
* on this display.
*
* @param windowingMode The windowing-mode to validate.
* @param r The {@link ActivityRecord} to check against.
* @param task The {@link Task} to check against.
* @param activityType An activity type.
* @return {@code true} if windowingMode is valid, {@code false} otherwise.
*/
boolean isValidWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
int activityType) {
// Make sure the windowing mode we are trying to use makes sense for what is supported.
boolean supportsMultiWindow = mAtmService.mSupportsMultiWindow;
boolean supportsSplitScreen = mAtmService.mSupportsSplitScreenMultiWindow;
boolean supportsFreeform = mAtmService.mSupportsFreeformWindowManagement;
boolean supportsPip = mAtmService.mSupportsPictureInPicture;
if (supportsMultiWindow) {
if (task != null) {
supportsSplitScreen = task.supportsSplitScreenWindowingMode();
supportsFreeform = task.supportsFreeform();
supportsMultiWindow = task.supportsMultiWindow()
// When the activity needs to be moved to PIP while the Task is not in PIP,
// it can be moved to a new created PIP Task, so WINDOWING_MODE_PINNED is
// always valid for Task as long as the device supports it.
|| (windowingMode == WINDOWING_MODE_PINNED && supportsPip);
} else if (r != null) {
supportsSplitScreen = r.supportsSplitScreenWindowingMode();
supportsFreeform = r.supportsFreeform();
supportsPip = r.supportsPictureInPicture();
supportsMultiWindow = r.supportsMultiWindow();
}
}
return windowingMode != WINDOWING_MODE_UNDEFINED
&& isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
supportsFreeform, supportsPip, activityType);
}
/**
* Check that the requested windowing-mode is appropriate for the specified task and/or activity
* on this display.
*
* @param windowingMode The windowing-mode to validate.
* @param r The {@link ActivityRecord} to check against.
* @param task The {@link Task} to check against.
* @param activityType An activity type.
* @return The provided windowingMode or the closest valid mode which is appropriate.
*/
int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, @Nullable Task task,
int activityType) {
final boolean inSplitScreenMode = isSplitScreenModeActivated();
if (!inSplitScreenMode && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Switch to the display's windowing mode if we are not in split-screen mode and we are
// trying to launch in split-screen secondary.
windowingMode = WINDOWING_MODE_UNDEFINED;
} else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
}
if (!isValidWindowingMode(windowingMode, r, task, activityType)) {
return WINDOWING_MODE_UNDEFINED;
}
return windowingMode;
}
boolean isTopRootTask(Task rootTask) {
return rootTask == getTopRootTask();
}
ActivityRecord topRunningActivity() {
return topRunningActivity(false /* considerKeyguardState */);
}
/**
* Returns the top running activity in the focused root task. In the case the focused root
* task has no such activity, the next focusable root task on this display is returned.
*
* @param considerKeyguardState Indicates whether the locked state should be considered. if
* {@code true} and the keyguard is locked, only activities that
* can be shown on top of the keyguard will be considered.
* @return The top running activity. {@code null} if none is available.
*/
ActivityRecord topRunningActivity(boolean considerKeyguardState) {
ActivityRecord topRunning = null;
final Task focusedRootTask = getFocusedRootTask();
if (focusedRootTask != null) {
topRunning = focusedRootTask.topRunningActivity();
}
// Look in other focusable root tasks.
if (topRunning == null) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
if (child.asTaskDisplayArea() != null) {
topRunning =
child.asTaskDisplayArea().topRunningActivity(considerKeyguardState);
if (topRunning != null) {
break;
}
continue;
}
final Task rootTask = mChildren.get(i).asTask();
// Only consider focusable root tasks other than the current focused one.
if (rootTask == focusedRootTask || !rootTask.isTopActivityFocusable()) {
continue;
}
topRunning = rootTask.topRunningActivity();
if (topRunning != null) {
break;
}
}
}
// This activity can be considered the top running activity if we are not considering
// the locked state, the keyguard isn't locked, or we can show when locked.
if (topRunning != null && considerKeyguardState
&& mRootWindowContainer.mTaskSupervisor.getKeyguardController()
.isKeyguardLocked()
&& !topRunning.canShowWhenLocked()) {
return null;
}
return topRunning;
}
protected int getRootTaskCount() {
final int[] count = new int[1];
forAllRootTasks(task -> {
count[0]++;
});
return count[0];
}
@Nullable
Task getOrCreateRootHomeTask() {
return getOrCreateRootHomeTask(false /* onTop */);
}
/**
* Returns the existing root home task or creates and returns a new one if it should exist
* for the display.
*
* @param onTop Only be used when there is no existing root home task. If true the root home
* task will be created at the top of the display, else at the bottom.
*/
@Nullable
Task getOrCreateRootHomeTask(boolean onTop) {
Task homeTask = getRootHomeTask();
// Take into account if this TaskDisplayArea can have a home task before trying to
// create the root task
if (homeTask == null && canHostHomeTask()) {
homeTask = createRootTask(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, onTop);
}
return homeTask;
}
boolean isSplitScreenModeActivated() {
Task task = getRootSplitScreenPrimaryTask();
return task != null && task.hasChild();
}
/**
* Returns the topmost root task on the display that is compatible with the input windowing
* mode. Null is no compatible root task on the display.
*/
Task getTopRootTaskInWindowingMode(int windowingMode) {
return getRootTask(windowingMode, ACTIVITY_TYPE_UNDEFINED);
}
void moveHomeRootTaskToFront(String reason) {
final Task homeRootTask = getOrCreateRootHomeTask();
if (homeRootTask != null) {
homeRootTask.moveToFront(reason);
}
}
/**
* Moves the focusable home activity to top. If there is no such activity, the root home task
* will still move to top.
*/
void moveHomeActivityToTop(String reason) {
final ActivityRecord top = getHomeActivity();
if (top == null) {
moveHomeRootTaskToFront(reason);
return;
}
top.moveFocusableActivityToTop(reason);
}
@Nullable
ActivityRecord getHomeActivity() {
return getHomeActivityForUser(mRootWindowContainer.mCurrentUser);
}
@Nullable
ActivityRecord getHomeActivityForUser(int userId) {
final Task rootHomeTask = getRootHomeTask();
if (rootHomeTask == null) {
return null;
}
final PooledPredicate p = PooledLambda.obtainPredicate(
TaskDisplayArea::isHomeActivityForUser, PooledLambda.__(ActivityRecord.class),
userId);
final ActivityRecord r = rootHomeTask.getActivity(p);
p.recycle();
return r;
}
private static boolean isHomeActivityForUser(ActivityRecord r, int userId) {
return r.isActivityTypeHome() && (userId == UserHandle.USER_ALL || r.mUserId == userId);
}
/**
* Adjusts the {@param rootTask} behind the last visible rootTask in the display if necessary.
* Generally used in conjunction with {@link #moveRootTaskBehindRootTask}.
*/
// TODO(b/151575894): Remove special root task movement methods.
void moveRootTaskBehindBottomMostVisibleRootTask(Task rootTask) {
if (rootTask.shouldBeVisible(null)) {
// Skip if the root task is already visible
return;
}
// Move the root task to the bottom to not affect the following visibility checks
rootTask.getParent().positionChildAt(POSITION_BOTTOM, rootTask,
false /* includingParents */);
// Find the next position where the root task should be placed
final boolean isRootTask = rootTask.isRootTask();
final int numRootTasks =
isRootTask ? mChildren.size() : rootTask.getParent().getChildCount();
for (int rootTaskNdx = 0; rootTaskNdx < numRootTasks; rootTaskNdx++) {
Task s;
if (isRootTask) {
final WindowContainer child = mChildren.get(rootTaskNdx);
if (child.asTaskDisplayArea() != null) {
s = child.asTaskDisplayArea().getBottomMostVisibleRootTask(rootTask);
} else {
s = child.asTask();
}
} else {
s = rootTask.getParent().getChildAt(rootTaskNdx).asTask();
}
if (s == rootTask || s == null) {
continue;
}
final int winMode = s.getWindowingMode();
final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
|| winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
if (s.shouldBeVisible(null) && isValidWindowingMode) {
// Move the provided root task to behind this root task
final int position = Math.max(0, rootTaskNdx - 1);
rootTask.getParent().positionChildAt(position, rootTask,
false /*includingParents */);
break;
}
}
}
@Nullable
private Task getBottomMostVisibleRootTask(Task excludeRootTask) {
return getRootTask(task -> {
final int winMode = task.getWindowingMode();
final boolean isValidWindowingMode = winMode == WINDOWING_MODE_FULLSCREEN
|| winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
return task.shouldBeVisible(null) && isValidWindowingMode;
}, false /* traverseTopToBottom */);
}
/**
* Moves the {@param rootTask} behind the given {@param behindRootTask} if possible. If
* {@param behindRootTask} is not currently in the display, then then the root task is moved
* to the back. Generally used in conjunction with
* {@link #moveRootTaskBehindBottomMostVisibleRootTask}.
*/
void moveRootTaskBehindRootTask(Task rootTask, Task behindRootTask) {
if (behindRootTask == null || behindRootTask == rootTask) {
return;
}
final WindowContainer parent = rootTask.getParent();
if (parent == null || parent != behindRootTask.getParent()) {
return;
}
// Note that positionChildAt will first remove the given root task before inserting into the
// list, so we need to adjust the insertion index to account for the removed index
// TODO: Remove this logic when WindowContainer.positionChildAt() is updated to adjust the
// position internally
final int rootTaskIndex = parent.mChildren.indexOf(rootTask);
final int behindRootTaskIndex = parent.mChildren.indexOf(behindRootTask);
final int insertIndex = rootTaskIndex <= behindRootTaskIndex
? behindRootTaskIndex - 1 : behindRootTaskIndex;
final int position = Math.max(0, insertIndex);
parent.positionChildAt(position, rootTask, false /* includingParents */);
}
boolean hasPinnedTask() {
return getRootPinnedTask() != null;
}
/**
* @return the root task currently above the {@param rootTask}. Can be null if the
* {@param rootTask} is already top-most.
*/
static Task getRootTaskAbove(Task rootTask) {
final WindowContainer wc = rootTask.getParent();
final int index = wc.mChildren.indexOf(rootTask) + 1;
return (index < wc.mChildren.size()) ? (Task) wc.mChildren.get(index) : null;
}
/** Returns true if the root task in the windowing mode is visible. */
boolean isRootTaskVisible(int windowingMode) {
final Task rootTask = getTopRootTaskInWindowingMode(windowingMode);
return rootTask != null && rootTask.isVisible();
}
void removeRootTask(Task rootTask) {
removeChild(rootTask);
}
int getDisplayId() {
return mDisplayContent.getDisplayId();
}
boolean isRemoved() {
return mRemoved;
}
/**
* Adds a listener to be notified whenever the root task order in the display changes. Currently
* only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the
* current animation when the system state changes.
*/
void registerRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
if (!mRootTaskOrderChangedCallbacks.contains(listener)) {
mRootTaskOrderChangedCallbacks.add(listener);
}
}
/**
* Removes a previously registered root task order change listener.
*/
void unregisterRootTaskOrderChangedListener(OnRootTaskOrderChangedListener listener) {
mRootTaskOrderChangedCallbacks.remove(listener);
}
/**
* Notifies of a root task order change
*
* @param rootTask The root task which triggered the order change
*/
void onRootTaskOrderChanged(Task rootTask) {
for (int i = mRootTaskOrderChangedCallbacks.size() - 1; i >= 0; i--) {
mRootTaskOrderChangedCallbacks.get(i).onRootTaskOrderChanged(rootTask);
}
}
@Override
boolean canCreateRemoteAnimationTarget() {
return true;
}
/**
* Exposes the home task capability of the TaskDisplayArea
*/
boolean canHostHomeTask() {
return mDisplayContent.supportsSystemDecorations() && mCanHostHomeTask;
}
/**
* Callback for when the order of the root tasks in the display changes.
*/
interface OnRootTaskOrderChangedListener {
void onRootTaskOrderChanged(Task rootTask);
}
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate();
try {
forAllRootTasks(rootTask -> {
rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
});
} finally {
mAtmService.mTaskSupervisor.endActivityVisibilityUpdate();
}
}
/**
* Removes the root tasks in the node applying the content removal node from the display.
*
* @return last reparented root task, or {@code null} if the root tasks had to be destroyed.
*/
Task remove() {
mPreferredTopFocusableRootTask = null;
// TODO(b/153090332): Allow setting content removal mode per task display area
final boolean destroyContentOnRemoval = mDisplayContent.shouldDestroyContentOnRemove();
final TaskDisplayArea toDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
Task lastReparentedRootTask = null;
// Root tasks could be reparented from the removed display area to other display area. After
// reparenting the last root task of the removed display area, the display area becomes
// ready to be released (no more root tasks). But, we cannot release it at that moment
// or the related WindowContainer will also be removed. So, we set display area as removed
// after reparenting root task finished.
// Keep the order from bottom to top.
int numRootTasks = mChildren.size();
for (int i = 0; i < numRootTasks; i++) {
final WindowContainer child = mChildren.get(i);
if (child.asTaskDisplayArea() != null) {
lastReparentedRootTask = child.asTaskDisplayArea().remove();
continue;
}
final Task task = mChildren.get(i).asTask();
// Always finish non-standard type root tasks and root tasks created by a organizer.
// TODO: For root tasks created by organizer, consider reparenting children tasks if
// the use case arises in the future.
if (destroyContentOnRemoval
|| !task.isActivityTypeStandardOrUndefined()
|| task.mCreatedByOrganizer) {
task.finishAllActivitiesImmediately();
} else {
// Reparent task to corresponding launch root or display area.
final WindowContainer launchRoot = task.supportsSplitScreenWindowingMode()
? toDisplayArea.getLaunchRootTask(
task.getWindowingMode(), task.getActivityType(), null /* options */)
: null;
task.reparent(launchRoot == null ? toDisplayArea : launchRoot, POSITION_TOP);
// Set the windowing mode to undefined by default to let the root task inherited the
// windowing mode.
task.setWindowingMode(WINDOWING_MODE_UNDEFINED);
lastReparentedRootTask = task;
}
// Root task may be removed from this display. Ensure each root task will be processed
// and the loop will end.
i -= numRootTasks - mChildren.size();
numRootTasks = mChildren.size();
}
if (lastReparentedRootTask != null) {
if (toDisplayArea.isSplitScreenModeActivated()
&& !lastReparentedRootTask.supportsSplitScreenWindowingMode()) {
// Dismiss split screen if the last reparented root task doesn't support split mode.
mAtmService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedRootTask();
toDisplayArea.onSplitScreenModeDismissed(lastReparentedRootTask);
} else if (!lastReparentedRootTask.isRootTask()) {
// Update focus when the last reparented root task is not a root task anymore.
// (For example, if it has been reparented to a split screen root task, move the
// focus to the split root task)
lastReparentedRootTask.getRootTask().moveToFront("display-removed");
}
}
mRemoved = true;
return lastReparentedRootTask;
}
/** Whether this task display area can request orientation. */
boolean canSpecifyOrientation() {
// Only allow to specify orientation if this TDA is not set to ignore orientation request,
// and it is the last focused one on this logical display that can request orientation
// request.
return !mIgnoreOrientationRequest
&& mDisplayContent.getOrientationRequestingTaskDisplayArea() == this;
}
@Override
protected boolean isTaskDisplayArea() {
return true;
}
@Override
TaskDisplayArea asTaskDisplayArea() {
return this;
}
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
pw.println(prefix + "TaskDisplayArea " + getName());
final String doublePrefix = prefix + " ";
super.dump(pw, doublePrefix, dumpAll);
if (mPreferredTopFocusableRootTask != null) {
pw.println(doublePrefix + "mPreferredTopFocusableRootTask="
+ mPreferredTopFocusableRootTask);
}
if (mLastFocusedRootTask != null) {
pw.println(doublePrefix + "mLastFocusedRootTask=" + mLastFocusedRootTask);
}
final String triplePrefix = doublePrefix + " ";
if (mLaunchRootTasks.size() > 0) {
pw.println(doublePrefix + "mLaunchRootTasks:");
for (int i = mLaunchRootTasks.size() - 1; i >= 0; --i) {
final LaunchRootTaskDef def = mLaunchRootTasks.get(i);
pw.println(triplePrefix
+ Arrays.toString(def.activityTypes) + " "
+ Arrays.toString(def.windowingModes) + " "
+ " task=" + def.task);
}
}
pw.println(doublePrefix + "Application tokens in top down Z order:");
for (int index = getChildCount() - 1; index >= 0; --index) {
final WindowContainer child = getChildAt(index);
if (child.asTaskDisplayArea() != null) {
child.dump(pw, doublePrefix, dumpAll);
continue;
}
final Task rootTask = child.asTask();
pw.println(doublePrefix + "* " + rootTask);
rootTask.dump(pw, triplePrefix, dumpAll);
}
}
}