Loading services/core/java/com/android/server/wm/RecentTasks.java +42 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,9 @@ class RecentTasks { private final ArrayList<Task> mTasks = new ArrayList<>(); private final ArrayList<Callbacks> mCallbacks = new ArrayList<>(); /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */ private final ArrayList<Task> mHiddenTasks = new ArrayList<>(); // These values are generally loaded from resources, but can be set dynamically in the tests private boolean mHasVisibleRecentTasks; private int mGlobalMaxNumTasks; Loading Loading @@ -1024,6 +1027,12 @@ class RecentTasks { void add(Task task) { if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task); // Clean up the hidden tasks when going to home because the user may not be unable to return // to the task from recents. if (!mHiddenTasks.isEmpty() && task.isActivityTypeHome()) { removeUnreachableHiddenTasks(task.getWindowingMode()); } final boolean isAffiliated = task.mAffiliatedTaskId != task.mTaskId || task.mNextAffiliateTaskId != INVALID_TASK_ID || task.mPrevAffiliateTaskId != INVALID_TASK_ID; Loading Loading @@ -1390,6 +1399,28 @@ class RecentTasks { return display.getIndexOf(stack) < display.getIndexOf(display.getRootHomeTask()); } /** Remove the tasks that user may not be able to return. */ private void removeUnreachableHiddenTasks(int windowingMode) { for (int i = mHiddenTasks.size() - 1; i >= 0; i--) { final Task hiddenTask = mHiddenTasks.get(i); if (!hiddenTask.hasChild()) { // The task was removed by other path. mHiddenTasks.remove(i); continue; } if (hiddenTask.getWindowingMode() != windowingMode || hiddenTask.getTopVisibleActivity() != null) { // The task may be reachable from the back stack of other windowing mode or it is // currently in use. Keep the task in the hidden list to avoid losing track, e.g. // after dismissing primary split screen. continue; } mHiddenTasks.remove(i); mSupervisor.removeTask(hiddenTask, false /* killProcess */, !REMOVE_FROM_RECENTS, "remove-hidden-task"); } } /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. Loading @@ -1406,6 +1437,14 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { // The added task is in recents so it is not hidden. mHiddenTasks.remove(task); if (removedTask.hasChild()) { // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task // from becoming dangling. mHiddenTasks.add(removedTask); } notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */); if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask + " for addition of task=" + task); Loading Loading @@ -1662,6 +1701,9 @@ class RecentTasks { pw.println("mFreezeTaskListReordering=" + mFreezeTaskListReordering); pw.println("mFreezeTaskListReorderingPendingTimeout=" + mService.mH.hasCallbacks(mResetFreezeTaskListOnTimeoutRunnable)); if (!mHiddenTasks.isEmpty()) { pw.println("mHiddenTasks=" + mHiddenTasks); } if (mTasks.isEmpty()) { return; } Loading services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +33 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -83,6 +85,7 @@ import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.function.Function; /** * Build/Install/Run: Loading Loading @@ -418,6 +421,36 @@ public class RecentTasksTest extends ActivityTestsBase { assertThat(mCallbacksRecorder.mRemoved).isEmpty(); } @Test public void testAddTasksHomeClearUntrackedTasks_expectFinish() { // There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK | // FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed // because user may not be able to return to the task. final String className = ".PermissionsReview"; final Function<Boolean, Task> taskBuilder = visible -> { final Task task = createTaskBuilder(className).build(); // Make the task non-empty. final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build(); r.setVisibility(visible); return task; }; final Task task1 = taskBuilder.apply(false /* visible */); mRecentTasks.add(task1); final Task task2 = taskBuilder.apply(true /* visible */); mRecentTasks.add(task2); // Only the last task is kept in recents and the previous 2 tasks will becomes untracked // tasks because their intents are identical. mRecentTasks.add(createTaskBuilder(className).build()); // Go home to trigger the removal of untracked tasks. mRecentTasks.add(createTaskBuilder(".Home").setStack(mDisplay.getRootHomeTask()).build()); // All activities in the invisible task should be finishing or removed. assertNull(task1.getTopNonFinishingActivity()); // The visible task should not be affected. assertNotNull(task2.getTopNonFinishingActivity()); } @Test public void testUsersTasks() { mRecentTasks.setOnlyTestVisibleRange(); Loading Loading
services/core/java/com/android/server/wm/RecentTasks.java +42 −0 Original line number Diff line number Diff line Loading @@ -173,6 +173,9 @@ class RecentTasks { private final ArrayList<Task> mTasks = new ArrayList<>(); private final ArrayList<Callbacks> mCallbacks = new ArrayList<>(); /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */ private final ArrayList<Task> mHiddenTasks = new ArrayList<>(); // These values are generally loaded from resources, but can be set dynamically in the tests private boolean mHasVisibleRecentTasks; private int mGlobalMaxNumTasks; Loading Loading @@ -1024,6 +1027,12 @@ class RecentTasks { void add(Task task) { if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task); // Clean up the hidden tasks when going to home because the user may not be unable to return // to the task from recents. if (!mHiddenTasks.isEmpty() && task.isActivityTypeHome()) { removeUnreachableHiddenTasks(task.getWindowingMode()); } final boolean isAffiliated = task.mAffiliatedTaskId != task.mTaskId || task.mNextAffiliateTaskId != INVALID_TASK_ID || task.mPrevAffiliateTaskId != INVALID_TASK_ID; Loading Loading @@ -1390,6 +1399,28 @@ class RecentTasks { return display.getIndexOf(stack) < display.getIndexOf(display.getRootHomeTask()); } /** Remove the tasks that user may not be able to return. */ private void removeUnreachableHiddenTasks(int windowingMode) { for (int i = mHiddenTasks.size() - 1; i >= 0; i--) { final Task hiddenTask = mHiddenTasks.get(i); if (!hiddenTask.hasChild()) { // The task was removed by other path. mHiddenTasks.remove(i); continue; } if (hiddenTask.getWindowingMode() != windowingMode || hiddenTask.getTopVisibleActivity() != null) { // The task may be reachable from the back stack of other windowing mode or it is // currently in use. Keep the task in the hidden list to avoid losing track, e.g. // after dismissing primary split screen. continue; } mHiddenTasks.remove(i); mSupervisor.removeTask(hiddenTask, false /* killProcess */, !REMOVE_FROM_RECENTS, "remove-hidden-task"); } } /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. Loading @@ -1406,6 +1437,14 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { // The added task is in recents so it is not hidden. mHiddenTasks.remove(task); if (removedTask.hasChild()) { // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task // from becoming dangling. mHiddenTasks.add(removedTask); } notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */); if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask + " for addition of task=" + task); Loading Loading @@ -1662,6 +1701,9 @@ class RecentTasks { pw.println("mFreezeTaskListReordering=" + mFreezeTaskListReordering); pw.println("mFreezeTaskListReorderingPendingTimeout=" + mService.mH.hasCallbacks(mResetFreezeTaskListOnTimeoutRunnable)); if (!mHiddenTasks.isEmpty()) { pw.println("mHiddenTasks=" + mHiddenTasks); } if (mTasks.isEmpty()) { return; } Loading
services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +33 −0 Original line number Diff line number Diff line Loading @@ -39,6 +39,8 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -83,6 +85,7 @@ import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.function.Function; /** * Build/Install/Run: Loading Loading @@ -418,6 +421,36 @@ public class RecentTasksTest extends ActivityTestsBase { assertThat(mCallbacksRecorder.mRemoved).isEmpty(); } @Test public void testAddTasksHomeClearUntrackedTasks_expectFinish() { // There may be multiple tasks with the same base intent by flags (FLAG_ACTIVITY_NEW_TASK | // FLAG_ACTIVITY_MULTIPLE_TASK). If the previous task is still active, it should be removed // because user may not be able to return to the task. final String className = ".PermissionsReview"; final Function<Boolean, Task> taskBuilder = visible -> { final Task task = createTaskBuilder(className).build(); // Make the task non-empty. final ActivityRecord r = new ActivityBuilder(mService).setTask(task).build(); r.setVisibility(visible); return task; }; final Task task1 = taskBuilder.apply(false /* visible */); mRecentTasks.add(task1); final Task task2 = taskBuilder.apply(true /* visible */); mRecentTasks.add(task2); // Only the last task is kept in recents and the previous 2 tasks will becomes untracked // tasks because their intents are identical. mRecentTasks.add(createTaskBuilder(className).build()); // Go home to trigger the removal of untracked tasks. mRecentTasks.add(createTaskBuilder(".Home").setStack(mDisplay.getRootHomeTask()).build()); // All activities in the invisible task should be finishing or removed. assertNull(task1.getTopNonFinishingActivity()); // The visible task should not be affected. assertNotNull(task2.getTopNonFinishingActivity()); } @Test public void testUsersTasks() { mRecentTasks.setOnlyTestVisibleRange(); Loading