Loading libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java +3 −3 Original line number Diff line number Diff line Loading @@ -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. Loading libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt 0 → 100644 +77 −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.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) ) } } } tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java 0 → 100644 +224 −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.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); } } tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +50 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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") Loading @@ -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 Loading Loading @@ -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 } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDoubleTapHelper.java +3 −3 Original line number Diff line number Diff line Loading @@ -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. Loading
libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt 0 → 100644 +77 −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.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) ) } } }
tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java 0 → 100644 +224 −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.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); } }
tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt +50 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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") Loading @@ -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 Loading Loading @@ -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 } }