Files
lawnchair/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
Brian Isganitis ae80cbfed3 Refactor test dagger graph to stub PerDisplayRepository instances.
These are not being used yet in Taskbar, but they are hardcoding
DEFAULT_DISPLAY for the primary ID. In tests, we want the primary to be
considered the ID of the virtual display tests are running on.

This change also moves dagger classes to its own file for organization.

Flag: TEST_ONLY
Bug: 415326979
Test: Taskbar multivalent tests
Change-Id: Ibd0cdf46bc53dbd7a3dd5f34d7171d9a6cdebb38
2025-05-12 17:10:57 -04:00

735 lines
31 KiB
Kotlin

/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar
import android.animation.AnimatorTestRule
import android.content.ComponentName
import android.content.Intent
import android.os.Process
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import androidx.test.core.app.ApplicationProvider
import com.android.launcher3.BubbleTextView
import com.android.launcher3.Flags.FLAG_ENABLE_ALT_TAB_KQS_FLATENNING
import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
import com.android.launcher3.R
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
import com.android.launcher3.model.BgDataModel
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.model.data.WorkspaceData
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.statehandlers.DesktopVisibilityController
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
import com.android.launcher3.taskbar.rules.AllTaskbarSandboxModules
import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper
import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
import com.android.launcher3.taskbar.rules.SandboxParams
import com.android.launcher3.taskbar.rules.TaskbarModeRule
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.taskbar.rules.TaskbarSandboxComponent
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.Preconditions.assertNotNull
import com.android.launcher3.util.TestUtil.getOnUiThread
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.quickstep.util.SingleTask
import com.android.quickstep.util.SingleTask.Companion.createTaskItemInfo
import com.android.systemui.shared.recents.model.Task
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS
import com.android.window.flags.Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU
import com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_OVERFLOW
import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
import com.android.wm.shell.desktopmode.IDesktopTaskListener
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
import java.util.function.Predicate
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.spy
import org.mockito.kotlin.whenever
@RunWith(LauncherMultivalentJUnit::class)
@EmulatedDevices(["pixelTablet2023"])
@EnableFlags(
FLAG_ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS,
FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
FLAG_ENABLE_BUBBLE_BAR,
FLAG_ENABLE_TASKBAR_OVERFLOW,
)
@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
class TaskbarOverflowTest {
@get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
val mockRecentsModelHelper: MockedRecentsModelHelper = MockedRecentsModelHelper()
@get:Rule(order = 1)
val context =
TaskbarWindowSandboxContext.create(
SandboxParams(
{
spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
doAnswer { desktopTaskListener = it.getArgument(0) }
.whenever(proxy)
.setDesktopTaskListener(anyOrNull())
}
},
DaggerTaskbarOverflowComponent.builder()
.bindRecentsModel(mockRecentsModelHelper.mockRecentsModel),
)
)
@get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(mockRecentsModelHelper)
@get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
@get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
@get:Rule(order = 5)
val taskbarUnitTestRule = TaskbarUnitTestRule(this, context, this::onControllersInitialized)
@InjectController lateinit var taskbarViewController: TaskbarViewController
@InjectController lateinit var recentAppsController: TaskbarRecentAppsController
@InjectController lateinit var bubbleBarViewController: BubbleBarViewController
@InjectController lateinit var bubbleStashController: BubbleStashController
@InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
private val desktopVisibilityController: DesktopVisibilityController
get() = DesktopVisibilityController.INSTANCE[context]
private var desktopTaskListener: IDesktopTaskListener? = null
private val modelCallback = ModelCallbacks()
private val taskbarContext: TaskbarActivityContext
get() = taskbarUnitTestRule.activityContext
private var currentControllerInitCallback: () -> Unit = {}
set(value) {
runOnMainSync { value.invoke() }
field = value
}
private fun onControllersInitialized() {
runOnMainSync {
if (!recentAppsController.canShowRunningApps) {
recentAppsController.onDestroy()
recentAppsController.canShowRunningApps = true
recentAppsController.init(
taskbarUnitTestRule.activityContext.controllers,
emptyList(),
)
}
currentControllerInitCallback.invoke()
}
}
@Before
fun ensureRunningAppsShowing() {
whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
runOnMainSync { recentsModel.resolvePendingTaskRequests() }
}
@Test
@TaskbarMode(PINNED)
fun testTaskbarWithMaxNumIcons_pinned() {
addRunningAppsAndVerifyOverflowState(0)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
}
@Test
@TaskbarMode(TRANSIENT)
fun testTaskbarWithMaxNumIcons_transient() {
addRunningAppsAndVerifyOverflowState(0)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
}
@Test
@TaskbarMode(PINNED)
fun testOverflownTaskbar_pinned() {
addRunningAppsAndVerifyOverflowState(5)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
}
@Test
@TaskbarMode(TRANSIENT)
fun testOverflownTaskbar_transient() {
addRunningAppsAndVerifyOverflowState(5)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin).isAtLeast(navButtonEndSpacing)
}
@Test
@TaskbarMode(PINNED)
fun testOverflownTaskbarWithNoSpaceForRecentApps_pinned() {
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
// Create two "recent" desktop tasks, and then add enough hotseat items so the taskbar
// reaches max number of items with hotseat item icons, all apps and divider icons only.
// I.e. so all desktop tasks are in taskbar overflow.
createDesktopTask(2)
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
taskbarView.updateItems(
createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
recentAppsController.shownTasks,
)
}
// Verify that taskbar overflow view is shown (eventhough it exceeds max taskbar icons).
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
assertThat(taskbarOverflowIconIndex).isEqualTo(maxNumberOfTaskbarIcons)
assertThat(overflowItems).containsExactlyElementsIn(0..1)
}
@Test
@TaskbarMode(PINNED)
fun testOverflownTaskbarWithNoSpaceForRecentApps_singleRecent_pinned() {
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
// Create a "recent" desktop task, and then add enough hotseat items so the taskbar
// reaches max number of items with hotseat item icons, all apps and divider icons only.
// I.e. so the single desktop tasks is in taskbar overflow.
createDesktopTask(1)
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount)
taskbarView.updateItems(
recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
recentAppsController.shownTasks,
)
}
// Verify that recent task is shown (eventhough it exceeds max taskbar icons), and that
// the taskbar overflow view is not added for the single recent app.
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
assertThat(taskbarOverflowIconIndex).isEqualTo(-1)
}
@Test
@TaskbarMode(PINNED)
fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() {
var initialMaxNumIconViews = maxNumberOfTaskbarIcons
assertThat(initialMaxNumIconViews).isGreaterThan(0)
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
assertThat(taskbarIconsCentered).isTrue()
}
@Test
@TaskbarMode(TRANSIENT)
fun testBubbleBarReducesTaskbarMaxNumIcons_transient() {
var initialMaxNumIconViews = maxNumberOfTaskbarIcons
assertThat(initialMaxNumIconViews).isGreaterThan(0)
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin)
.isAtLeast(
navButtonEndSpacing +
bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt()
)
}
@Test
@TaskbarMode(TRANSIENT)
fun testBubbleBarReducesTaskbarMaxNumIcons_transientBubbleInitiallyStashed() {
var initialMaxNumIconViews = maxNumberOfTaskbarIcons
assertThat(initialMaxNumIconViews).isGreaterThan(0)
currentControllerInitCallback = {
bubbleStashController.stashBubbleBarImmediate()
bubbleBarViewController.setHiddenForBubbles(false)
}
val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
assertThat(taskbarIconsCentered).isTrue()
assertThat(taskbarEndMargin)
.isAtLeast(
navButtonEndSpacing +
bubbleBarViewController.collapsedWidthWithMaxVisibleBubbles.toInt()
)
}
@Test
@TaskbarMode(TRANSIENT)
fun testStashingBubbleBarMaintainsMaxNumIcons_transient() {
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
val initialNumIcons = currentNumberOfTaskbarIcons
val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
runOnMainSync { bubbleStashController.stashBubbleBarImmediate() }
assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
}
@Test
@TaskbarMode(PINNED)
fun testHidingBubbleBarIncreasesMaxNumIcons_pinned() {
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
val initialNumIcons = currentNumberOfTaskbarIcons
val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) }
runOnMainSync { animatorTestRule.advanceTimeBy(150) }
val maxNumIconViews = maxNumberOfTaskbarIcons
assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
assertThat(taskbarIconsCentered).isTrue()
}
@Test
@TaskbarMode(TRANSIENT)
fun testHidingBubbleBarIncreasesMaxNumIcons_transient() {
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
val initialNumIcons = currentNumberOfTaskbarIcons
val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) }
runOnMainSync { animatorTestRule.advanceTimeBy(150) }
val maxNumIconViews = maxNumberOfTaskbarIcons
assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(taskbarOverflowIconIndex).isEqualTo(initialNumIcons.coerceAtLeast(2))
assertThat(taskbarIconsCentered).isTrue()
}
@Test
@TaskbarMode(PINNED)
@DisableFlags(FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
fun testPressingOverflowButtonOpensKeyboardQuickSwitch() {
val maxNumIconViews = maxNumberOfTaskbarIcons
// Assume there are at least all apps and divider icon, as they would appear once running
// apps are added, even if not present initially.
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
val targetOverflowSize = 5
val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
createDesktopTask(createdTasks)
assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount)
tapOverflowIcon()
// Keyboard quick switch view is shown only after list of recent task is asynchronously
// retrieved from the recents model.
runOnMainSync { recentsModel.resolvePendingTaskRequests() }
assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
.containsExactlyElementsIn(0..<createdTasks)
tapOverflowIcon()
assertThat(keyboardQuickSwitchController.isShown).isFalse()
}
@Test
@TaskbarMode(PINNED)
fun testHotseatItemTasksNotShownInRecents() {
val maxNumIconViews = maxNumberOfTaskbarIcons
// Assume there are at least all apps and divider icon, as they would appear once running
// apps are added, even if not present initially.
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
val hotseatItems = createHotseatItems(1)
val targetOverflowSize = 5
val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
createDesktopTaskWithTasksFromPackages(
listOf("fake") +
listOf(hotseatItems[0]?.targetPackage ?: "") +
List(createdTasks - 2) { "fake" }
)
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
taskbarView.updateItems(
recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
recentAppsController.shownTasks,
)
}
assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size)
assertThat(overflowItems)
.containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList())
}
@Test
@TaskbarMode(PINNED)
@DisableFlags(FLAG_ENABLE_ALT_TAB_KQS_FLATENNING)
fun testHotseatItemTasksNotShownInKQS() {
val maxNumIconViews = maxNumberOfTaskbarIcons
// Assume there are at least all apps and divider icon, as they would appear once running
// apps are added, even if not present initially.
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
val hotseatItems = createHotseatItems(1)
val targetOverflowSize = 5
val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
createDesktopTaskWithTasksFromPackages(
listOf("fake") +
listOf(hotseatItems[0]?.targetPackage ?: "") +
List(createdTasks - 2) { "fake" }
)
runOnMainSync {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
taskbarView.updateItems(
recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
recentAppsController.shownTasks,
)
}
tapOverflowIcon()
// Keyboard quick switch view is shown only after list of recent task is asynchronously
// retrieved from the recents model.
runOnMainSync { recentsModel.resolvePendingTaskRequests() }
assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
.containsExactlyElementsIn(listOf(0) + (2..<createdTasks).toList())
}
@Test
@TaskbarMode(PINNED)
@EnableFlags(FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU)
fun pinToTaskbarShortcut_unpinPinnedItem() {
// Create two tasks and two pinned items.
createDesktopTask(2)
val hotseatItems = createHotseatItems(2)
var shortcut: SystemShortcut<*>? = null
var hotseatIcon: BubbleTextView? = null
runOnMainSync {
val taskbarView = setUpTaskbarAndModelCallback(hotseatItems)
hotseatIcon =
taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
it.tag is WorkspaceItemInfo
}
shortcut =
taskbarContext.controllers.taskbarPopupController.createPinShortcut(
taskbarContext,
hotseatIcon!!.tag as ItemInfo,
hotseatIcon,
) as SystemShortcut<*>
}
assertNotNull(shortcut)
runOnMainSync { shortcut?.onClick(hotseatIcon) }
// After unpinning the first item, only the second app is left.
assertThat(modelCallback.hotseatItems.map { info -> info.title })
.isEqualTo(listOf("Test App 1"))
// The unpinned app doesn't have a task so the shown tasks won't change.
assertThat(recentAppsController.shownTasks.map { it.tasks[0].key.id })
.isEqualTo(listOf(0, 1))
}
@Test
@TaskbarMode(PINNED)
@EnableFlags(FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU)
fun pinToTaskbarShortcut_unpinPinnedItemWithTask() {
// Create two hotseat items with a task for both of them respectively.
var hotseatItems =
createHotseatItems(2).mapIndexed { idx, item -> TaskItemInfo(idx, item) }.toTypedArray()
createDesktopTaskWithTasksFromPackages(hotseatItems.mapNotNull { it.targetPackage })
var shortcut: SystemShortcut<*>? = null
var hotseatIcon: BubbleTextView? = null
runOnMainSync {
val taskbarView = setUpTaskbarAndModelCallback(hotseatItems.map { it }.toTypedArray())
hotseatIcon =
taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
it.tag is WorkspaceItemInfo
}
shortcut =
taskbarContext.controllers.taskbarPopupController.createPinShortcut(
taskbarContext,
hotseatIcon!!.tag as ItemInfo,
hotseatIcon,
) as SystemShortcut<*>
}
// Before unpinning the app, both of the apps should be pinned and no shown task available.
assertThat(modelCallback.hotseatItems.map { info -> info.title })
.isEqualTo(listOf("Test App 0", "Test App 1"))
assertThat(recentAppsController.shownTasks.map { it.tasks[0].key.id })
.isEqualTo(emptyList<Int>())
assertNotNull(shortcut)
runOnMainSync { shortcut?.onClick(hotseatIcon) }
// After unpinning the app, app 0 is removed and its task is shown as a recent task.
assertThat(modelCallback.hotseatItems.map { info -> info.title })
.isEqualTo(listOf("Test App 1"))
assertThat(recentAppsController.shownTasks.map { it.tasks[0].key.id }).isEqualTo(listOf(0))
}
@Test
@TaskbarMode(PINNED)
@EnableFlags(FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU)
fun pinToTaskbarShortcut_pinRecentTask() {
// Create two tasks and two pinned items.
createDesktopTask(2)
val hotseatItems = createHotseatItems(2)
var shortcut: SystemShortcut<*>? = null
var recentTaskIcon: BubbleTextView? = null
runOnMainSync {
val taskbarView = setUpTaskbarAndModelCallback(hotseatItems)
// Get the first recent task icon
recentTaskIcon =
taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
it.tag is GroupTask
}
val recentTaskInfo =
createTaskItemInfo(
recentTaskIcon!!.tag as SingleTask,
WorkspaceItemInfo().apply {
title = "Test App 2"
intent = Intent().apply { `package` = "fake" }
},
)
shortcut =
taskbarContext.controllers.taskbarPopupController.createPinShortcut(
taskbarContext,
recentTaskInfo,
recentTaskIcon,
) as SystemShortcut<*>
}
assertNotNull(shortcut)
runOnMainSync { shortcut?.onClick(recentTaskIcon) }
// After pinning the recent task, it should be included in the hotseat items.
assertThat(modelCallback.hotseatItems.map { info -> info.title })
.isEqualTo(listOf("Test App 0", "Test App 1", "Test App 2"))
// As the task is pinned, the shown tasks should remove it from the list
assertThat(recentAppsController.shownTasks.map { it.tasks[0].key.id }).isEqualTo(listOf(1))
}
private fun setUpTaskbarAndModelCallback(hotseatItems: Array<WorkspaceItemInfo>): TaskbarView {
val taskbarView: TaskbarView =
taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
taskbarView.updateItems(hotseatItems, recentAppsController.shownTasks)
modelCallback.recentAppsController = recentAppsController
context.baseContext.appComponent.launcherAppState.model.addCallbacksAndLoad(modelCallback)
modelCallback.bindItemsAdded(hotseatItems.toList())
return taskbarView
}
private fun createDesktopTask(tasksToAdd: Int) {
createDesktopTaskWithTasksFromPackages((0..<tasksToAdd).map { "fake" })
}
private fun createDesktopTaskWithTasksFromPackages(packages: List<String>) {
val tasks =
packages.mapIndexed({ index, p ->
Task(
Task.TaskKey(
index,
0,
Intent().apply { `package` = p },
ComponentName(p, ""),
Process.myUserHandle().identifier,
2000,
)
)
})
val displayId = context.virtualDisplay.display.displayId
recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, displayId, tasks)))
for (task in 1..tasks.size) {
desktopTaskListener?.onTasksVisibilityChanged(displayId, task)
}
runOnMainSync { recentsModel.resolvePendingTaskRequests() }
}
private val navButtonEndSpacing: Int
get() {
return taskbarUnitTestRule.activityContext.resources.getDimensionPixelSize(
taskbarUnitTestRule.activityContext.deviceProfile.inv.inlineNavButtonsEndSpacing
)
}
private val taskbarOverflowIconIndex: Int
get() {
return getOnUiThread {
taskbarViewController.iconViews.indexOfFirst { it is TaskbarOverflowView }
}
}
private val maxNumberOfTaskbarIcons: Int
get() = getOnUiThread { taskbarViewController.maxNumIconViews }
private val currentNumberOfTaskbarIcons: Int
get() = getOnUiThread { taskbarViewController.iconViews.size }
private val taskbarIconsCentered: Boolean
get() {
return getOnUiThread {
val iconLayoutBounds =
taskbarViewController.transientTaskbarIconLayoutBoundsInParent
val availableWidth = taskbarUnitTestRule.activityContext.deviceProfile.widthPx
iconLayoutBounds.left - (availableWidth - iconLayoutBounds.right) < 2
}
}
private val taskbarEndMargin: Int
get() {
return getOnUiThread {
taskbarUnitTestRule.activityContext.deviceProfile.widthPx -
taskbarViewController.transientTaskbarIconLayoutBoundsInParent.right
}
}
private val overflowItems: List<Int>
get() {
return getOnUiThread {
val overflowIcon =
taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
if (overflowIcon is TaskbarOverflowView) {
overflowIcon.itemIds
} else {
emptyList()
}
}
}
private fun tapOverflowIcon() {
runOnMainSync {
val overflowIcon =
taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
assertThat(overflowIcon?.callOnClick()).isTrue()
}
}
/**
* Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
* * max number of icons in the taskbar remains unchanged
* * number of icons in the taskbar is at most max number of icons
* * whether the taskbar overflow icon is shown, and its position in taskbar.
*
* Returns max number of icons.
*/
private fun addRunningAppsAndVerifyOverflowState(targetOverflowSize: Int): Int {
val maxNumIconViews = maxNumberOfTaskbarIcons
assertThat(maxNumIconViews).isGreaterThan(0)
// Assume there are at least all apps and divider icon, as they would appear once running
// apps are added, even if not present initially.
val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
assertThat(initialIconCount).isLessThan(maxNumIconViews)
createDesktopTask(maxNumIconViews - initialIconCount + targetOverflowSize)
assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
assertThat(taskbarOverflowIconIndex)
.isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1)
if (targetOverflowSize > 0) {
assertThat(overflowItems).containsExactlyElementsIn(0..targetOverflowSize)
}
return maxNumIconViews
}
private class ModelCallbacks : BgDataModel.Callbacks {
var hotseatItems = mutableListOf<WorkspaceItemInfo>()
var recentAppsController: TaskbarRecentAppsController? = null
override fun bindCompleteModel(itemIdMap: WorkspaceData, isBindingSync: Boolean) =
bindItemsAdded(itemIdMap.toList())
override fun bindItemsAdded(items: List<ItemInfo>) {
runOnMainSync {
items
.filter { item ->
item is WorkspaceItemInfo &&
!hotseatItems.any { it.targetPackage == item.targetPackage }
}
.forEach { item -> hotseatItems.add(item as WorkspaceItemInfo) }
recentAppsController?.updateHotseatItemInfos(hotseatItems.toTypedArray())
}
}
override fun bindWorkspaceComponentsRemoved(matcher: Predicate<ItemInfo?>) {
runOnMainSync {
for (i in hotseatItems.size - 1 downTo 0) {
if (matcher.test(hotseatItems[i])) {
hotseatItems.removeAt(i)
}
}
recentAppsController?.updateHotseatItemInfos(hotseatItems.toTypedArray())
}
}
}
}
/** TaskbarOverflowComponent used to bind the RecentsModel. */
@LauncherAppSingleton
@Component(modules = [AllTaskbarSandboxModules::class])
interface TaskbarOverflowComponent : TaskbarSandboxComponent {
@Component.Builder
interface Builder : TaskbarSandboxComponent.Builder {
@BindsInstance fun bindRecentsModel(model: RecentsModel): Builder
override fun build(): TaskbarOverflowComponent
}
}