blob: fe91ba56cb97aa2e8408a3464ebf71a725420ddc [file] [log] [blame]
Wale Ogunwaledfbeed72019-11-20 08:57:39 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19import static com.android.server.wm.ActivityStack.TAG_ADD_REMOVE;
20import static com.android.server.wm.ActivityStack.TAG_TASKS;
21import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
22import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
23import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
24
25import android.app.ActivityOptions;
26import android.content.Intent;
27import android.content.pm.ActivityInfo;
28import android.os.Debug;
29import android.util.Slog;
30
31import com.android.internal.util.function.pooled.PooledConsumer;
32import com.android.internal.util.function.pooled.PooledFunction;
33import com.android.internal.util.function.pooled.PooledLambda;
34
35import java.util.ArrayList;
36
37/** Helper class for processing the reset of a task. */
38class ResetTargetTaskHelper {
39 private Task mTask;
40 private ActivityStack mParent;
41 private Task mTargetTask;
42 private ActivityRecord mRoot;
43 private boolean mForceReset;
44 private boolean mCanMoveOptions;
45 private boolean mTargetTaskFound;
46 private int mActivityReparentPosition;
47 private ActivityOptions mTopOptions;
48 private ArrayList<ActivityRecord> mResultActivities = new ArrayList<>();
49 private ArrayList<ActivityRecord> mAllActivities = new ArrayList<>();
50 private ArrayList<Task> mCreatedTasks = new ArrayList<>();
51
52 private void reset(Task task) {
53 mTask = task;
54 mRoot = null;
55 mCanMoveOptions = true;
56 mTopOptions = null;
57 mResultActivities.clear();
58 mAllActivities.clear();
59 mCreatedTasks.clear();
60 }
61
62 ActivityOptions process(ActivityStack parent, Task targetTask, boolean forceReset) {
63 mParent = parent;
64 mForceReset = forceReset;
65 mTargetTask = targetTask;
66 mTargetTaskFound = false;
67 mActivityReparentPosition = -1;
68
69 final PooledConsumer c = PooledLambda.obtainConsumer(
70 ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
71 parent.forAllTasks(c);
72 c.recycle();
73
74 reset(null);
75 mParent = null;
76 return mTopOptions;
77 }
78
79 private void processTask(Task task) {
80 mRoot = task.getRootActivity(true);
81 if (mRoot == null) return;
82
83 reset(task);
84 final boolean isTargetTask = task == mTargetTask;
85 if (isTargetTask) mTargetTaskFound = true;
86
87 final PooledFunction f = PooledLambda.obtainFunction(
88 ResetTargetTaskHelper::processActivity, this,
89 PooledLambda.__(ActivityRecord.class), isTargetTask);
90 task.forAllActivities(f);
91 f.recycle();
92
93 processCreatedTasks();
94 }
95
96 private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
97 // End processing if we have reached the root.
98 if (r == mRoot) return true;
99
100 mAllActivities.add(r);
101 final int flags = r.info.flags;
102 final boolean finishOnTaskLaunch =
103 (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
104 final boolean allowTaskReparenting =
105 (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
106 final boolean clearWhenTaskReset =
107 (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
108
109 if (isTargetTask) {
110 if (!finishOnTaskLaunch && !clearWhenTaskReset) {
111 if (r.resultTo != null) {
112 // If this activity is sending a reply to a previous activity, we can't do
113 // anything with it now until we reach the start of the reply chain.
114 // NOTE: that we are assuming the result is always to the previous activity,
115 // which is almost always the case but we really shouldn't count on.
116 mResultActivities.add(r);
117 return false;
118 }
119 if (allowTaskReparenting && r.taskAffinity != null
120 && !r.taskAffinity.equals(mTask.affinity)) {
121 // If this activity has an affinity for another task, then we need to move
122 // it out of here. We will move it as far out of the way as possible, to the
123 // bottom of the activity stack. This also keeps it correctly ordered with
124 // any activities we previously moved.
125 // TODO: We should probably look for other stacks also, since corresponding
126 // task with the same affinity is unlikely to be in the same stack.
127 final Task targetTask;
128 final ActivityRecord bottom = mParent.getActivity(
129 (ar) -> true, false /*traverseTopToBottom*/);
130
131 if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) {
132 // If the activity currently at the bottom has the same task affinity as
133 // the one we are moving, then merge it into the same task.
134 targetTask = bottom.getTask();
135 if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
136 + r + " out to bottom task " + targetTask);
137 } else {
138 targetTask = mParent.createTask(
139 mParent.mStackSupervisor.getNextTaskIdForUserLocked(r.mUserId),
140 r.info, null /* intent */, null /* voiceSession */,
141 null /* voiceInteractor */, false /* toTop */);
142 targetTask.affinityIntent = r.intent;
143 mCreatedTasks.add(targetTask);
144 if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
145 + r + " out to new task " + targetTask);
146 }
147
148 mResultActivities.add(r);
149 processResultActivities(r, targetTask, 0 /*bottom*/, true, true);
150 mParent.positionChildAtBottom(targetTask);
151 mParent.mStackSupervisor.mRecentTasks.add(targetTask);
152 return false;
153 }
154 }
155 if (mForceReset || finishOnTaskLaunch || clearWhenTaskReset) {
156 // If the activity should just be removed either because it asks for it, or the
157 // task should be cleared, then finish it and anything that is part of its reply
158 // chain.
159 if (clearWhenTaskReset) {
160 // In this case, we want to finish this activity and everything above it,
161 // so be sneaky and pretend like these are all in the reply chain.
162 finishActivities(mAllActivities, "clearWhenTaskReset");
163 } else {
164 mResultActivities.add(r);
165 finishActivities(mResultActivities, "reset-task");
166 }
167
168 mResultActivities.clear();
169 return false;
170 } else {
171 // If we were in the middle of a chain, well the activity that started it all
172 // doesn't want anything special, so leave it all as-is.
173 mResultActivities.clear();
174 }
175
176 return false;
177
178 } else {
179 mResultActivities.add(r);
180 if (r.resultTo != null) {
181 // If this activity is sending a reply to a previous activity, we can't do
182 // anything with it now until we reach the start of the reply chain.
183 // NOTE: that we are assuming the result is always to the previous activity,
184 // which is almost always the case but we really shouldn't count on.
185 return false;
186 } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null
187 && mTargetTask.affinity.equals(r.taskAffinity)) {
188 // This activity has an affinity for our task. Either remove it if we are
189 // clearing or move it over to our task. Note that we currently punt on the case
190 // where we are resetting a task that is not at the top but who has activities
191 // above with an affinity to it... this is really not a normal case, and we will
192 // need to later pull that task to the front and usually at that point we will
193 // do the reset and pick up those remaining activities. (This only happens if
194 // someone starts an activity in a new task from an activity in a task that is
195 // not currently on top.)
196 if (mForceReset || finishOnTaskLaunch) {
197 finishActivities(mResultActivities, "move-affinity");
198 return false;
199 }
200 if (mActivityReparentPosition == -1) {
201 mActivityReparentPosition = mTargetTask.getChildCount();
202 }
203
204 processResultActivities(
205 r, mTargetTask, mActivityReparentPosition, false, false);
206
207 mParent.positionChildAtTop(mTargetTask);
208
209 // Now we've moved it in to place...but what if this is a singleTop activity and
210 // we have put it on top of another instance of the same activity? Then we drop
211 // the instance below so it remains singleTop.
212 if (r.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
213 final ArrayList<ActivityRecord> taskActivities = mTargetTask.mChildren;
214 final int targetNdx = taskActivities.indexOf(r);
215 if (targetNdx > 0) {
216 final ActivityRecord p = taskActivities.get(targetNdx - 1);
217 if (p.intent.getComponent().equals(r.intent.getComponent())) {
218 p.finishIfPossible("replace", false /* oomAdj */);
219 }
220 }
221 }
222 }
223 return false;
224 }
225 }
226
227 private void finishActivities(ArrayList<ActivityRecord> activities, String reason) {
228 boolean noOptions = mCanMoveOptions;
229
230 while (!activities.isEmpty()) {
231 final ActivityRecord p = activities.remove(0);
232 if (p.finishing) continue;
233
234 noOptions = takeOption(p, noOptions);
235
236 if (DEBUG_TASKS) Slog.w(TAG_TASKS,
237 "resetTaskIntendedTask: calling finishActivity on " + p);
238 p.finishIfPossible(reason, false /* oomAdj */);
239 }
240 }
241
242 private void processResultActivities(ActivityRecord target, Task targetTask, int position,
243 boolean ignoreFinishing, boolean takeOptions) {
244 boolean noOptions = mCanMoveOptions;
245
246 while (!mResultActivities.isEmpty()) {
247 final ActivityRecord p = mResultActivities.remove(0);
248 if (ignoreFinishing&& p.finishing) continue;
249
250 if (takeOptions) {
251 noOptions = takeOption(p, noOptions);
252 }
253 if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing activity " + p + " from task="
254 + mTask + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4));
255 if (DEBUG_TASKS) Slog.v(TAG_TASKS,
256 "Pushing next activity " + p + " out to target's task " + target);
257 p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
258 }
259 }
260
261 private void processCreatedTasks() {
262 if (mCreatedTasks.isEmpty()) return;
263
264 ActivityDisplay display = mParent.getDisplay();
265 final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
266 if (singleTaskInstanceDisplay) {
267 display = mParent.mRootActivityContainer.getDefaultDisplay();
268 }
269
270 final int windowingMode = mParent.getWindowingMode();
271 final int activityType = mParent.getActivityType();
272 if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) {
273 return;
274 }
275
276 while (!mCreatedTasks.isEmpty()) {
277 final Task targetTask = mCreatedTasks.remove(mCreatedTasks.size() - 1);
278 final ActivityStack targetStack = display.getOrCreateStack(
279 windowingMode, activityType, false /* onTop */);
280 targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE,
281 false /* animate */, true /* deferResume */, "resetTargetTask");
282 }
283 }
284
285 private boolean takeOption(ActivityRecord p, boolean noOptions) {
286 mCanMoveOptions = false;
287 if (noOptions && mTopOptions == null) {
288 mTopOptions = p.takeOptionsLocked(false /* fromClient */);
289 if (mTopOptions != null) {
290 noOptions = false;
291 }
292 }
293 return noOptions;
294 }
295}