1/*
2 * Copyright 2018 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
17package androidx.recyclerview.widget;
18
19import android.app.Instrumentation;
20import android.os.SystemClock;
21import android.view.Gravity;
22import android.view.MotionEvent;
23import android.view.View;
24import android.view.ViewConfiguration;
25
26/**
27 * RV specific layout tests.
28 */
29public class TouchUtils {
30    public static void tapView(Instrumentation inst, RecyclerView recyclerView,
31            View v) {
32        int[] xy = new int[2];
33        v.getLocationOnScreen(xy);
34
35        final int viewWidth = v.getWidth();
36        final int viewHeight = v.getHeight();
37
38        final float x = xy[0] + (viewWidth / 2.0f);
39        float y = xy[1] + (viewHeight / 2.0f);
40
41        long downTime = SystemClock.uptimeMillis();
42        long eventTime = SystemClock.uptimeMillis();
43
44        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
45                MotionEvent.ACTION_DOWN, x, y, 0);
46        inst.sendPointerSync(event);
47        inst.waitForIdleSync();
48
49        eventTime = SystemClock.uptimeMillis();
50        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
51        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
52                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
53        inst.sendPointerSync(event);
54        inst.waitForIdleSync();
55
56        eventTime = SystemClock.uptimeMillis();
57        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
58        inst.sendPointerSync(event);
59        inst.waitForIdleSync();
60    }
61
62    public static void touchAndCancelView(Instrumentation inst, View v) {
63        int[] xy = new int[2];
64        v.getLocationOnScreen(xy);
65
66        final int viewWidth = v.getWidth();
67        final int viewHeight = v.getHeight();
68
69        final float x = xy[0] + (viewWidth / 2.0f);
70        float y = xy[1] + (viewHeight / 2.0f);
71
72        long downTime = SystemClock.uptimeMillis();
73        long eventTime = SystemClock.uptimeMillis();
74
75        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
76                MotionEvent.ACTION_DOWN, x, y, 0);
77        inst.sendPointerSync(event);
78        inst.waitForIdleSync();
79
80        eventTime = SystemClock.uptimeMillis();
81        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
82        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
83                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
84        inst.sendPointerSync(event);
85        inst.waitForIdleSync();
86
87    }
88
89    public static void clickView(Instrumentation inst, View v) {
90        int[] xy = new int[2];
91        v.getLocationOnScreen(xy);
92
93        final int viewWidth = v.getWidth();
94        final int viewHeight = v.getHeight();
95
96        final float x = xy[0] + (viewWidth / 2.0f);
97        float y = xy[1] + (viewHeight / 2.0f);
98
99        long downTime = SystemClock.uptimeMillis();
100        long eventTime = SystemClock.uptimeMillis();
101
102        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
103                MotionEvent.ACTION_DOWN, x, y, 0);
104        inst.sendPointerSync(event);
105        inst.waitForIdleSync();
106
107        eventTime = SystemClock.uptimeMillis();
108        final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
109        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
110                x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
111        inst.sendPointerSync(event);
112        inst.waitForIdleSync();
113
114        eventTime = SystemClock.uptimeMillis();
115        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
116        inst.sendPointerSync(event);
117        inst.waitForIdleSync();
118
119        try {
120            Thread.sleep(1000);
121        } catch (InterruptedException e) {
122            e.printStackTrace();
123        }
124    }
125
126    public static void longClickView(Instrumentation inst, View v) {
127        int[] xy = new int[2];
128        v.getLocationOnScreen(xy);
129
130        final int viewWidth = v.getWidth();
131        final int viewHeight = v.getHeight();
132
133        final float x = xy[0] + (viewWidth / 2.0f);
134        float y = xy[1] + (viewHeight / 2.0f);
135
136        long downTime = SystemClock.uptimeMillis();
137        long eventTime = SystemClock.uptimeMillis();
138
139        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
140                MotionEvent.ACTION_DOWN, x, y, 0);
141        inst.sendPointerSync(event);
142        inst.waitForIdleSync();
143
144        try {
145            Thread.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f));
146        } catch (InterruptedException e) {
147            e.printStackTrace();
148        }
149
150        eventTime = SystemClock.uptimeMillis();
151        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
152        inst.sendPointerSync(event);
153        inst.waitForIdleSync();
154    }
155
156    public static void scrollView(int axis, int axisValue, int inputDevice, View v) {
157        MotionEvent.PointerProperties[] pointerProperties = { new MotionEvent.PointerProperties() };
158        MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
159        coords.setAxisValue(axis, axisValue);
160        MotionEvent.PointerCoords[] pointerCoords = { coords };
161        MotionEvent e = MotionEvent.obtain(
162                0, System.currentTimeMillis(), MotionEvent.ACTION_SCROLL,
163                1, pointerProperties, pointerCoords, 0, 0, 1, 1, 0, 0, inputDevice, 0);
164        v.onGenericMotionEvent(e);
165        e.recycle();
166    }
167
168    public static void dragViewToTop(Instrumentation inst, View v) {
169        dragViewToTop(inst, v, calculateStepsForDistance(v.getTop()));
170    }
171
172    public static void dragViewToTop(Instrumentation inst, View v, int stepCount) {
173        int[] xy = new int[2];
174        v.getLocationOnScreen(xy);
175
176        final int viewWidth = v.getWidth();
177        final int viewHeight = v.getHeight();
178
179        final float x = xy[0] + (viewWidth / 2.0f);
180        float fromY = xy[1] + (viewHeight / 2.0f);
181        float toY = 0;
182
183        drag(inst, x, x, fromY, toY, stepCount);
184    }
185
186    private static void getStartLocation(View v, int gravity, int[] xy) {
187        v.getLocationOnScreen(xy);
188
189        final int viewWidth = v.getWidth();
190        final int viewHeight = v.getHeight();
191
192        switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
193            case Gravity.TOP:
194                break;
195            case Gravity.CENTER_VERTICAL:
196                xy[1] += viewHeight / 2;
197                break;
198            case Gravity.BOTTOM:
199                xy[1] += viewHeight - 1;
200                break;
201            default:
202                // Same as top -- do nothing
203        }
204
205        switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
206            case Gravity.LEFT:
207                break;
208            case Gravity.CENTER_HORIZONTAL:
209                xy[0] += viewWidth / 2;
210                break;
211            case Gravity.RIGHT:
212                xy[0] += viewWidth - 1;
213                break;
214            default:
215                // Same as left -- do nothing
216        }
217    }
218
219    public static int dragViewTo(Instrumentation inst, View v, int gravity, int toX,
220            int toY) {
221        int[] xy = new int[2];
222
223        getStartLocation(v, gravity, xy);
224
225        final int fromX = xy[0];
226        final int fromY = xy[1];
227
228        int deltaX = fromX - toX;
229        int deltaY = fromY - toY;
230
231        int distance = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
232        drag(inst, fromX, toX, fromY, toY, calculateStepsForDistance(distance));
233
234        return distance;
235    }
236
237    public static int dragViewToX(Instrumentation inst, View v, int gravity, int toX) {
238        int[] xy = new int[2];
239
240        getStartLocation(v, gravity, xy);
241
242        final int fromX = xy[0];
243        final int fromY = xy[1];
244
245        int deltaX = fromX - toX;
246
247        drag(inst, fromX, toX, fromY, fromY, calculateStepsForDistance(deltaX));
248
249        return deltaX;
250    }
251
252    public static int dragViewToY(Instrumentation inst, View v, int gravity, int toY) {
253        int[] xy = new int[2];
254
255        getStartLocation(v, gravity, xy);
256
257        final int fromX = xy[0];
258        final int fromY = xy[1];
259
260        int deltaY = fromY - toY;
261
262        drag(inst, fromX, fromX, fromY, toY, calculateStepsForDistance(deltaY));
263
264        return deltaY;
265    }
266
267
268    public static void drag(Instrumentation inst, float fromX, float toX, float fromY,
269            float toY, int stepCount) {
270        long downTime = SystemClock.uptimeMillis();
271        long eventTime = SystemClock.uptimeMillis();
272
273        float y = fromY;
274        float x = fromX;
275
276        float yStep = (toY - fromY) / stepCount;
277        float xStep = (toX - fromX) / stepCount;
278
279        MotionEvent event = MotionEvent.obtain(downTime, eventTime,
280                MotionEvent.ACTION_DOWN, x, y, 0);
281        inst.sendPointerSync(event);
282        for (int i = 0; i < stepCount; ++i) {
283            y += yStep;
284            x += xStep;
285            eventTime = SystemClock.uptimeMillis();
286            event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
287            inst.sendPointerSync(event);
288        }
289
290        eventTime = SystemClock.uptimeMillis();
291        event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
292        inst.sendPointerSync(event);
293        inst.waitForIdleSync();
294    }
295
296    private static int calculateStepsForDistance(int distance) {
297        return 1 + Math.abs(distance) / 10;
298    }
299}
300