blob: 3aa91d52a47d046ab48fe71c1d3f25b53eb706f8 [file] [log] [blame]
/*
* 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) {
reset(task);
mRoot = task.getRootActivity(true);
if (mRoot == null) return;
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.getBottomMostActivity();
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 ActivityRecord p = mTargetTask.getActivityBelow(r);
if (p != null) {
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;
}
}