1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.common.util; 18 19 import static org.mockito.Matchers.argThat; 20 import static org.mockito.Matchers.eq; 21 import static org.mockito.Mockito.inOrder; 22 import static org.mockito.Mockito.mock; 23 import static org.mockito.Mockito.times; 24 25 import android.app.Instrumentation; 26 import android.app.UiAutomation; 27 import android.os.SystemClock; 28 import android.view.InputDevice; 29 import android.view.MotionEvent; 30 import android.view.View; 31 32 import org.mockito.ArgumentMatcher; 33 import org.mockito.InOrder; 34 35 public final class CtsMouseUtil { 36 CtsMouseUtil()37 private CtsMouseUtil() {} 38 installHoverListener(View view)39 public static View.OnHoverListener installHoverListener(View view) { 40 return installHoverListener(view, true); 41 } 42 installHoverListener(View view, boolean result)43 public static View.OnHoverListener installHoverListener(View view, boolean result) { 44 final View.OnHoverListener mockListener = mock(View.OnHoverListener.class); 45 view.setOnHoverListener((v, event) -> { 46 // Clone the event to work around event instance reuse in the framework. 47 mockListener.onHover(v, MotionEvent.obtain(event)); 48 return result; 49 }); 50 return mockListener; 51 } 52 clearHoverListener(View view)53 public static void clearHoverListener(View view) { 54 view.setOnHoverListener(null); 55 } 56 obtainMouseEvent(int action, View anchor, int offsetX, int offsetY)57 public static MotionEvent obtainMouseEvent(int action, View anchor, int offsetX, int offsetY) { 58 final long eventTime = SystemClock.uptimeMillis(); 59 final int[] screenPos = new int[2]; 60 anchor.getLocationOnScreen(screenPos); 61 final int x = screenPos[0] + offsetX; 62 final int y = screenPos[1] + offsetY; 63 MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0); 64 event.setSource(InputDevice.SOURCE_MOUSE); 65 return event; 66 } 67 68 /** 69 * Emulates a hover move on a point relative to the top-left corner of the passed {@link View}. 70 * Offset parameters are used to compute the final screen coordinates of the tap point. 71 * 72 * @param instrumentation the instrumentation used to run the test 73 * @param anchor the anchor view to determine the tap location on the screen 74 * @param offsetX extra X offset for the move 75 * @param offsetY extra Y offset for the move 76 */ emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX, int offsetY)77 public static void emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX, 78 int offsetY) { 79 final long downTime = SystemClock.uptimeMillis(); 80 final UiAutomation uiAutomation = instrumentation.getUiAutomation(); 81 final int[] screenPos = new int[2]; 82 anchor.getLocationOnScreen(screenPos); 83 final int x = screenPos[0] + offsetX; 84 final int y = screenPos[1] + offsetY; 85 86 injectHoverEvent(uiAutomation, downTime, x, y); 87 } 88 injectHoverEvent(UiAutomation uiAutomation, long downTime, int xOnScreen, int yOnScreen)89 private static void injectHoverEvent(UiAutomation uiAutomation, long downTime, int xOnScreen, 90 int yOnScreen) { 91 MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_HOVER_MOVE, 92 xOnScreen, yOnScreen, 0); 93 event.setSource(InputDevice.SOURCE_MOUSE); 94 uiAutomation.injectInputEvent(event, true); 95 event.recycle(); 96 } 97 98 public static class ActionMatcher implements ArgumentMatcher<MotionEvent> { 99 private final int mAction; 100 ActionMatcher(int action)101 public ActionMatcher(int action) { 102 mAction = action; 103 } 104 105 @Override matches(MotionEvent actual)106 public boolean matches(MotionEvent actual) { 107 return actual.getAction() == mAction; 108 } 109 110 @Override toString()111 public String toString() { 112 return "action=" + MotionEvent.actionToString(mAction); 113 } 114 } 115 116 public static class PositionMatcher extends ActionMatcher { 117 private final int mX; 118 private final int mY; 119 PositionMatcher(int action, int x, int y)120 public PositionMatcher(int action, int x, int y) { 121 super(action); 122 mX = x; 123 mY = y; 124 } 125 126 @Override matches(MotionEvent actual)127 public boolean matches(MotionEvent actual) { 128 return super.matches(actual) 129 && ((int) actual.getX()) == mX 130 && ((int) actual.getY()) == mY; 131 } 132 133 @Override toString()134 public String toString() { 135 return super.toString() + "@(" + mX + "," + mY + ")"; 136 } 137 } 138 verifyEnterMove(View.OnHoverListener listener, View view, int moveCount)139 public static void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) { 140 final InOrder inOrder = inOrder(listener); 141 verifyEnterMoveInternal(listener, view, moveCount, inOrder); 142 inOrder.verifyNoMoreInteractions(); 143 } 144 verifyEnterMoveExit( View.OnHoverListener listener, View view, int moveCount)145 public static void verifyEnterMoveExit( 146 View.OnHoverListener listener, View view, int moveCount) { 147 final InOrder inOrder = inOrder(listener); 148 verifyEnterMoveInternal(listener, view, moveCount, inOrder); 149 inOrder.verify(listener, times(1)).onHover(eq(view), 150 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT))); 151 inOrder.verifyNoMoreInteractions(); 152 } 153 verifyEnterMoveInternal( View.OnHoverListener listener, View view, int moveCount, InOrder inOrder)154 private static void verifyEnterMoveInternal( 155 View.OnHoverListener listener, View view, int moveCount, InOrder inOrder) { 156 inOrder.verify(listener, times(1)).onHover(eq(view), 157 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER))); 158 inOrder.verify(listener, times(moveCount)).onHover(eq(view), 159 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE))); 160 } 161 } 162 163