• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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