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