mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-20 03:08:19 +00:00
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
735 lines
31 KiB
Kotlin
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
|
|
}
|
|
}
|