diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 4eedd5d079ad0..8edb3137c4167 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -1523,7 +1523,7 @@ class Task extends TaskFragment { mTaskSupervisor.removeTask(this, false /* killProcess */, !REMOVE_FROM_RECENTS, reason); } - } else if (!mReuseTask && !mCreatedByOrganizer) { + } else if (!mReuseTask && shouldRemoveSelfOnLastChildRemoval()) { // Remove entire task if it doesn't have any activity left and it isn't marked for reuse // or created by task organizer. if (!isRootTask()) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 3411104dbff05..3985cbcaa419b 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2304,6 +2304,10 @@ class TaskFragment extends WindowContainer { mMinHeight = minHeight; } + boolean shouldRemoveSelfOnLastChildRemoval() { + return !mCreatedByOrganizer || mIsRemovalRequested; + } + @Override void removeChild(WindowContainer child) { removeChild(child, true /* removeSelfIfPossible */); @@ -2319,7 +2323,7 @@ class TaskFragment extends WindowContainer { mBackScreenshots.remove(r.mActivityComponent.flattenToString()); } } - if (removeSelfIfPossible && (!mCreatedByOrganizer || mIsRemovalRequested) && !hasChild()) { + if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) { removeImmediately("removeLastChild " + child); } } @@ -2337,13 +2341,18 @@ class TaskFragment extends WindowContainer { return; } mIsRemovalRequested = true; - forAllActivities(r -> { - if (withTransition) { + // The task order may be changed by finishIfPossible() for adjusting focus if there are + // nested tasks, so add all activities into a list to avoid missed removals. + final ArrayList removingActivities = new ArrayList<>(); + forAllActivities((Consumer) removingActivities::add); + for (int i = removingActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = removingActivities.get(i); + if (withTransition && r.isVisible()) { r.finishIfPossible(reason, false /* oomAdj */); } else { r.destroyIfPossible(reason); } - }); + } } void setDelayLastActivityRemoval(boolean delay) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index fe41734d0232e..636c6bc77e0a2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -145,6 +145,27 @@ public class TaskTests extends WindowTestsBase { verify(mAtm.getLockTaskController(), atLeast(1)).clearLockedTask(rootTask); } + @Test + public void testRemoveContainer_multipleNestedTasks() { + final Task rootTask = createTask(mDisplayContent); + rootTask.mCreatedByOrganizer = true; + final Task task1 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); + final Task task2 = new TaskBuilder(mSupervisor).setParentTaskFragment(rootTask).build(); + final ActivityRecord activity1 = createActivityRecord(task1); + final ActivityRecord activity2 = createActivityRecord(task2); + activity1.setVisible(false); + + // All activities under the root task should be finishing. + rootTask.remove(true /* withTransition */, "test"); + assertTrue(activity1.finishing); + assertTrue(activity2.finishing); + + // After all activities activities are destroyed, the root task should also be removed. + activity1.removeImmediately(); + activity2.removeImmediately(); + assertFalse(rootTask.isAttached()); + } + @Test public void testRemoveContainer_deferRemoval() { final Task rootTask = createTask(mDisplayContent);