Introduce TestRule for setting Taskbar mode.

This rule is separate from TaskbarUnitTestRule, because mode isn't
relevant to all Taskbar controllers.

To keep MainThreadInitializedObject behavior consistent across
Robolectric and Instrumented environments, context needs to be a SandboxContext.

Test: TaskbarModeRuleTest
Bug: 230027385
Flag: TEST_ONLY
Change-Id: Iae2e3627b9002e13ee6da135113e7b8bcc4e7d47
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
new file mode 100644
index 0000000..3b53cdc
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.launcher3.taskbar.TaskbarModeRule.Mode
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+
+/**
+ * Allows tests to specify which Taskbar [Mode] to run under.
+ *
+ * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
+ * are properly sandboxed.
+ *
+ * Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
+ * rule is a no-op.
+ *
+ * Make sure this rule precedes any rules that depend on [DisplayController], or else the instance
+ * might be inconsistent across the test lifecycle.
+ */
+class TaskbarModeRule(private val context: SandboxContext) : TestRule {
+    /** The selected Taskbar mode. */
+    enum class Mode {
+        TRANSIENT,
+        PINNED,
+        THREE_BUTTONS,
+    }
+
+    /** Overrides Taskbar [mode] for a test. */
+    @Retention(AnnotationRetention.RUNTIME)
+    @Target(AnnotationTarget.FUNCTION)
+    annotation class TaskbarMode(val mode: Mode)
+
+    override fun apply(base: Statement, description: Description): Statement {
+        val taskbarMode = description.getAnnotation(TaskbarMode::class.java) ?: return base
+
+        return object : Statement() {
+            override fun evaluate() {
+                val mode = taskbarMode.mode
+
+                context.putObject(
+                    DisplayController.INSTANCE,
+                    object : DisplayController(context) {
+                        override fun getInfo(): Info {
+                            return spy(super.getInfo()) {
+                                on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
+                                on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
+                                on { navigationMode } doReturn
+                                    when (mode) {
+                                        Mode.TRANSIENT,
+                                        Mode.PINNED -> NavigationMode.NO_BUTTON
+                                        Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS
+                                    }
+                            }
+                        }
+                    },
+                )
+
+                base.evaluate()
+            }
+        }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
new file mode 100644
index 0000000..7dfbb9a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarModeRuleTest {
+
+    private val context = SandboxContext(getInstrumentation().targetContext)
+
+    @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+
+    @Test
+    @TaskbarMode(TRANSIENT)
+    fun testTaskbarMode_transient_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isTrue()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+        assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+    }
+
+    @Test
+    @TaskbarMode(TRANSIENT)
+    fun testTaskbarMode_transient_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isTrue()
+        assertThat(dp.isGestureMode).isTrue()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testTaskbarMode_pinned_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isTrue()
+        assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testTaskbarMode_pinned_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isFalse()
+        assertThat(dp.isGestureMode).isTrue()
+    }
+
+    @Test
+    @TaskbarMode(THREE_BUTTONS)
+    fun testTaskbarMode_threeButtons_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+        assertThat(DisplayController.getNavigationMode(context))
+            .isEqualTo(NavigationMode.THREE_BUTTONS)
+    }
+
+    @Test
+    @TaskbarMode(THREE_BUTTONS)
+    fun testTaskbarMode_threeButtons_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isFalse()
+        assertThat(dp.isGestureMode).isFalse()
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
index a999e7f..1c900b8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
@@ -18,6 +18,7 @@
 
 import android.app.Instrumentation
 import android.app.PendingIntent
+import android.content.Context
 import android.content.IIntentSender
 import android.content.Intent
 import androidx.test.platform.app.InstrumentationRegistry
@@ -37,6 +38,8 @@
 /**
  * Manages the Taskbar lifecycle for unit tests.
  *
+ * Tests need to provide their target [context] through the constructor.
+ *
  * 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
@@ -58,7 +61,7 @@
  * }
  * ```
  */
-class TaskbarUnitTestRule : MethodRule {
+class TaskbarUnitTestRule(private val context: Context) : MethodRule {
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
     private val serviceTestRule = ServiceTestRule()
 
@@ -76,7 +79,6 @@
             override fun evaluate() {
                 this@TaskbarUnitTestRule.target = target
 
-                val context = instrumentation.targetContext
                 instrumentation.runOnMainSync {
                     assumeTrue(
                         LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
index fe4e2d2..9a514bf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -42,7 +42,7 @@
 @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
 class TaskbarAllAppsControllerTest {
 
-    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext)
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @InjectController lateinit var allAppsController: TaskbarAllAppsController
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
index eebd8f9..918ec7d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -41,7 +41,7 @@
 @EmulatedDevices(["pixelFoldable2023"])
 class TaskbarOverlayControllerTest {
 
-    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule()
+    @get:Rule val taskbarUnitTestRule = TaskbarUnitTestRule(getInstrumentation().targetContext)
     @InjectController lateinit var overlayController: TaskbarOverlayController
 
     private val taskbarContext: TaskbarActivityContext