mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-18 10:18:20 +00:00
Initial TaskbarUnitTestRule with example overlay controller tests.
Flag: TEST_ONLY Bug: 230027385 Test: TaskbarOverlayControllerTest Change-Id: I858906ece7e67677962ec8b4432bfcca5ec30283
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.app.PendingIntent
|
||||
import android.content.IIntentSender
|
||||
import android.content.Intent
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ServiceTestRule
|
||||
import com.android.launcher3.LauncherAppState
|
||||
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
|
||||
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
|
||||
import com.android.quickstep.AllAppsActionManager
|
||||
import com.android.quickstep.TouchInteractionService
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.rules.MethodRule
|
||||
import org.junit.runners.model.FrameworkMethod
|
||||
import org.junit.runners.model.Statement
|
||||
|
||||
/**
|
||||
* Manages the Taskbar lifecycle for unit tests.
|
||||
*
|
||||
* See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
|
||||
*/
|
||||
class TaskbarUnitTestRule : MethodRule {
|
||||
private val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
private val serviceTestRule = ServiceTestRule()
|
||||
|
||||
private lateinit var taskbarManager: TaskbarManager
|
||||
private lateinit var target: Any
|
||||
|
||||
val activityContext: TaskbarActivityContext
|
||||
get() {
|
||||
return taskbarManager.currentActivityContext
|
||||
?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
|
||||
}
|
||||
|
||||
override fun apply(base: Statement, method: FrameworkMethod, target: Any): Statement {
|
||||
return object : Statement() {
|
||||
override fun evaluate() {
|
||||
this@TaskbarUnitTestRule.target = target
|
||||
|
||||
val context = instrumentation.targetContext
|
||||
instrumentation.runOnMainSync {
|
||||
assumeTrue(
|
||||
LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
|
||||
)
|
||||
}
|
||||
|
||||
// Check for existing Taskbar instance from Launcher process.
|
||||
val launcherTaskbarManager: TaskbarManager? =
|
||||
if (!isRunningInRobolectric) {
|
||||
try {
|
||||
val tisBinder =
|
||||
serviceTestRule.bindService(
|
||||
Intent(context, TouchInteractionService::class.java)
|
||||
) as? TISBinder
|
||||
tisBinder?.taskbarManager
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
instrumentation.runOnMainSync {
|
||||
taskbarManager =
|
||||
TaskbarManager(
|
||||
context,
|
||||
AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
|
||||
PendingIntent(IIntentSender.Default())
|
||||
},
|
||||
object : TaskbarNavButtonCallbacks {},
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
// Replace Launcher Taskbar window with test instance.
|
||||
instrumentation.runOnMainSync {
|
||||
launcherTaskbarManager?.removeTaskbarRootViewFromWindow()
|
||||
taskbarManager.onUserUnlocked() // Required to complete initialization.
|
||||
}
|
||||
|
||||
injectControllers()
|
||||
base.evaluate()
|
||||
} finally {
|
||||
// Revert Taskbar window.
|
||||
instrumentation.runOnMainSync {
|
||||
taskbarManager.destroy()
|
||||
launcherTaskbarManager?.addTaskbarRootViewToWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Simulates Taskbar recreation lifecycle. */
|
||||
fun recreateTaskbar() {
|
||||
taskbarManager.recreateTaskbar()
|
||||
injectControllers()
|
||||
}
|
||||
|
||||
private fun injectControllers() {
|
||||
val controllers = activityContext.controllers
|
||||
val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
|
||||
target.javaClass.fields
|
||||
.filter { it.isAnnotationPresent(InjectController::class.java) }
|
||||
.forEach {
|
||||
it.set(
|
||||
target,
|
||||
controllerFieldsByType[it.type]?.get(controllers)
|
||||
?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotates test controller fields to inject the corresponding controllers from the current
|
||||
* [TaskbarControllers] instance.
|
||||
*
|
||||
* Controllers are injected during test setup and upon calling [recreateTaskbar].
|
||||
*
|
||||
* Multiple controllers can be injected if needed.
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
annotation class InjectController
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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.overlay
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo
|
||||
import android.view.MotionEvent
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import com.android.launcher3.AbstractFloatingView
|
||||
import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
|
||||
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
|
||||
import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
|
||||
import com.android.launcher3.AbstractFloatingView.hasOpenView
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.launcher3.taskbar.TaskbarUnitTestRule
|
||||
import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit
|
||||
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(LauncherMultivalentJUnit::class)
|
||||
@EmulatedDevices(["pixelFoldable2023"])
|
||||
class TaskbarOverlayControllerTest {
|
||||
|
||||
@get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
|
||||
@InjectController lateinit var overlayController: TaskbarOverlayController
|
||||
|
||||
private val taskbarContext: TaskbarActivityContext
|
||||
get() = taskbarUnitTestRule.activityContext
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testRequestWindow_twice_reusesWindow() {
|
||||
val context1 = overlayController.requestWindow()
|
||||
val context2 = overlayController.requestWindow()
|
||||
assertThat(context1).isSameInstanceAs(context2)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
|
||||
val context1 = overlayController.requestWindow()
|
||||
overlayController.hideWindow()
|
||||
|
||||
val context2 = overlayController.requestWindow()
|
||||
assertThat(context1).isNotSameInstanceAs(context2)
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testRequestWindow_addsProxyView() {
|
||||
TestOverlayView.show(overlayController.requestWindow())
|
||||
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testRequestWindow_closeProxyView_closesOverlay() {
|
||||
val overlay = TestOverlayView.show(overlayController.requestWindow())
|
||||
AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
|
||||
assertThat(overlay.isOpen).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testHideWindow_closesOverlay() {
|
||||
val overlay = TestOverlayView.show(overlayController.requestWindow())
|
||||
overlayController.hideWindow()
|
||||
assertThat(overlay.isOpen).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testTwoOverlays_closeOne_windowStaysOpen() {
|
||||
val context = overlayController.requestWindow()
|
||||
val overlay1 = TestOverlayView.show(context)
|
||||
val overlay2 = TestOverlayView.show(context)
|
||||
|
||||
overlay1.close(false)
|
||||
assertThat(overlay2.isOpen).isTrue()
|
||||
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testTwoOverlays_closeAll_closesWindow() {
|
||||
val context = overlayController.requestWindow()
|
||||
val overlay1 = TestOverlayView.show(context)
|
||||
val overlay2 = TestOverlayView.show(context)
|
||||
|
||||
overlay1.close(false)
|
||||
overlay2.close(false)
|
||||
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testRecreateTaskbar_closesWindow() {
|
||||
TestOverlayView.show(overlayController.requestWindow())
|
||||
taskbarUnitTestRule.recreateTaskbar()
|
||||
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskMovedToFront_closesOverlay() {
|
||||
lateinit var overlay: TestOverlayView
|
||||
getInstrumentation().runOnMainSync {
|
||||
overlay = TestOverlayView.show(overlayController.requestWindow())
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
|
||||
// Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
|
||||
getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
|
||||
lateinit var overlay: TestOverlayView
|
||||
getInstrumentation().runOnMainSync {
|
||||
overlay = TestOverlayView.show(overlayController.requestWindow())
|
||||
taskbarContext.controllers.sharedState?.allAppsVisible = false
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
|
||||
getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTaskStackChanged_allAppsOpen_closesOverlay() {
|
||||
lateinit var overlay: TestOverlayView
|
||||
getInstrumentation().runOnMainSync {
|
||||
overlay = TestOverlayView.show(overlayController.requestWindow())
|
||||
taskbarContext.controllers.sharedState?.allAppsVisible = true
|
||||
}
|
||||
|
||||
TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
|
||||
getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() {
|
||||
val overlayContext = overlayController.requestWindow()
|
||||
val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP }
|
||||
|
||||
overlayController.updateLauncherDeviceProfile(
|
||||
overlayController.launcherDeviceProfile
|
||||
.toBuilder(overlayContext)
|
||||
.setGestureMode(false)
|
||||
.build()
|
||||
)
|
||||
|
||||
assertThat(overlay.isOpen).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() {
|
||||
val overlayContext = overlayController.requestWindow()
|
||||
val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS }
|
||||
|
||||
overlayController.updateLauncherDeviceProfile(
|
||||
overlayController.launcherDeviceProfile
|
||||
.toBuilder(overlayContext)
|
||||
.setGestureMode(false)
|
||||
.build()
|
||||
)
|
||||
|
||||
assertThat(overlay.isOpen).isTrue()
|
||||
}
|
||||
|
||||
private class TestOverlayView
|
||||
private constructor(
|
||||
private val overlayContext: TaskbarOverlayContext,
|
||||
) : AbstractFloatingView(overlayContext, null) {
|
||||
|
||||
var type = TYPE_OPTIONS_POPUP
|
||||
|
||||
private fun show() {
|
||||
mIsOpen = true
|
||||
overlayContext.dragLayer.addView(this)
|
||||
}
|
||||
|
||||
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false
|
||||
|
||||
override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this)
|
||||
|
||||
override fun isOfType(type: Int): Boolean = (type and this.type) != 0
|
||||
|
||||
companion object {
|
||||
/** Adds a generic View to the Overlay window for testing. */
|
||||
fun show(context: TaskbarOverlayContext): TestOverlayView {
|
||||
return TestOverlayView(context).apply { show() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user