Expose method to clear all visible recent tasks

- Atomically remove all the visible tasks so that SysUI doesn't need to
  remove each task individually.

Bug: 80471073
Test: atest FrameworksServicesTests:RecentTasksTest#testRemoveAllVisibleTasks
Change-Id: I23a6e152e94d5462948ab40adc9d7baf593847e1
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 398644af..2f18b89 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -204,6 +204,19 @@
     }
 
     /**
+     * Removes all visible recent tasks from the system.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
+    public void removeAllVisibleRecentTasks() {
+        try {
+            getService().removeAllVisibleRecentTasks();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Return the maximum number of recents entries that we will maintain and show.
      * @hide
      */
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index ece7f83..46664c6 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -139,6 +139,7 @@
     ComponentName getCallingActivity(in IBinder token);
     void setFocusedTask(int taskId);
     boolean removeTask(int taskId);
+    void removeAllVisibleRecentTasks();
     List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
     List<ActivityManager.RunningTaskInfo> getFilteredTasks(int maxNum, int ignoreActivityType,
             int ignoreWindowingMode);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index dce72b4..0ded963 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -394,6 +394,22 @@
     }
 
     /**
+     * Removes all the recent tasks.
+     */
+    public void removeAllRecentTasks() {
+        mBackgroundExecutor.submit(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    ActivityTaskManager.getService().removeAllVisibleRecentTasks();
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Failed to remove all tasks", e);
+                }
+            }
+        });
+    }
+
+    /**
      * Cancels the current window transtion to/from Recents for the given task id.
      */
     public void cancelWindowTransition(int taskId) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d1585cf..609ad75 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3230,12 +3230,12 @@
     }
 
     @Override
-    public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+    public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) {
         if (wasTrimmed) {
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
-            removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                    false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+            removeTaskByIdLocked(task.taskId, killProcess, false /* removeFromRecents */,
+                    !PAUSE_IMMEDIATELY, "recent-task-trimmed");
         }
         task.removedFromRecents();
     }
diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
index 3b3263c..8ab4a37 100644
--- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java
@@ -1668,6 +1668,19 @@
     }
 
     @Override
+    public void removeAllVisibleRecentTasks() {
+        enforceCallerIsRecentsOrHasPermission(REMOVE_TASKS, "removeAllVisibleRecentTasks()");
+        synchronized (mGlobalLock) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                getRecentTasks().removeAllVisibleTasks();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public boolean shouldUpRecreateTask(IBinder token, String destAffinity) {
         synchronized (mGlobalLock) {
             final ActivityRecord srec = ActivityRecord.forTokenLocked(token);
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index fb6b5c1..e11e003 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -106,7 +106,6 @@
     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
     private static final String TAG_TASKS = TAG + POSTFIX_TASKS;
-    private static final boolean TRIMMED = true;
 
     private static final int DEFAULT_INITIAL_CAPACITY = 5;
 
@@ -134,7 +133,7 @@
         /**
          * Called when a task is removed from the recent tasks list.
          */
-        void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed);
+        void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess);
     }
 
     /**
@@ -322,9 +321,9 @@
         }
     }
 
-    private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+    private void notifyTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) {
         for (int i = 0; i < mCallbacks.size(); i++) {
-            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed);
+            mCallbacks.get(i).onRecentTaskRemoved(task, wasTrimmed, killProcess);
         }
     }
 
@@ -547,6 +546,16 @@
         }
     }
 
+    void removeAllVisibleTasks() {
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            if (isVisibleRecentTask(tr)) {
+                mTasks.remove(i);
+                notifyTaskRemoved(tr, true /* wasTrimmed */, true /* killProcess */);
+            }
+        }
+    }
+
     void cleanupDisabledPackageTasksLocked(String packageName, Set<String> filterByClasses,
             int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
@@ -1048,7 +1057,7 @@
      */
     void remove(TaskRecord task) {
         mTasks.remove(task);
-        notifyTaskRemoved(task, !TRIMMED);
+        notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */);
     }
 
     /**
@@ -1060,7 +1069,7 @@
         // Remove from the end of the list until we reach the max number of recents
         while (recentsCount > mGlobalMaxNumTasks) {
             final TaskRecord tr = mTasks.remove(recentsCount - 1);
-            notifyTaskRemoved(tr, TRIMMED);
+            notifyTaskRemoved(tr, true /* wasTrimmed */, false /* killProcess */);
             recentsCount--;
             if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming over max-recents task=" + tr
                     + " max=" + mGlobalMaxNumTasks);
@@ -1114,7 +1123,7 @@
 
             // Task is no longer active, trim it from the list
             mTasks.remove(task);
-            notifyTaskRemoved(task, TRIMMED);
+            notifyTaskRemoved(task, true /* wasTrimmed */, false /* killProcess */);
             notifyTaskPersisterLocked(task, false /* flush */);
         }
     }
@@ -1268,7 +1277,7 @@
         // callbacks here.
         final TaskRecord removedTask = mTasks.remove(removeIndex);
         if (removedTask != task) {
-            notifyTaskRemoved(removedTask, !TRIMMED);
+            notifyTaskRemoved(removedTask, false /* wasTrimmed */, false /* killProcess */);
             if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming task=" + removedTask
                     + " for addition of task=" + task);
         }
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index 227a70f..ee484d6 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
@@ -43,6 +44,7 @@
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityTaskManager;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -51,7 +53,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -276,13 +277,11 @@
     public void testAddTaskCompatibleActivityType_expectRemove() throws Exception {
         // Test with undefined activity type since the type is not persisted by the task persister
         // and we want to ensure that a new task will match a restored task
-        Configuration config1 = new Configuration();
-        config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
         TaskRecord task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .build();
-        task1.onConfigurationChanged(config1);
+        setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED);
         assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED);
         mRecentTasks.add(task1);
         mCallbacksRecorder.clear();
@@ -302,14 +301,12 @@
 
     @Test
     public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() throws Exception {
-        Configuration config1 = new Configuration();
-        config1.windowConfiguration.setActivityType(ACTIVITY_TYPE_UNDEFINED);
         TaskRecord task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .setUserId(TEST_USER_0_ID)
                 .build();
-        task1.onConfigurationChanged(config1);
+        setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED);
         assertTrue(task1.getActivityType() == ACTIVITY_TYPE_UNDEFINED);
         mRecentTasks.add(task1);
         mCallbacksRecorder.clear();
@@ -329,24 +326,20 @@
 
     @Test
     public void testAddTaskCompatibleWindowingMode_expectRemove() throws Exception {
-        Configuration config1 = new Configuration();
-        config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED);
         TaskRecord task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .build();
-        task1.onConfigurationChanged(config1);
+        setTaskWindowingMode(task1, WINDOWING_MODE_UNDEFINED);
         assertTrue(task1.getWindowingMode() == WINDOWING_MODE_UNDEFINED);
         mRecentTasks.add(task1);
         mCallbacksRecorder.clear();
 
-        Configuration config2 = new Configuration();
-        config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         TaskRecord task2 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .build();
-        task2.onConfigurationChanged(config2);
+        setTaskWindowingMode(task2, WINDOWING_MODE_FULLSCREEN);
         assertTrue(task2.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
         mRecentTasks.add(task2);
 
@@ -359,23 +352,19 @@
 
     @Test
     public void testAddTaskIncompatibleWindowingMode_expectNoRemove() throws Exception {
-        Configuration config1 = new Configuration();
-        config1.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         TaskRecord task1 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .build();
-        task1.onConfigurationChanged(config1);
+        setTaskWindowingMode(task1, WINDOWING_MODE_FULLSCREEN);
         assertTrue(task1.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
         mRecentTasks.add(task1);
 
-        Configuration config2 = new Configuration();
-        config2.windowConfiguration.setWindowingMode(WINDOWING_MODE_PINNED);
         TaskRecord task2 = createTaskBuilder(".Task1")
                 .setFlags(FLAG_ACTIVITY_NEW_TASK)
                 .setStack(mStack)
                 .build();
-        task2.onConfigurationChanged(config2);
+        setTaskWindowingMode(task2, WINDOWING_MODE_PINNED);
         assertTrue(task2.getWindowingMode() == WINDOWING_MODE_PINNED);
         mRecentTasks.add(task2);
 
@@ -644,6 +633,43 @@
     }
 
     @Test
+    public void testRemoveAllVisibleTasks() throws Exception {
+        mRecentTasks.setParameters(-1 /* min */, 3 /* max */, 100 /* ms */);
+
+        // Create some set of tasks, some of which are visible and some are not
+        TaskRecord t1 = createTaskBuilder("com.android.pkg1", ".Task1").build();
+        mRecentTasks.add(t1);
+        mRecentTasks.add(setTaskActivityType(
+                createTaskBuilder("com.android.pkg1", ".HomeTask").build(),
+                ACTIVITY_TYPE_HOME));
+        TaskRecord t2 = createTaskBuilder("com.android.pkg2", ".Task2").build();
+        mRecentTasks.add(t2);
+        mRecentTasks.add(setTaskWindowingMode(
+                createTaskBuilder("com.android.pkg1", ".PipTask").build(),
+                WINDOWING_MODE_PINNED));
+        TaskRecord t3 = createTaskBuilder("com.android.pkg3", ".Task3").build();
+        mRecentTasks.add(t3);
+
+        // Create some more tasks that are out of visible range, but are still visible
+        TaskRecord t4 = createTaskBuilder("com.android.pkg3", ".Task4").build();
+        mRecentTasks.add(t4);
+        TaskRecord t5 = createTaskBuilder("com.android.pkg3", ".Task5").build();
+        mRecentTasks.add(t5);
+
+        // Create some more tasks that are out of the active session range, but are still visible
+        TaskRecord t6 = createTaskBuilder("com.android.pkg3", ".Task6").build();
+        t6.lastActiveTime = SystemClock.elapsedRealtime() - 200;
+        mRecentTasks.add(t6);
+        TaskRecord t7 = createTaskBuilder("com.android.pkg3", ".Task7").build();
+        t7.lastActiveTime = SystemClock.elapsedRealtime() - 200;
+        mRecentTasks.add(t7);
+
+        // Remove all the visible tasks and ensure that they are removed
+        mRecentTasks.removeAllVisibleTasks();
+        assertTrimmed(t1, t2, t3, t4, t5, t6, t7);
+    }
+
+    @Test
     public void testNotRecentsComponent_denyApiAccess() throws Exception {
         doReturn(PackageManager.PERMISSION_DENIED).when(mService)
                 .checkGetTasksPermission(anyString(), anyInt(), anyInt());
@@ -754,6 +780,22 @@
         return task;
     }
 
+    private TaskRecord setTaskActivityType(TaskRecord task,
+            @WindowConfiguration.ActivityType int activityType) {
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setActivityType(activityType);
+        task.onConfigurationChanged(config1);
+        return task;
+    }
+
+    private TaskRecord setTaskWindowingMode(TaskRecord task,
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        Configuration config1 = new Configuration();
+        config1.windowConfiguration.setWindowingMode(windowingMode);
+        task.onConfigurationChanged(config1);
+        return task;
+    }
+
     private boolean arrayContainsUser(int[] userIds, int targetUserId) {
         Arrays.sort(userIds);
         return Arrays.binarySearch(userIds, targetUserId) >= 0;
@@ -880,7 +922,7 @@
         }
 
         @Override
-        public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
+        public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed, boolean killProcess) {
             if (wasTrimmed) {
                 trimmed.add(task);
             }