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