summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java21
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java338
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java2
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java6
-rw-r--r--services/core/java/com/android/server/wm/ResetTargetTaskHelper.java295
-rw-r--r--services/core/java/com/android/server/wm/Task.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java73
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java2
8 files changed, 427 insertions, 338 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f24fa077bb6c..210a3c79f620 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -333,6 +333,9 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
/**
* An entry in the history stack, representing an activity.
@@ -1581,6 +1584,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return this;
}
+ @Override
+ boolean hasActivity() {
+ // I am an activity!
+ return true;
+ }
+
void setProcess(WindowProcessController proc) {
app = proc;
final ActivityRecord root = task != null ? task.getRootActivity() : null;
@@ -3418,11 +3427,21 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
@Override
- boolean forAllActivities(ToBooleanFunction<ActivityRecord> callback) {
+ boolean forAllActivities(Function<ActivityRecord, Boolean> callback) {
return callback.apply(this);
}
@Override
+ void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
+ callback.accept(this);
+ }
+
+ @Override
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ return callback.test(this) ? this : null;
+ }
+
+ @Override
protected void setLayer(Transaction t, int layer) {
if (!mSurfaceAnimator.hasLeash()) {
t.setLayer(mSurfaceControl, layer);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ac94125469ba..d5350ebc10cb 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -62,7 +62,6 @@ import static com.android.server.am.ActivityStackProto.DISPLAY_ID;
import static com.android.server.am.ActivityStackProto.FULLSCREEN;
import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY;
import static com.android.server.am.ActivityStackProto.STACK;
-import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSED;
import static com.android.server.wm.ActivityStack.ActivityState.PAUSING;
import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
@@ -119,7 +118,6 @@ import static com.android.server.wm.StackProto.DEFER_REMOVAL;
import static com.android.server.wm.StackProto.FILLS_PARENT;
import static com.android.server.wm.StackProto.MINIMIZE_AMOUNT;
import static com.android.server.wm.StackProto.WINDOW_CONTAINER;
-import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
@@ -203,7 +201,7 @@ import java.util.function.Consumer;
class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarget,
ConfigurationContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_ATM;
- private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
+ static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
private static final String TAG_APP = TAG + POSTFIX_APP;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
@@ -212,7 +210,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
private static final String TAG_STACK = TAG + POSTFIX_STACK;
private static final String TAG_STATES = TAG + POSTFIX_STATES;
private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
- private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
+ static final String TAG_TASKS = TAG + POSTFIX_TASKS;
private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
private static final String TAG_USER_LEAVING = TAG + POSTFIX_USER_LEAVING;
private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY;
@@ -516,6 +514,8 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
}
}
+ private static final ResetTargetTaskHelper sResetTargetTaskHelper = new ResetTargetTaskHelper();
+
int numActivities() {
int count = 0;
for (int taskNdx = getChildCount() - 1; taskNdx >= 0; --taskNdx) {
@@ -2980,7 +2980,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
// If the caller has requested that the target task be
// reset, then do so.
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
- resetTaskIfNeededLocked(r, r);
+ resetTaskIfNeeded(r, r);
doShow = topRunningNonDelayedActivityLocked(null) == r;
}
} else if (options != null && options.getAnimationType()
@@ -3052,297 +3052,6 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
}
/**
- * Helper method for {@link #resetTaskIfNeededLocked(ActivityRecord, ActivityRecord)}.
- * Performs a reset of the given task, if needed for new activity start.
- * @param task The task containing the Activity (taskTop) that might be reset.
- * @param forceReset Flag indicating if clear task was requested
- * @return An ActivityOptions that needs to be processed.
- */
- private ActivityOptions resetTargetTaskIfNeededLocked(Task task, boolean forceReset) {
- ActivityOptions topOptions = null;
-
- // Tracker of the end of currently handled reply chain (sublist) of activities. What happens
- // to activities in the same chain will depend on what the end activity of the chain needs.
- int replyChainEnd = -1;
- boolean canMoveOptions = true;
- int numTasksCreated = 0;
-
- // We only do this for activities that are not the root of the task (since if we finish
- // the root, we may no longer have the task!).
- final int numActivities = task.getChildCount();
- int lastActivityNdx = task.findRootIndex(true /* effectiveRoot */);
- if (lastActivityNdx == -1) {
- lastActivityNdx = 0;
- }
- for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = task.getChildAt(i);
- // TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
- if (target.isRootOfTask()) break;
-
- final int flags = target.info.flags;
- final boolean finishOnTaskLaunch =
- (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
- final boolean allowTaskReparenting =
- (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
- final boolean clearWhenTaskReset =
- (target.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
-
- if (!finishOnTaskLaunch
- && !clearWhenTaskReset
- && target.resultTo != null) {
- // If this activity is sending a reply to a previous
- // activity, we can't do anything with it now until
- // we reach the start of the reply chain.
- // XXX note that we are assuming the result is always
- // to the previous activity, which is almost always
- // the case but we really shouldn't count on.
- if (replyChainEnd < 0) {
- replyChainEnd = i;
- }
- } else if (!finishOnTaskLaunch
- && !clearWhenTaskReset
- && allowTaskReparenting
- && target.taskAffinity != null
- && !target.taskAffinity.equals(task.affinity)) {
- // If this activity has an affinity for another
- // task, then we need to move it out of here. We will
- // move it as far out of the way as possible, to the
- // bottom of the activity stack. This also keeps it
- // correctly ordered with any activities we previously
- // moved.
- // TODO: We should probably look for other stacks also, since corresponding task
- // with the same affinity is unlikely to be in the same stack.
- final Task targetTask;
- final ActivityRecord bottom =
- hasChild() && getChildAt(0).hasChild() ?
- getChildAt(0).getChildAt(0) : null;
- if (bottom != null && target.taskAffinity.equals(bottom.getTask().affinity)) {
- // If the activity currently at the bottom has the
- // same task affinity as the one we are moving,
- // then merge it into the same task.
- targetTask = bottom.getTask();
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
- + " out to bottom task " + targetTask);
- } else {
- targetTask = createTask(
- mStackSupervisor.getNextTaskIdForUserLocked(target.mUserId),
- target.info, null /* intent */, null /* voiceSession */,
- null /* voiceInteractor */, false /* toTop */);
- targetTask.affinityIntent = target.intent;
- numTasksCreated++;
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
- + " out to new task " + targetTask);
- }
-
- boolean noOptions = canMoveOptions;
- final int start = replyChainEnd < 0 ? i : replyChainEnd;
- for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = task.getChildAt(srcPos);
- if (p.finishing) {
- continue;
- }
-
- canMoveOptions = false;
- if (noOptions && topOptions == null) {
- topOptions = p.takeOptionsLocked(false /* fromClient */);
- if (topOptions != null) {
- noOptions = false;
- }
- }
- if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
- "Removing activity " + p + " from task=" + task + " adding to task="
- + targetTask + " Callers=" + Debug.getCallers(4));
- if (DEBUG_TASKS) Slog.v(TAG_TASKS,
- "Pushing next activity " + p + " out to target's task " + target);
- p.reparent(targetTask, 0 /* position - bottom */, "resetTargetTaskIfNeeded");
- }
-
- positionChildAtBottom(targetTask);
- mStackSupervisor.mRecentTasks.add(targetTask);
- replyChainEnd = -1;
- } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
- // If the activity should just be removed -- either
- // because it asks for it, or the task should be
- // cleared -- then finish it and anything that is
- // part of its reply chain.
- int end;
- if (clearWhenTaskReset) {
- // In this case, we want to finish this activity
- // and everything above it, so be sneaky and pretend
- // like these are all in the reply chain.
- end = task.getChildCount() - 1;
- } else if (replyChainEnd < 0) {
- end = i;
- } else {
- end = replyChainEnd;
- }
- boolean noOptions = canMoveOptions;
- for (int srcPos = i; srcPos <= end; srcPos++) {
- ActivityRecord p = task.getChildAt(srcPos);
- if (p.finishing) {
- continue;
- }
- canMoveOptions = false;
- if (noOptions && topOptions == null) {
- topOptions = p.takeOptionsLocked(false /* fromClient */);
- if (topOptions != null) {
- noOptions = false;
- }
- }
- if (DEBUG_TASKS) Slog.w(TAG_TASKS,
- "resetTaskIntendedTask: calling finishActivity on " + p);
- if (p.finishIfPossible("reset-task", false /* oomAdj */)
- == FINISH_RESULT_REMOVED) {
- end--;
- srcPos--;
- }
- }
- replyChainEnd = -1;
- } else {
- // If we were in the middle of a chain, well the
- // activity that started it all doesn't want anything
- // special, so leave it all as-is.
- replyChainEnd = -1;
- }
- }
-
- // Create target stack for the newly created tasks if necessary
- if (numTasksCreated > 0) {
- ActivityDisplay display = getDisplay();
- final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
- if (singleTaskInstanceDisplay) {
- display = mRootActivityContainer.getDefaultDisplay();
- }
-
- if (singleTaskInstanceDisplay || display.alwaysCreateStack(getWindowingMode(),
- getActivityType())) {
- for (int index = numTasksCreated - 1; index >= 0; index--) {
- final Task targetTask = getChildAt(index);
- final ActivityStack targetStack = display.getOrCreateStack(getWindowingMode(),
- getActivityType(), false /* onTop */);
- targetTask.reparent(targetStack, false /* toTop */,
- REPARENT_LEAVE_STACK_IN_PLACE, false /* animate */,
- true /* deferResume */, "resetTargetTask");
- }
- }
- }
-
- return topOptions;
- }
-
- /**
- * Helper method for {@link #resetTaskIfNeededLocked(ActivityRecord, ActivityRecord)}.
- * Processes all of the activities in a given Task looking for an affinity with the task
- * of resetTaskIfNeededLocked.taskTop.
- * @param affinityTask The task we are looking for an affinity to.
- * @param task Task that resetTaskIfNeededLocked.taskTop belongs to.
- * @param topTaskIsHigher True if #task has already been processed by resetTaskIfNeededLocked.
- * @param forceReset Flag indicating if clear task was requested
- */
- // TODO: Consider merging with #resetTargetTaskIfNeededLocked() above
- private int resetAffinityTaskIfNeededLocked(Task affinityTask, Task task,
- boolean topTaskIsHigher, boolean forceReset, int taskInsertionPoint) {
- // Tracker of the end of currently handled reply chain (sublist) of activities. What happens
- // to activities in the same chain will depend on what the end activity of the chain needs.
- int replyChainEnd = -1;
- final String taskAffinity = task.affinity;
-
- final int numActivities = task.getChildCount();
-
- // Do not operate on or below the effective root Activity.
- int lastActivityNdx = affinityTask.findRootIndex(true /* effectiveRoot */);
- if (lastActivityNdx == -1) {
- lastActivityNdx = 0;
- }
- for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = task.getChildAt(i);
- // TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
- if (target.isRootOfTask()) break;
-
- final int flags = target.info.flags;
- boolean finishOnTaskLaunch = (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
- boolean allowTaskReparenting = (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
-
- if (target.resultTo != null) {
- // If this activity is sending a reply to a previous
- // activity, we can't do anything with it now until
- // we reach the start of the reply chain.
- // XXX note that we are assuming the result is always
- // to the previous activity, which is almost always
- // the case but we really shouldn't count on.
- if (replyChainEnd < 0) {
- replyChainEnd = i;
- }
- } else if (topTaskIsHigher
- && allowTaskReparenting
- && taskAffinity != null
- && taskAffinity.equals(target.taskAffinity)) {
- // This activity has an affinity for our task. Either remove it if we are
- // clearing or move it over to our task. Note that
- // we currently punt on the case where we are resetting a
- // task that is not at the top but who has activities above
- // with an affinity to it... this is really not a normal
- // case, and we will need to later pull that task to the front
- // and usually at that point we will do the reset and pick
- // up those remaining activities. (This only happens if
- // someone starts an activity in a new task from an activity
- // in a task that is not currently on top.)
- if (forceReset || finishOnTaskLaunch) {
- final int start = replyChainEnd >= 0 ? replyChainEnd : i;
- if (DEBUG_TASKS) Slog.v(TAG_TASKS,
- "Finishing task at index " + start + " to " + i);
- for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = task.getChildAt(srcPos);
- if (p.finishing) {
- continue;
- }
- p.finishIfPossible("move-affinity", false /* oomAdj */);
- }
- } else {
- if (taskInsertionPoint < 0) {
- taskInsertionPoint = task.getChildCount();
-
- }
-
- final int start = replyChainEnd >= 0 ? replyChainEnd : i;
- if (DEBUG_TASKS) Slog.v(TAG_TASKS,
- "Reparenting from task=" + affinityTask + ":" + start + "-" + i
- + " to task=" + task + ":" + taskInsertionPoint);
- for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = task.getChildAt(srcPos);
- p.reparent(task, taskInsertionPoint, "resetAffinityTaskIfNeededLocked");
-
- if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
- "Removing and adding activity " + p + " to stack at " + task
- + " callers=" + Debug.getCallers(3));
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Pulling activity " + p
- + " from " + srcPos + " in to resetting task " + task);
- }
- positionChildAtTop(task);
-
- // Now we've moved it in to place... but what if this is
- // a singleTop activity and we have put it on top of another
- // instance of the same activity? Then we drop the instance
- // below so it remains singleTop.
- if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
- final ArrayList<ActivityRecord> taskActivities = task.mChildren;
- final int targetNdx = taskActivities.indexOf(target);
- if (targetNdx > 0) {
- final ActivityRecord p = taskActivities.get(targetNdx - 1);
- if (p.intent.getComponent().equals(target.intent.getComponent())) {
- p.finishIfPossible("replace", false /* oomAdj */);
- }
- }
- }
- }
-
- replyChainEnd = -1;
- }
- }
- return taskInsertionPoint;
- }
-
- /**
* Reset the task by reparenting the activities that have same affinity to the task or
* reparenting the activities that have different affinityies out of the task, while these
* activities allow task reparenting.
@@ -3352,37 +3061,16 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
* @return The non-finishing top activity of the task after reset or the original task top
* activity if all activities within the task are finishing.
*/
- final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
- ActivityRecord newActivity) {
+ ActivityRecord resetTaskIfNeeded(ActivityRecord taskTop, ActivityRecord newActivity) {
final boolean forceReset =
(newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
final Task task = taskTop.getTask();
- // False until we evaluate the Task associated with taskTop. Switches to true
- // for remaining tasks. Used for later tasks to reparent to task.
- boolean taskFound = false;
-
// If ActivityOptions are moved out and need to be aborted or moved to taskTop.
- ActivityOptions topOptions = null;
-
- // Preserve the location for reparenting in the new task.
- int reparentInsertionPoint = -1;
+ final ActivityOptions topOptions = sResetTargetTaskHelper.process(this, task, forceReset);
- for (int i = getChildCount() - 1; i >= 0; --i) {
- final Task targetTask = getChildAt(i);
-
- if (targetTask == task) {
- topOptions = resetTargetTaskIfNeededLocked(task, forceReset);
- taskFound = true;
- } else {
- reparentInsertionPoint = resetAffinityTaskIfNeededLocked(targetTask, task,
- taskFound, forceReset, reparentInsertionPoint);
- }
- }
-
- int taskNdx = mChildren.indexOf(task);
- if (taskNdx >= 0) {
- ActivityRecord newTop = getChildAt(taskNdx).getTopNonFinishingActivity();
+ if (mChildren.contains(task)) {
+ final ActivityRecord newTop = task.getTopNonFinishingActivity();
if (newTop != null) {
taskTop = newTop;
}
@@ -3391,11 +3079,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
if (topOptions != null) {
// If we got some ActivityOptions from an activity on top that
// was removed from the task, propagate them to the new real top.
- if (taskTop != null) {
- taskTop.updateOptionsLocked(topOptions);
- } else {
- topOptions.abort();
- }
+ taskTop.updateOptionsLocked(topOptions);
}
return taskTop;
@@ -5219,7 +4903,7 @@ class ActivityStack extends WindowContainer<Task> implements BoundsAnimationTarg
displayContent.layoutAndAssignWindowLayersIfNeeded();
}
- private void positionChildAtBottom(Task child) {
+ void positionChildAtBottom(Task child) {
// If there are other focusable stacks on the display, the z-order of the display should not
// be changed just because a task was placed at the bottom. E.g. if it is moving the topmost
// task to bottom, the next focusable stack on the same display should be focused.
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index be7f88700d88..21d5861d3197 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1812,7 +1812,7 @@ class ActivityStarter {
final boolean resetTask =
reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0;
if (resetTask) {
- targetTaskTop = mTargetStack.resetTaskIfNeededLocked(targetTaskTop, mStartActivity);
+ targetTaskTop = mTargetStack.resetTaskIfNeeded(targetTaskTop, mStartActivity);
}
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 456f65009eae..dd58fe3bb7da 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -4512,6 +4512,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
mName = name;
}
+ @Override
+ boolean hasActivity() {
+ // I am a non-app-window-container :P
+ return false;
+ }
+
void addChild(WindowToken token) {
addChild(token, mWindowComparator);
}
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
new file mode 100644
index 000000000000..fe91ba56cb97
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 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 com.android.server.wm.ActivityStack.TAG_ADD_REMOVE;
+import static com.android.server.wm.ActivityStack.TAG_TASKS;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
+
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Debug;
+import android.util.Slog;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledFunction;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.ArrayList;
+
+/** Helper class for processing the reset of a task. */
+class ResetTargetTaskHelper {
+ private Task mTask;
+ private ActivityStack mParent;
+ private Task mTargetTask;
+ private ActivityRecord mRoot;
+ private boolean mForceReset;
+ private boolean mCanMoveOptions;
+ private boolean mTargetTaskFound;
+ private int mActivityReparentPosition;
+ private ActivityOptions mTopOptions;
+ private ArrayList<ActivityRecord> mResultActivities = new ArrayList<>();
+ private ArrayList<ActivityRecord> mAllActivities = new ArrayList<>();
+ private ArrayList<Task> mCreatedTasks = new ArrayList<>();
+
+ private void reset(Task task) {
+ mTask = task;
+ mRoot = null;
+ mCanMoveOptions = true;
+ mTopOptions = null;
+ mResultActivities.clear();
+ mAllActivities.clear();
+ mCreatedTasks.clear();
+ }
+
+ ActivityOptions process(ActivityStack parent, Task targetTask, boolean forceReset) {
+ mParent = parent;
+ mForceReset = forceReset;
+ mTargetTask = targetTask;
+ mTargetTaskFound = false;
+ mActivityReparentPosition = -1;
+
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
+ parent.forAllTasks(c);
+ c.recycle();
+
+ reset(null);
+ mParent = null;
+ return mTopOptions;
+ }
+
+ private void processTask(Task task) {
+ mRoot = task.getRootActivity(true);
+ if (mRoot == null) return;
+
+ reset(task);
+ final boolean isTargetTask = task == mTargetTask;
+ if (isTargetTask) mTargetTaskFound = true;
+
+ final PooledFunction f = PooledLambda.obtainFunction(
+ ResetTargetTaskHelper::processActivity, this,
+ PooledLambda.__(ActivityRecord.class), isTargetTask);
+ task.forAllActivities(f);
+ f.recycle();
+
+ processCreatedTasks();
+ }
+
+ private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
+ // End processing if we have reached the root.
+ if (r == mRoot) return true;
+
+ mAllActivities.add(r);
+ final int flags = r.info.flags;
+ final boolean finishOnTaskLaunch =
+ (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
+ final boolean allowTaskReparenting =
+ (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
+ final boolean clearWhenTaskReset =
+ (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
+
+ if (isTargetTask) {
+ if (!finishOnTaskLaunch && !clearWhenTaskReset) {
+ if (r.resultTo != null) {
+ // If this activity is sending a reply to a previous activity, we can't do
+ // anything with it now until we reach the start of the reply chain.
+ // NOTE: that we are assuming the result is always to the previous activity,
+ // which is almost always the case but we really shouldn't count on.
+ mResultActivities.add(r);
+ return false;
+ }
+ if (allowTaskReparenting && r.taskAffinity != null
+ && !r.taskAffinity.equals(mTask.affinity)) {
+ // If this activity has an affinity for another task, then we need to move
+ // it out of here. We will move it as far out of the way as possible, to the
+ // bottom of the activity stack. This also keeps it correctly ordered with
+ // any activities we previously moved.
+ // TODO: We should probably look for other stacks also, since corresponding
+ // task with the same affinity is unlikely to be in the same stack.
+ final Task targetTask;
+ final ActivityRecord bottom = mParent.getActivity(
+ (ar) -> true, false /*traverseTopToBottom*/);
+
+ if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) {
+ // If the activity currently at the bottom has the same task affinity as
+ // the one we are moving, then merge it into the same task.
+ targetTask = bottom.getTask();
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+ + r + " out to bottom task " + targetTask);
+ } else {
+ targetTask = mParent.createTask(
+ mParent.mStackSupervisor.getNextTaskIdForUserLocked(r.mUserId),
+ r.info, null /* intent */, null /* voiceSession */,
+ null /* voiceInteractor */, false /* toTop */);
+ targetTask.affinityIntent = r.intent;
+ mCreatedTasks.add(targetTask);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+ + r + " out to new task " + targetTask);
+ }
+
+ mResultActivities.add(r);
+ processResultActivities(r, targetTask, 0 /*bottom*/, true, true);
+ mParent.positionChildAtBottom(targetTask);
+ mParent.mStackSupervisor.mRecentTasks.add(targetTask);
+ return false;
+ }
+ }
+ if (mForceReset || finishOnTaskLaunch || clearWhenTaskReset) {
+ // If the activity should just be removed either because it asks for it, or the
+ // task should be cleared, then finish it and anything that is part of its reply
+ // chain.
+ if (clearWhenTaskReset) {
+ // In this case, we want to finish this activity and everything above it,
+ // so be sneaky and pretend like these are all in the reply chain.
+ finishActivities(mAllActivities, "clearWhenTaskReset");
+ } else {
+ mResultActivities.add(r);
+ finishActivities(mResultActivities, "reset-task");
+ }
+
+ mResultActivities.clear();
+ return false;
+ } else {
+ // If we were in the middle of a chain, well the activity that started it all
+ // doesn't want anything special, so leave it all as-is.
+ mResultActivities.clear();
+ }
+
+ return false;
+
+ } else {
+ mResultActivities.add(r);
+ if (r.resultTo != null) {
+ // If this activity is sending a reply to a previous activity, we can't do
+ // anything with it now until we reach the start of the reply chain.
+ // NOTE: that we are assuming the result is always to the previous activity,
+ // which is almost always the case but we really shouldn't count on.
+ return false;
+ } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null
+ && mTargetTask.affinity.equals(r.taskAffinity)) {
+ // This activity has an affinity for our task. Either remove it if we are
+ // clearing or move it over to our task. Note that we currently punt on the case
+ // where we are resetting a task that is not at the top but who has activities
+ // above with an affinity to it... this is really not a normal case, and we will
+ // need to later pull that task to the front and usually at that point we will
+ // do the reset and pick up those remaining activities. (This only happens if
+ // someone starts an activity in a new task from an activity in a task that is
+ // not currently on top.)
+ if (mForceReset || finishOnTaskLaunch) {
+ finishActivities(mResultActivities, "move-affinity");
+ return false;
+ }
+ if (mActivityReparentPosition == -1) {
+ mActivityReparentPosition = mTargetTask.getChildCount();
+ }
+
+ processResultActivities(
+ r, mTargetTask, mActivityReparentPosition, false, false);
+
+ mParent.positionChildAtTop(mTargetTask);
+
+ // Now we've moved it in to place...but what if this is a singleTop activity and
+ // we have put it on top of another instance of the same activity? Then we drop
+ // the instance below so it remains singleTop.
+ if (r.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
+ final ArrayList<ActivityRecord> taskActivities = mTargetTask.mChildren;
+ final int targetNdx = taskActivities.indexOf(r);
+ if (targetNdx > 0) {
+ final ActivityRecord p = taskActivities.get(targetNdx - 1);
+ if (p.intent.getComponent().equals(r.intent.getComponent())) {
+ p.finishIfPossible("replace", false /* oomAdj */);
+ }
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private void finishActivities(ArrayList<ActivityRecord> activities, String reason) {
+ boolean noOptions = mCanMoveOptions;
+
+ while (!activities.isEmpty()) {
+ final ActivityRecord p = activities.remove(0);
+ if (p.finishing) continue;
+
+ noOptions = takeOption(p, noOptions);
+
+ if (DEBUG_TASKS) Slog.w(TAG_TASKS,
+ "resetTaskIntendedTask: calling finishActivity on " + p);
+ p.finishIfPossible(reason, false /* oomAdj */);
+ }
+ }
+
+ private void processResultActivities(ActivityRecord target, Task targetTask, int position,
+ boolean ignoreFinishing, boolean takeOptions) {
+ boolean noOptions = mCanMoveOptions;
+
+ while (!mResultActivities.isEmpty()) {
+ final ActivityRecord p = mResultActivities.remove(0);
+ if (ignoreFinishing&& p.finishing) continue;
+
+ if (takeOptions) {
+ noOptions = takeOption(p, noOptions);
+ }
+ if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing activity " + p + " from task="
+ + mTask + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4));
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS,
+ "Pushing next activity " + p + " out to target's task " + target);
+ p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
+ }
+ }
+
+ private void processCreatedTasks() {
+ if (mCreatedTasks.isEmpty()) return;
+
+ ActivityDisplay display = mParent.getDisplay();
+ final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
+ if (singleTaskInstanceDisplay) {
+ display = mParent.mRootActivityContainer.getDefaultDisplay();
+ }
+
+ final int windowingMode = mParent.getWindowingMode();
+ final int activityType = mParent.getActivityType();
+ if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) {
+ return;
+ }
+
+ while (!mCreatedTasks.isEmpty()) {
+ final Task targetTask = mCreatedTasks.remove(mCreatedTasks.size() - 1);
+ final ActivityStack targetStack = display.getOrCreateStack(
+ windowingMode, activityType, false /* onTop */);
+ targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE,
+ false /* animate */, true /* deferResume */, "resetTargetTask");
+ }
+ }
+
+ private boolean takeOption(ActivityRecord p, boolean noOptions) {
+ mCanMoveOptions = false;
+ if (noOptions && mTopOptions == null) {
+ mTopOptions = p.takeOptionsLocked(false /* fromClient */);
+ if (mTopOptions != null) {
+ noOptions = false;
+ }
+ }
+ return noOptions;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 6ddd943dcf68..c7fd3bd44c8c 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1067,12 +1067,30 @@ class Task extends WindowContainer<ActivityRecord> implements ConfigurationConta
/** Returns the first non-finishing activity from the bottom. */
ActivityRecord getRootActivity() {
- final int rootActivityIndex = findRootIndex(false /* effectiveRoot */);
- if (rootActivityIndex == -1) {
- // There are no non-finishing activities in the task.
- return null;
+ // TODO: Figure out why we historical ignore relinquish identity for this case...
+ return getRootActivity(true /*ignoreRelinquishIdentity*/, false /*setToBottomIfNone*/);
+ }
+
+ ActivityRecord getRootActivity(boolean setToBottomIfNone) {
+ return getRootActivity(false /*ignoreRelinquishIdentity*/, false /*setToBottomIfNone*/);
+ }
+
+ ActivityRecord getRootActivity(boolean ignoreRelinquishIdentity, boolean setToBottomIfNone) {
+ ActivityRecord root;
+ if (ignoreRelinquishIdentity) {
+ root = getActivity((r) -> !r.finishing, false /*traverseTopToBottom*/);
+ } else {
+ root = getActivity((r) ->
+ !r.finishing && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0,
+ false /*traverseTopToBottom*/);
+ }
+
+ if (root == null && setToBottomIfNone) {
+ // All activities in the task are either finishing or relinquish task identity.
+ // But we still want to update the intent, so let's use the bottom activity.
+ root = getActivity((r) -> true, false /*traverseTopToBottom*/);
}
- return getChildAt(rootActivityIndex);
+ return root;
}
ActivityRecord getTopNonFinishingActivity() {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 3e78d5ef0f65..453514b2f46e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -79,6 +79,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -775,10 +776,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
/**
* @return {@code true} if in this subtree of the hierarchy we have an
- * {@ode ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
+ * {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
*/
boolean isAppTransitioning() {
- return forAllActivities(app -> app.isAnimating(TRANSITION));
+ return getActivity(app -> app.isAnimating(TRANSITION)) != null;
}
/**
@@ -1094,7 +1095,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
wrapper.release();
}
- boolean forAllActivities(ToBooleanFunction<ActivityRecord> callback) {
+ boolean forAllActivities(Function<ActivityRecord, Boolean> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllActivities(callback)) {
return true;
@@ -1103,6 +1104,72 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return false;
}
+ void forAllActivities(Consumer<ActivityRecord> callback) {
+ forAllActivities(callback, true /*traverseTopToBottom*/);
+ }
+
+ void forAllActivities(Consumer<ActivityRecord> callback, boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllActivities(callback, traverseTopToBottom);
+ }
+ }
+ }
+
+ /** @return {@code true} if this node or any of its children contains an activity. */
+ boolean hasActivity() {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ if (mChildren.get(i).hasActivity()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback) {
+ return getActivity(callback, true /*traverseTopToBottom*/);
+ }
+
+ ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final ActivityRecord r = mChildren.get(i).getActivity(callback, traverseTopToBottom);
+ if (r != null) {
+ return r;
+ }
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ final ActivityRecord r = mChildren.get(i).getActivity(callback, traverseTopToBottom);
+ if (r != null) {
+ return r;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) {
+ // Break down into 4 calls to avoid object creation due to capturing input params.
+ if (includeFinishing) {
+ if (includeOverlays) {
+ return getActivity((r) -> true);
+ }
+ return getActivity((r) -> !r.mTaskOverlay);
+ } else if (includeOverlays) {
+ return getActivity((r) -> !r.finishing);
+ }
+
+ return getActivity((r) -> !r.finishing && !r.mTaskOverlay);
+ }
+
void forAllWallpaperWindows(Consumer<WallpaperWindowToken> callback) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).forAllWallpaperWindows(callback);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 3a6ed5400f61..b71659e7e894 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -1096,7 +1096,7 @@ public class ActivityStackTests extends ActivityTestsBase {
taskTop.finishing = true;
final ActivityRecord newR = new ActivityBuilder(mService).build();
- final ActivityRecord result = mStack.resetTaskIfNeededLocked(taskTop, newR);
+ final ActivityRecord result = mStack.resetTaskIfNeeded(taskTop, newR);
assertThat(result).isEqualTo(taskTop);
}