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

Commit 83f09e94 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add CUJ for dragging notification icon to split" into tm-qpr-dev

parents 72ef6b43 cc34ae62
Loading
Loading
Loading
Loading
+39 −23
Original line number Diff line number Diff line
@@ -71,15 +71,17 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
    splitLeftTop: Boolean
) {
    assertLayers {
        val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
        this.isInvisible(component)
            .then()
            .invoke("splitAppLayerBoundsBecomesVisible") {
                val dividerRegion = it.layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
                it.visibleRegion(component).overlaps(if (splitLeftTop) {
                it.visibleRegion(component).overlaps(
                    if (splitLeftTop) {
                        getSplitLeftTopRegion(dividerRegion, rotation)
                    } else {
                        getSplitRightBottomRegion(dividerRegion, rotation)
                })
                    }
                )
            }
    }
}
@@ -91,11 +93,13 @@ fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
) {
    assertLayersEnd {
        val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
        visibleRegion(component).overlaps(if (splitLeftTop) {
        visibleRegion(component).overlaps(
            if (splitLeftTop) {
                getSplitLeftTopRegion(dividerRegion, rotation)
            } else {
                getSplitRightBottomRegion(dividerRegion, rotation)
        })
            }
        )
    }
}

@@ -192,22 +196,30 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
    val displayBounds = WindowUtils.getDisplayBounds(rotation)
    return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
        Region.from(0, 0, displayBounds.bounds.right,
            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
        Region.from(
            0, 0, displayBounds.bounds.right,
            dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
        )
    } else {
        Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
            displayBounds.bounds.bottom)
        Region.from(
            0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
            displayBounds.bounds.bottom
        )
    }
}

fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
    val displayBounds = WindowUtils.getDisplayBounds(rotation)
    return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
        Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
            displayBounds.bounds.right, displayBounds.bounds.bottom)
        Region.from(
            0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
            displayBounds.bounds.right, displayBounds.bounds.bottom
        )
    } else {
        Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
            displayBounds.bounds.right, displayBounds.bounds.bottom)
        Region.from(
            dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
            displayBounds.bounds.right, displayBounds.bounds.bottom
        )
    }
}

@@ -223,10 +235,14 @@ fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
    val displayBounds = WindowUtils.getDisplayBounds(rotation)
    return if (displayBounds.width > displayBounds.height) {
        Region.from(dividerRegion.bounds.right, 0, displayBounds.bounds.right,
                displayBounds.bounds.bottom)
        Region.from(
            dividerRegion.bounds.right, 0, displayBounds.bounds.right,
            displayBounds.bounds.bottom
        )
    } else {
        Region.from(0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
                displayBounds.bounds.bottom)
        Region.from(
            0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
            displayBounds.bounds.bottom
        )
    }
}
+148 −6
Original line number Diff line number Diff line
@@ -17,8 +17,18 @@
package com.android.wm.shell.flicker.helpers

import android.app.Instrumentation
import android.graphics.Point
import android.os.SystemClock
import android.view.InputDevice
import android.view.MotionEvent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components

class SplitScreenHelper(
@@ -30,20 +40,152 @@ class SplitScreenHelper(
    companion object {
        const val TEST_REPETITIONS = 1
        const val TIMEOUT_MS = 3_000L
        const val DRAG_DURATION_MS = 1_000L
        const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
        const val GESTURE_STEP_MS = 16L

        private val notificationScrollerSelector: BySelector
            get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
        private val notificationContentSelector: BySelector
            get() = By.text("Notification content")

        fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
            SplitScreenHelper(instrumentation,
            SplitScreenHelper(
                instrumentation,
                Components.SplitScreenActivity.LABEL,
                Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
                Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
            )

        fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
            SplitScreenHelper(instrumentation,
            SplitScreenHelper(
                instrumentation,
                Components.SplitScreenSecondaryActivity.LABEL,
                Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent())
                Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
            )

        fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
            SplitScreenHelper(instrumentation,
            SplitScreenHelper(
                instrumentation,
                Components.NonResizeableActivity.LABEL,
                Components.NonResizeableActivity.COMPONENT.toFlickerComponent())
                Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
            )

        fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
            SplitScreenHelper(
                instrumentation,
                Components.SendNotificationActivity.LABEL,
                Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
            )

        fun dragFromNotificationToSplit(
            instrumentation: Instrumentation,
            device: UiDevice,
            wmHelper: WindowManagerStateHelper
        ) {
            val displayBounds = wmHelper.currentState.layerState
                .displays.firstOrNull { !it.isVirtual }
                ?.layerStackSpace
                ?: error("Display not found")

            // Pull down the notifications
            device.swipe(
                displayBounds.centerX(), 5,
                displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
            )
            SystemClock.sleep(TIMEOUT_MS)

            // Find the target notification
            val notificationScroller = device.wait(
                Until.findObject(notificationScrollerSelector), TIMEOUT_MS
            )
            var notificationContent = notificationScroller.findObject(notificationContentSelector)

            while (notificationContent == null) {
                device.swipe(
                    displayBounds.centerX(), displayBounds.centerY(),
                    displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
                )
                notificationContent = notificationScroller.findObject(notificationContentSelector)
            }

            // Drag to split
            var dragStart = notificationContent.visibleCenter
            var dragMiddle = Point(dragStart.x + 50, dragStart.y)
            var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
            val downTime = SystemClock.uptimeMillis()

            touch(
                instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
                TIMEOUT_MS, dragStart
            )
            // It needs a horizontal movement to trigger the drag
            touchMove(
                instrumentation, downTime, SystemClock.uptimeMillis(),
                DRAG_DURATION_MS, dragStart, dragMiddle
            )
            touchMove(
                instrumentation, downTime, SystemClock.uptimeMillis(),
                DRAG_DURATION_MS, dragMiddle, dragEnd
            )
            // Wait for a while to start splitting
            SystemClock.sleep(TIMEOUT_MS)
            touch(
                instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
                GESTURE_STEP_MS, dragEnd
            )
            SystemClock.sleep(TIMEOUT_MS)
        }

        fun touch(
            instrumentation: Instrumentation,
            action: Int,
            downTime: Long,
            eventTime: Long,
            duration: Long,
            point: Point
        ) {
            val motionEvent = MotionEvent.obtain(
                downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
            )
            motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
            instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
            motionEvent.recycle()
            SystemClock.sleep(duration)
        }

        fun touchMove(
            instrumentation: Instrumentation,
            downTime: Long,
            eventTime: Long,
            duration: Long,
            from: Point,
            to: Point
        ) {
            val steps: Long = duration / GESTURE_STEP_MS
            var currentTime = eventTime
            var currentX = from.x.toFloat()
            var currentY = from.y.toFloat()
            val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
            val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()

            for (i in 1..steps) {
                val motionMove = MotionEvent.obtain(
                    downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
                )
                motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
                instrumentation.uiAutomation.injectInputEvent(motionMove, true)
                motionMove.recycle()

                currentTime += GESTURE_STEP_MS
                if (i == steps - 1) {
                    currentX = to.x.toFloat()
                    currentY = to.y.toFloat()
                } else {
                    currentX += stepX
                    currentY += stepY
                }
                SystemClock.sleep(GESTURE_STEP_MS)
            }
        }
    }
}
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.splitscreen

import android.platform.test.annotations.Presubmit
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.RequiresDevice
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group1
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized

/**
 * Test enter split screen by dragging app icon from notification.
 * This test is only for large screen devices.
 *
 * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
 */
@RequiresDevice
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group1
class EnterSplitScreenByDragFromNotification(
    testSpec: FlickerTestParameter
) : SplitScreenBase(testSpec) {

    private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)

    @Before
    fun before() {
        Assume.assumeTrue(taplInstrumentation.isTablet)
    }

    override val transition: FlickerBuilder.() -> Unit
        get() = {
            super.transition(this)
            setup {
                eachRun {
                    // Send a notification
                    sendNotificationApp.launchViaIntent(wmHelper)
                    val sendNotification = device.wait(
                        Until.findObject(By.text("Send Notification")),
                        SplitScreenHelper.TIMEOUT_MS
                    )
                    sendNotification?.click() ?: error("Send notification button not found")

                    taplInstrumentation.goHome()
                    primaryApp.launchViaIntent(wmHelper)
                }
            }
            transitions {
                SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
            }
            teardown {
                eachRun {
                    sendNotificationApp.exit(wmHelper)
                }
            }
        }

    @Presubmit
    @Test
    fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()

    @Presubmit
    @Test
    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)

    @Presubmit
    @Test
    fun secondaryAppLayerBecomesVisible() =
        testSpec.layerBecomesVisible(sendNotificationApp.component)

    @Presubmit
    @Test
    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
        testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
    )

    @Presubmit
    @Test
    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
        testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */
    )

    @Presubmit
    @Test
    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)

    @Presubmit
    @Test
    fun secondaryAppWindowIsVisibleAtEnd() =
        testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component)

    companion object {
        @Parameterized.Parameters(name = "{0}")
        @JvmStatic
        fun getParams(): List<FlickerTestParameter> {
            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
                repetitions = SplitScreenHelper.TEST_REPETITIONS,
                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
                supportedNavigationModes =
                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
            )
        }
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -92,6 +92,17 @@
            </intent-filter>
        </activity>

        <activity android:name=".SendNotificationActivity"
                  android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
                  android:theme="@style/CutoutShortEdges"
                  android:label="SendNotificationApp"
                  android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <activity android:name=".NonResizeableActivity"
                  android:resizeableActivity="false"
                  android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
+30 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
 Copyright 2021 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.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@android:color/black">

        <Button
            android:id="@+id/button_send_notification"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_centerVertical="true"
            android:text="Send Notification" />
</RelativeLayout>
Loading