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