Don't trigger getRecentTasks() calls when waiting for an old call

TaskbarRecentAppsController#reloadRecentTasksIfNeeded() triggers a call
to fetch Recents tasks, that call is expensive as it goes through WM
Shell and the system server. If we trigger reloadRecentTasksIfNeeded()
multiple times we're queueing up multiple calls to fetch Recents tasks.

With this CL we avoid queueing up multiple calls, and instead just make
one new call when the old one finishes. That way we can only ever have
one call getRecentTasks() call triggered through
reloadRecentTasksIfNeeded() posted at any one time.

Bug: 415090968
Flag: com.android.window.flags.enable_taskbar_recent_tasks_throttle_bugfix
Test: TaskbarRecentAppsControllerTest
Change-Id: I58b66e4564af4e64837317a9de7be398395d8568
This commit is contained in:
Gustav Sennton
2025-05-14 13:28:51 +00:00
parent 894224b3d2
commit ffa5c2ad83
2 changed files with 130 additions and 18 deletions

View File

@@ -16,6 +16,8 @@
package com.android.launcher3.taskbar
import android.content.Context
import android.util.Log
import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BubbleTextView.RunningAppState
@@ -57,6 +59,9 @@ class TaskbarRecentAppsController(
}
}
val enableRecentTasksThrottle =
DesktopExperienceFlags.ENABLE_TASKBAR_RECENT_TASKS_THROTTLE_BUGFIX.isTrue
// TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
var canShowRecentApps = enableRecentsInTaskbar()
@VisibleForTesting
@@ -175,6 +180,11 @@ class TaskbarRecentAppsController(
// tasks again if we have already requested it and the task list has not changed
private var taskListChangeId = -1
// Whether we're currently loading recents tasks
private var loadingRecentsTasks = false
// Whether we need to reload recents tasks when the current loading operation is finished
private var needsRecentsTasksReload = false
// Whether we've loaded recents tasks at least once
private var recentTasksLoaded = false
fun init(taskbarControllers: TaskbarControllers, previousShownTasks: List<GroupTask>) {
@@ -240,24 +250,34 @@ class TaskbarRecentAppsController(
}
private fun reloadRecentTasksIfNeeded() {
if (!recentsModel.isTaskListValid(taskListChangeId)) {
taskListChangeId =
recentsModel.getTasks(RecentsFilterState.EMPTY_FILTER) { tasks ->
recentTasksLoaded = true
allRecentTasks = tasks
val oldRunningTaskdIds = runningTaskIds
val oldMinimizedTaskIds = minimizedTaskIds
desktopTasks =
allRecentTasks.filterIsInstance<DesktopTask>().flatMap { it.tasks }
val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
if (
onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
) {
controllers.taskbarViewController.commitRunningAppsToUI()
}
}
if (recentsModel.isTaskListValid(taskListChangeId)) return
if (enableRecentTasksThrottle && loadingRecentsTasks) {
Log.v(TAG, "reloadRecentTasksIfNeeded: tried to reload while loading recents tasks")
needsRecentsTasksReload = true
return
}
Log.v(TAG, "reloadRecentTasksIfNeeded: load recents tasks")
// Only indicate that recents tasks are loading if the enableRecentTasksThrottle flag is on
loadingRecentsTasks = enableRecentTasksThrottle
taskListChangeId =
recentsModel.getTasks(RecentsFilterState.EMPTY_FILTER) { tasks ->
loadingRecentsTasks = false
recentTasksLoaded = true
allRecentTasks = tasks
val oldRunningTaskdIds = runningTaskIds
val oldMinimizedTaskIds = minimizedTaskIds
desktopTasks = allRecentTasks.filterIsInstance<DesktopTask>().flatMap { it.tasks }
val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
if (onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged) {
controllers.taskbarViewController.commitRunningAppsToUI()
}
if (needsRecentsTasksReload) {
Log.v(TAG, "reloadRecentTasksIfNeeded: reload recents tasks")
needsRecentsTasksReload = false
reloadRecentTasksIfNeeded()
}
}
}
/**
@@ -462,6 +482,8 @@ class TaskbarRecentAppsController(
get() = tasks.map { task -> task.key.packageName }
private companion object {
private val TAG = "TaskbarRecentAppsController"
const val MAX_RECENT_TASKS = 2
}
}

View File

@@ -24,7 +24,9 @@ import android.content.res.Resources
import android.graphics.Rect
import android.os.Process
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.annotation.UiThreadTest
import com.android.internal.R
@@ -60,6 +62,7 @@ import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.never
import org.mockito.kotlin.times
@@ -72,6 +75,7 @@ import org.mockito.kotlin.whenever
class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
@get:Rule val mockitoRule = MockitoJUnit.rule()
@get:Rule val setFlagsRule = SetFlagsRule()
@get:Rule
val disableControllerForCertainTestsWatcher =
object : TestWatcher() {
@@ -118,6 +122,16 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
recentAppsController = TaskbarRecentAppsController(mockContext, mockRecentsModel)
recentAppsController.canShowRunningApps = canShowRunningAndRecentAppsAtInit
recentAppsController.canShowRecentApps = canShowRunningAndRecentAppsAtInit
// To ensure the initial getTasks() call is not seen as "loading" for the rest of the test,
// execute its callback.
doAnswer {
val callback: Consumer<ArrayList<GroupTask>> = it.getArgument(1)
callback.accept(arrayListOf())
taskListChangeId
}
.whenever(mockRecentsModel)
.getTasks(any(), any<Consumer<List<GroupTask>>>())
recentAppsController.init(taskbarControllers, emptyList())
taskbarControllers.onPostInit()
@@ -148,7 +162,7 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
runningTasks = listOf(createTask(1, RUNNING_APP_PACKAGE_1)),
recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
)
verify(mockRecentsModel, never()).getTasks(any<Consumer<List<GroupTask>>>())
verify(mockRecentsModel, never()).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
@@ -166,6 +180,82 @@ class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
verify(mockRecentsModel, times(1)).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
@EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENT_TASKS_THROTTLE_BUGFIX)
fun recentTasksChanged_duringGetTasksLoading_dontCallGetTasks() {
// getTasks() should have been called once from init().
verify(mockRecentsModel, times(1)).getTasks(any(), any<Consumer<List<GroupTask>>>())
// Override the mock answer for getTasks() so it doesn't call the callback immediately.
doAnswer { taskListChangeId }
.whenever(mockRecentsModel)
.getTasks(any(), any<Consumer<List<GroupTask>>>())
recentTasksChangedListener?.onRecentTasksChanged()
// By not invoking the callback passed to getTasks() we here emulate getTasks() loading.
recentTasksChangedListener?.onRecentTasksChanged()
// getTasks() is only called two times overall (init + once more).
verify(mockRecentsModel, times(2)).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
@EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENT_TASKS_THROTTLE_BUGFIX)
fun recentTasksChanged_duringGetTasksLoading_getTasksCalledWhenLoadingDone() {
val callbackCaptor = argumentCaptor<Consumer<List<GroupTask>>>()
// getTasks() should have been called once from init().
verify(mockRecentsModel, times(1)).getTasks(any(), callbackCaptor.capture())
// Override the mock answer for getTasks() so it doesn't call the callback immediately.
doAnswer { taskListChangeId }
.whenever(mockRecentsModel)
.getTasks(any(), any<Consumer<List<GroupTask>>>())
recentTasksChangedListener?.onRecentTasksChanged()
// By not invoking the callback passed to getTasks() we here emulate getTasks() loading.
recentTasksChangedListener?.onRecentTasksChanged()
callbackCaptor.lastValue.accept(emptyList())
// getTasks() is called again now that the first getTasks() call finished.
verify(mockRecentsModel, times(3)).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
@DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENT_TASKS_THROTTLE_BUGFIX)
fun recentTasksChanged_duringGetTasksLoading_flagDisabled_callGetTasks() {
// getTasks() should have been called once from init().
verify(mockRecentsModel, times(1)).getTasks(any(), any<Consumer<List<GroupTask>>>())
// Override the mock answer for getTasks() so it doesn't call the callback immediately.
doAnswer { taskListChangeId }
.whenever(mockRecentsModel)
.getTasks(any(), any<Consumer<List<GroupTask>>>())
recentTasksChangedListener?.onRecentTasksChanged()
// By not invoking the callback passed to getTasks() we here emulate getTasks() loading.
recentTasksChangedListener?.onRecentTasksChanged()
// getTasks() is called once per onRecentTasksChanged() invocation (and once at init)
verify(mockRecentsModel, times(3)).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
@DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENT_TASKS_THROTTLE_BUGFIX)
fun recentTasksChanged_duringGetTasksLoading_flagDisabled_getTasksNotCalledWhenLoadingDone() {
val callbackCaptor = argumentCaptor<Consumer<List<GroupTask>>>()
// getTasks() should have been called once from init().
verify(mockRecentsModel, times(1)).getTasks(any(), callbackCaptor.capture())
// Override the mock answer for getTasks() so it doesn't call the callback immediately.
doAnswer { taskListChangeId }
.whenever(mockRecentsModel)
.getTasks(any(), any<Consumer<List<GroupTask>>>())
recentTasksChangedListener?.onRecentTasksChanged()
recentTasksChangedListener?.onRecentTasksChanged()
verify(mockRecentsModel, times(3)).getTasks(any(), any<Consumer<List<GroupTask>>>())
callbackCaptor.lastValue.accept(emptyList())
// getTasks() is called once per onRecentTasksChanged() invocation (and once at init)
verify(mockRecentsModel, times(3)).getTasks(any(), any<Consumer<List<GroupTask>>>())
}
@Test
fun getDesktopItemState_nullItemInfo_returnsNotRunning() {
setInDesktopMode(true)