Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fa54eeae authored by Sergey Pinkevich's avatar Sergey Pinkevich Committed by Android (Google) Code Review
Browse files

Merge "[E2E test] Drag window to enter split screen mode" into main

parents 8354004b 08171ab7
Loading
Loading
Loading
Loading
+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()
+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)
    }
}
+67 −6
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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()

@@ -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
+3 −3
Original line number Diff line number Diff line
@@ -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.
@@ -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(