| /* |
| * Copyright (C) 2016 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.compatibility.common.util; |
| |
| import android.app.Instrumentation; |
| import android.app.UiAutomation; |
| import android.graphics.Point; |
| import android.os.SystemClock; |
| import android.support.test.rule.ActivityTestRule; |
| import android.util.SparseArray; |
| import android.view.InputDevice; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.ViewGroup; |
| |
| /** |
| * Test utilities for touch emulation. |
| */ |
| public final class CtsTouchUtils { |
| /** |
| * Interface definition for a callback to be invoked when an event has been injected. |
| */ |
| public interface EventInjectionListener { |
| /** |
| * Callback method to be invoked when a {MotionEvent#ACTION_DOWN} has been injected. |
| * @param xOnScreen X coordinate of the injected event. |
| * @param yOnScreen Y coordinate of the injected event. |
| */ |
| public void onDownInjected(int xOnScreen, int yOnScreen); |
| |
| /** |
| * Callback method to be invoked when a {MotionEvent#ACTION_MOVE} has been injected. |
| * @param xOnScreen X coordinates of the injected event. |
| * @param yOnScreen Y coordinates of the injected event. |
| */ |
| public void onMoveInjected(int[] xOnScreen, int[] yOnScreen); |
| |
| /** |
| * Callback method to be invoked when a {MotionEvent#ACTION_UP} has been injected. |
| * @param xOnScreen X coordinate of the injected event. |
| * @param yOnScreen Y coordinate of the injected event. |
| */ |
| public void onUpInjected(int xOnScreen, int yOnScreen); |
| } |
| |
| private CtsTouchUtils() {} |
| |
| /** |
| * Emulates a tap in the center of the passed {@link View}. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to "tap" |
| */ |
| public static void emulateTapOnViewCenter(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view) { |
| emulateTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2, |
| view.getHeight() / 2); |
| } |
| |
| /** |
| * Emulates a tap on a point relative to the top-left corner of the passed {@link View}. Offset |
| * parameters are used to compute the final screen coordinates of the tap point. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param anchorView the anchor view to determine the tap location on the screen |
| * @param offsetX extra X offset for the tap |
| * @param offsetY extra Y offset for the tap |
| */ |
| public static void emulateTapOnView(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View anchorView, |
| int offsetX, int offsetY) { |
| final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop(); |
| // Get anchor coordinates on the screen |
| final int[] viewOnScreenXY = new int[2]; |
| anchorView.getLocationOnScreen(viewOnScreenXY); |
| int xOnScreen = viewOnScreenXY[0] + offsetX; |
| int yOnScreen = viewOnScreenXY[1] + offsetY; |
| final UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); |
| injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); |
| injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); |
| |
| // Wait for the system to process all events in the queue |
| if (activityTestRule != null) { |
| WidgetTestUtils.runOnMainAndDrawSync(activityTestRule, |
| activityTestRule.getActivity().getWindow().getDecorView(), null); |
| } else { |
| instrumentation.waitForIdleSync(); |
| } |
| } |
| |
| /** |
| * Emulates a double tap in the center of the passed {@link View}. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to "double tap" |
| */ |
| public static void emulateDoubleTapOnViewCenter(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view) { |
| emulateDoubleTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2, |
| view.getHeight() / 2); |
| } |
| |
| /** |
| * Emulates a double tap on a point relative to the top-left corner of the passed {@link View}. |
| * Offset parameters are used to compute the final screen coordinates of the tap points. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param anchorView the anchor view to determine the tap location on the screen |
| * @param offsetX extra X offset for the taps |
| * @param offsetY extra Y offset for the taps |
| */ |
| public static void emulateDoubleTapOnView(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View anchorView, |
| int offsetX, int offsetY) { |
| final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop(); |
| // Get anchor coordinates on the screen |
| final int[] viewOnScreenXY = new int[2]; |
| anchorView.getLocationOnScreen(viewOnScreenXY); |
| int xOnScreen = viewOnScreenXY[0] + offsetX; |
| int yOnScreen = viewOnScreenXY[1] + offsetY; |
| final UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); |
| injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); |
| injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); |
| injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); |
| injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); |
| injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); |
| |
| // Wait for the system to process all events in the queue |
| if (activityTestRule != null) { |
| WidgetTestUtils.runOnMainAndDrawSync(activityTestRule, |
| activityTestRule.getActivity().getWindow().getDecorView(), null); |
| } else { |
| instrumentation.waitForIdleSync(); |
| } |
| } |
| |
| /** |
| * Emulates a linear drag gesture between 2 points across the screen. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param dragStartX Start X of the emulated drag gesture |
| * @param dragStartY Start Y of the emulated drag gesture |
| * @param dragAmountX X amount of the emulated drag gesture |
| * @param dragAmountY Y amount of the emulated drag gesture |
| */ |
| public static void emulateDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) { |
| emulateDragGesture(instrumentation, activityTestRule, |
| dragStartX, dragStartY, dragAmountX, dragAmountY, |
| 2000, 20, null); |
| } |
| |
| private static void emulateDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, |
| int dragDurationMs, int moveEventCount) { |
| emulateDragGesture(instrumentation, activityTestRule, |
| dragStartX, dragStartY, dragAmountX, dragAmountY, |
| dragDurationMs, moveEventCount, null); |
| } |
| |
| private static void emulateDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| int dragStartX, int dragStartY, int dragAmountX, int dragAmountY, |
| int dragDurationMs, int moveEventCount, |
| EventInjectionListener eventInjectionListener) { |
| // We are using the UiAutomation object to inject events so that drag works |
| // across view / window boundaries (such as for the emulated drag and drop |
| // sequences) |
| final UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| injectDownEvent(uiAutomation, downTime, dragStartX, dragStartY, eventInjectionListener); |
| |
| // Inject a sequence of MOVE events that emulate the "move" part of the gesture |
| injectMoveEventsForDrag(uiAutomation, downTime, true, dragStartX, dragStartY, |
| dragStartX + dragAmountX, dragStartY + dragAmountY, moveEventCount, dragDurationMs, |
| eventInjectionListener); |
| |
| injectUpEvent(uiAutomation, downTime, true, dragStartX + dragAmountX, |
| dragStartY + dragAmountY, eventInjectionListener); |
| |
| // Wait for the system to process all events in the queue |
| if (activityTestRule != null) { |
| WidgetTestUtils.runOnMainAndDrawSync(activityTestRule, |
| activityTestRule.getActivity().getWindow().getDecorView(), null); |
| } else { |
| instrumentation.waitForIdleSync(); |
| } |
| } |
| |
| /** |
| * Emulates a series of linear drag gestures across the screen between multiple points without |
| * lifting the finger. Note that this function does not support curve movements between the |
| * points. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param coordinates the ordered list of points for the drag gesture |
| */ |
| public static void emulateDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, SparseArray<Point> coordinates) { |
| emulateDragGesture(instrumentation, activityTestRule, coordinates, 2000, 20); |
| } |
| |
| private static void emulateDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| SparseArray<Point> coordinates, int dragDurationMs, int moveEventCount) { |
| final int coordinatesSize = coordinates.size(); |
| if (coordinatesSize < 2) { |
| throw new IllegalArgumentException("Need at least 2 points for emulating drag"); |
| } |
| // We are using the UiAutomation object to inject events so that drag works |
| // across view / window boundaries (such as for the emulated drag and drop |
| // sequences) |
| final UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| injectDownEvent(uiAutomation, downTime, coordinates.get(0).x, coordinates.get(0).y, null); |
| |
| // Move to each coordinate. |
| for (int i = 0; i < coordinatesSize - 1; i++) { |
| // Inject a sequence of MOVE events that emulate the "move" part of the gesture. |
| injectMoveEventsForDrag(uiAutomation, |
| downTime, |
| true, |
| coordinates.get(i).x, |
| coordinates.get(i).y, |
| coordinates.get(i + 1).x, |
| coordinates.get(i + 1).y, |
| moveEventCount, |
| dragDurationMs, |
| null); |
| } |
| |
| injectUpEvent(uiAutomation, |
| downTime, |
| true, |
| coordinates.get(coordinatesSize - 1).x, |
| coordinates.get(coordinatesSize - 1).y, |
| null); |
| |
| // Wait for the system to process all events in the queue |
| if (activityTestRule != null) { |
| WidgetTestUtils.runOnMainAndDrawSync(activityTestRule, |
| activityTestRule.getActivity().getWindow().getDecorView(), null); |
| } else { |
| instrumentation.waitForIdleSync(); |
| } |
| } |
| |
| private static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen, |
| int yOnScreen, EventInjectionListener eventInjectionListener) { |
| MotionEvent eventDown = MotionEvent.obtain( |
| downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1); |
| eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN); |
| uiAutomation.injectInputEvent(eventDown, true); |
| if (eventInjectionListener != null) { |
| eventInjectionListener.onDownInjected(xOnScreen, yOnScreen); |
| } |
| eventDown.recycle(); |
| return downTime; |
| } |
| |
| private static void injectMoveEventForTap(UiAutomation uiAutomation, long downTime, |
| int touchSlop, int xOnScreen, int yOnScreen) { |
| MotionEvent eventMove = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE, |
| xOnScreen + (touchSlop / 2.0f), yOnScreen + (touchSlop / 2.0f), 1); |
| eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN); |
| uiAutomation.injectInputEvent(eventMove, true); |
| eventMove.recycle(); |
| } |
| |
| private static void injectMoveEventsForDrag(UiAutomation uiAutomation, long downTime, |
| boolean useCurrentEventTime, int dragStartX, int dragStartY, int dragEndX, int dragEndY, |
| int moveEventCount, int dragDurationMs, EventInjectionListener eventInjectionListener) { |
| final int dragAmountX = dragEndX - dragStartX; |
| final int dragAmountY = dragEndY - dragStartY; |
| final int sleepTime = dragDurationMs / moveEventCount; |
| |
| // sleep for a bit to emulate the overall drag gesture. |
| long prevEventTime = downTime; |
| SystemClock.sleep(sleepTime); |
| for (int i = 0; i < moveEventCount; i++) { |
| // Note that the first MOVE event is generated "away" from the coordinates |
| // of the start / DOWN event, and the last MOVE event is generated |
| // at the same coordinates as the subsequent UP event. |
| final int moveX = dragStartX + dragAmountX * (i + 1) / moveEventCount; |
| final int moveY = dragStartY + dragAmountY * (i + 1) / moveEventCount; |
| long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime; |
| |
| // If necessary, generate history for our next MOVE event. The history is generated |
| // to be spaced at 10 millisecond intervals, interpolating the coordinates from the |
| // last generated MOVE event to our current one. |
| int historyEventCount = (int) ((eventTime - prevEventTime) / 10); |
| int[] xCoordsForListener = (eventInjectionListener == null) ? null : |
| new int[Math.max(1, historyEventCount)]; |
| int[] yCoordsForListener = (eventInjectionListener == null) ? null : |
| new int[Math.max(1, historyEventCount)]; |
| MotionEvent eventMove = null; |
| if (historyEventCount == 0) { |
| eventMove = MotionEvent.obtain( |
| downTime, eventTime, MotionEvent.ACTION_MOVE, moveX, moveY, 1); |
| if (eventInjectionListener != null) { |
| xCoordsForListener[0] = moveX; |
| yCoordsForListener[0] = moveY; |
| } |
| } else { |
| final int prevMoveX = dragStartX + dragAmountX * i / moveEventCount; |
| final int prevMoveY = dragStartY + dragAmountY * i / moveEventCount; |
| final int deltaMoveX = moveX - prevMoveX; |
| final int deltaMoveY = moveY - prevMoveY; |
| final long deltaTime = (eventTime - prevEventTime); |
| for (int historyIndex = 0; historyIndex < historyEventCount; historyIndex++) { |
| int stepMoveX = prevMoveX + deltaMoveX * (historyIndex + 1) / historyEventCount; |
| int stepMoveY = prevMoveY + deltaMoveY * (historyIndex + 1) / historyEventCount; |
| long stepEventTime = useCurrentEventTime |
| ? prevEventTime + deltaTime * (historyIndex + 1) / historyEventCount |
| : downTime; |
| if (historyIndex == 0) { |
| // Generate the first event in our sequence |
| eventMove = MotionEvent.obtain(downTime, stepEventTime, |
| MotionEvent.ACTION_MOVE, stepMoveX, stepMoveY, 1); |
| } else { |
| // and then add to it |
| eventMove.addBatch(stepEventTime, stepMoveX, stepMoveY, 1.0f, 1.0f, 1); |
| } |
| if (eventInjectionListener != null) { |
| xCoordsForListener[historyIndex] = stepMoveX; |
| yCoordsForListener[historyIndex] = stepMoveY; |
| } |
| } |
| } |
| |
| eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN); |
| uiAutomation.injectInputEvent(eventMove, true); |
| if (eventInjectionListener != null) { |
| eventInjectionListener.onMoveInjected(xCoordsForListener, yCoordsForListener); |
| } |
| eventMove.recycle(); |
| prevEventTime = eventTime; |
| |
| // sleep for a bit to emulate the overall drag gesture. |
| SystemClock.sleep(sleepTime); |
| } |
| } |
| |
| private static void injectUpEvent(UiAutomation uiAutomation, long downTime, |
| boolean useCurrentEventTime, int xOnScreen, int yOnScreen, |
| EventInjectionListener eventInjectionListener) { |
| long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime; |
| MotionEvent eventUp = MotionEvent.obtain( |
| downTime, eventTime, MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 1); |
| eventUp.setSource(InputDevice.SOURCE_TOUCHSCREEN); |
| uiAutomation.injectInputEvent(eventUp, true); |
| if (eventInjectionListener != null) { |
| eventInjectionListener.onUpInjected(xOnScreen, yOnScreen); |
| } |
| eventUp.recycle(); |
| } |
| |
| /** |
| * Emulates a fling gesture across the horizontal center of the passed view. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to fling |
| * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will |
| * be a downwards gesture |
| * @return The vertical amount of emulated fling in pixels |
| */ |
| public static int emulateFlingGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture) { |
| return emulateFlingGesture(instrumentation, activityTestRule, |
| view, isDownwardsFlingGesture, null); |
| } |
| |
| /** |
| * Emulates a fling gesture across the horizontal center of the passed view. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to fling |
| * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will |
| * be a downwards gesture |
| * @param eventInjectionListener optional listener to notify about the injected events |
| * @return The vertical amount of emulated fling in pixels |
| */ |
| public static int emulateFlingGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture, |
| EventInjectionListener eventInjectionListener) { |
| final ViewConfiguration configuration = ViewConfiguration.get(view.getContext()); |
| final int flingVelocity = (configuration.getScaledMinimumFlingVelocity() + |
| configuration.getScaledMaximumFlingVelocity()) / 2; |
| // Get view coordinates on the screen |
| final int[] viewOnScreenXY = new int[2]; |
| view.getLocationOnScreen(viewOnScreenXY); |
| |
| // Our fling gesture will be from 25% height of the view to 75% height of the view |
| // for downwards fling gesture, and the other way around for upwards fling gesture |
| final int viewHeight = view.getHeight(); |
| final int x = viewOnScreenXY[0] + view.getWidth() / 2; |
| final int startY = isDownwardsFlingGesture ? viewOnScreenXY[1] + viewHeight / 4 |
| : viewOnScreenXY[1] + 3 * viewHeight / 4; |
| final int amountY = isDownwardsFlingGesture ? viewHeight / 2 : -viewHeight / 2; |
| |
| // Compute fling gesture duration based on the distance (50% height of the view) and |
| // fling velocity |
| final int durationMs = (1000 * viewHeight) / (2 * flingVelocity); |
| |
| // And do the same event injection sequence as our generic drag gesture |
| emulateDragGesture(instrumentation, activityTestRule, |
| x, startY, 0, amountY, durationMs, durationMs / 16, |
| eventInjectionListener); |
| |
| return amountY; |
| } |
| |
| private static class ViewStateSnapshot { |
| final View mFirst; |
| final View mLast; |
| final int mFirstTop; |
| final int mLastBottom; |
| final int mChildCount; |
| private ViewStateSnapshot(ViewGroup viewGroup) { |
| mChildCount = viewGroup.getChildCount(); |
| if (mChildCount == 0) { |
| mFirst = mLast = null; |
| mFirstTop = mLastBottom = Integer.MIN_VALUE; |
| } else { |
| mFirst = viewGroup.getChildAt(0); |
| mLast = viewGroup.getChildAt(mChildCount - 1); |
| mFirstTop = mFirst.getTop(); |
| mLastBottom = mLast.getBottom(); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| final ViewStateSnapshot that = (ViewStateSnapshot) o; |
| return mFirstTop == that.mFirstTop && |
| mLastBottom == that.mLastBottom && |
| mFirst == that.mFirst && |
| mLast == that.mLast && |
| mChildCount == that.mChildCount; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = mFirst != null ? mFirst.hashCode() : 0; |
| result = 31 * result + (mLast != null ? mLast.hashCode() : 0); |
| result = 31 * result + mFirstTop; |
| result = 31 * result + mLastBottom; |
| result = 31 * result + mChildCount; |
| return result; |
| } |
| } |
| |
| /** |
| * Emulates a scroll to the bottom of the specified {@link ViewGroup}. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param viewGroup View group |
| */ |
| public static void emulateScrollToBottom(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) { |
| final int[] viewGroupOnScreenXY = new int[2]; |
| viewGroup.getLocationOnScreen(viewGroupOnScreenXY); |
| |
| final int emulatedX = viewGroupOnScreenXY[0] + viewGroup.getWidth() / 2; |
| final int emulatedStartY = viewGroupOnScreenXY[1] + 3 * viewGroup.getHeight() / 4; |
| final int swipeAmount = viewGroup.getHeight() / 2; |
| |
| ViewStateSnapshot prev; |
| ViewStateSnapshot next = new ViewStateSnapshot(viewGroup); |
| do { |
| prev = next; |
| emulateDragGesture(instrumentation, activityTestRule, |
| emulatedX, emulatedStartY, 0, -swipeAmount, 300, 10); |
| next = new ViewStateSnapshot(viewGroup); |
| } while (!prev.equals(next)); |
| } |
| |
| /** |
| * Emulates a long press in the center of the passed {@link View}. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to "long press" |
| */ |
| public static void emulateLongPressOnViewCenter(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view) { |
| emulateLongPressOnViewCenter(instrumentation, activityTestRule, view, 0); |
| } |
| |
| /** |
| * Emulates a long press in the center of the passed {@link View}. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to "long press" |
| * @param extraWaitMs the duration of emulated "long press" in milliseconds starting |
| * after system-level long press timeout. |
| */ |
| public static void emulateLongPressOnViewCenter(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view, long extraWaitMs) { |
| final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); |
| // Use instrumentation to emulate a tap on the spinner to bring down its popup |
| final int[] viewOnScreenXY = new int[2]; |
| view.getLocationOnScreen(viewOnScreenXY); |
| int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2; |
| int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2; |
| |
| emulateLongPressOnScreen(instrumentation, activityTestRule, |
| xOnScreen, yOnScreen, touchSlop, extraWaitMs, true); |
| } |
| |
| /** |
| * Emulates a long press confirmed on a point relative to the top-left corner of the passed |
| * {@link View}. Offset parameters are used to compute the final screen coordinates of the |
| * press point. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param view the view to "long press" |
| * @param offsetX extra X offset for the tap |
| * @param offsetY extra Y offset for the tap |
| */ |
| public static void emulateLongPressOnView(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, View view, int offsetX, int offsetY) { |
| final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop(); |
| final int[] viewOnScreenXY = new int[2]; |
| view.getLocationOnScreen(viewOnScreenXY); |
| int xOnScreen = viewOnScreenXY[0] + offsetX; |
| int yOnScreen = viewOnScreenXY[1] + offsetY; |
| |
| emulateLongPressOnScreen(instrumentation, activityTestRule, |
| xOnScreen, yOnScreen, touchSlop, 0, true); |
| } |
| |
| /** |
| * Emulates a long press then a linear drag gesture between 2 points across the screen. |
| * This is used for drag selection. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param dragStartX Start X of the emulated drag gesture |
| * @param dragStartY Start Y of the emulated drag gesture |
| * @param dragAmountX X amount of the emulated drag gesture |
| * @param dragAmountY Y amount of the emulated drag gesture |
| */ |
| public static void emulateLongPressAndDragGesture(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) { |
| emulateLongPressOnScreen(instrumentation, activityTestRule, dragStartX, dragStartY, |
| 0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */); |
| emulateDragGesture(instrumentation, activityTestRule, dragStartX, dragStartY, dragAmountX, |
| dragAmountY); |
| } |
| |
| /** |
| * Emulates a long press on the screen. |
| * |
| * @param instrumentation the instrumentation used to run the test |
| * @param xOnScreen X position on screen for the "long press" |
| * @param yOnScreen Y position on screen for the "long press" |
| * @param extraWaitMs extra duration of emulated long press in milliseconds added |
| * after the system-level "long press" timeout. |
| * @param upGesture whether to include an up event. |
| */ |
| private static void emulateLongPressOnScreen(Instrumentation instrumentation, |
| ActivityTestRule<?> activityTestRule, |
| int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) { |
| final UiAutomation uiAutomation = instrumentation.getUiAutomation(); |
| final long downTime = SystemClock.uptimeMillis(); |
| |
| injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null); |
| injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen); |
| SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs); |
| if (upGesture) { |
| injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null); |
| } |
| |
| // Wait for the system to process all events in the queue |
| if (activityTestRule != null) { |
| WidgetTestUtils.runOnMainAndDrawSync(activityTestRule, |
| activityTestRule.getActivity().getWindow().getDecorView(), null); |
| } else { |
| instrumentation.waitForIdleSync(); |
| } |
| } |
| } |