From cc34ae620f9b39406f553aad31a5752de3c0c01f Mon Sep 17 00:00:00 2001 From: Chilun Huang Date: Mon, 6 Jun 2022 11:31:31 +0800 Subject: [PATCH] Add CUJ for dragging notification icon to split Add CUJ to enter split screen by dragging notification icon. Bug: 231399941 Test: atest EnterSplitScreenByDragFromNotification Change-Id: I5d9dc065346256ec7a5fbfd9a1fede3bdb91d8ba --- .../wm/shell/flicker/CommonAssertions.kt | 62 ++++--- .../flicker/helpers/SplitScreenHelper.kt | 154 +++++++++++++++++- .../EnterSplitScreenByDragFromNotification.kt | 139 ++++++++++++++++ .../test-apps/flickerapp/AndroidManifest.xml | 11 ++ .../res/layout/activity_notification.xml | 30 ++++ .../wm/shell/flicker/testapp/Components.java | 6 + .../testapp/SendNotificationActivity.java | 61 +++++++ 7 files changed, 434 insertions(+), 29 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml create mode 100644 libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt index 5dacdbf4b6383..cba396a82a872 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt @@ -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) { - getSplitLeftTopRegion(dividerRegion, rotation) - } else { - getSplitRightBottomRegion(dividerRegion, rotation) - }) + 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) { - getSplitLeftTopRegion(dividerRegion, rotation) - } else { - getSplitRightBottomRegion(dividerRegion, rotation) - }) + 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 + ) } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt index b902e5d1b454e..d4298b8f29a03 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt @@ -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) + } + } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 0000000000000..7323d992ecd45 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,139 @@ +/* + * 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 { + 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) + ) + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml index bd98585b67ec7..bc0b0b6292b4d 100644 --- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml +++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml @@ -92,6 +92,17 @@ + + + + + + + + + + +