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