1 /* 2 * Copyright (C) 2020 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 android.server.wm; 18 19 import static android.server.wm.StateLogger.log; 20 21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 22 23 import android.app.Instrumentation; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.os.SystemClock; 27 import android.view.InputDevice; 28 import android.view.KeyCharacterMap; 29 import android.view.KeyEvent; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.ViewConfiguration; 33 34 public class TouchHelper { 35 public final Context mContext; 36 public final Instrumentation mInstrumentation; 37 public final WindowManagerStateHelper mWmState; 38 TouchHelper(Instrumentation instrumentation, WindowManagerStateHelper wmState)39 public TouchHelper(Instrumentation instrumentation, WindowManagerStateHelper wmState) { 40 mInstrumentation = instrumentation; 41 mContext = mInstrumentation.getContext(); 42 mWmState = wmState; 43 } 44 45 /** 46 * Insert an input event (ACTION_DOWN -> ACTION_CANCEL) to ensures the display to be focused 47 * without triggering potential clicked to impact the test environment. 48 * (e.g: Keyguard credential activated unexpectedly.) 49 * 50 * @param displayId the display ID to gain focused by inject swipe action 51 */ touchAndCancelOnDisplayCenterSync(int displayId)52 public void touchAndCancelOnDisplayCenterSync(int displayId) { 53 WindowManagerState.DisplayContent dc = mWmState.getDisplay(displayId); 54 if (dc == null) { 55 // never get wm state before? 56 mWmState.computeState(); 57 dc = mWmState.getDisplay(displayId); 58 } 59 if (dc == null) { 60 log("Cannot tap on display: " + displayId); 61 return; 62 } 63 final Rect bounds = dc.getDisplayRect(); 64 final int x = bounds.left + bounds.width() / 2; 65 final int y = bounds.top + bounds.height() / 2; 66 final long downTime = SystemClock.uptimeMillis(); 67 injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, true /* sync */); 68 69 final long eventTime = SystemClock.uptimeMillis(); 70 final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 71 final int tapX = x + Math.round(touchSlop / 2.0f); 72 final int tapY = y + Math.round(touchSlop / 2.0f); 73 injectMotion(downTime, eventTime, MotionEvent.ACTION_CANCEL, tapX, tapY, displayId, 74 true /* sync */); 75 } 76 tapOnDisplaySync(int x, int y, int displayId)77 public void tapOnDisplaySync(int x, int y, int displayId) { 78 tapOnDisplay(x, y, displayId, true /* sync*/); 79 } 80 tapOnDisplay(int x, int y, int displayId, boolean sync)81 public void tapOnDisplay(int x, int y, int displayId, boolean sync) { 82 tapOnDisplay(x, y, displayId, sync, /* waitAnimations */ true); 83 } 84 tapOnDisplay(int x, int y, int displayId, boolean sync, boolean waitAnimations)85 public void tapOnDisplay(int x, int y, int displayId, boolean sync, boolean waitAnimations) { 86 final long downTime = SystemClock.uptimeMillis(); 87 injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync, 88 waitAnimations); 89 90 final long upTime = SystemClock.uptimeMillis(); 91 injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync, 92 waitAnimations); 93 94 if (waitAnimations) { 95 mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == displayId, 96 "top focused displayId: " + displayId); 97 } 98 // This is needed after a tap in multi-display to ensure that the display focus has really 99 // changed, if needed. The call to syncInputTransaction will wait until focus change has 100 // propagated from WMS to native input before returning. 101 mInstrumentation.getUiAutomation().syncInputTransactions(waitAnimations); 102 } 103 tapOnCenter(Rect bounds, int displayId)104 public void tapOnCenter(Rect bounds, int displayId) { 105 tapOnCenter(bounds, displayId, true /* waitAnimation */); 106 } 107 tapOnCenter(Rect bounds, int displayId, boolean waitAnimation)108 public void tapOnCenter(Rect bounds, int displayId, boolean waitAnimation) { 109 final int tapX = bounds.left + bounds.width() / 2; 110 final int tapY = bounds.top + bounds.height() / 2; 111 tapOnDisplay(tapX, tapY, displayId, true /* sync */, waitAnimation); 112 } 113 tapOnViewCenter(View view)114 public void tapOnViewCenter(View view) { 115 tapOnViewCenter(view, true /* waitAnimations */); 116 } 117 tapOnViewCenter(View view, boolean waitAnimations)118 public void tapOnViewCenter(View view, boolean waitAnimations) { 119 final int[] topleft = new int[2]; 120 view.getLocationOnScreen(topleft); 121 int x = topleft[0] + view.getWidth() / 2; 122 int y = topleft[1] + view.getHeight() / 2; 123 tapOnDisplay(x, y, view.getDisplay().getDisplayId(), true /* sync */, waitAnimations); 124 } 125 tapOnTaskCenter(WindowManagerState.Task task)126 public void tapOnTaskCenter(WindowManagerState.Task task) { 127 tapOnCenter(task.getBounds(), task.mDisplayId); 128 } 129 tapOnDisplayCenter(int displayId)130 public void tapOnDisplayCenter(int displayId) { 131 final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect(); 132 tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId); 133 } 134 tapOnDisplayCenterAsync(int displayId)135 public void tapOnDisplayCenterAsync(int displayId) { 136 final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect(); 137 tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */); 138 } 139 injectMotion(long downTime, long eventTime, int action, int x, int y, int displayId, boolean sync)140 public static void injectMotion(long downTime, long eventTime, int action, 141 int x, int y, int displayId, boolean sync) { 142 injectMotion(downTime, eventTime, action, x, y, displayId, sync, 143 true /* waitForAnimations */); 144 } 145 injectMotion(long downTime, long eventTime, int action, int x, int y, int displayId, boolean sync, boolean waitAnimations)146 public static void injectMotion(long downTime, long eventTime, int action, 147 int x, int y, int displayId, boolean sync, boolean waitAnimations) { 148 final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 149 x, y, 0 /* metaState */); 150 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 151 event.setDisplayId(displayId); 152 getInstrumentation().getUiAutomation().injectInputEvent(event, sync, waitAnimations); 153 } 154 injectKey(int keyCode, boolean longPress, boolean sync)155 public static void injectKey(int keyCode, boolean longPress, boolean sync) { 156 final long downTime = injectKeyActionDown(keyCode, longPress, sync); 157 injectKeyActionUp(keyCode, downTime, /* cancelled = */ false, sync); 158 } 159 injectKeyActionDown(int keyCode, boolean longPress, boolean sync)160 public static long injectKeyActionDown(int keyCode, boolean longPress, boolean sync) { 161 final long downTime = SystemClock.uptimeMillis(); 162 int repeatCount = 0; 163 final KeyEvent downEvent = 164 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount); 165 getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync); 166 if (longPress) { 167 repeatCount += 1; 168 final KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(), 169 KeyEvent.ACTION_DOWN, keyCode, repeatCount); 170 getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync); 171 } 172 return downTime; 173 } 174 injectKeyActionUp(int keyCode, long downTime, boolean cancelled, boolean sync)175 public static void injectKeyActionUp(int keyCode, long downTime, boolean cancelled, 176 boolean sync) { 177 final int flags; 178 if (cancelled) { 179 flags = KeyEvent.FLAG_CANCELED; 180 } else { 181 flags = 0; 182 } 183 final KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(), 184 KeyEvent.ACTION_UP, keyCode, /* repeatCount = */ 0, /* metaState= */ 0, 185 KeyCharacterMap.VIRTUAL_KEYBOARD, /* scancode= */ 0, flags); 186 getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync); 187 } 188 tapOnTaskCenterAsync(WindowManagerState.Task task)189 public void tapOnTaskCenterAsync(WindowManagerState.Task task) { 190 final Rect bounds = task.getBounds(); 191 final int x = bounds.left + bounds.width() / 2; 192 final int y = bounds.top + bounds.height() / 2; 193 tapOnDisplay(x, y, task.mDisplayId, false /* sync*/); 194 } 195 triggerBackEventByGesture(int displayId, boolean sync, boolean waitForAnimations)196 public void triggerBackEventByGesture(int displayId, boolean sync, boolean waitForAnimations) { 197 final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect(); 198 int midHeight = bounds.top + bounds.height() / 2; 199 int midWidth = bounds.left + bounds.width() / 2; 200 final SwipeSession session = new SwipeSession(displayId, sync, waitForAnimations); 201 session.quickSwipe(0, midHeight, midWidth, midHeight, 10); 202 mWmState.waitForAppTransitionIdleOnDisplay(displayId); 203 } 204 205 /** 206 * Helper class for injecting a sequence of motion event to simulate a gesture swipe. 207 */ 208 static class SwipeSession { 209 private static final int INJECT_INPUT_DELAY_MILLIS = 5; 210 private final int mDisplayId; 211 private final boolean mSync; 212 private final boolean mWaitForAnimations; 213 private int mStartX; 214 private int mStartY; 215 private int mEndX; 216 private int mEndY; 217 private long mStartDownTime = -1; 218 private long mNextEventTime = -1; 219 SwipeSession(int displayId, boolean sync, boolean waitForAnimations)220 SwipeSession(int displayId, 221 boolean sync, boolean waitForAnimations) { 222 mDisplayId = displayId; 223 mSync = sync; 224 mWaitForAnimations = waitForAnimations; 225 } 226 beginSwipe(int startX, int startY)227 long beginSwipe(int startX, int startY) { 228 mStartX = startX; 229 mStartY = startY; 230 mStartDownTime = SystemClock.uptimeMillis(); 231 injectMotion(mStartDownTime, mStartDownTime, MotionEvent.ACTION_DOWN, mStartX, mStartY, 232 mDisplayId, mSync, mWaitForAnimations); 233 return mStartDownTime; 234 } 235 continueSwipe(int endX, int endY, int steps)236 void continueSwipe(int endX, int endY, int steps) { 237 if (steps <= 0) { 238 steps = 1; 239 } 240 mEndX = endX; 241 mEndY = endY; 242 // inject in every INJECT_INPUT_DELAY_MILLIS ms. 243 final int delayMillis = INJECT_INPUT_DELAY_MILLIS; 244 mNextEventTime = mStartDownTime + delayMillis; 245 final int stepGapX = (mEndX - mStartX) / steps; 246 final int stepGapY = (mEndY - mStartY) / steps; 247 for (int i = 0; i < steps; i++) { 248 SystemClock.sleep(delayMillis); 249 final int nextX = mStartX + stepGapX * i; 250 final int nextY = mStartY + stepGapY * i; 251 injectMotion(mStartDownTime, mNextEventTime, 252 MotionEvent.ACTION_MOVE, nextX, nextY, 253 mDisplayId, mSync, mWaitForAnimations); 254 mNextEventTime += delayMillis; 255 } 256 } 257 finishSwipe()258 void finishSwipe() { 259 injectMotion(mStartDownTime, mNextEventTime, MotionEvent.ACTION_UP, mEndX, mEndY, 260 mDisplayId, mSync, mWaitForAnimations); 261 } 262 cancelSwipe()263 void cancelSwipe() { 264 injectMotion(mStartDownTime, mNextEventTime, MotionEvent.ACTION_CANCEL, mEndX, mEndY, 265 mDisplayId, mSync, mWaitForAnimations); 266 } 267 quickSwipe(int startX, int startY, int endX, int endY, int steps)268 void quickSwipe(int startX, int startY, int endX, int endY, int steps) { 269 beginSwipe(startX, startY); 270 continueSwipe(endX, endY, steps); 271 SystemClock.sleep(INJECT_INPUT_DELAY_MILLIS); 272 finishSwipe(); 273 } 274 } 275 } 276