Merge "Make OverviewCommandHelper commands and OverviewCommandHelperTest display-aware" into main

This commit is contained in:
Treehugger Robot
2025-03-14 13:18:28 -07:00
committed by Android (Google) Code Review
2 changed files with 153 additions and 24 deletions
@@ -91,27 +91,33 @@ constructor(
*/
private var keyboardTaskFocusIndex = -1
// TODO (b/397942185): get per-display interface
private val containerInterface: BaseContainerInterface<*, *>
get() = overviewComponentObserver.getContainerInterface(DEFAULT_DISPLAY)
private fun getContainerInterface(displayId: Int) =
overviewComponentObserver.getContainerInterface(displayId)
// TODO (b/397942185): get per-display RecentsView
private val visibleRecentsView: RecentsView<*, *>?
get() = containerInterface.getVisibleRecentsView<RecentsView<*, *>>()
private fun getVisibleRecentsView(displayId: Int) =
getContainerInterface(displayId).getVisibleRecentsView<RecentsView<*, *>>()
/**
* Adds a command to be executed next, after all pending tasks are completed. Max commands that
* can be queued is [.MAX_QUEUE_SIZE]. Requests after reaching that limit will be silently
* dropped.
*
* @param type The type of the command
* @param onDisplays The display to run the command on
*/
@BinderThread
fun addCommand(type: CommandType): CommandInfo? {
@JvmOverloads
fun addCommand(
type: CommandType,
displayId: Int = DEFAULT_DISPLAY,
isLastOfBatch: Boolean = true,
): CommandInfo? {
if (commandQueue.size >= MAX_QUEUE_SIZE) {
Log.d(TAG, "command not added: $type - queue is full ($commandQueue).")
return null
}
val command = CommandInfo(type)
val command = CommandInfo(type, displayId = displayId, isLastOfBatch = isLastOfBatch)
commandQueue.add(command)
Log.d(TAG, "command added: $command")
@@ -129,6 +135,35 @@ constructor(
return command
}
@BinderThread
fun addCommandsForDisplays(type: CommandType, displayIds: IntArray): CommandInfo? {
if (displayIds.isEmpty()) return null
var lastCommand: CommandInfo? = null
displayIds.forEachIndexed({ i, displayId ->
lastCommand = addCommand(type, displayId, i == displayIds.size - 1)
})
return lastCommand
}
@BinderThread
fun addCommandsForAllDisplays(type: CommandType) =
addCommandsForDisplays(
type,
recentsDisplayModel.activeDisplayResources
.map { resource -> resource.displayId }
.toIntArray(),
)
@BinderThread
fun addCommandsForDisplaysExcept(type: CommandType, excludedDisplayId: Int) =
addCommandsForDisplays(
type,
recentsDisplayModel.activeDisplayResources
.map { resource -> resource.displayId }
.filter { displayId -> displayId != excludedDisplayId }
.toIntArray(),
)
fun canStartHomeSafely(): Boolean = commandQueue.isEmpty() || commandQueue.first().type == HOME
/** Clear pending or completed commands from the queue */
@@ -143,7 +178,7 @@ constructor(
* completion (returns false).
*/
@UiThread
private fun processNextCommand() =
private fun processNextCommand(): Unit =
traceSection("OverviewCommandHelper.processNextCommand") {
val command: CommandInfo? = commandQueue.firstOrNull()
if (command == null) {
@@ -182,7 +217,7 @@ constructor(
*/
@VisibleForTesting
fun executeCommand(command: CommandInfo, onCallbackResult: () -> Unit): Boolean {
val recentsView = visibleRecentsView
val recentsView = getVisibleRecentsView(command.displayId)
Log.d(TAG, "executeCommand: $command - visibleRecentsView: $recentsView")
return if (recentsView != null) {
executeWhenRecentsIsVisible(command, recentsView, onCallbackResult)
@@ -230,6 +265,7 @@ constructor(
launchTask(recentsView, taskView, command, onCallbackResult)
}
}
TOGGLE -> {
launchTask(
recentsView,
@@ -238,6 +274,7 @@ constructor(
onCallbackResult,
)
}
HOME -> {
recentsView.startHome()
true
@@ -294,6 +331,7 @@ constructor(
command: CommandInfo,
onCallbackResult: () -> Unit,
): Boolean {
val containerInterface = getContainerInterface(command.displayId)
val recentsViewContainer = containerInterface.getCreatedContainer()
val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
val deviceProfile = recentsViewContainer?.getDeviceProfile()
@@ -335,6 +373,7 @@ constructor(
if (keyboardTaskFocusIndex == -1) return true
}
KEYBOARD_INPUT ->
if (uiController != null && deviceProfile?.isTablet == true) {
if (
@@ -348,6 +387,7 @@ constructor(
} else {
keyboardTaskFocusIndex = 0
}
HOME -> {
ActiveGestureProtoLogProxy.logExecuteHomeCommand()
// Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
@@ -357,12 +397,14 @@ constructor(
touchInteractionService.startActivity(overviewComponentObserver.homeIntent)
return true
}
SHOW ->
// When Recents is not currently visible, the command's type is SHOW
// when overview is triggered via the keyboard overview button or Action+Tab
// keys (Not Alt+Tab which is KQS). The overview button on-screen in 3-button
// nav is TYPE_TOGGLE.
keyboardTaskFocusIndex = 0
TOGGLE -> {}
}
@@ -378,7 +420,7 @@ constructor(
Log.d(TAG, "switching to Overview state - onAnimationStart: $command")
super.onAnimationStart(animation)
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
logShowOverviewFrom(command)
}
override fun onAnimationEnd(animation: Animator) {
@@ -402,7 +444,7 @@ constructor(
val gestureState =
touchInteractionService.createGestureState(
focusedDisplayId,
command.displayId,
GestureState.DEFAULT_STATE,
GestureState.TrackpadGestureType.NONE,
)
@@ -432,7 +474,7 @@ constructor(
}
updateRecentsViewFocus(command)
logShowOverviewFrom(command.type)
logShowOverviewFrom(command)
containerInterface.runOnInitBackgroundStateUI {
Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
interactionHandler.onGestureEnded(
@@ -456,12 +498,13 @@ constructor(
}
}
val displayId = gestureState.displayId
val taskAnimationManager =
recentsDisplayModel.getTaskAnimationManager(displayId)
recentsDisplayModel.getTaskAnimationManager(command.displayId)
?: run {
Log.e(TAG, "No TaskAnimationManager found for display $displayId")
ActiveGestureProtoLogProxy.logOnTaskAnimationManagerNotAvailable(displayId)
Log.e(TAG, "No TaskAnimationManager found for display ${command.displayId}")
ActiveGestureProtoLogProxy.logOnTaskAnimationManagerNotAvailable(
command.displayId
)
return false
}
if (taskAnimationManager.isRecentsAnimationRunning) {
@@ -526,8 +569,13 @@ constructor(
}
private fun updateRecentsViewFocus(command: CommandInfo) {
val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
if (command.type != KEYBOARD_INPUT && command.type != HIDE && command.type != SHOW) {
val recentsView: RecentsView<*, *> = getVisibleRecentsView(command.displayId) ?: return
if (
command.type != KEYBOARD_INPUT &&
command.type != HIDE &&
command.type != SHOW &&
command.type != TOGGLE
) {
return
}
@@ -547,7 +595,7 @@ constructor(
}
private fun onRecentsViewFocusUpdated(command: CommandInfo) {
val recentsView: RecentsView<*, *> = visibleRecentsView ?: return
val recentsView: RecentsView<*, *> = getVisibleRecentsView(command.displayId) ?: return
if (command.type != HIDE || keyboardTaskFocusIndex == PagedView.INVALID_PAGE) {
return
}
@@ -565,10 +613,11 @@ constructor(
return true
}
private fun logShowOverviewFrom(commandType: CommandType) {
private fun logShowOverviewFrom(command: CommandInfo) {
val containerInterface = getContainerInterface(command.displayId)
val container = containerInterface.getCreatedContainer() ?: return
val event =
when (commandType) {
when (command.type) {
SHOW -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
HIDE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
TOGGLE -> LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
@@ -601,6 +650,8 @@ constructor(
var status: CommandStatus = CommandStatus.IDLE,
val createTime: Long = SystemClock.elapsedRealtime(),
private var animationCallbacks: RecentsAnimationCallbacks? = null,
val displayId: Int = DEFAULT_DISPLAY,
val isLastOfBatch: Boolean = true,
) {
fun setAnimationCallbacks(recentsAnimationCallbacks: RecentsAnimationCallbacks) {
this.animationCallbacks = recentsAnimationCallbacks
@@ -17,6 +17,7 @@
package com.android.quickstep
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.filters.SmallTest
import com.android.launcher3.Flags
import com.android.launcher3.util.LauncherMultivalentJUnit
@@ -25,6 +26,7 @@ import com.android.launcher3.util.rule.setFlags
import com.android.quickstep.OverviewCommandHelper.CommandInfo
import com.android.quickstep.OverviewCommandHelper.CommandInfo.CommandStatus
import com.android.quickstep.OverviewCommandHelper.CommandType
import com.android.quickstep.fallback.window.RecentsDisplayModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
@@ -41,9 +43,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@SmallTest
@RunWith(LauncherMultivalentJUnit::class)
@@ -57,18 +59,38 @@ class OverviewCommandHelperTest {
private var pendingCallbacksWithDelays = mutableListOf<Long>()
private val recentsDisplayModel: RecentsDisplayModel = mock()
private val defaultDisplayResource: RecentsDisplayModel.RecentsDisplayResource = mock()
private val secondaryDisplayResource: RecentsDisplayModel.RecentsDisplayResource = mock()
private val executeCommandDisplayIds = mutableListOf<Int>()
private fun setupDefaultDisplay() {
whenever(defaultDisplayResource.displayId).thenReturn(DEFAULT_DISPLAY)
whenever(recentsDisplayModel.activeDisplayResources)
.thenReturn(listOf(defaultDisplayResource))
}
private fun setupMultipleDisplays() {
whenever(defaultDisplayResource.displayId).thenReturn(DEFAULT_DISPLAY)
whenever(secondaryDisplayResource.displayId).thenReturn(1)
whenever(recentsDisplayModel.activeDisplayResources)
.thenReturn(listOf(defaultDisplayResource, secondaryDisplayResource))
}
@Suppress("UNCHECKED_CAST")
@Before
fun setup() {
setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_OVERVIEW_COMMAND_HELPER_TIMEOUT)
setupDefaultDisplay()
sut =
spy(
OverviewCommandHelper(
touchInteractionService = mock(),
overviewComponentObserver = mock(),
dispatcherProvider = TestDispatcherProvider(dispatcher),
recentsDisplayModel = mock(),
recentsDisplayModel = recentsDisplayModel,
focusState = mock(),
taskbarManager = mock(),
)
@@ -86,6 +108,8 @@ class OverviewCommandHelperTest {
}
}
}
val commandInfo = invocation.arguments[0] as CommandInfo
executeCommandDisplayIds.add(commandInfo.displayId)
delayInMillis == null // if no callback to execute, returns success
}
.`when`(sut)
@@ -175,7 +199,61 @@ class OverviewCommandHelperTest {
assertThat(commandInfo2.status).isEqualTo(CommandStatus.COMPLETED)
}
@Test
fun whenAllDisplaysCommandIsAdded_singleCommandProcessedForDefaultDisplay() =
testScope.runTest {
executeCommandDisplayIds.clear()
// Add command to queue
val commandInfo: CommandInfo = sut.addCommandsForAllDisplays(CommandType.HOME)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
runCurrent()
assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED)
assertThat(executeCommandDisplayIds).containsExactly(DEFAULT_DISPLAY)
}
@Test
fun whenAllDisplaysCommandIsAdded_multipleCommandsProcessedForMultipleDisplays() =
testScope.runTest {
setupMultipleDisplays()
executeCommandDisplayIds.clear()
// Add command to queue
val commandInfo: CommandInfo = sut.addCommandsForAllDisplays(CommandType.HOME)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
runCurrent()
assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED)
assertThat(executeCommandDisplayIds)
.containsExactly(DEFAULT_DISPLAY, EXTERNAL_DISPLAY_ID)
}
@Test
fun whenAllExceptDisplayCommandIsAdded_otherDisplayProcessed() =
testScope.runTest {
setupMultipleDisplays()
executeCommandDisplayIds.clear()
// Add command to queue
val commandInfo: CommandInfo =
sut.addCommandsForDisplaysExcept(CommandType.HOME, DEFAULT_DISPLAY)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
runCurrent()
assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED)
assertThat(executeCommandDisplayIds).containsExactly(EXTERNAL_DISPLAY_ID)
}
@Test
fun whenSingleDisplayCommandIsAdded_thatDisplayIsProcessed() =
testScope.runTest {
executeCommandDisplayIds.clear()
val displayId = 5
// Add command to queue
val commandInfo: CommandInfo = sut.addCommand(CommandType.HOME, displayId)!!
assertThat(commandInfo.status).isEqualTo(CommandStatus.IDLE)
runCurrent()
assertThat(commandInfo.status).isEqualTo(CommandStatus.COMPLETED)
assertThat(executeCommandDisplayIds).containsExactly(displayId)
}
private companion object {
const val QUEUE_TIMEOUT = 5001L
const val EXTERNAL_DISPLAY_ID = 1
}
}