Merge "Add flicker test for pinch out pip screen resizing"
This commit is contained in:
committed by
Android (Google) Code Review
commit
ebc7ee824c
@@ -43,16 +43,16 @@ public class PipDoubleTapHelper {
|
||||
* <p>MAX - maximum allowed screen size</p>
|
||||
*/
|
||||
@IntDef(value = {
|
||||
SIZE_SPEC_CUSTOM,
|
||||
SIZE_SPEC_DEFAULT,
|
||||
SIZE_SPEC_MAX
|
||||
SIZE_SPEC_MAX,
|
||||
SIZE_SPEC_CUSTOM
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface PipSizeSpec {}
|
||||
|
||||
static final int SIZE_SPEC_CUSTOM = 2;
|
||||
static final int SIZE_SPEC_DEFAULT = 0;
|
||||
static final int SIZE_SPEC_MAX = 1;
|
||||
static final int SIZE_SPEC_CUSTOM = 2;
|
||||
|
||||
/**
|
||||
* Returns MAX or DEFAULT {@link PipSizeSpec} to toggle to/from.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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.pip
|
||||
|
||||
import android.platform.test.annotations.Postsubmit
|
||||
import android.view.Surface
|
||||
import androidx.test.filters.RequiresDevice
|
||||
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.dsl.FlickerBuilder
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
/**
|
||||
* Test expanding a pip window via pinch out gesture.
|
||||
*/
|
||||
@RequiresDevice
|
||||
@RunWith(Parameterized::class)
|
||||
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
class ExpandPipOnPinchOpenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
|
||||
override val transition: FlickerBuilder.() -> Unit
|
||||
get() = buildTransition {
|
||||
transitions {
|
||||
pipApp.pinchOpenPipWindow(wmHelper, 0.4f, 30)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the visible region area of [pipApp] always increases during the animation.
|
||||
*/
|
||||
@Postsubmit
|
||||
@Test
|
||||
fun pipLayerAreaIncreases() {
|
||||
testSpec.assertLayers {
|
||||
val pipLayerList = this.layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
|
||||
pipLayerList.zipWithNext { previous, current ->
|
||||
previous.visibleRegion.notBiggerThan(current.visibleRegion.region)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates the test configurations.
|
||||
*
|
||||
* See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring
|
||||
* repetitions, screen orientation and navigation modes.
|
||||
*/
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
@JvmStatic
|
||||
fun getParams(): List<FlickerTestParameter> {
|
||||
return FlickerTestParameterFactory.getInstance()
|
||||
.getConfigNonRotationTests(
|
||||
supportedRotations = listOf(Surface.ROTATION_0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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.server.wm.flicker.helpers;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.Instrumentation;
|
||||
import android.app.UiAutomation;
|
||||
import android.os.SystemClock;
|
||||
import android.view.InputDevice;
|
||||
import android.view.InputEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.MotionEvent.PointerCoords;
|
||||
import android.view.MotionEvent.PointerProperties;
|
||||
|
||||
/**
|
||||
* Injects gestures given an {@link Instrumentation} object.
|
||||
*/
|
||||
public class GestureHelper {
|
||||
// Inserted after each motion event injection.
|
||||
private static final int MOTION_EVENT_INJECTION_DELAY_MILLIS = 5;
|
||||
|
||||
private final UiAutomation mUiAutomation;
|
||||
|
||||
/**
|
||||
* A pair of floating point values.
|
||||
*/
|
||||
public static class Tuple {
|
||||
public float x;
|
||||
public float y;
|
||||
|
||||
public Tuple(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
public GestureHelper(Instrumentation instrumentation) {
|
||||
mUiAutomation = instrumentation.getUiAutomation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
|
||||
*
|
||||
* @param startPoint1 initial coordinates of the first pointer
|
||||
* @param startPoint2 initial coordinates of the second pointer
|
||||
* @param endPoint1 final coordinates of the first pointer
|
||||
* @param endPoint2 final coordinates of the second pointer
|
||||
* @param steps number of steps to take to animate pinching
|
||||
* @return true if gesture is injected successfully
|
||||
*/
|
||||
public boolean pinch(@NonNull Tuple startPoint1, @NonNull Tuple startPoint2,
|
||||
@NonNull Tuple endPoint1, @NonNull Tuple endPoint2, int steps) {
|
||||
PointerProperties ptrProp1 = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
|
||||
PointerProperties ptrProp2 = getPointerProp(1, MotionEvent.TOOL_TYPE_FINGER);
|
||||
|
||||
PointerCoords ptrCoord1 = getPointerCoord(startPoint1.x, startPoint1.y, 1, 1);
|
||||
PointerCoords ptrCoord2 = getPointerCoord(startPoint2.x, startPoint2.y, 1, 1);
|
||||
|
||||
PointerProperties[] ptrProps = new PointerProperties[] {
|
||||
ptrProp1, ptrProp2
|
||||
};
|
||||
|
||||
PointerCoords[] ptrCoords = new PointerCoords[] {
|
||||
ptrCoord1, ptrCoord2
|
||||
};
|
||||
|
||||
long downTime = SystemClock.uptimeMillis();
|
||||
|
||||
if (!primaryPointerDown(ptrProp1, ptrCoord1, downTime)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nonPrimaryPointerDown(ptrProps, ptrCoords, downTime, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint1, endPoint2 },
|
||||
downTime, steps)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nonPrimaryPointerUp(ptrProps, ptrCoords, downTime, 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return primaryPointerUp(ptrProp1, ptrCoord1, downTime);
|
||||
}
|
||||
|
||||
private boolean primaryPointerDown(@NonNull PointerProperties prop,
|
||||
@NonNull PointerCoords coord, long downTime) {
|
||||
MotionEvent event = getMotionEvent(downTime, downTime, MotionEvent.ACTION_DOWN, 1,
|
||||
new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
|
||||
|
||||
return injectEventSync(event);
|
||||
}
|
||||
|
||||
private boolean nonPrimaryPointerDown(@NonNull PointerProperties[] props,
|
||||
@NonNull PointerCoords[] coords, long downTime, int index) {
|
||||
// at least 2 pointers are needed
|
||||
if (props.length != coords.length || coords.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
|
||||
MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_DOWN
|
||||
+ (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
|
||||
|
||||
return injectEventSync(event);
|
||||
}
|
||||
|
||||
private boolean movePointers(@NonNull PointerProperties[] props,
|
||||
@NonNull PointerCoords[] coords, @NonNull Tuple[] endPoints, long downTime, int steps) {
|
||||
// the number of endpoints should be the same as the number of pointers
|
||||
if (props.length != coords.length || coords.length != endPoints.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// prevent division by 0 and negative number of steps
|
||||
if (steps < 1) {
|
||||
steps = 1;
|
||||
}
|
||||
|
||||
// save the starting points before updating any pointers
|
||||
Tuple[] startPoints = new Tuple[coords.length];
|
||||
|
||||
for (int i = 0; i < coords.length; i++) {
|
||||
startPoints[i] = new Tuple(coords[i].x, coords[i].y);
|
||||
}
|
||||
|
||||
MotionEvent event;
|
||||
long eventTime;
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
// inject a delay between movements
|
||||
SystemClock.sleep(MOTION_EVENT_INJECTION_DELAY_MILLIS);
|
||||
|
||||
// update the coordinates
|
||||
for (int j = 0; j < coords.length; j++) {
|
||||
coords[j].x += (endPoints[j].x - startPoints[j].x) / steps;
|
||||
coords[j].y += (endPoints[j].y - startPoints[j].y) / steps;
|
||||
}
|
||||
|
||||
eventTime = SystemClock.uptimeMillis();
|
||||
|
||||
event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_MOVE,
|
||||
coords.length, props, coords);
|
||||
|
||||
boolean didInject = injectEventSync(event);
|
||||
|
||||
if (!didInject) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean primaryPointerUp(@NonNull PointerProperties prop,
|
||||
@NonNull PointerCoords coord, long downTime) {
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
|
||||
MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_UP, 1,
|
||||
new PointerProperties[]{ prop }, new PointerCoords[]{ coord });
|
||||
|
||||
return injectEventSync(event);
|
||||
}
|
||||
|
||||
private boolean nonPrimaryPointerUp(@NonNull PointerProperties[] props,
|
||||
@NonNull PointerCoords[] coords, long downTime, int index) {
|
||||
// at least 2 pointers are needed
|
||||
if (props.length != coords.length || coords.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long eventTime = SystemClock.uptimeMillis();
|
||||
|
||||
MotionEvent event = getMotionEvent(downTime, eventTime, MotionEvent.ACTION_POINTER_UP
|
||||
+ (index << MotionEvent.ACTION_POINTER_INDEX_SHIFT), coords.length, props, coords);
|
||||
|
||||
return injectEventSync(event);
|
||||
}
|
||||
|
||||
private PointerCoords getPointerCoord(float x, float y, float pressure, float size) {
|
||||
PointerCoords ptrCoord = new PointerCoords();
|
||||
ptrCoord.x = x;
|
||||
ptrCoord.y = y;
|
||||
ptrCoord.pressure = pressure;
|
||||
ptrCoord.size = size;
|
||||
return ptrCoord;
|
||||
}
|
||||
|
||||
private PointerProperties getPointerProp(int id, int toolType) {
|
||||
PointerProperties ptrProp = new PointerProperties();
|
||||
ptrProp.id = id;
|
||||
ptrProp.toolType = toolType;
|
||||
return ptrProp;
|
||||
}
|
||||
|
||||
private static MotionEvent getMotionEvent(long downTime, long eventTime, int action,
|
||||
int pointerCount, PointerProperties[] ptrProps, PointerCoords[] ptrCoords) {
|
||||
return MotionEvent.obtain(downTime, eventTime, action, pointerCount,
|
||||
ptrProps, ptrCoords, 0, 0, 1.0f, 1.0f,
|
||||
0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
|
||||
}
|
||||
|
||||
private boolean injectEventSync(InputEvent event) {
|
||||
return mUiAutomation.injectInputEvent(event, true);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import android.media.session.MediaSessionManager
|
||||
import android.util.Log
|
||||
import androidx.test.uiautomator.By
|
||||
import androidx.test.uiautomator.Until
|
||||
import com.android.server.wm.flicker.helpers.GestureHelper.Tuple
|
||||
import com.android.server.wm.flicker.testapp.ActivityOptions
|
||||
import com.android.server.wm.traces.common.Rect
|
||||
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
|
||||
@@ -44,6 +45,8 @@ open class PipAppHelper(instrumentation: Instrumentation) :
|
||||
get() =
|
||||
mediaSessionManager.getActiveSessions(null).firstOrNull { it.packageName == `package` }
|
||||
|
||||
private val gestureHelper: GestureHelper = GestureHelper(mInstrumentation)
|
||||
|
||||
open fun clickObject(resId: String) {
|
||||
val selector = By.res(`package`, resId)
|
||||
val obj = uiDevice.findObject(selector) ?: error("Could not find `$resId` object")
|
||||
@@ -51,6 +54,50 @@ open class PipAppHelper(instrumentation: Instrumentation) :
|
||||
obj.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the PIP window my using the pinch out gesture.
|
||||
*
|
||||
* @param percent The percentage by which to increase the pip window size.
|
||||
* @throws IllegalArgumentException if percentage isn't between 0.0f and 1.0f
|
||||
*/
|
||||
fun pinchOpenPipWindow(wmHelper: WindowManagerStateHelper, percent: Float, steps: Int) {
|
||||
// the percentage must be between 0.0f and 1.0f
|
||||
if (percent <= 0.0f || percent > 1.0f) {
|
||||
throw IllegalArgumentException("Percent must be between 0.0f and 1.0f")
|
||||
}
|
||||
|
||||
val windowRect = getWindowRect(wmHelper)
|
||||
|
||||
// first pointer's initial x coordinate is halfway between the left edge and the center
|
||||
val initLeftX = (windowRect.centerX() - windowRect.width / 4).toFloat()
|
||||
// second pointer's initial x coordinate is halfway between the right edge and the center
|
||||
val initRightX = (windowRect.centerX() + windowRect.width / 4).toFloat()
|
||||
|
||||
// horizontal distance the window should increase by
|
||||
val distIncrease = windowRect.width * percent
|
||||
|
||||
// final x-coordinates
|
||||
val finalLeftX = initLeftX - (distIncrease / 2)
|
||||
val finalRightX = initRightX + (distIncrease / 2)
|
||||
|
||||
// y-coordinate is the same throughout this animation
|
||||
val yCoord = windowRect.centerY().toFloat()
|
||||
|
||||
var adjustedSteps = MIN_STEPS_TO_ANIMATE
|
||||
|
||||
// if distance per step is at least 1, then we can use the number of steps requested
|
||||
if (distIncrease.toInt() / (steps * 2) >= 1) {
|
||||
adjustedSteps = steps
|
||||
}
|
||||
|
||||
// if the distance per step is less than 1, carry out the animation in two steps
|
||||
gestureHelper.pinch(
|
||||
Tuple(initLeftX, yCoord), Tuple(initRightX, yCoord),
|
||||
Tuple(finalLeftX, yCoord), Tuple(finalRightX, yCoord), adjustedSteps)
|
||||
|
||||
waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the app through an intent instead of interacting with the launcher and waits until
|
||||
* the app window is in PIP mode
|
||||
@@ -194,5 +241,8 @@ open class PipAppHelper(instrumentation: Instrumentation) :
|
||||
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
|
||||
private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
|
||||
private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
|
||||
// minimum number of steps to take, when animating gestures, needs to be 2
|
||||
// so that there is at least a single intermediate layer that flicker tests can check
|
||||
private const val MIN_STEPS_TO_ANIMATE = 2
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user