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

This commit is contained in:
TreeHugger Robot
2022-06-09 03:38:24 +00:00
committed by Android (Google) Code Review
7 changed files with 434 additions and 29 deletions

View File

@@ -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
)
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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<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)
)
}
}
}

View File

@@ -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"

View File

@@ -0,0 +1,30 @@
<?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>

View File

@@ -88,6 +88,12 @@ public class Components {
PACKAGE_NAME + ".SplitScreenSecondaryActivity");
}
public static class SendNotificationActivity {
public static final String LABEL = "SendNotificationApp";
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
PACKAGE_NAME + ".SendNotificationActivity");
}
public static class LaunchBubbleActivity {
public static final String LABEL = "LaunchBubbleApp";
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,

View File

@@ -0,0 +1,61 @@
/*
* 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.testapp;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class SendNotificationActivity extends Activity {
private NotificationManager mNotificationManager;
private String mChannelId = "Channel id";
private String mChannelName = "Channel name";
private NotificationChannel mChannel;
private int mNotifyId = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification);
mChannel = new NotificationChannel(mChannelId, mChannelName,
NotificationManager.IMPORTANCE_DEFAULT);
mNotificationManager = getSystemService(NotificationManager.class);
mNotificationManager.createNotificationChannel(mChannel);
}
private void sendNotification(View v) {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, SendNotificationActivity.class),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
Notification notification = new Notification.Builder(this, mChannelId)
.setContentTitle("Notification App")
.setContentText("Notification content")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.ic_message)
.setContentIntent(pendingIntent)
.build();
mNotificationManager.notify(mNotifyId, notification);
}
}