Add flicker test for snap resizing using keyboard shortcuts.

Bug: 375400066
Flag: com.android.window.flags.enable_task_resizing_keyboard_shortcuts
Test: atest SnapResizeAppWindowLeftWithKeyboard
Test: atest SnapResizeAppWindowRightWithKeyboard

Change-Id: I899963a7680ac5b9a75309438d8bc818f69613db
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index f4f60d7..9972247 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -262,6 +262,40 @@
                 ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
             )
 
+        val SNAP_RESIZE_LEFT_WITH_KEYBOARD =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_KEYBOARD"),
+                extractor =
+                    TaggedScenarioExtractorBuilder()
+                        .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                        .setTransitionMatcher(
+                            TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                        )
+                        .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversLeftHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
+        val SNAP_RESIZE_RIGHT_WITH_KEYBOARD =
+            FlickerConfigEntry(
+                scenarioId = ScenarioId("SNAP_RESIZE_RIGHT_WITH_KEYBOARD"),
+                extractor =
+                    TaggedScenarioExtractorBuilder()
+                        .setTargetTag(CujType.CUJ_DESKTOP_MODE_SNAP_RESIZE)
+                        .setTransitionMatcher(
+                            TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+                        )
+                        .build(),
+                assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS + listOf(
+                    AppWindowCoversRightHalfScreenAtEnd(
+                        DESKTOP_MODE_APP, SNAP_WINDOW_MAX_DIFF_THRESHOLD_RATIO
+                    )
+                ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+            )
+
         val SNAP_RESIZE_LEFT_WITH_DRAG =
             FlickerConfigEntry(
                 scenarioId = ScenarioId("SNAP_RESIZE_LEFT_WITH_DRAG"),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt
new file mode 100644
index 0000000..b0b78a2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowLeftWithKeyboard.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_LEFT_WITH_KEYBOARD
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithKeyboardShortcuts
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window using keyboard shortcut META + [.
+ *
+ * Assert that the app window fills the left half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowLeftWithKeyboard : SnapResizeAppWindowWithKeyboardShortcuts(toLeft = true) {
+    @ExpectedScenarios(["SNAP_RESIZE_LEFT_WITH_KEYBOARD"])
+    @Test
+    override fun snapResizeAppWindowWithKeyboardShortcuts() =
+        super.snapResizeAppWindowWithKeyboardShortcuts()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_LEFT_WITH_KEYBOARD)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt
new file mode 100644
index 0000000..b9b9188
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/SnapResizeAppWindowRightWithKeyboard.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.DesktopModeFlickerScenarios.Companion.SNAP_RESIZE_RIGHT_WITH_KEYBOARD
+import com.android.wm.shell.scenarios.SnapResizeAppWindowWithKeyboardShortcuts
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Snap resize app window using keyboard shortcut META + ].
+ *
+ * Assert that the app window fills the right half the display after being snap resized.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class SnapResizeAppWindowRightWithKeyboard : SnapResizeAppWindowWithKeyboardShortcuts(
+    toLeft = false
+) {
+    @ExpectedScenarios(["SNAP_RESIZE_RIGHT_WITH_KEYBOARD"])
+    @Test
+    override fun snapResizeAppWindowWithKeyboardShortcuts() =
+        super.snapResizeAppWindowWithKeyboardShortcuts()
+
+    companion object {
+        @JvmStatic
+        @FlickerConfigProvider
+        fun flickerConfigProvider(): FlickerConfig =
+            FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(SNAP_RESIZE_RIGHT_WITH_KEYBOARD)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt
new file mode 100644
index 0000000..0680644
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/SnapResizeAppWindowWithKeyboardShortcuts.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.KeyEventHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+@Ignore("Test Base Class")
+abstract class SnapResizeAppWindowWithKeyboardShortcuts(
+    private val toLeft: Boolean = true,
+    isResizable: Boolean = true
+) {
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val keyEventHelper = KeyEventHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = if (isResizable) {
+        DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    } else {
+        DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+    }
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_90)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() &&
+                Flags.enableTaskResizingKeyboardShortcuts() && tapl.isTablet)
+        testApp.enterDesktopMode(wmHelper, device)
+    }
+
+    @Test
+    open fun snapResizeAppWindowWithKeyboardShortcuts() {
+        testApp.snapResizeWithKeyboard(
+            wmHelper,
+            instrumentation.context,
+            keyEventHelper,
+            toLeft,
+        )
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+    }
+}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index c1c5dc6..2e7b207 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -28,6 +28,9 @@
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.parsers.WindowManagerStateHelper
 import android.tools.traces.wm.WindowingMode
+import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
+import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
+import android.view.KeyEvent.META_META_ON
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.window.DesktopModeFlags
@@ -213,6 +216,26 @@
                 ?: error("Unable to find object with resource id $buttonResId")
         snapResizeButton.click()
 
+        waitAndVerifySnapResize(wmHelper, context, toLeft)
+    }
+
+    fun snapResizeWithKeyboard(
+        wmHelper: WindowManagerStateHelper,
+        context: Context,
+        keyEventHelper: KeyEventHelper,
+        toLeft: Boolean,
+    ) {
+        val bracketKey = if (toLeft) KEYCODE_LEFT_BRACKET else KEYCODE_RIGHT_BRACKET
+        keyEventHelper.actionDown(bracketKey, META_META_ON)
+        keyEventHelper.actionUp(bracketKey, META_META_ON)
+        waitAndVerifySnapResize(wmHelper, context, toLeft)
+    }
+
+    private fun waitAndVerifySnapResize(
+        wmHelper: WindowManagerStateHelper,
+        context: Context,
+        toLeft: Boolean
+    ) {
         val displayRect = getDisplayRect(wmHelper)
         val insets = getWindowInsets(
             context, WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
new file mode 100644
index 0000000..ebd8cc3
--- /dev/null
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/KeyEventHelper.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.os.SystemClock
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.ACTION_UP
+import android.view.KeyEvent
+
+/**
+ * Helper class for injecting a custom key event. This is used for instrumenting keyboard shortcut
+ * actions.
+ */
+class KeyEventHelper(
+    private val instr: Instrumentation,
+) {
+
+    fun actionDown(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) {
+        injectKeyEvent(ACTION_DOWN, keyCode, metaState, downTime = time, eventTime = time)
+    }
+
+    fun actionUp(keyCode: Int, metaState: Int = 0, time: Long = SystemClock.uptimeMillis()) {
+        injectKeyEvent(ACTION_UP, keyCode, metaState, downTime = time, eventTime = time)
+    }
+
+    private fun injectKeyEvent(
+        action: Int,
+        keyCode: Int,
+        metaState: Int = 0,
+        downTime: Long = SystemClock.uptimeMillis(),
+        eventTime: Long = SystemClock.uptimeMillis()
+    ): KeyEvent {
+        val event = KeyEvent(downTime, eventTime, action, keyCode, /* repeat= */ 0, metaState)
+        injectKeyEvent(event)
+        return event
+    }
+
+    private fun injectKeyEvent(event: KeyEvent) {
+        instr.uiAutomation.injectInputEvent(event, true)
+    }
+}
\ No newline at end of file