Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 340a523b authored by Riddle Hsu's avatar Riddle Hsu
Browse files

Remove trimmable tasks on activity idle

Previously, the unreachable/inactive tasks may be removed when
adding a task to recents list. That may causes the indirect caller
to encounter index out of bounds exception when iteration tasks.

Because activity-idle should be a standalone event that it should
not be called in any loop, and it is also the place to handle
stopping and finishing activities. So it is a relatively safe place
to remove the trimmable tasks. Besides, the movement of invocation
may avoid additional time when launching activity that triggers trim.

Also simplify the setup of RecentTasksTest to reduce 60%+ execution
time of the test class.

Bug: 169124675
Test: atest RecentTasksTest
Change-Id: I49003b834ac55a44bf36ad59f920d4e662f9e427
parent efd3defd
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1315,6 +1315,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
        if (mRootWindowContainer.allResumedActivitiesIdle()) {
            if (r != null) {
                mService.scheduleAppGcsLocked();
                mRecentTasks.onActivityIdle(r);
            }

            if (mLaunchingActivityWakeLock.isHeld()) {
+20 −14
Original line number Diff line number Diff line
@@ -181,6 +181,9 @@ class RecentTasks {
    /** The non-empty tasks that are removed from recent tasks (see {@link #removeForAddTask}). */
    private final ArrayList<Task> mHiddenTasks = new ArrayList<>();

    /** Whether to trim inactive tasks when activities are idle. */
    private boolean mCheckTrimmableTasksOnIdle;

    // These values are generally loaded from resources, but can be set dynamically in the tests
    private boolean mHasVisibleRecentTasks;
    private int mGlobalMaxNumTasks;
@@ -1045,16 +1048,6 @@ class RecentTasks {
    void add(Task task) {
        if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "add: task=" + task);

        // Only allow trimming task if it is not updating visibility for activities, so the caller
        // doesn't need to handle unexpected size and index when looping task containers.
        final boolean canTrimTask = !mSupervisor.inActivityVisibilityUpdate();

        // Clean up the hidden tasks when going to home because the user may not be unable to return
        // to the task from recents.
        if (canTrimTask && !mHiddenTasks.isEmpty() && task.isActivityTypeHome()) {
            removeUnreachableHiddenTasks(task.getWindowingMode());
        }

        final boolean isAffiliated = task.mAffiliatedTaskId != task.mTaskId
                || task.mNextAffiliateTaskId != INVALID_TASK_ID
                || task.mPrevAffiliateTaskId != INVALID_TASK_ID;
@@ -1183,10 +1176,7 @@ class RecentTasks {
            cleanupLocked(task.mUserId);
        }

        // Trim the set of tasks to the active set
        if (canTrimTask) {
            trimInactiveRecentTasks();
        }
        mCheckTrimmableTasksOnIdle = true;
        notifyTaskPersisterLocked(task, false /* flush */);
    }

@@ -1212,6 +1202,22 @@ class RecentTasks {
        notifyTaskRemoved(task, false /* wasTrimmed */, false /* killProcess */);
    }

    /**
     * Called when an activity reports idle. The caller should not be in any loop that iterates
     * window hierarchy. so it is safe (e.g. index out of bound) to remove inactive tasks.
     */
    void onActivityIdle(ActivityRecord r) {
        // 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() && r.isActivityTypeHome()) {
            removeUnreachableHiddenTasks(r.getWindowingMode());
        }
        if (mCheckTrimmableTasksOnIdle) {
            mCheckTrimmableTasksOnIdle = false;
            trimInactiveRecentTasks();
        }
    }

    /**
     * Trims the recents task list to the global max number of recents.
     */
+47 −101
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.server.wm;

import static android.app.ActivityManager.RECENT_WITH_EXCLUDED;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -48,7 +47,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -58,12 +57,10 @@ import static java.lang.Integer.MAX_VALUE;
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.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.RemoteException;
@@ -100,14 +97,14 @@ public class RecentTasksTest extends WindowTestsBase {
    private static final int TEST_USER_0_ID = 0;
    private static final int TEST_USER_1_ID = 10;
    private static final int TEST_QUIET_USER_ID = 20;
    private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
    private static final UserInfo DEFAULT_USER_INFO = new UserInfo(TEST_USER_0_ID,
            "default", 0 /* flags */);
    private static final UserInfo QUIET_PROFILE_USER_INFO = new UserInfo(TEST_QUIET_USER_ID,
            "quiet_profile", null /* iconPath */, UserInfo.FLAG_QUIET_MODE,
            UserManager.USER_TYPE_PROFILE_MANAGED);
    private static final int INVALID_STACK_ID = 999;

    private TaskDisplayArea mTaskContainer;
    private Task mStack;
    private TestTaskPersister mTaskPersister;
    private TestRecentTasks mRecentTasks;
    private TestRunningTasks mRunningTasks;
@@ -133,8 +130,6 @@ public class RecentTasksTest extends WindowTestsBase {
        mRunningTasks = new TestRunningTasks();
        mAtm.mTaskSupervisor.setRunningTasks(mRunningTasks);

        mStack = mTaskContainer.createStack(
                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
        mCallbacksRecorder = new CallbacksRecorder();
        mRecentTasks.registerCallback(mCallbacksRecorder);

@@ -214,9 +209,9 @@ public class RecentTasksTest extends WindowTestsBase {

        // Add N+1 tasks to ensure the previous task is trimmed
        mRecentTasks.add(mTasks.get(1));
        triggerTrimAndAssertTrimmed(mTasks.get(0));
        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(0)), anyBoolean());
        verify(mTaskPersister, times(1)).wakeup(eq(mTasks.get(1)), anyBoolean());
        assertTrimmed(mTasks.get(0));
    }

    @Test
@@ -278,31 +273,6 @@ public class RecentTasksTest extends WindowTestsBase {
        assertThat(mCallbacksRecorder.mRemoved).isEmpty();
    }

    @Test
    public void testAddTasksInVisibilityUpdate_expectNoTrim() {
        mRecentTasks.setOnlyTestVisibleRange();
        mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
        mRecentTasks.add(mTasks.get(0));

        doAnswer(invocation -> {
            assertTrue(mSupervisor.inActivityVisibilityUpdate());
            // Simulate an activity is resumed by EnsureActivitiesVisibleHelper. If its state is
            // change to RESUMED, it will also be added to recents.
            mRecentTasks.add(mTasks.get(1));
            invocation.callRealMethod();
            return null;
        }).when(mSupervisor).endActivityVisibilityUpdate();

        mTaskContainer.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */,
                false /* preserveWindows */, false /* notifyClients */, false /* userLeaving */);

        assertFalse(mSupervisor.inActivityVisibilityUpdate());
        assertThat(mCallbacksRecorder.mAdded).hasSize(2);
        // Expect nothing is trimmed because we don't want the loop of ensure-visibility to be
        // impacted by the arbitrary number of task removals.
        assertNoTasksTrimmed();
    }

    @Test
    public void testAddTasksMultipleTasks_expectRemovedNoTrim() {
        // Add multiple same-affinity non-document tasks, ensure that it removes the other task,
@@ -336,7 +306,7 @@ public class RecentTasksTest extends WindowTestsBase {
                .setParentTask(mTaskContainer.getRootHomeTask()).build();
        Task task2 = createTaskBuilder(".Task1")
                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
                .setParentTask(mStack).build();
                .build();
        mRecentTasks.add(task1);
        mRecentTasks.add(task2);
        assertThat(mCallbacksRecorder.mAdded).hasSize(2);
@@ -351,17 +321,15 @@ public class RecentTasksTest extends WindowTestsBase {
        // 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
        Task task1 = createTaskBuilder(".Task1")
                .setActivityType(ACTIVITY_TYPE_UNDEFINED)
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED);
        assertThat(task1.getActivityType()).isEqualTo(ACTIVITY_TYPE_UNDEFINED);
        mRecentTasks.add(task1);
        mCallbacksRecorder.clear();

        Task task2 = createTaskBuilder(".Task1")
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        assertEquals(ACTIVITY_TYPE_STANDARD, task2.getActivityType());
        mRecentTasks.add(task2);
@@ -375,18 +343,16 @@ public class RecentTasksTest extends WindowTestsBase {
    @Test
    public void testAddTaskCompatibleActivityTypeDifferentUser_expectNoRemove() {
        Task task1 = createTaskBuilder(".Task1")
                .setActivityType(ACTIVITY_TYPE_UNDEFINED)
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .setUserId(TEST_USER_0_ID)
                .build();
        setTaskActivityType(task1, ACTIVITY_TYPE_UNDEFINED);
        assertEquals(ACTIVITY_TYPE_UNDEFINED, task1.getActivityType());
        mRecentTasks.add(task1);
        mCallbacksRecorder.clear();

        Task task2 = createTaskBuilder(".Task1")
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .setUserId(TEST_USER_1_ID)
                .build();
        assertEquals(ACTIVITY_TYPE_STANDARD, task2.getActivityType());
@@ -401,18 +367,15 @@ public class RecentTasksTest extends WindowTestsBase {
    public void testAddTaskCompatibleWindowingMode_expectRemove() {
        Task task1 = createTaskBuilder(".Task1")
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        setTaskWindowingMode(task1, WINDOWING_MODE_UNDEFINED);
        assertEquals(WINDOWING_MODE_UNDEFINED, task1.getWindowingMode());
        doReturn(WINDOWING_MODE_UNDEFINED).when(task1).getWindowingMode();
        mRecentTasks.add(task1);
        mCallbacksRecorder.clear();

        Task task2 = createTaskBuilder(".Task1")
                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        setTaskWindowingMode(task2, WINDOWING_MODE_FULLSCREEN);
        assertEquals(WINDOWING_MODE_FULLSCREEN, task2.getWindowingMode());
        mRecentTasks.add(task2);

@@ -426,18 +389,16 @@ public class RecentTasksTest extends WindowTestsBase {
    @Test
    public void testAddTaskIncompatibleWindowingMode_expectNoRemove() {
        Task task1 = createTaskBuilder(".Task1")
                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        setTaskWindowingMode(task1, WINDOWING_MODE_FULLSCREEN);
        assertEquals(WINDOWING_MODE_FULLSCREEN, task1.getWindowingMode());
        mRecentTasks.add(task1);

        Task task2 = createTaskBuilder(".Task1")
                .setWindowingMode(WINDOWING_MODE_PINNED)
                .setFlags(FLAG_ACTIVITY_NEW_TASK)
                .setParentTask(mStack)
                .build();
        setTaskWindowingMode(task2, WINDOWING_MODE_PINNED);
        assertEquals(WINDOWING_MODE_PINNED, task2.getWindowingMode());
        mRecentTasks.add(task2);

@@ -453,19 +414,19 @@ public class RecentTasksTest extends WindowTestsBase {
        // Add task to recents
        final String taskAffinity = "affinity";
        final int uid = 10123;
        final Task task1 = createTaskBuilder(".Task1").setParentTask(mStack).build();
        final Task task1 = createTaskBuilder(".Task1").build();
        task1.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE);
        mRecentTasks.add(task1);

        // Add another task to recents, and make sure the previous task was removed.
        final Task task2 = createTaskBuilder(".Task2").setParentTask(mStack).build();
        final Task task2 = createTaskBuilder(".Task2").build();
        task2.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid, LAUNCH_MULTIPLE);
        mRecentTasks.add(task2);
        assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */,
                true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size());

        // Add another single-instance task to recents, and make sure no task is removed.
        final Task task3 = createTaskBuilder(".Task3").setParentTask(mStack).build();
        final Task task3 = createTaskBuilder(".Task3").build();
        task3.affinity = ActivityRecord.computeTaskAffinity(taskAffinity, uid,
                LAUNCH_SINGLE_INSTANCE);
        mRecentTasks.add(task3);
@@ -521,6 +482,7 @@ public class RecentTasksTest extends WindowTestsBase {
        // Go home to trigger the removal of untracked tasks.
        mRecentTasks.add(createTaskBuilder(".Home").setParentTask(mTaskContainer.getRootHomeTask())
                .build());
        triggerIdleToTrim();

        // The task was added into recents again so it is not hidden and shouldn't be removed.
        assertNotNull(task1.getTopNonFinishingActivity());
@@ -615,7 +577,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(mTasks.get(2));

        // Ensure that the last task was trimmed as an inactive task
        assertTrimmed(mTasks.get(0));
        triggerTrimAndAssertTrimmed(mTasks.get(0));
    }

    @Test
@@ -630,7 +592,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(mTasks.get(1));

        // Ensure that the quiet user's tasks was trimmed once the new tasks were added
        assertTrimmed(qt1, qt2);
        triggerTrimAndAssertTrimmed(qt1, qt2);
    }

    @Test
@@ -645,12 +607,8 @@ public class RecentTasksTest extends WindowTestsBase {
        // Force a small sleep just beyond the session duration
        SystemClock.sleep(75);

        Task t2 = createTaskBuilder(".Task2").build();
        t2.touchActiveTime();
        mRecentTasks.add(t2);

        // Assert that the old task has been removed due to being out of the active session
        assertTrimmed(t1);
        triggerTrimAndAssertTrimmed(t1);
    }

    @Test
@@ -672,15 +630,15 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(excludedTask2);

        // The last excluded task should be trimmed, while the first-most excluded task should not
        assertTrimmed(excludedTask1);
        triggerTrimAndAssertTrimmed(excludedTask1);
    }

    @Test
    public void testVisibleTasks_excludedFromRecents_firstTaskNotVisible() {
        // Create some set of tasks, some of which are visible and some are not
        Task homeTask = setTaskActivityType(
                createTaskBuilder("com.android.pkg1", ".HomeTask").build(),
                ACTIVITY_TYPE_HOME);
        Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
                .setParentTask(mTaskContainer.getRootHomeTask())
                .build();
        homeTask.mUserSetupComplete = true;
        mRecentTasks.add(homeTask);
        Task excludedTask1 = createTaskBuilder(".ExcludedTask1")
@@ -699,9 +657,9 @@ public class RecentTasksTest extends WindowTestsBase {
        Task t1 = createTaskBuilder("com.android.pkg1", ".Task1").build();
        t1.mUserSetupComplete = true;
        mRecentTasks.add(t1);
        Task homeTask = setTaskActivityType(
                createTaskBuilder("com.android.pkg1", ".HomeTask").build(),
                ACTIVITY_TYPE_HOME);
        Task homeTask = createTaskBuilder("com.android.pkg1", ".HomeTask")
                .setParentTask(mTaskContainer.getRootHomeTask())
                .build();
        homeTask.mUserSetupComplete = true;
        mRecentTasks.add(homeTask);
        Task excludedTask1 = createTaskBuilder(".ExcludedTask1")
@@ -739,7 +697,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(mTasks.get(4));

        // Ensure that there are a minimum number of tasks regardless of session length
        assertNoTasksTrimmed();
        triggerTrimAndAssertNoTasksTrimmed();
    }

    @Test
@@ -754,7 +712,7 @@ public class RecentTasksTest extends WindowTestsBase {
        }

        // Ensure that only the last number of max tasks are kept
        assertTrimmed(mTasks.get(0), mTasks.get(1));
        triggerTrimAndAssertTrimmed(mTasks.get(0), mTasks.get(1));
    }

    /**
@@ -782,12 +740,12 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(mTasks.get(3));

        // excludedTask is not trimmed.
        assertTrimmed(mTasks.get(0));
        triggerTrimAndAssertTrimmed(mTasks.get(0));

        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);

        // Only visible tasks removed.
        assertTrimmed(mTasks.get(0), mTasks.get(1), mTasks.get(2), mTasks.get(3));
        triggerTrimAndAssertTrimmed(mTasks.get(0), mTasks.get(1), mTasks.get(2), mTasks.get(3));
    }

    @Test
@@ -910,7 +868,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());
        mRecentTasks.add(createTaskBuilder(".Task3").setParentTask(aboveHomeStack).build());

        assertNoTasksTrimmed();
        triggerTrimAndAssertNoTasksTrimmed();
    }

    @Test
@@ -932,7 +890,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(createTaskBuilder(".HomeTask1").setParentTask(homeStack).build());
        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(aboveHomeStack).build());

        assertTrimmed(behindHomeTask);
        triggerTrimAndAssertTrimmed(behindHomeTask);
    }

    @Test
@@ -951,7 +909,7 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(createTaskBuilder(".Task2").setParentTask(otherDisplayStack).build());
        mRecentTasks.add(createTaskBuilder(".HomeTask2").setParentTask(homeStack).build());

        assertNoTasksTrimmed();
        triggerTrimAndAssertNoTasksTrimmed();
    }

    @Test
@@ -978,14 +936,12 @@ public class RecentTasksTest extends WindowTestsBase {
        // Create some set of tasks, some of which are visible and some are not
        Task t1 = createTaskBuilder("com.android.pkg1", ".Task1").build();
        mRecentTasks.add(t1);
        mRecentTasks.add(setTaskActivityType(
                createTaskBuilder("com.android.pkg1", ".HomeTask").build(),
                ACTIVITY_TYPE_HOME));
        mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".HomeTask")
                .setParentTask(mTaskContainer.getRootHomeTask()).build());
        Task t2 = createTaskBuilder("com.android.pkg2", ".Task2").build();
        mRecentTasks.add(t2);
        mRecentTasks.add(setTaskWindowingMode(
                createTaskBuilder("com.android.pkg1", ".PipTask").build(),
                WINDOWING_MODE_PINNED));
        mRecentTasks.add(createTaskBuilder("com.android.pkg1", ".PipTask")
                .setWindowingMode(WINDOWING_MODE_PINNED).build());
        Task t3 = createTaskBuilder("com.android.pkg3", ".Task3").build();
        mRecentTasks.add(t3);

@@ -1005,7 +961,7 @@ public class RecentTasksTest extends WindowTestsBase {

        // Remove all the visible tasks and ensure that they are removed
        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);
        assertTrimmed(t1, t2, t3, t4, t5, t6, t7);
        triggerTrimAndAssertTrimmed(t1, t2, t3, t4, t5, t6, t7);
    }

    @Test
@@ -1030,7 +986,7 @@ public class RecentTasksTest extends WindowTestsBase {

        // Remove all the visible tasks and ensure that they are removed
        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);
        assertTrimmed(t1, t2);
        triggerTrimAndAssertTrimmed(t1, t2);
    }

    @Test
@@ -1040,7 +996,6 @@ public class RecentTasksTest extends WindowTestsBase {
        mRecentTasks.add(task);
        // Only keep the task in RecentTasks.
        task.removeIfPossible();
        mStack.removeIfPossible();

        // The following APIs should not restore task from recents to the active list.
        assertNotRestoreTask(() -> mAtm.setFocusedTask(taskId));
@@ -1236,7 +1191,6 @@ public class RecentTasksTest extends WindowTestsBase {
    private TaskBuilder createTaskBuilder(String packageName, String className) {
        return new TaskBuilder(mAtm.mTaskSupervisor)
                .setComponent(new ComponentName(packageName, className))
                .setParentTask(mStack)
                .setUserId(TEST_USER_0_ID);
    }

@@ -1253,27 +1207,19 @@ public class RecentTasksTest extends WindowTestsBase {
        return task;
    }

    private Task setTaskActivityType(Task task,
            @WindowConfiguration.ActivityType int activityType) {
        Configuration config1 = new Configuration();
        config1.windowConfiguration.setActivityType(activityType);
        task.onConfigurationChanged(config1);
        return task;
    }

    private Task setTaskWindowingMode(Task task,
            @WindowConfiguration.WindowingMode int windowingMode) {
        Configuration config1 = new Configuration();
        config1.windowConfiguration.setWindowingMode(windowingMode);
        task.onConfigurationChanged(config1);
        return task;
    private void triggerIdleToTrim() {
        doNothing().when(mAtm).scheduleAppGcsLocked();
        final ActivityRecord r = mRootWindowContainer.topRunningActivity();
        mSupervisor.activityIdleInternal(r != null ? r : mock(ActivityRecord.class),
                false /* fromTimeout */, false /* processPausingActivities */, null /* config */);
    }

    private void assertNoTasksTrimmed() {
        assertTrimmed();
    private void triggerTrimAndAssertNoTasksTrimmed() {
        triggerTrimAndAssertTrimmed();
    }

    private void assertTrimmed(Task... tasks) {
    private void triggerTrimAndAssertTrimmed(Task... tasks) {
        triggerIdleToTrim();
        final ArrayList<Task> trimmed = mCallbacksRecorder.mTrimmed;
        final ArrayList<Task> removed = mCallbacksRecorder.mRemoved;
        assertWithMessage("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size())