165 lines
6.5 KiB
Kotlin
165 lines
6.5 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.app.Instrumentation
|
|
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.
|
|
*
|
|
* The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is
|
|
* that code that is executed on the main thread in production should also happen on that thread
|
|
* when tested.
|
|
*
|
|
* `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test
|
|
* executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will
|
|
* prevent those messages from being processed until after the test body finishes.
|
|
*
|
|
* To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform
|
|
* only sections of the test body on the main thread synchronously:
|
|
* ```
|
|
* @Test
|
|
* fun example() {
|
|
* instrumentation.runOnMainSync { doWorkThatPostsMessage() }
|
|
* // Second lambda will not execute until message is processed.
|
|
* instrumentation.runOnMainSync { verifyMessageResults() }
|
|
* }
|
|
* ```
|
|
*/
|
|
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
|
|
}
|