diff options
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); } |