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