1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chromoting;
6
7import android.graphics.PointF;
8import android.os.SystemClock;
9import android.util.SparseArray;
10import android.view.InputDevice;
11import android.view.MotionEvent;
12
13/**
14 * Utility for creating MotionEvents for multi-touch event simulation. The events are created by
15 * calling MotionEvent.obtain(...) with suitable parameters.
16 */
17public class TouchEventGenerator {
18    /**
19     * Stores the current set of held-down fingers. This is a sparse array since the indices of the
20     * fingers are not contiguous. For example, if fingers 0, 1, 2 are pressed then finger 1 is
21     * lifted, the device ids 0, 2 would correspond to the indexes 0, 1.
22     */
23    private SparseArray<PointF> mFingerPositions = new SparseArray<PointF>();
24
25    /**
26     * The event's DownTime. This is set to the system's current time when the first finger is
27     * pressed.
28     */
29    private long mDownTime;
30
31    /**
32     * Creates a fake event with the given action and device id. The event is generated using the
33     * information in |mFingerPositions|.
34     *
35     * @param actionMasked The (masked) action to generate, for example, ACTION_POINTER_DOWN.
36     * @param id The device id of the event. There must already be an entry for |id| in
37     *        |mFingerPositions|, so that |id| can be converted to an index and combined with
38     *        |actionMasked| to set the new event's |action| property.
39     */
40    private MotionEvent obtainEvent(int actionMasked, int id) {
41        int actionIndex = mFingerPositions.indexOfKey(id);
42        assert actionIndex >= 0;
43        int action = (actionIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT) | actionMasked;
44
45        long eventTime = SystemClock.uptimeMillis();
46        int size = mFingerPositions.size();
47
48        // Generate the arrays of pointers and positions for the event.
49        MotionEvent.PointerProperties[] pointers = new MotionEvent.PointerProperties[size];
50        MotionEvent.PointerCoords[] positions = new MotionEvent.PointerCoords[size];
51        for (int i = 0; i < size; i++) {
52            int key = mFingerPositions.keyAt(i);
53            PointF position = mFingerPositions.valueAt(i);
54
55            pointers[i] = new MotionEvent.PointerProperties();
56            pointers[i].id = key;
57
58            positions[i] = new MotionEvent.PointerCoords();
59            positions[i].x = position.x;
60            positions[i].y = position.y;
61        }
62
63        return MotionEvent.obtain(mDownTime, eventTime, action, size, pointers, positions,
64                0, 0, 1, 1, id, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
65    }
66
67    /**
68     * Obtains a finger-down event.
69     *
70     * @param id The device id of the new finger that is pressed. The caller must ensure this is
71     *        a finger not currently held down.
72     * @param x The x-coordinate of the new finger position.
73     * @param y The y-coordinate of the new finger position.
74     */
75    public MotionEvent obtainDownEvent(int id, float x, float y) {
76        assert mFingerPositions.get(id) == null;
77        mFingerPositions.put(id, new PointF(x, y));
78        int actionMasked;
79        if (mFingerPositions.size() == 1) {
80            mDownTime = SystemClock.uptimeMillis();
81            actionMasked = MotionEvent.ACTION_DOWN;
82        } else {
83            actionMasked = MotionEvent.ACTION_POINTER_DOWN;
84        }
85        return obtainEvent(actionMasked, id);
86    }
87
88    /**
89     * Obtains a finger-up event.
90     *
91     * @param id The device id of the finger to be lifted. The caller must ensure this is a
92     *        finger previously held down.
93     */
94    public MotionEvent obtainUpEvent(int id) {
95        assert mFingerPositions.get(id) != null;
96
97        int actionMasked = mFingerPositions.size() == 1
98                ? MotionEvent.ACTION_UP : MotionEvent.ACTION_POINTER_UP;
99
100        MotionEvent event = obtainEvent(actionMasked, id);
101        mFingerPositions.remove(id);
102        return event;
103    }
104
105    /**
106     * Obtains a finger-move event. This moves a single finger, keeping any others in the same
107     * same position.
108     *
109     * @param id The device id of the finger being moved. The caller must ensure this is a
110     *        finger previously held down.
111     * @param x The x-coordinate of the new finger position.
112     * @param y The y-coordinate of the new finger position.
113     */
114    public MotionEvent obtainMoveEvent(int id, float x, float y) {
115        PointF position = mFingerPositions.get(id);
116        assert position != null;
117        position.x = x;
118        position.y = y;
119        return obtainEvent(MotionEvent.ACTION_MOVE, id);
120    }
121}
122