• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.server.wm;
18 
19 import static com.android.server.wm.ActivityStack.TAG_ADD_REMOVE;
20 import static com.android.server.wm.ActivityStack.TAG_TASKS;
21 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE;
22 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
23 
24 import android.app.ActivityOptions;
25 import android.content.Intent;
26 import android.content.pm.ActivityInfo;
27 import android.os.Debug;
28 import android.util.Slog;
29 
30 import com.android.internal.util.function.pooled.PooledConsumer;
31 import com.android.internal.util.function.pooled.PooledFunction;
32 import com.android.internal.util.function.pooled.PooledLambda;
33 
34 import java.util.ArrayList;
35 
36 /** Helper class for processing the reset of a task. */
37 class ResetTargetTaskHelper {
38     private Task mTask;
39     private Task mTargetTask;
40     private ActivityStack mTargetStack;
41     private ActivityRecord mRoot;
42     private boolean mForceReset;
43     private boolean mCanMoveOptions;
44     private boolean mTargetTaskFound;
45     private int mActivityReparentPosition;
46     private ActivityOptions mTopOptions;
47     private ArrayList<ActivityRecord> mResultActivities = new ArrayList<>();
48     private ArrayList<ActivityRecord> mAllActivities = new ArrayList<>();
49     private ArrayList<ActivityRecord> mPendingReparentActivities = new ArrayList<>();
50 
reset(Task task)51     private void reset(Task task) {
52         mTask = task;
53         mRoot = null;
54         mCanMoveOptions = true;
55         mTopOptions = null;
56         mResultActivities.clear();
57         mAllActivities.clear();
58     }
59 
process(Task targetTask, boolean forceReset)60     ActivityOptions process(Task targetTask, boolean forceReset) {
61         mForceReset = forceReset;
62         mTargetTask = targetTask;
63         mTargetTaskFound = false;
64         mTargetStack = targetTask.getStack();
65         mActivityReparentPosition = -1;
66 
67         final PooledConsumer c = PooledLambda.obtainConsumer(
68                 ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
69         targetTask.mWmService.mRoot.forAllLeafTasks(c, true /*traverseTopToBottom*/);
70         c.recycle();
71 
72         processPendingReparentActivities();
73         reset(null);
74         return mTopOptions;
75     }
76 
processTask(Task task)77     private void processTask(Task task) {
78         reset(task);
79         mRoot = task.getRootActivity(true);
80         if (mRoot == null) return;
81 
82         final boolean isTargetTask = task == mTargetTask;
83         if (isTargetTask) mTargetTaskFound = true;
84 
85         final PooledFunction f = PooledLambda.obtainFunction(
86                 ResetTargetTaskHelper::processActivity, this,
87                 PooledLambda.__(ActivityRecord.class), isTargetTask);
88         task.forAllActivities(f);
89         f.recycle();
90     }
91 
processActivity(ActivityRecord r, boolean isTargetTask)92     private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
93         // End processing if we have reached the root.
94         if (r == mRoot) return true;
95 
96         mAllActivities.add(r);
97         final int flags = r.info.flags;
98         final boolean finishOnTaskLaunch =
99                 (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
100         final boolean allowTaskReparenting =
101                 (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
102         final boolean clearWhenTaskReset =
103                 (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
104 
105         if (isTargetTask) {
106             if (!finishOnTaskLaunch && !clearWhenTaskReset) {
107                 if (r.resultTo != null) {
108                     // If this activity is sending a reply to a previous activity, we can't do
109                     // anything with it now until we reach the start of the reply chain.
110                     // NOTE: that we are assuming the result is always to the previous activity,
111                     // which is almost always the case but we really shouldn't count on.
112                     mResultActivities.add(r);
113                     return false;
114                 }
115                 if (allowTaskReparenting && r.taskAffinity != null
116                         && !r.taskAffinity.equals(mTask.affinity)) {
117                     // If this activity has an affinity for another task, then we need to move
118                     // it out of here. We will move it as far out of the way as possible, to the
119                     // bottom of the activity stack. This also keeps it correctly ordered with
120                     // any activities we previously moved.
121 
122                     // Handle this activity after we have done traversing the hierarchy.
123                     mPendingReparentActivities.add(r);
124                     return false;
125                 }
126             }
127             if (mForceReset || finishOnTaskLaunch || clearWhenTaskReset) {
128                 // If the activity should just be removed either because it asks for it, or the
129                 // task should be cleared, then finish it and anything that is part of its reply
130                 // chain.
131                 if (clearWhenTaskReset) {
132                     // In this case, we want to finish this activity and everything above it,
133                     // so be sneaky and pretend like these are all in the reply chain.
134                     finishActivities(mAllActivities, "clearWhenTaskReset");
135                 } else {
136                     mResultActivities.add(r);
137                     finishActivities(mResultActivities, "reset-task");
138                 }
139 
140                 mResultActivities.clear();
141                 return false;
142             } else {
143                 // If we were in the middle of a chain, well the activity that started it all
144                 // doesn't want anything special, so leave it all as-is.
145                 mResultActivities.clear();
146             }
147 
148             return false;
149 
150         } else {
151             mResultActivities.add(r);
152             if (r.resultTo != null) {
153                 // If this activity is sending a reply to a previous activity, we can't do
154                 // anything with it now until we reach the start of the reply chain.
155                 // NOTE: that we are assuming the result is always to the previous activity,
156                 // which is almost always the case but we really shouldn't count on.
157                 return false;
158             } else if (mTargetTaskFound && allowTaskReparenting && mTargetTask.affinity != null
159                     && mTargetTask.affinity.equals(r.taskAffinity)) {
160                 // This activity has an affinity for our task. Either remove it if we are
161                 // clearing or move it over to our task. Note that we currently punt on the case
162                 // where we are resetting a task that is not at the top but who has activities
163                 // above with an affinity to it... this is really not a normal case, and we will
164                 // need to later pull that task to the front and usually at that point we will
165                 // do the reset and pick up those remaining activities. (This only happens if
166                 // someone starts an activity in a new task from an activity in a task that is
167                 // not currently on top.)
168                 if (mForceReset || finishOnTaskLaunch) {
169                     finishActivities(mResultActivities, "move-affinity");
170                     return false;
171                 }
172                 if (mActivityReparentPosition == -1) {
173                     mActivityReparentPosition = mTargetTask.getChildCount();
174                 }
175 
176                 processResultActivities(
177                         r, mTargetTask, mActivityReparentPosition, false, false);
178 
179                 // Now we've moved it in to place...but what if this is a singleTop activity and
180                 // we have put it on top of another instance of the same activity? Then we drop
181                 // the instance below so it remains singleTop.
182                 if (r.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
183                     final ActivityRecord p = mTargetTask.getActivityBelow(r);
184                     if (p != null) {
185                         if (p.intent.getComponent().equals(r.intent.getComponent())) {
186                             p.finishIfPossible("replace", false /* oomAdj */);
187                         }
188                     }
189                 }
190             }
191             return false;
192         }
193     }
194 
finishActivities(ArrayList<ActivityRecord> activities, String reason)195     private void finishActivities(ArrayList<ActivityRecord> activities, String reason) {
196         boolean noOptions = mCanMoveOptions;
197 
198         while (!activities.isEmpty()) {
199             final ActivityRecord p = activities.remove(0);
200             if (p.finishing) continue;
201 
202             noOptions = takeOption(p, noOptions);
203 
204             if (DEBUG_TASKS) Slog.w(TAG_TASKS,
205                     "resetTaskIntendedTask: calling finishActivity on " + p);
206             p.finishIfPossible(reason, false /* oomAdj */);
207         }
208     }
209 
processResultActivities(ActivityRecord target, Task targetTask, int position, boolean ignoreFinishing, boolean takeOptions)210     private void processResultActivities(ActivityRecord target, Task targetTask, int position,
211             boolean ignoreFinishing, boolean takeOptions) {
212         boolean noOptions = mCanMoveOptions;
213 
214         while (!mResultActivities.isEmpty()) {
215             final ActivityRecord p = mResultActivities.remove(0);
216             if (ignoreFinishing&& p.finishing) continue;
217 
218             if (takeOptions) {
219                 noOptions = takeOption(p, noOptions);
220             }
221             if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, "Removing activity " + p + " from task="
222                     + mTask + " adding to task=" + targetTask + " Callers=" + Debug.getCallers(4));
223             if (DEBUG_TASKS) Slog.v(TAG_TASKS,
224                     "Pushing next activity " + p + " out to target's task " + target);
225             p.reparent(targetTask, position, "resetTargetTaskIfNeeded");
226         }
227     }
228 
processPendingReparentActivities()229     private void processPendingReparentActivities() {
230         if (mPendingReparentActivities.isEmpty()) {
231             return;
232         }
233 
234         final ActivityTaskManagerService atmService = mTargetStack.mAtmService;
235         TaskDisplayArea taskDisplayArea = mTargetStack.getDisplayArea();
236         final boolean singleTaskInstanceDisplay =
237                 taskDisplayArea.mDisplayContent.isSingleTaskInstance();
238         if (singleTaskInstanceDisplay) {
239             taskDisplayArea = atmService.mRootWindowContainer.getDefaultTaskDisplayArea();
240         }
241 
242         final int windowingMode = mTargetStack.getWindowingMode();
243         final int activityType = mTargetStack.getActivityType();
244 
245         while (!mPendingReparentActivities.isEmpty()) {
246             final ActivityRecord r = mPendingReparentActivities.remove(0);
247             final boolean alwaysCreateTask = DisplayContent.alwaysCreateStack(windowingMode,
248                     activityType);
249             final Task task = alwaysCreateTask
250                     ? taskDisplayArea.getBottomMostTask() : mTargetStack.getBottomMostTask();
251             Task targetTask = null;
252             if (task != null && r.taskAffinity.equals(task.affinity)) {
253                 // If the activity currently at the bottom has the same task affinity as
254                 // the one we are moving, then merge it into the same task.
255                 targetTask = task;
256                 if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
257                         + r + " out to bottom task " + targetTask);
258             }
259             if (targetTask == null) {
260                 if (alwaysCreateTask) {
261                     targetTask = taskDisplayArea.getOrCreateStack(windowingMode,
262                             activityType, false /* onTop */);
263                 } else {
264                     targetTask = mTargetStack.reuseOrCreateTask(r.info, null /*intent*/,
265                             false /*toTop*/);
266                 }
267                 targetTask.affinityIntent = r.intent;
268             }
269             r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded");
270             atmService.mStackSupervisor.mRecentTasks.add(targetTask);
271         }
272     }
273 
takeOption(ActivityRecord p, boolean noOptions)274     private boolean takeOption(ActivityRecord p, boolean noOptions) {
275         mCanMoveOptions = false;
276         if (noOptions && mTopOptions == null) {
277             mTopOptions = p.takeOptionsLocked(false /* fromClient */);
278             if (mTopOptions != null) {
279                 noOptions = false;
280             }
281         }
282         return noOptions;
283     }
284 }
285