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