1/*
2 * Copyright (C) 2015 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 com.android.server.accessibility;
18
19import android.accessibilityservice.IAccessibilityServiceClient;
20import android.os.Handler;
21import android.os.Looper;
22import android.os.Message;
23import android.os.RemoteException;
24import android.os.SystemClock;
25import android.util.Slog;
26import android.util.SparseArray;
27import android.view.InputDevice;
28import android.view.KeyEvent;
29import android.view.MotionEvent;
30import android.view.WindowManagerPolicy;
31import android.view.accessibility.AccessibilityEvent;
32import com.android.internal.os.SomeArgs;
33import com.android.server.accessibility.AccessibilityManagerService.Service;
34
35import java.util.List;
36
37/**
38 * Injects MotionEvents to permit {@code AccessibilityService}s to touch the screen on behalf of
39 * users.
40 *
41 * All methods except {@code injectEvents} must be called only from the main thread.
42 */
43public class MotionEventInjector implements EventStreamTransformation {
44    private static final String LOG_TAG = "MotionEventInjector";
45    private static final int MESSAGE_SEND_MOTION_EVENT = 1;
46    private static final int MESSAGE_INJECT_EVENTS = 2;
47    private static final int MAX_POINTERS = 11; // Non-binding maximum
48
49    private final Handler mHandler;
50    private final SparseArray<Boolean> mOpenGesturesInProgress = new SparseArray<>();
51
52    // These two arrays must be the same length
53    private MotionEvent.PointerProperties[] mPointerProperties =
54            new MotionEvent.PointerProperties[MAX_POINTERS];
55    private MotionEvent.PointerCoords[] mPointerCoords =
56            new MotionEvent.PointerCoords[MAX_POINTERS];
57    private EventStreamTransformation mNext;
58    private IAccessibilityServiceClient mServiceInterfaceForCurrentGesture;
59    private int mSequenceForCurrentGesture;
60    private int mSourceOfInjectedGesture = InputDevice.SOURCE_UNKNOWN;
61    private boolean mIsDestroyed = false;
62
63    /**
64     * @param looper A looper on the main thread to use for dispatching new events
65     */
66    public MotionEventInjector(Looper looper) {
67        mHandler = new Handler(looper, new Callback());
68    }
69
70    /**
71     * Schedule a series of events for injection. These events must comprise a complete, valid
72     * sequence. All gestures currently in progress will be cancelled, and all {@code downTime}
73     * and {@code eventTime} fields will be offset by the current time.
74     *
75     * @param events The events to inject. Must all be from the same source.
76     * @param serviceInterface The interface to call back with a result when the gesture is
77     * either complete or cancelled.
78     */
79    public void injectEvents(List<MotionEvent> events,
80            IAccessibilityServiceClient serviceInterface, int sequence) {
81        SomeArgs args = SomeArgs.obtain();
82        args.arg1 = events;
83        args.arg2 = serviceInterface;
84        args.argi1 = sequence;
85        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
86    }
87
88    @Override
89    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
90        cancelAnyPendingInjectedEvents();
91        sendMotionEventToNext(event, rawEvent, policyFlags);
92    }
93
94    @Override
95    public void onKeyEvent(KeyEvent event, int policyFlags) {
96        if (mNext != null) {
97            mNext.onKeyEvent(event, policyFlags);
98        }
99    }
100
101    @Override
102    public void onAccessibilityEvent(AccessibilityEvent event) {
103        if (mNext != null) {
104            mNext.onAccessibilityEvent(event);
105        }
106    }
107
108    @Override
109    public void setNext(EventStreamTransformation next) {
110        mNext = next;
111    }
112
113    @Override
114    public void clearEvents(int inputSource) {
115        /*
116         * Reset state for motion events passing through so we won't send a cancel event for
117         * them.
118         */
119        if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
120            mOpenGesturesInProgress.put(inputSource, false);
121        }
122    }
123
124    @Override
125    public void onDestroy() {
126        cancelAnyPendingInjectedEvents();
127        mIsDestroyed = true;
128    }
129
130    private void injectEventsMainThread(List<MotionEvent> events,
131            IAccessibilityServiceClient serviceInterface, int sequence) {
132        if (mIsDestroyed) {
133            try {
134                serviceInterface.onPerformGestureResult(sequence, false);
135            } catch (RemoteException re) {
136                Slog.e(LOG_TAG, "Error sending status with mIsDestroyed to " + serviceInterface,
137                        re);
138            }
139            return;
140        }
141        cancelAnyPendingInjectedEvents();
142        mSourceOfInjectedGesture = events.get(0).getSource();
143        cancelAnyGestureInProgress(mSourceOfInjectedGesture);
144        mServiceInterfaceForCurrentGesture = serviceInterface;
145        mSequenceForCurrentGesture = sequence;
146        if (mNext == null) {
147            notifyService(false);
148            return;
149        }
150
151        long startTime = SystemClock.uptimeMillis();
152        for (int i = 0; i < events.size(); i++) {
153            MotionEvent event = events.get(i);
154            int numPointers = event.getPointerCount();
155            if (numPointers > mPointerCoords.length) {
156                mPointerCoords = new MotionEvent.PointerCoords[numPointers];
157                mPointerProperties = new MotionEvent.PointerProperties[numPointers];
158            }
159            for (int j = 0; j < numPointers; j++) {
160                if (mPointerCoords[j] == null) {
161                    mPointerCoords[j] = new MotionEvent.PointerCoords();
162                    mPointerProperties[j] = new MotionEvent.PointerProperties();
163                }
164                event.getPointerCoords(j, mPointerCoords[j]);
165                event.getPointerProperties(j, mPointerProperties[j]);
166            }
167
168            /*
169             * MotionEvent doesn't have a setEventTime() method (it carries around history data,
170             * which could become inconsistent), so we need to obtain a new one.
171             */
172            MotionEvent offsetEvent = MotionEvent.obtain(startTime + event.getDownTime(),
173                    startTime + event.getEventTime(), event.getAction(), numPointers,
174                    mPointerProperties, mPointerCoords, event.getMetaState(),
175                    event.getButtonState(), event.getXPrecision(), event.getYPrecision(),
176                    event.getDeviceId(), event.getEdgeFlags(), event.getSource(),
177                    event.getFlags());
178            Message message = mHandler.obtainMessage(MESSAGE_SEND_MOTION_EVENT, offsetEvent);
179            mHandler.sendMessageDelayed(message, event.getEventTime());
180        }
181    }
182
183    private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
184            int policyFlags) {
185        if (mNext != null) {
186            mNext.onMotionEvent(event, rawEvent, policyFlags);
187            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
188                mOpenGesturesInProgress.put(event.getSource(), true);
189            }
190            if ((event.getActionMasked() == MotionEvent.ACTION_UP)
191                    || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
192                mOpenGesturesInProgress.put(event.getSource(), false);
193            }
194        }
195    }
196
197    private void cancelAnyGestureInProgress(int source) {
198        if ((mNext != null) && mOpenGesturesInProgress.get(source, false)) {
199            long now = SystemClock.uptimeMillis();
200            MotionEvent cancelEvent =
201                    MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
202            sendMotionEventToNext(cancelEvent, cancelEvent,
203                    WindowManagerPolicy.FLAG_PASS_TO_USER);
204        }
205    }
206
207    private void cancelAnyPendingInjectedEvents() {
208        if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
209            cancelAnyGestureInProgress(mSourceOfInjectedGesture);
210            mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
211            notifyService(false);
212        }
213
214    }
215
216    private void notifyService(boolean success) {
217        try {
218            mServiceInterfaceForCurrentGesture.onPerformGestureResult(
219                    mSequenceForCurrentGesture, success);
220        } catch (RemoteException re) {
221            Slog.e(LOG_TAG, "Error sending motion event injection status to "
222                    + mServiceInterfaceForCurrentGesture, re);
223        }
224    }
225
226    private class Callback implements Handler.Callback {
227        @Override
228        public boolean handleMessage(Message message) {
229            if (message.what == MESSAGE_INJECT_EVENTS) {
230                SomeArgs args = (SomeArgs) message.obj;
231                injectEventsMainThread((List<MotionEvent>) args.arg1,
232                        (IAccessibilityServiceClient) args.arg2, args.argi1);
233                args.recycle();
234                return true;
235            }
236            if (message.what != MESSAGE_SEND_MOTION_EVENT) {
237                throw new IllegalArgumentException("Unknown message: " + message.what);
238            }
239            MotionEvent motionEvent = (MotionEvent) message.obj;
240            sendMotionEventToNext(motionEvent, motionEvent,
241                    WindowManagerPolicy.FLAG_PASS_TO_USER);
242            // If the message queue is now empty, then this gesture is complete
243            if (!mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
244                notifyService(true);
245            }
246            return true;
247        }
248    }
249}
250