Loading libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/fundamentals/EnterSplitScreenWithDragTest.kt 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.functional.fundamentals import android.platform.test.annotations.Postsubmit import android.platform.test.rule.ScreenRecordRule import com.android.wm.shell.scenarios.EnterSplitScreenWithDrag import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner /* Functional test for [EnterSplitScreenWithDrag]. */ @RunWith(BlockJUnit4ClassRunner::class) @Postsubmit @ScreenRecordRule.ScreenRecord class EnterSplitScreenWithDragTest : EnterSplitScreenWithDrag() libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenWithDrag.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.Rotation import android.tools.device.apphelpers.CalculatorAppHelper import android.tools.traces.parsers.WindowManagerStateHelper import android.view.KeyEvent.KEYCODE_META_RIGHT 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.SimpleAppHelper import org.junit.After import org.junit.Before import org.junit.Ignore import org.junit.Test @Ignore("Test Base Class") abstract class EnterSplitScreenWithDrag(val rotation: Rotation = Rotation.ROTATION_0) : TestScenarioBase(rotation) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() private val wmHelper = WindowManagerStateHelper(instrumentation) private val device = UiDevice.getInstance(instrumentation) private val keyEventHelper = KeyEventHelper(InstrumentationRegistry.getInstrumentation()) private val simpleAppHelper = SimpleAppHelper(instrumentation) val calculatorApp = CalculatorAppHelper(instrumentation) val testApp = DesktopModeAppHelper(simpleAppHelper) @Before fun setup() { // Launch app in order to enter split screen simpleAppHelper.launchViaIntent(wmHelper) } @Test open fun enterSplitScreenWithDrag() { testApp.dragFromFullscreenToSplit(wmHelper, device, DesktopModeAppHelper.SplitDirection.RIGHT) // Open allApps via keyboard shortcut keyEventHelper.press(KEYCODE_META_RIGHT) tapl.allApps .getAppIcon(calculatorApp.appName) .launch(calculatorApp.packageName) } @After fun teardown() { testApp.exit(wmHelper) calculatorApp.exit(wmHelper) } } tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +67 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.helpers import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.Context import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK Loading Loading @@ -123,12 +124,7 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : device: UiDevice, motionEventHelper: MotionEventHelper ) { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val startX = windowRect.centerX() // Start dragging a little under the top to prevent dragging the notification shade. val startY = 10 val (startX, startY) = getAppHandleDragPosition(wmHelper) val displayRect = getDisplayRect(wmHelper) // The position we want to drag to Loading @@ -144,6 +140,63 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : } } /** * Drags a window to one side of the screen to enter split-screen mode. * * This function simulates a user dragging an application window from near the top-center * to either the left or right edge of the display, triggering a split-screen view. * * @param wmHelper Helper class to get information about the window manager state, such as the * window's current position and size. * @param device The UiDevice instance used to interact with the device, specifically to perform * the drag gesture. * @param direction The target side of the screen to drag the window to, either * {@link SplitDirection#LEFT} or {@link SplitDirection#RIGHT}. * @param motionEventHelper A helper for dispatching motion events. It is used for touch-based * input. */ fun dragFromFullscreenToSplit( wmHelper: WindowManagerStateHelper, device: UiDevice, direction: SplitDirection, motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH), ) { if (!isInFullscreenMode(wmHelper)) { error("Should be in Fullscreen mode") } val (startX, startY) = getAppHandleDragPosition(wmHelper) val displayRect = getDisplayRect(wmHelper) // Determine the end coordinates for the drag. val endY = displayRect.centerY() val endX = when (direction) { SplitDirection.LEFT -> displayRect.left + 20 // Target the left edge with an offset. SplitDirection.RIGHT -> displayRect.right - 20 // Target the right edge with an offset. } val steps = 100 // Number of move steps in the drag gesture. // Perform the drag action. if (motionEventHelper.inputMethod == TOUCH && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) { // We need more sleep because we are waiting for the split screen indicator. motionEventHelper.holdToDrag(startX, startY, endX, endY, steps, sleepTimeBeforeDrop = 1000) } else { // Use standard UiDevice.drag for other input methods (e.g., mouse). device.drag(startX, startY, endX, endY, steps) } } private fun getAppHandleDragPosition(wmHelper: WindowManagerStateHelper): Pair<Int, Int> { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val startX = windowRect.centerX() // Start dragging a little under the top to prevent dragging the notification shade. val startY = 10 return Pair(startX, startY) } private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 { return caption ?.children Loading Loading @@ -731,6 +784,9 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) = wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM private fun isInFullscreenMode(wmHelper: WindowManagerStateHelper) = wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FULLSCREEN private fun isAnyDesktopWindowVisible(wmHelper: WindowManagerStateHelper) = wmHelper.currentState.wmState.hasFreeformWindow() Loading Loading @@ -772,6 +828,11 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : CENTER } enum class SplitDirection { LEFT, RIGHT } private companion object { val TIMEOUT: Duration = Duration.ofSeconds(3) const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge Loading tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt +3 −3 Original line number Diff line number Diff line Loading @@ -83,7 +83,7 @@ class MotionEventHelper( * Drag from [startX], [startY] to [endX], [endY] with a "hold" period after touching down * and before moving. */ fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) { fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int, sleepTimeBeforeDrop: Long = REGULAR_CLICK_LENGTH) { val downTime = SystemClock.uptimeMillis() actionDown(startX, startY, time = downTime) SystemClock.sleep(100L) // Hold before dragging. Loading @@ -96,8 +96,8 @@ class MotionEventHelper( downTime, withMotionEventInjectDelay = true ) SystemClock.sleep(REGULAR_CLICK_LENGTH) actionUp(startX, endX, downTime) SystemClock.sleep(sleepTimeBeforeDrop) actionUp(endX, endY, downTime) } private fun injectMotionEvent( Loading Loading
libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/fundamentals/EnterSplitScreenWithDragTest.kt 0 → 100644 +29 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.functional.fundamentals import android.platform.test.annotations.Postsubmit import android.platform.test.rule.ScreenRecordRule import com.android.wm.shell.scenarios.EnterSplitScreenWithDrag import org.junit.runner.RunWith import org.junit.runners.BlockJUnit4ClassRunner /* Functional test for [EnterSplitScreenWithDrag]. */ @RunWith(BlockJUnit4ClassRunner::class) @Postsubmit @ScreenRecordRule.ScreenRecord class EnterSplitScreenWithDragTest : EnterSplitScreenWithDrag()
libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/EnterSplitScreenWithDrag.kt 0 → 100644 +67 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.Rotation import android.tools.device.apphelpers.CalculatorAppHelper import android.tools.traces.parsers.WindowManagerStateHelper import android.view.KeyEvent.KEYCODE_META_RIGHT 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.SimpleAppHelper import org.junit.After import org.junit.Before import org.junit.Ignore import org.junit.Test @Ignore("Test Base Class") abstract class EnterSplitScreenWithDrag(val rotation: Rotation = Rotation.ROTATION_0) : TestScenarioBase(rotation) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val tapl = LauncherInstrumentation() private val wmHelper = WindowManagerStateHelper(instrumentation) private val device = UiDevice.getInstance(instrumentation) private val keyEventHelper = KeyEventHelper(InstrumentationRegistry.getInstrumentation()) private val simpleAppHelper = SimpleAppHelper(instrumentation) val calculatorApp = CalculatorAppHelper(instrumentation) val testApp = DesktopModeAppHelper(simpleAppHelper) @Before fun setup() { // Launch app in order to enter split screen simpleAppHelper.launchViaIntent(wmHelper) } @Test open fun enterSplitScreenWithDrag() { testApp.dragFromFullscreenToSplit(wmHelper, device, DesktopModeAppHelper.SplitDirection.RIGHT) // Open allApps via keyboard shortcut keyEventHelper.press(KEYCODE_META_RIGHT) tapl.allApps .getAppIcon(calculatorApp.appName) .launch(calculatorApp.packageName) } @After fun teardown() { testApp.exit(wmHelper) calculatorApp.exit(wmHelper) } }
tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt +67 −6 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.helpers import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.content.Context import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK Loading Loading @@ -123,12 +124,7 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : device: UiDevice, motionEventHelper: MotionEventHelper ) { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val startX = windowRect.centerX() // Start dragging a little under the top to prevent dragging the notification shade. val startY = 10 val (startX, startY) = getAppHandleDragPosition(wmHelper) val displayRect = getDisplayRect(wmHelper) // The position we want to drag to Loading @@ -144,6 +140,63 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : } } /** * Drags a window to one side of the screen to enter split-screen mode. * * This function simulates a user dragging an application window from near the top-center * to either the left or right edge of the display, triggering a split-screen view. * * @param wmHelper Helper class to get information about the window manager state, such as the * window's current position and size. * @param device The UiDevice instance used to interact with the device, specifically to perform * the drag gesture. * @param direction The target side of the screen to drag the window to, either * {@link SplitDirection#LEFT} or {@link SplitDirection#RIGHT}. * @param motionEventHelper A helper for dispatching motion events. It is used for touch-based * input. */ fun dragFromFullscreenToSplit( wmHelper: WindowManagerStateHelper, device: UiDevice, direction: SplitDirection, motionEventHelper: MotionEventHelper = MotionEventHelper(getInstrumentation(), TOUCH), ) { if (!isInFullscreenMode(wmHelper)) { error("Should be in Fullscreen mode") } val (startX, startY) = getAppHandleDragPosition(wmHelper) val displayRect = getDisplayRect(wmHelper) // Determine the end coordinates for the drag. val endY = displayRect.centerY() val endX = when (direction) { SplitDirection.LEFT -> displayRect.left + 20 // Target the left edge with an offset. SplitDirection.RIGHT -> displayRect.right - 20 // Target the right edge with an offset. } val steps = 100 // Number of move steps in the drag gesture. // Perform the drag action. if (motionEventHelper.inputMethod == TOUCH && DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue) { // We need more sleep because we are waiting for the split screen indicator. motionEventHelper.holdToDrag(startX, startY, endX, endY, steps, sleepTimeBeforeDrop = 1000) } else { // Use standard UiDevice.drag for other input methods (e.g., mouse). device.drag(startX, startY, endX, endY, steps) } } private fun getAppHandleDragPosition(wmHelper: WindowManagerStateHelper): Pair<Int, Int> { val windowRect = wmHelper.getWindowRegion(innerHelper).bounds val startX = windowRect.centerX() // Start dragging a little under the top to prevent dragging the notification shade. val startY = 10 return Pair(startX, startY) } private fun getMaximizeButtonForTheApp(caption: UiObject2?): UiObject2 { return caption ?.children Loading Loading @@ -731,6 +784,9 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : private fun isInDesktopWindowingMode(wmHelper: WindowManagerStateHelper) = wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FREEFORM private fun isInFullscreenMode(wmHelper: WindowManagerStateHelper) = wmHelper.getWindow(innerHelper)?.windowingMode == WINDOWING_MODE_FULLSCREEN private fun isAnyDesktopWindowVisible(wmHelper: WindowManagerStateHelper) = wmHelper.currentState.wmState.hasFreeformWindow() Loading Loading @@ -772,6 +828,11 @@ open class DesktopModeAppHelper(private val innerHelper: StandardAppHelper) : CENTER } enum class SplitDirection { LEFT, RIGHT } private companion object { val TIMEOUT: Duration = Duration.ofSeconds(3) const val SNAP_RESIZE_DRAG_INSET: Int = 5 // inset to avoid dragging to display edge Loading
tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/MotionEventHelper.kt +3 −3 Original line number Diff line number Diff line Loading @@ -83,7 +83,7 @@ class MotionEventHelper( * Drag from [startX], [startY] to [endX], [endY] with a "hold" period after touching down * and before moving. */ fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int) { fun holdToDrag(startX: Int, startY: Int, endX: Int, endY: Int, steps: Int, sleepTimeBeforeDrop: Long = REGULAR_CLICK_LENGTH) { val downTime = SystemClock.uptimeMillis() actionDown(startX, startY, time = downTime) SystemClock.sleep(100L) // Hold before dragging. Loading @@ -96,8 +96,8 @@ class MotionEventHelper( downTime, withMotionEventInjectDelay = true ) SystemClock.sleep(REGULAR_CLICK_LENGTH) actionUp(startX, endX, downTime) SystemClock.sleep(sleepTimeBeforeDrop) actionUp(endX, endY, downTime) } private fun injectMotionEvent( Loading