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.os.SystemClock; 27 import android.view.InputDevice; 28 import android.view.MotionEvent; 29 import android.view.View; 30 31 import androidx.test.InstrumentationRegistry; 32 33 import org.mockito.ArgumentMatcher; 34 import org.mockito.InOrder; 35 36 public final class CtsMouseUtil { 37 38 // TODO(b/272376728): make it an instance object instead 39 private static final UserHelper sUserHelper = new UserHelper( 40 InstrumentationRegistry.getInstrumentation().getTargetContext()); 41 CtsMouseUtil()42 private CtsMouseUtil() {} 43 installHoverListener(View view)44 public static View.OnHoverListener installHoverListener(View view) { 45 return installHoverListener(view, true); 46 } 47 installHoverListener(View view, boolean result)48 public static View.OnHoverListener installHoverListener(View view, boolean result) { 49 final View.OnHoverListener mockListener = mock(View.OnHoverListener.class); 50 view.setOnHoverListener((v, event) -> { 51 // Clone the event to work around event instance reuse in the framework. 52 mockListener.onHover(v, MotionEvent.obtain(event)); 53 return result; 54 }); 55 return mockListener; 56 } 57 clearHoverListener(View view)58 public static void clearHoverListener(View view) { 59 view.setOnHoverListener(null); 60 } 61 obtainMouseEvent(int action, View anchor, int offsetX, int offsetY)62 public static MotionEvent obtainMouseEvent(int action, View anchor, int offsetX, int offsetY) { 63 final long eventTime = SystemClock.uptimeMillis(); 64 final int[] screenPos = new int[2]; 65 anchor.getLocationOnScreen(screenPos); 66 final int x = screenPos[0] + offsetX; 67 final int y = screenPos[1] + offsetY; 68 MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0); 69 sUserHelper.injectDisplayIdIfNeeded(event); 70 event.setSource(InputDevice.SOURCE_MOUSE); 71 return event; 72 } 73 74 /** 75 * Emulates a hover move on a point relative to the top-left corner of the passed {@link View}. 76 * Offset parameters are used to compute the final screen coordinates of the tap point. 77 * 78 * @param instrumentation the instrumentation used to run the test 79 * @param anchor the anchor view to determine the tap location on the screen 80 * @param offsetX extra X offset for the move 81 * @param offsetY extra Y offset for the move 82 */ emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX, int offsetY)83 public static void emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX, 84 int offsetY) { 85 final long downTime = SystemClock.uptimeMillis(); 86 final int[] screenPos = new int[2]; 87 anchor.getLocationOnScreen(screenPos); 88 final int x = screenPos[0] + offsetX; 89 final int y = screenPos[1] + offsetY; 90 injectHoverEvent(instrumentation, downTime, x, y); 91 } 92 injectHoverEvent(Instrumentation instrumentation, long downTime, int xOnScreen, int yOnScreen)93 private static void injectHoverEvent(Instrumentation instrumentation, long downTime, 94 int xOnScreen, int yOnScreen) { 95 MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_HOVER_MOVE, 96 xOnScreen, yOnScreen, 0); 97 sUserHelper.injectDisplayIdIfNeeded(event); 98 event.setSource(InputDevice.SOURCE_MOUSE); 99 instrumentation.sendPointerSync(event); 100 event.recycle(); 101 } 102 103 public static class ActionMatcher implements ArgumentMatcher<MotionEvent> { 104 private final int mAction; 105 ActionMatcher(int action)106 public ActionMatcher(int action) { 107 mAction = action; 108 } 109 110 @Override matches(MotionEvent actual)111 public boolean matches(MotionEvent actual) { 112 return actual.getAction() == mAction; 113 } 114 115 @Override toString()116 public String toString() { 117 return "action=" + MotionEvent.actionToString(mAction); 118 } 119 } 120 121 public static class PositionMatcher extends ActionMatcher { 122 private final int mX; 123 private final int mY; 124 PositionMatcher(int action, int x, int y)125 public PositionMatcher(int action, int x, int y) { 126 super(action); 127 mX = x; 128 mY = y; 129 } 130 131 @Override matches(MotionEvent actual)132 public boolean matches(MotionEvent actual) { 133 return super.matches(actual) 134 && Math.round(actual.getX()) == mX 135 && Math.round(actual.getY()) == mY; 136 } 137 138 @Override toString()139 public String toString() { 140 return super.toString() + "@(" + mX + "," + mY + ")"; 141 } 142 } 143 verifyEnterMove(View.OnHoverListener listener, View view, int moveCount)144 public static void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) { 145 final InOrder inOrder = inOrder(listener); 146 verifyEnterMoveInternal(listener, view, moveCount, inOrder); 147 inOrder.verifyNoMoreInteractions(); 148 } 149 verifyEnterMoveExit( View.OnHoverListener listener, View view, int moveCount)150 public static void verifyEnterMoveExit( 151 View.OnHoverListener listener, View view, int moveCount) { 152 final InOrder inOrder = inOrder(listener); 153 verifyEnterMoveInternal(listener, view, moveCount, inOrder); 154 inOrder.verify(listener, times(1)).onHover(eq(view), 155 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT))); 156 inOrder.verifyNoMoreInteractions(); 157 } 158 verifyEnterMoveInternal( View.OnHoverListener listener, View view, int moveCount, InOrder inOrder)159 private static void verifyEnterMoveInternal( 160 View.OnHoverListener listener, View view, int moveCount, InOrder inOrder) { 161 inOrder.verify(listener, times(1)).onHover(eq(view), 162 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER))); 163 inOrder.verify(listener, times(moveCount)).onHover(eq(view), 164 argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE))); 165 } 166 } 167 168